Appearance
24|MCP 集成:共享工具的标准协议
上一篇讲了 agent 间协作。还有一个外部集成问题:agent 要用的工具,如果不想每个 agent 都自己实现一遍,怎么共享?
ops-assistant 写了个 search_runbook(),函数在本地代码里。如果只有这一个项目用,直连函数最简单。但 IDE 插件也想查 Runbook,桌面小工具也想复用同一个检索工具。复制一份函数过去能跑,后面变成三份代码一起维护。MCP 解决的就是"多个客户端共享同一套工具"。
MCP 不是远程调函数
很多人把 MCP 当成"远程函数调用"。这是第一个要纠正的认知。

MCP(Model Context Protocol)定义的能力不止工具调用一种。它有三类能力:tools、resources、prompts。
tools 是模型主动调用的动作(查日志、查指标),这个最常用。resources 是模型可以按地址读取的数据——一份配置文件、一张服务拓扑表,平时不塞进上下文,模型需要时自己去读。prompts 是服务端预先写好的提示模板,客户端按名字取来用。
把 MCP 等同于远程函数调用,会漏掉 resources 和 prompts 这两条线。适配 MCP 时不能只接 tools,三类都要考虑。
四层架构
MCP 集成不是简单调远程函数,是四层架构:配置、连接、适配、调用。每层管一件事。

text
┌─────────────────────────────────────────┐
│ 配置层:多来源 MCP server 配置合并 │
├─────────────────────────────────────────┤
│ 连接层:维护连接、认证、传输安全 │
├─────────────────────────────────────────┤
│ 适配层:MCP 能力转内部格式 │
├─────────────────────────────────────────┤
│ 调用层:复用内部权限、错误处理、执行流程 │
└─────────────────────────────────────────┘配置层收集多来源配置(用户本地、项目级、企业策略)并按优先级合并。企业策略支持 allowlist/denylist,denylist 中的服务器即使在用户配置里存在也被强制移除。
连接层建立和维持连接,处理认证和传输安全。
适配层把 MCP 协议的数据结构转成内部格式——tools 转成内部 Tool(第 18 篇的接口),resources 映射成可检索数据源,prompts 映射成可加载模板。注意 MCP 工具的 schema 字段叫 inputSchema(和 Anthropic Messages API 同源),不是 OpenAI 的 parameters。
调用层让 MCP 工具走和内部工具完全相同的执行路径——同样的权限检查、参数校验、错误处理。MCP 工具不享特殊待遇,安全边界一致。
传输:stdio 和 Streamable HTTP
MCP 当前规范定义两种传输:stdio 和 Streamable HTTP。

stdio 把 server 作为子进程拉起,通过标准输入输出传 JSON-RPC 消息。适合 server 和 client 同机,简单。
Streamable HTTP 是远程传输,单一端点支持 POST/GET,可选 SSE 流式。它取代了旧版的 HTTP+SSE(协议版本 2024-11-05 里的传输,现已废弃,仅作向后兼容保留)。两者都基于 HTTP,但 Streamable HTTP 用单一 MCP 端点处理 POST 和 GET,不是旧版那种分离的 SSE 长连接加 POST 端点。
DNS rebinding 防护
远程/本地 HTTP 传输有个容易被忽视的攻击面:DNS rebinding。
恶意网页把一个公网域名解析到 127.0.0.1,浏览器里运行的 JS 就能对本地 MCP server 发起"看似跨域、实际打本地"的请求,读到本地数据甚至触发本地工具。stdio 传输不受影响(不走 HTTP),Streamable HTTP 要防。
规范要求 Streamable HTTP server 做三件事:校验 Origin/Host 头(浏览器无法伪造 Host,这是阻断 DNS rebinding 的关键)、本地绑定 127.0.0.1 而非 0.0.0.0、所有连接认证。这是真实 CVE 背后的漏洞类型,不是理论风险。
python
ALLOWED_HOSTS = {"localhost", "127.0.0.1", "::1"}
def validate_request_headers(origin, host):
if host and host not in ALLOWED_HOSTS:
raise PermissionError(f"reject host: {host}")
if origin and origin not in ALLOWED_ORIGINS:
raise PermissionError(f"reject origin: {origin}")连接层还要处理会话(Mcp-Session-Id 头维持会话)、协议版本头(客户端和服务端协商版本)、认证过期(OAuth token 过期引导用户重新授权)。
Anthropic MCP connector
Anthropic 的 Messages API 内置 MCP connector,直接在请求里声明 MCP server,服务端替你连:
python
client.beta.messages.create(
model="claude-...",
messages=[{"role": "user", "content": "查 Runbook"}],
mcp_servers=[{"type": "url", "url": "https://...", "name": "runbook"}],
tools=[{"type": "mcp_toolset", "mcp_server_name": "runbook"}],
)服务端处理连接和调用,模型返回 mcp_tool_use,结果以 mcp_tool_result 回填。OpenAI 侧没有这种内置 connector,MCP 连接由客户端自己维护,工具转成 function 类型后照常走工具调用流程。一个是连接管理外包给服务端,一个全在客户端。
动态刷新
MCP 服务器的工具列表可能在运行时变化(服务器升级、新工具上线)。工具发现结果可以在 agent loop 轮次间动态刷新。
但运行时刷新有风险:agent 正在用的工具可能突然消失或改名。新发现的工具延迟一个轮次再暴露,给当前轮次完成的机会;被移除的工具标记弃用,不立即删除。工具列表顺序影响 prompt cache 缓存断点,动态增删要保证不破坏缓存前缀。
和本地工具的取舍
MCP 不是必须。工具只在 ops-assistant 内部使用时,本地函数更简单、更直接。
| 场景 | 推荐方案 |
|---|---|
| 只有 ops-assistant 用 | 本地函数 |
| IDE、小工具也要用 | MCP Server |
| 工具需要独立部署、水平扩展 | MCP over Streamable HTTP |
| 工具敏感,不能暴露给外部 | 本地函数 + 严格权限 |
MCP 工具相比本地函数多一层序列化和协议转换开销,性能上有代价。但 MCP 的价值在复用和标准化,不在性能——多个客户端共享同一套工具、远程服务按协议暴露后任何 agent 都能自动发现,这是本地函数给不了的。没有多客户端复用需求时,为用 MCP 而拆分只会增加进程管理复杂度。
MCP 也不是通用 RPC 框架的替代品。它解决的是模型上下文的标准化暴露,不是通用远程调用。只想从远程服务拉数据,直接用 HTTP API 更简单。
第四阶段收尾
到这里工程化与协作设计七篇完成:工具接口、权限体系、BashTool 安全、只读写分级、多 Agent 模式、协作机制、MCP 集成。agent 现在能安全行动、能协作、能接外部工具。
但前面所有概念都还是分散的设计决策。最后一阶段把它们串进一个能跑的 ops-assistant 项目:目录怎么组织、工具怎么接、会话怎么存、报告怎么出、上线要注意什么。