MCP

The tutorial covers the following features of AgentScope in support of the MCP (Model Context Protocol):

  • Support both HTTP (streamable HTTP and SSE) and StdIO MCP servers

  • Provide both stateful and stateless MCP clients

  • Provide both server-level and function-level MCP tool management

Here the stateful/stateless distinction refers to whether the client maintains a persistent session with the MCP server or not. The table below summarizes the supported MCP client types and protocols:

Supported MCP client types and protocols

Client Type

HTTP (Streamable HTTP and SSE)

StdIO

Stateful Client

HttpStatefulClient

HttpStatelessClient

Stateless Client

StdIOStatefulClient

import asyncio
import json
import os

from agentscope.mcp import HttpStatefulClient, HttpStatelessClient
from agentscope.tool import Toolkit

MCP Client

In AgentScope, MCP clients are responsible for

  • connecting to the MCP server,

  • obtaining tool functions from the server, and

  • calling tool functions in the MCP server.

There are two types of MCP clients in AgentScope: Stateful and Stateless. They only differ in how to manage the session with the MCP server.

  • Stateful Client: The stateful MCP client maintains a persistent session with the MCP server within its lifetime. The developers should explicitly call connect() and close() methods to manage the connection lifecycle.

  • Stateless Client: The stateless MCP client creates a new session when calling the tool function, and destroys the session right after the tool function call, which is much more lightweight.

Note

  • The StdIO MCP server only has stateful client, when connect() is called, it will start the MCP server locally and then connect to it.

  • For stateful clients, developers must ensure the client is connected when calling the tool functions.

Taking Gaode map MCP server as an example, the creation of stateful and stateless clients are very similar:

stateful_client = HttpStatefulClient(
    # The name to identify the MCP
    name="mcp_services_stateful",
    transport="streamable_http",
    url=f"https://mcp.amap.com/mcp?key={os.environ['GAODE_API_KEY']}",
)

stateless_client = HttpStatelessClient(
    # The name to identify the MCP
    name="mcp_services_stateless",
    transport="streamable_http",
    url=f"https://mcp.amap.com/mcp?key={os.environ['GAODE_API_KEY']}",
)

Both stateful and stateless clients provide the following methods:

MCP Client Methods

Method

Description

list_tools

List all tools available in the MCP server.

get_callable_function

Get a callable function object from the MCP server by its name.

MCP as Tool

AgentScope provides fine-grained management of MCP tools, including both server-level and function-level management.

Server-Level Management

You can register all tools from an MCP server into Toolkit as follows.

Tip

Optionally, you can specify a group name to organize the tools. Refer to Tool section for group-wise tools management.

toolkit = Toolkit()


async def example_register_stateless_mcp() -> None:
    """Example of registering MCP tools from a stateless client."""
    # Register all tools from the MCP server
    await toolkit.register_mcp_client(
        stateless_client,
        # group_name="map_services",  # Optional group name
    )

    print(
        "Total number of MCP tools registered:",
        len(toolkit.get_json_schemas()),
    )

    maps_geo = next(
        tool
        for tool in toolkit.get_json_schemas()
        if tool["function"]["name"] == "maps_geo"
    )
    print("\nThe example ``maps_geo`` function:")
    print(
        json.dumps(
            maps_geo,
            indent=4,
            ensure_ascii=False,
        ),
    )


asyncio.run(example_register_stateless_mcp())
Total number of MCP tools registered: 15

The example ``maps_geo`` function:
{
    "type": "function",
    "function": {
        "name": "maps_geo",
        "description": "将详细的结构化地址转换为经纬度坐标。支持对地标性名胜景区、建筑物名称解析为经纬度坐标",
        "parameters": {
            "type": "object",
            "properties": {
                "address": {
                    "type": "string",
                    "description": "待解析的结构化地址信息"
                },
                "city": {
                    "type": "string",
                    "description": "指定查询的城市"
                }
            },
            "required": [
                "address"
            ]
        }
    }
}

To remove the registered tools, you can use the remove_tool_function to remove a specific tool function, or remove_mcp_clients to remove all tools from a specific MCP.

async def example_remove_mcp_tools() -> None:
    """Example of removing MCP tools."""
    print(
        "Total number of tools before removal: ",
        len(toolkit.get_json_schemas()),
    )

    # Remove a specific tool function by its name
    toolkit.remove_tool_function("maps_geo")
    print("Number of tools: ", len(toolkit.get_json_schemas()))

    # Remove all tools from the MCP client by its name
    await toolkit.remove_mcp_clients(client_names=["mcp_services_stateless"])
    print("Number of tools: ", len(toolkit.get_json_schemas()))


asyncio.run(example_remove_mcp_tools())
Total number of tools before removal:  15
Number of tools:  14
Number of tools:  0

Function-Level Management

We notice the demand for more fine-grained control over MCP tools, such as post-processing the tool results, or use them to create a more complex tool function.

Therefore, AgentScope supports to obtain the callable function object from MCP by its name, so that you can

  • call it directly,

  • wrap it into your own function, or anyway you like.

Additionally, you can specify whether to wrap the tool result into ToolResponse object in AgentScope, so that you can use it seamlessly with the Toolkit. If you set wrap_tool_result=False, the raw result type mcp.types.CallToolResult will be returned.

Taking the maps_geo function as an example, you can obtain it as a callable function object as follows:

async def example_function_level_usage() -> None:
    """Example of using function-level MCP tool."""
    func_obj = await stateless_client.get_callable_function(
        func_name="maps_geo",
        # Whether to wrap the tool result into ToolResponse in AgentScope
        wrap_tool_result=True,
    )

    # You can obtain its name, description and json schema
    print("Function name:", func_obj.name)
    print("Function description:", func_obj.description)
    print(
        "Function JSON schema:",
        json.dumps(func_obj.json_schema, indent=4, ensure_ascii=False),
    )

    # Call the function object directly
    res = await func_obj(
        address="Tiananmen Square",
        city="Beijing",
    )
    print("\nFunction call result:")
    print(res)


asyncio.run(example_function_level_usage())
Function name: maps_geo
Function description: 将详细的结构化地址转换为经纬度坐标。支持对地标性名胜景区、建筑物名称解析为经纬度坐标
Function JSON schema: {
    "type": "function",
    "function": {
        "name": "maps_geo",
        "description": "将详细的结构化地址转换为经纬度坐标。支持对地标性名胜景区、建筑物名称解析为经纬度坐标",
        "parameters": {
            "type": "object",
            "properties": {
                "address": {
                    "type": "string",
                    "description": "待解析的结构化地址信息"
                },
                "city": {
                    "type": "string",
                    "description": "指定查询的城市"
                }
            },
            "required": [
                "address"
            ]
        }
    }
}

Function call result:
ToolResponse(content=[{'type': 'text', 'text': '{"results":[{"country":"中国","province":"北京市","city":"北京市","citycode":"010","district":"东城区","street":[],"number":[],"adcode":"110101","location":"116.397455,39.909187","level":"兴趣点"}]}'}], metadata=None, stream=False, is_last=True, is_interrupted=False, id='2025-08-15 09:50:00.992_066f7d')

Further Reading

For more details, see:

Total running time of the script: (0 minutes 6.325 seconds)

Gallery generated by Sphinx-Gallery