Some checks failed
Pre-commit / run (ubuntu-latest) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_en (ubuntu-latest, 3.10) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_zh (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.12) (push) Has been cancelled
208 lines
6.8 KiB
Python
208 lines
6.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
.. _mcp:
|
|
|
|
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:
|
|
|
|
.. list-table:: Supported MCP client types and protocols
|
|
:header-rows: 1
|
|
|
|
* - 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.
|
|
# - When multiple `HttpStatefulClients` or `StdIOStatefulClients` are connected, they should be closed in Last In First Out (LIFO) order to prevent errors.
|
|
#
|
|
# 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:
|
|
#
|
|
# .. list-table:: MCP Client Methods
|
|
# :header-rows: 1
|
|
#
|
|
# * - 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 :ref:`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())
|
|
|
|
# %%
|
|
# 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())
|
|
|
|
# %%
|
|
# 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())
|
|
|
|
# %%
|
|
# Further Reading
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
#
|
|
# For more details, see:
|
|
#
|
|
# - :ref:`tool`
|
|
# - :ref:`agent`
|
|
#
|