前言:每加一个工具就要改 Agent 代码?
上一篇我们实现了 A2A 协议,Agent 之间可以通信协作了。但还有一个没解决的问题:工具管理。
现在每加一个新工具,需要:
1. 写工具函数
2. 用 @register_tool 注册
3. 重启 Agent
4. 祈祷 LLM 能理解新工具的描述
MCP(Model Context Protocol)的目标是让工具变成可插拔的插件——Agent 启动时自动发现可用工具,不需要改一行代码。
一、MCP 协议的核心架构
┌─────────────────────────────────────────────┐
│ MCP 架构(简化版) │
├─────────────────────────────────────────────┤
│ │
│ Agent(MCP Client) │
│ ├─ 工具发现 → 向 Server 请求工具列表 │
│ ├─ 工具调用 → 通过 JSON-RPC 调用远程工具 │
│ └─ 自动注册 → 下载的工具自动加入 TOOLS 字典 │
│ │
│ MCP Server A MCP Server B │
│ ├─ search ├─ send_email │
│ ├─ calculator ├─ create_sheet │
│ └─ read_file └─ generate_report │
│ │
│ 通信协议:JSON-RPC 2.0 over HTTP/stdio │
└─────────────────────────────────────────────┘
二、JSON-RPC 通信层
MCP 使用 JSON-RPC 2.0 作为通信协议:
import json, uuid, time
from dataclasses import dataclass, asdict
from typing import Optional, Callable
from enum import Enum
class JSONRPC:
"""JSON-RPC 2.0 协议实现"""
VERSION = "2.0"
@staticmethod
def request(method: str, params: dict = None, id: str = None) -> dict:
return {
"jsonrpc": JSONRPC.VERSION,
"method": method,
"params": params or {},
"id": id or str(uuid.uuid4())[:8],
}
@staticmethod
def response(id: str, result: dict) -> dict:
return {
"jsonrpc": JSONRPC.VERSION,
"result": result,
"id": id,
}
@staticmethod
def error(id: str, code: int, message: str, data: dict = None) -> dict:
return {
"jsonrpc": JSONRPC.VERSION,
"error": {"code": code, "message": message, "data": data or {}},
"id": id,
}
@staticmethod
def is_request(msg: dict) -> bool:
return "method" in msg
@staticmethod
def is_response(msg: dict) -> bool:
return "result" in msg or "error" in msg
MCP 标准方法定义
class MCPMethods:
"""MCP 协议规定的标准方法"""
INITIALIZE = "initialize" # 初始化连接
TOOLS_LIST = "tools/list" # 获取工具列表
TOOLS_CALL = "tools/call" # 调用工具
RESOURCES_LIST = "resources/list" # 获取资源列表
PING = "ping" # 心跳
三、MCP Server:工具的"商店货架"
class MCPServer:
"""MCP 服务端——暴露工具给 Agent 调用"""
def __init__(self, name: str, version: str = "1.0.0"):
self.name = name
self.version = version
self.tools: dict[str, dict] = {} # tool_name -> {definition, handler}
self._method_handlers: dict[str, Callable] = {
MCPMethods.INITIALIZE: self._handle_initialize,
MCPMethods.TOOLS_LIST: self._handle_tools_list,
MCPMethods.TOOLS_CALL: self._handle_tools_call,
MCPMethods.PING: self._handle_ping,
}
def register_tool(self, name: str, description: str,
parameters: list[dict], handler: Callable):
"""注册一个工具到这个 MCP Server"""
self.tools[name] = {
"definition": {
"name": name,
"description": description,
"inputSchema": {
"type": "object",
"properties": {
p["name"]: {
"type": p.get("type", "string"),
"description": p.get("description", ""),
}
for p in parameters
},
"required": [p["name"] for p in parameters if p.get("required", True)],
},
},
"handler": handler,
}
def handle_request(self, raw: dict) -> dict:
"""处理 JSON-RPC 请求,路由到对应方法"""
method = raw.get("method", "")
req_id = raw.get("id", "")
handler = self._method_handlers.get(method)
if not handler:
return JSONRPC.error(req_id, -32601, f"Method not found: {method}")
try:
result = handler(raw.get("params", {}))
return JSONRPC.response(req_id, result)
except Exception as e:
return JSONRPC.error(req_id, -32000, str(e))
def _handle_initialize(self, params: dict) -> dict:
return {
"protocolVersion": "2024-11-05",
"serverInfo": {"name": self.name, "version": self.version},
"capabilities": {"tools": {}},
}
def _handle_tools_list(self, params: dict) -> dict:
return {
"tools": [t["definition"] for t in self.tools.values()]
}
def _handle_tools_call(self, params: dict) -> dict:
tool_name = params.get("name", "")
arguments = params.get("arguments", {})
tool = self.tools.get(tool_name)
if not tool:
raise ValueError(f"Tool not found: {tool_name}")
result = tool["handler"](**arguments)
return {
"content": [{"type": "text", "text": str(result)}],
"isError": False,
}
def _handle_ping(self, params: dict) -> dict:
return {"status": "ok"}
四、MCP Client:Agent 的工具发现引擎
class MCPClient:
"""MCP 客户端——Agent 通过 Client 发现和调用远程工具"""
def __init__(self, agent: IntegratedAgent):
self.agent = agent
self.servers: dict[str, MCPServer] = {} # 本示例用内存直连,生产用 HTTP
self.discovered_tools: dict[str, dict] = {} # tool_name -> {server, definition}
def connect_server(self, server: MCPServer):
"""连接 MCP Server"""
self.servers[server.name] = server
# 发送 initialize 请求
self._send_and_receive(server, MCPMethods.INITIALIZE, {})
# 自动发现工具
resp = self._send_and_receive(server, MCPMethods.TOOLS_LIST, {})
tools = resp.get("result", {}).get("tools", [])
for tool_def in tools:
name = tool_def["name"]
self.discovered_tools[name] = {
"server": server.name,
"definition": tool_def,
}
print(f" [DISCOVER] {name} from {server.name}")
# 自动注册到 Agent 的工具系统
self._register_to_agent()
def _send_and_receive(self, server: MCPServer,
method: str, params: dict) -> dict:
"""发送 JSON-RPC 请求并等待响应"""
request = JSONRPC.request(method, params)
return server.handle_request(request)
def call_tool(self, tool_name: str, arguments: dict) -> str:
"""调用远程工具"""
info = self.discovered_tools.get(tool_name)
if not info:
return f"Error: Tool '{tool_name}' not found"
server = self.servers[info["server"]]
resp = self._send_and_receive(server, MCPMethods.TOOLS_CALL, {
"name": tool_name,
"arguments": arguments,
})
if "error" in resp:
return f"MCP Error: {resp['error']['message']}"
content = resp.get("result", {}).get("content", [])
if content:
return content[0].get("text", str(content))
return str(resp)
def _register_to_agent(self):
"""将发现的工具注册到 Agent 的工具系统"""
for name, info in self.discovered_tools.items():
schema = info["definition"].get("inputSchema", {})
props = schema.get("properties", {})
# 为每个远程工具创建一个包装函数
def make_wrapper(tool_name):
def wrapper(**kwargs):
return self.call_tool(tool_name, kwargs)
return wrapper
# 注册到 Agent
from tools import register_tool, TOOL_REGISTRY
register_tool(
name=name,
description=info["definition"]["description"],
parameters=[
{"name": pname, "type": pinfo.get("type", "string"),
"description": pinfo.get("description", ""),
"required": pname in schema.get("required", [])}
for pname, pinfo in props.items()
],
category="mcp",
)(make_wrapper(name))
五、完整示例:搭建两个 MCP Server
# ====== 创建搜索服务 ======
search_server = MCPServer("search-service")
search_server.register_tool(
name="web_search",
description="搜索互联网获取信息。用于查询最新新闻、事实、人物。",
parameters=[
{"name": "query", "type": "string",
"description": "搜索关键词", "required": True},
{"name": "max_results", "type": "number",
"description": "结果数,默认5", "required": False},
],
handler=lambda query, max_results=5: f"搜索结果({max_results}条): ..."
)
# ====== 创建通知服务 ======
notify_server = MCPServer("notification-service")
notify_server.register_tool(
name="send_email",
description="发送邮件。用于通知、报告分发。",
parameters=[
{"name": "to", "type": "string",
"description": "收件人邮箱", "required": True},
{"name": "subject", "type": "string",
"description": "邮件主题", "required": True},
{"name": "body", "type": "string",
"description": "邮件正文", "required": True},
],
handler=lambda to, subject, body: f"邮件已发送至{to}"
)
# ====== Agent 连接 Server ======
agent = IntegratedAgent(tools={}, llm_client=client)
mcp = MCPClient(agent)
mcp.connect_server(search_server)
mcp.connect_server(notify_server)
# Agent 现在自动拥有了 web_search 和 send_email 两个工具
# 不需要改 Agent 代码
result = agent.run("搜索今天AI新闻,并发送摘要到boss@zyentor.com")
六、MCP vs Function Calling 对比
| 维度 | MCP | Function Calling(原生) |
|---|---|---|
| 工具管理 | 热插拔,启动时自动发现 | 硬编码,需要重启 |
| 跨 Agent 共享 | 一个 Server 多个 Agent 共享 | 每个 Agent 独立配置 |
| 安全隔离 | Server 级沙箱 | 函数级校验 |
| 协议标准 | JSON-RPC 2.0,业界标准 | 各厂商自定义 |
| 生态 | 可发布到 MCP Registry | 无生态 |
| 延迟 | 多一层 RPC 调用(+5-20ms) | 直接函数调用 |
MCP 的代价是多一层 RPC 调用,但换来的是工具生态的标准化。 对于生产环境,这个代价完全值得。
七、总结
MCP 协议的核心价值:让工具变成插件,让 Agent 拥有"应用商店"。
- JSON-RPC 通信:轻量、标准化、业经验证
- Server/Client 架构:工具服务化,一个 Server 多个 Agent 共享
- 自动发现:Agent 启动时自动拉取可用工具,零代码扩展
- 安全隔离:每个 MCP Server 是独立的沙箱
和 Function Calling 的关系:MCP 是工具的上层管理和分发机制,Function Calling 是底层调用协议。两者互补——MCP 管"有哪些工具",Function Calling 管"怎么调用工具"。
如果觉得有用,欢迎 点赞 + 收藏 + 关注。下一篇文章将是这个系列的阶段性总结。
📌 《手搓生产级 AI Agent 系统》系列第 5 篇
上一篇:A2A 协议——让两个 Agent 互相通信
🔜 下一篇:系列总结 + 生产级 Agent 架构全景