# 6. MCP
# 6.1 原理
MCP 把外部能力统一为 Server 暴露的 Tools、Resources、Prompts。Harness 作为 Client,负责连接、能力发现、命名空间、权限、调用、结果归一化和连接生命周期。
完整链路:
配置加载 // 读取 MCP Server 地址、传输方式、认证和启停配置
→ 建立 stdio / SSE / Streamable HTTP / in-process transport // 创建与 Server 匹配的双向通信通道
→ initialize 能力协商 // 交换协议版本及双方支持的能力集合
→ list_tools/list_resources/list_prompts // 发现 Server 当前暴露的工具、资源和提示模板
→ 转为模型 Tool Schema // 将 MCP Tool 定义转换成模型可理解的函数 Schema
→ 模型生成 tool call // 模型基于工具描述产生全限定名和结构化参数
→ 权限检查与审批 // 在访问外部 Server 前校验身份、范围和风险
→ call_tool // 通过已初始化连接发送实际工具调用请求
→ 进度/elicitation/错误/超时处理 // 消费进度、补充信息请求及各种协议终态
→ 结果大小控制与上下文注入 // 对结果校验、裁剪,或保存到外部文件并只保留引用
关键问题:
- 命名冲突:使用
mcp__server__tool全限定名,权限规则也必须匹配全限定名,避免外部工具伪装成内建Write。 - 能力与连接分离:工具清单可缓存,但连接断开、会话过期或授权过期要重新初始化。
- 大结果限制:MCP 输出可能远超上下文,应分页、过滤、截断或存入外部文件,只给模型摘要和受控引用。
- 不可信元数据:Tool 描述、schema、resource URI 和
_meta都来自外部 Server,需要长度上限、协议校验和安全渲染。 - 认证:远程 Server 可能使用 OAuth;Token 应放安全存储,支持过期刷新、撤销与 step-up authorization。
# 6.2 Claude Code 源码
- MCP 名称解析与权限匹配显式使用全限定名,防止未加前缀的 MCP 工具碰撞内建工具:
mcpStringUtils.ts:19、mcpStringUtils.ts:55。 - Client 将远端工具转成内部 Tool,限制描述长度并设置权限检查;实际调用支持进度、超时、URL Elicitation 重试和结果归一化:
client.ts:1772、client.ts:2815、client.ts:3031。 - 大输出会写入持久文件,并返回可继续按需读取的提示,而不是把全部内容塞入上下文:
client.ts:2722。
# 6.3 Codex 源码
Codex 在 Turn 开始时根据输入解析必须就绪的 MCP Server,再捕获 Step Context,避免工具清单尚未加载就发起采样:turn.rs:185。MCP 调用经过准备、审批决策、执行和指标上报;拒绝与取消会产生明确 ToolResult,而不是静默丢弃:mcp_tool_call.rs:228、mcp_tool_call.rs:289。
# 6.4 伪代码
async function invokeMcp(fqName, args): // 定义一次受控 MCP Tool 调用入口
server, tool = parseQualifiedName(fqName) // 从全限定名解析 Server 和 Tool,避免名称碰撞
conn = connectionManager.ensureReady(server) // 获取已完成 initialize 的健康连接,必要时重连
spec = conn.cachedTools[tool] // 读取协商后缓存的工具描述、Schema 和超时配置
validatedArgs = validate(spec.inputSchema, args) // 按远端工具 Schema 严格验证模型参数
decision = permission.check(fqName, validatedArgs) // 按 Server、Tool 和参数范围执行权限判断
requireAllowed(decision) // 非 ALLOW 决策立即终止,不能向远端发送请求
result = await withTimeout( // 给整个远程调用设置可取消的超时边界
conn.callTool(tool, validatedArgs, onProgress), // 发起 call_tool,并把进度映射为本地事件
spec.timeout) // 使用工具级或 Server 级配置的最大时长
normalized = validateAndNormalizeMcpContent(result) // 校验外部内容类型并转成内部统一结果结构
if tokenEstimate(normalized) > outputBudget: // 判断完整结果是否会挤占过多模型上下文
handle = persistLargeOutput(normalized) // 将大结果写入持久化存储并生成受控访问引用
return summarizeWithHandle(normalized, handle) // 只返回有界摘要和后续按需读取方式
return normalized // 小结果可直接反馈给 Agent Loop
# 6.5 面试题
问:MCP 是不是 Function Calling 的替代品?
不是。Function Calling 是模型输出结构化工具意图的机制;MCP 是 Harness 与外部能力提供方之间的标准协议。常见链路是“模型 Function Call → Harness 路由 → MCP call_tool”。
# 6.6 原理深化:MCP 的一致性边界是“连接版本 + 工具快照”
MCP Client 不能只缓存一张工具表。initialize 协商得到的是当前这次连接支持的能力,list_tools 得到的是该连接版本下的工具快照;模型看到 Schema 后,到实际 call_tool 前如果 Server 重连、工具被禁用或 Schema 改变,就会出现“判断时和执行时使用了不同工具定义”的问题。可靠实现应给连接和工具目录增加 generation/version,Step 保存对应快照,执行时确认连接版本仍然兼容;不兼容时终止当前调用并重新采样,而不是拿旧参数调用新工具。
Codex 在 Turn 前先解析显式提及所依赖的 Server,再构造 Skill、Plugin 与工具注入,这体现“依赖就绪先于 Prompt 固化”的顺序:turn.rs:186、turn.rs:728。Claude Code 则在远端 Tool 转换为内部 Tool 时完成命名空间、描述长度和权限元数据治理,调用后再对外部内容做大小控制。两者共同说明 MCP 只是能力来源,进入 Harness 后必须服从统一 Tool 生命周期,不能建立一条绕过审批、结果上限和 Trace 的“协议捷径”。
远程调用还包含三种独立失败:Transport 失败表示消息未可靠到达,Protocol 失败表示请求/响应不满足 MCP 契约,Tool 失败表示 Server 已执行但业务动作失败。重试策略必须据此区分:Transport 断线只有在确认未执行或工具幂等时才能重放;业务失败应作为 ToolResult 返回模型;认证或 Elicitation 则进入显式等待状态。把三者都包装成字符串 error,会同时破坏恢复、UI 和可观测归因。