第 26 章:集成 MCP Server——对接本地工具服务

难度:中等
你的 Agent 需要调用本地的一个文件搜索服务。这个服务实现了 MCP(Model Context Protocol)协议——怎么让 AgentScope 的 Toolkit 能调用它?
任务目标
理解 MCP 协议,将一个 MCP Server 提供的工具注册到 AgentScope 的 Toolkit 中,让 ReActAgent 可以像调用普通 Python 函数一样调用 MCP 工具。1
2
3
4
5
6
7
8
9flowchart TD
A["MCP Server"] -->|"提供工具列表"| B["MCPClientBase"]
B -->|"转换为 RegisteredToolFunction"| C["Toolkit"]
C -->|"JSON Schema"| D["ReActAgent"]
D -->|"ToolUseBlock"| C
C -->|"call_tool_function"| B
B -->|"远程调用"| A
A -->|"返回结果"| B
B -->|"ToolResponse"| C
知识补全:MCP 协议
MCP(Model Context Protocol)是一个开放协议,让 AI 应用能够连接外部数据源和工具。核心概念:
- MCP Server:提供工具(tools)和资源(resources)的服务端
- MCP Client:连接 Server、发现工具、调用工具的客户端
- Transport:通信方式——stdio(标准输入输出)或 HTTP/SSE
MCP 的工具调用流程:
- Client 连接 Server
- Client 获取工具列表(每个工具有 name、description、inputSchema)
- Client 调用工具(传入 name 和 arguments)
- Server 返回结果(text、image 等内容块)
AgentScope 的 MCP 模块
打开 src/agentscope/mcp/ 目录:1
2
3
4
5
6
7
8src/agentscope/mcp/
├── __init__.py
├── _client_base.py # MCPClientBase 抽象基类
├── _http_stateful_client.py # HTTP 有状态客户端
├── _http_stateless_client.py # HTTP 无状态客户端
├── _mcp_function.py # MCP 工具函数包装
├── _stateful_client_base.py # 有状态客户端基类
└── _stdio_stateful_client.py # stdio 传输客户端
MCPClientBase
MCPClientBase(_client_base.py)是所有 MCP 客户端的基类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# _client_base.py
class MCPClientBase:
def __init__(self, name: str) -> None:
self.name = name
async def get_callable_function(
self,
func_name: str,
wrap_tool_result: bool = True,
execution_timeout: float | None = None,
) -> Callable: ...
def _convert_mcp_content_to_as_blocks(mcp_content_blocks: list)
-> List[TextBlock | ImageBlock | AudioBlock | VideoBlock]: ...
核心方法:
get_callable_function:获取一个可调用的 Python 函数,它内部封装了对 MCP Server 的远程调用_convert_mcp_content_to_as_blocks:将 MCP 的内容类型转换为 AgentScope 的 ContentBlock
Step 1:理解 MCP 工具的注册流程
1.1 从 MCP Server 获取工具列表
MCP Server 启动后,Client 可以获取它提供的所有工具。每个工具包含:1
2
3
4
5
6
7
8
9
10
11
12{
"name": "search_files",
"description": "搜索指定目录中的文件",
"inputSchema": {
"type": "object",
"properties": {
"directory": {"type": "string", "description": "搜索目录"},
"pattern": {"type": "string", "description": "文件名模式"}
},
"required": ["directory"]
}
}
1.2 转换为 Toolkit 可用的格式
_mcp_function.py 中的代码将 MCP 工具转换为 RegisteredToolFunction:
- 从
inputSchema获取 JSON Schema(不需要从 Python docstring 自动生成) - 用
get_callable_function获取包装后的 Python 函数 - 调用
toolkit.register_tool_function()注册,传入json_schema参数
1.3 注册方式
1 | # 简化的 MCP 工具注册流程 |
Step 2:三种 MCP 传输方式
2.1 stdio 传输
StdioStatefulClient(_stdio_stateful_client.py)通过子进程的 stdin/stdout 通信:1
2
3
4
5
6# 用法示意(非真实 API)
client = StdioStatefulClient(
name="filesystem",
command="npx",
args=["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
)
适用场景:本地命令行工具,如文件系统访问、Git 操作。
2.2 HTTP 传输
HTTPStatefulClient(_http_stateful_client.py)通过 HTTP POST 通信:1
2
3
4client = HTTPStatefulClient(
name="web-tools",
url="http://localhost:8080/mcp",
)
HTTPStatelessClient(_http_stateless_client.py)是无状态版本,每次调用独立。
适用场景:远程服务、需要独立部署的工具服务。
Step 3:完整集成示例
3.1 创建一个简单的 MCP Server
用 Python 创建一个最简单的 MCP Server(不需要 API key):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24# simple_mcp_server.py
from mcp.server import Server
from mcp.types import Tool, TextContent
server = Server("demo-tools")
async def echo(message: str) -> list[TextContent]:
"""回显输入的消息。
Args:
message: 要回显的消息
"""
return [TextContent(type="text", text=f"Echo: {message}")]
async def add(a: int, b: int) -> list[TextContent]:
"""计算两个数的和。
Args:
a: 第一个数
b: 第二个数
"""
return [TextContent(type="text", text=f"{a} + {b} = {a + b}")]
3.2 用 stdio 客户端连接
1 | import asyncio |
3.3 与 ReActAgent 集成
1 | agent = ReActAgent( |
Step 4:工具分组管理
MCP 工具可以注册到单独的分组中,方便动态激活/停用:1
2
3
4
5
6
7
8# 注册时指定分组
toolkit.register_tool_function(
func,
group_name="mcp_filesystem",
)
# 默认不激活
toolkit.create_tool_group("mcp_filesystem", description="文件系统 MCP 工具", active=False)
这样 MCP 工具默认不会被模型看到。Agent 可以通过 reset_equipped_tools meta 工具在运行时决定是否激活 MCP 工具。
设计一瞥
设计一瞥:MCP 工具 vs 普通 Python 工具——有什么区别?
对 Toolkit 来说,两者都是RegisteredToolFunction。区别在于source字段(_types.py:18):
"function":普通 Python 函数"mcp_server":MCP 远程工具调用方式也不同:MCP 工具通过
get_callable_function获得的包装函数内部走网络通信(HTTP 或 stdio),而不是直接调用 Python 函数。但call_tool_function不关心这些——它只看到RegisteredToolFunction,调用original_func,拿到结果包装为ToolResponse。这就是抽象层的威力:上层代码不需要知道底层实现。
完整流程图
1 | sequenceDiagram |
试一试:对比 stdio 和 HTTP 客户端
这个练习不需要 API key(纯源码阅读)。
目标:理解两种传输方式的实现差异。
步骤:
打开
src/agentscope/mcp/_stdio_stateful_client.py,找到它如何启动子进程和通过 stdin/stdout 通信打开
src/agentscope/mcp/_http_stateful_client.py,找到 HTTP 请求的构造方式回答:为什么
get_callable_function返回的是Callable而不是直接返回结果?(提示:Toolkit 的call_tool_function需要一个可调用对象)进阶:查看
_convert_mcp_content_to_as_blocks方法,理解 MCP 的内容类型如何映射到 AgentScope 的 ContentBlock:
1 | grep -n "_convert_mcp_content_to_as_blocks" src/agentscope/mcp/_client_base.py |
PR 检查清单
提交 MCP 集成的 PR 时:
- MCPClientBase 子类:实现
get_callable_function抽象方法 - 内容转换:正确处理 MCP 的 TextContent、ImageContent 等类型
- 工具分组:MCP 工具注册到
mcp_{name}分组,方便管理 - 错误处理:MCP Server 不可达时的优雅降级
- 测试:用 mock MCP Server 测试工具发现和调用
- Docstring:所有公共方法按项目规范
- pre-commit 通过
检查点
你现在理解了:
- MCP 协议:开放标准,让 AI 应用连接外部工具和数据源
- MCPClientBase:AgentScope 的 MCP 客户端抽象,核心方法是
get_callable_function - 注册流程:获取 MCP 工具列表 → 包装为可调用函数 → 注册到 Toolkit
- 传输方式:stdio(子进程通信)和 HTTP(网络通信)
- 抽象层的价值:MCP 工具和普通 Python 工具对上层代码完全透明
自检练习:
- MCP 工具的 JSON Schema 从哪里来?和普通 Python 工具有什么区别?(提示:普通工具从 docstring 自动生成,MCP 工具从
inputSchema直接获取) - 如果 MCP Server 在调用过程中崩溃了,
call_tool_function会怎么处理?(提示:get_callable_function返回的包装函数内部应该有 try/except)
参考答案:
- MCP 工具的 JSON Schema 来自 MCP Server 提供的
inputSchema字段——Server 在tools/list响应中直接给出完整的 Schema。而普通 Python 工具是通过_parse_tool_function()从函数签名和 docstring 自动生成的。区别在于:MCP 工具的 Schema 是外部提供的现成数据,Python 工具的 Schema 是框架从代码推断出来的。call_tool_function内部有try/except捕获 MCP 相关异常。如果 MCP Server 崩溃,会捕获McpError或通用Exception,把它们转换成包含错误信息的ToolResponse(如"Error occurred when calling MCP tool: ...")。Agent 收到的是文本形式的错误响应,而不是未处理的异常。这样 Agent 可以在下一轮循环中处理错误(比如重试或告知用户)。
下一章预告
MCP 工具已经集成好了。下一章,我们进入高级扩展——给工具加限流中间件、创建场景分组、注册 Agent Skill,让工具管理更上一层楼。
下一章:第 27 章 高级扩展