AI 工具的能力边界,很大程度上取决于它能接入多少外部系统。一个只能操作本地文件的助手,和一个可以查数据库、发 Slack 消息、操作 GitHub Issues 的助手,解决的是完全不同量级的问题。
如何让 AI 工具和形形色色的外部服务打通,同时不要为每一个服务写一套专门的集成代码------这是 AI 工具平台面临的共同挑战。Claude Code 的解决方案是:遵循一套开放标准,把集成的复杂性推给服务提供方,Claude Code 自己只负责「按标准说话」。这个标准,就是MCP(Model Context Protocol,模型上下文协议)。
本文带大家拆解 Claude Code 里 MCP 协议的完整实现,从传输层到工具调用,到 OAuth 鉴权,到 Elicitation 双向通信。
一、MCP 是什么
MCP 是 Anthropic 推出的一套工具暴露标准。任何外部服务,只要实现了 MCP Server 接口,就能把自己的能力作为工具暴露给任何支持 MCP 的 AI 客户端(Claude Code、Claude Desktop 等)。
这就类似于苹果手机的 App Store------不管是谁写的 App,只要按照苹果的规范开发并上架,所有 iPhone 用户都可以安装使用。MCP 也是如此:不管是 GitHub、Slack、本地数据库,还是你自己写的工具,只要实现了 MCP 接口,Claude 就能「学会」使用它们,不需要 Anthropic 为每一个服务专门编写集成代码。
MCP 协议的核心设计思想是工具自我描述------服务器动态告诉客户端「我有哪些工具,每个工具接受什么参数,有什么效果」,客户端(这里是 Claude)通过这个描述自主决定何时调用哪个工具。这和传统 REST API 集成「调用方需要事先了解所有接口」的思路有根本的不同。
二、三种传输层
Claude Code 的 MCP 客户端(src/services/mcp/client.ts)支持三种传输协议,对应不同的 MCP Server 部署方式:
#mermaid-svg-NC6twE2h7d109XsH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NC6twE2h7d109XsH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NC6twE2h7d109XsH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NC6twE2h7d109XsH .error-icon{fill:#552222;}#mermaid-svg-NC6twE2h7d109XsH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NC6twE2h7d109XsH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NC6twE2h7d109XsH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NC6twE2h7d109XsH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NC6twE2h7d109XsH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NC6twE2h7d109XsH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NC6twE2h7d109XsH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NC6twE2h7d109XsH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NC6twE2h7d109XsH .marker.cross{stroke:#333333;}#mermaid-svg-NC6twE2h7d109XsH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NC6twE2h7d109XsH p{margin:0;}#mermaid-svg-NC6twE2h7d109XsH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NC6twE2h7d109XsH .cluster-label text{fill:#333;}#mermaid-svg-NC6twE2h7d109XsH .cluster-label span{color:#333;}#mermaid-svg-NC6twE2h7d109XsH .cluster-label span p{background-color:transparent;}#mermaid-svg-NC6twE2h7d109XsH .label text,#mermaid-svg-NC6twE2h7d109XsH span{fill:#333;color:#333;}#mermaid-svg-NC6twE2h7d109XsH .node rect,#mermaid-svg-NC6twE2h7d109XsH .node circle,#mermaid-svg-NC6twE2h7d109XsH .node ellipse,#mermaid-svg-NC6twE2h7d109XsH .node polygon,#mermaid-svg-NC6twE2h7d109XsH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NC6twE2h7d109XsH .rough-node .label text,#mermaid-svg-NC6twE2h7d109XsH .node .label text,#mermaid-svg-NC6twE2h7d109XsH .image-shape .label,#mermaid-svg-NC6twE2h7d109XsH .icon-shape .label{text-anchor:middle;}#mermaid-svg-NC6twE2h7d109XsH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NC6twE2h7d109XsH .rough-node .label,#mermaid-svg-NC6twE2h7d109XsH .node .label,#mermaid-svg-NC6twE2h7d109XsH .image-shape .label,#mermaid-svg-NC6twE2h7d109XsH .icon-shape .label{text-align:center;}#mermaid-svg-NC6twE2h7d109XsH .node.clickable{cursor:pointer;}#mermaid-svg-NC6twE2h7d109XsH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NC6twE2h7d109XsH .arrowheadPath{fill:#333333;}#mermaid-svg-NC6twE2h7d109XsH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NC6twE2h7d109XsH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NC6twE2h7d109XsH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NC6twE2h7d109XsH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NC6twE2h7d109XsH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NC6twE2h7d109XsH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NC6twE2h7d109XsH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NC6twE2h7d109XsH .cluster text{fill:#333;}#mermaid-svg-NC6twE2h7d109XsH .cluster span{color:#333;}#mermaid-svg-NC6twE2h7d109XsH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NC6twE2h7d109XsH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NC6twE2h7d109XsH rect.text{fill:none;stroke-width:0;}#mermaid-svg-NC6twE2h7d109XsH .icon-shape,#mermaid-svg-NC6twE2h7d109XsH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NC6twE2h7d109XsH .icon-shape p,#mermaid-svg-NC6twE2h7d109XsH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NC6twE2h7d109XsH .icon-shape .label rect,#mermaid-svg-NC6twE2h7d109XsH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NC6twE2h7d109XsH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NC6twE2h7d109XsH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NC6twE2h7d109XsH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Stdio 传输
SSE 传输
Streamable HTTP 传输
Claude Code
MCP Client
本地子进程
(命令行工具)
本地 HTTP 服务
(Docker 容器等)
远程 HTTP 服务
(云端 MCP Server)
Stdio(标准输入输出) :MCP Server 以子进程形式运行,通过 stdin/stdout 交换 JSON-RPC 消息。配置格式是 { type: 'stdio', command: 'npx', args: ['@some/mcp-server'] }。这是最简单的部署方式,不需要启动单独的服务,Claude Code 启动时会自动拉起对应的子进程。
SSE(Server-Sent Events):MCP Server 通过 HTTP 长连接推送事件流。适合本地运行的 HTTP 服务,比如跑在 Docker 里的数据库 MCP Server。
Streamable HTTP:较新的传输方式,支持双向流式通信,适合需要高吞吐量或低延迟的场景,也是大多数云端 MCP Server 推荐的方式。
三种传输层在代码里的选择逻辑很清晰:
typescript
if (config.type === 'stdio') {
transport = new StdioClientTransport({ command, args, env })
} else if (config.type === 'sse') {
transport = new SSEClientTransport(url, fetchOptions)
} else {
transport = new StreamableHTTPClientTransport(url, options)
}
传输层之上是统一的 Client 类(来自 @modelcontextprotocol/sdk),负责 JSON-RPC 消息的序列化/反序列化和协议握手,上层代码不需要关心底层用的是哪种传输。
三、连接启动:枚举工具和资源
Claude Code 启动时,会对每一个已配置的 MCP Server 发起连接,然后调用 tools/list 和 resources/list 拿到这个 Server 暴露的所有能力。这个过程在 getMcpToolsCommandsAndResources() 里完成,对应 src/services/mcp/client.ts。
枚举回来的每一个 MCP 工具,都会被包装成一个 MCPTool 实例------这就是第三篇里提到的「MCP 工具适配器」。从 Claude 的视角看,这些工具和 BashTool、FileReadTool 没有任何区别,都以相同格式出现在 system prompt 里。
除了工具,MCP Server 还可以暴露资源(Resources)------类似于「可读取的文档或数据集」。Claude Code 提供了两个专门工具来访问 MCP 资源:
ListMcpResourcesTool:列出某个 MCP Server 暴露的所有资源ReadMcpResourceTool:读取特定资源的内容
资源和工具的区别在于:工具是「做某件事」(有副作用),资源是「读某个东西」(无副作用)。一个数据库 MCP Server 可能同时暴露「执行 SQL 查询」(工具)和「数据库 schema」(资源)。
四、OAuth 鉴权
很多 MCP Server 需要鉴权------访问 GitHub、Slack、Google Drive 的 MCP Server 需要知道「是哪个用户在操作」。Claude Code 内置了完整的 OAuth 2.0 流程,让这些鉴权对用户尽量透明。
自动 token 刷新
当 MCP 工具调用返回 HTTP 401 错误时,handleOAuth401Error() 会先检查本地是否有缓存的 token,如果 token 过期就调用 checkAndRefreshOAuthTokenIfNeeded() 自动刷新,然后重试工具调用。整个过程对用户是透明的,不会弹出任何提示。
首次授权流程
如果没有缓存的 token(比如第一次使用某个需要鉴权的 MCP Server),Claude Code 会触发 OAuth 授权流程:显示一个授权链接或二维码,等用户在浏览器里完成授权,再把 token 存到本地 Keychain,后续调用就可以自动使用了。
McpAuthTool:主动授权工具
还有一个特殊的工具叫 McpAuthTool------当某些 MCP Server 需要用户主动触发授权(而不是在工具调用失败时被动触发)时,Claude 可以主动调用这个工具,让用户在终端里完成授权操作。
五、Elicitation:MCP Server 主动询问
MCP 2.0 协议引入了一个有趣的能力------Elicitation(主动询问) 。通常是「Claude 调用 MCP 工具」,但 Elicitation 反过来:MCP Server 在工具执行过程中,主动向 Claude(或用户)请求额外信息。
典型场景:一个文件同步工具在执行同步之前需要知道「目标路径是什么」「是否覆盖已有文件」,与其要求调用方在调用时就提供所有参数,不如在执行过程中动态询问。
在 Claude Code 里,MCP Server 发起 Elicitation 的技术信号是一个 -32042 错误码。QueryEngine 收到这个信号后,调用 handleElicitation 回调:
typescript
// QueryEngineConfig 里
handleElicitation?: ToolUseContext['handleElicitation']
这个回调最终会在终端 UI 里渲染一个类似 AskUserQuestionTool 的交互界面,让用户填写所需信息,再把结果返回给 MCP Server,让它继续执行。
整个流程如图所示:
用户 MCP Server MCP Client Claude 用户 MCP Server MCP Client Claude #mermaid-svg-lBDohdZsjWPb7fbu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lBDohdZsjWPb7fbu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lBDohdZsjWPb7fbu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lBDohdZsjWPb7fbu .error-icon{fill:#552222;}#mermaid-svg-lBDohdZsjWPb7fbu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lBDohdZsjWPb7fbu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lBDohdZsjWPb7fbu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lBDohdZsjWPb7fbu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lBDohdZsjWPb7fbu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lBDohdZsjWPb7fbu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lBDohdZsjWPb7fbu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lBDohdZsjWPb7fbu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lBDohdZsjWPb7fbu .marker.cross{stroke:#333333;}#mermaid-svg-lBDohdZsjWPb7fbu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lBDohdZsjWPb7fbu p{margin:0;}#mermaid-svg-lBDohdZsjWPb7fbu .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-lBDohdZsjWPb7fbu text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-lBDohdZsjWPb7fbu .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-lBDohdZsjWPb7fbu .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-lBDohdZsjWPb7fbu .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-lBDohdZsjWPb7fbu .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-lBDohdZsjWPb7fbu #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-lBDohdZsjWPb7fbu .sequenceNumber{fill:white;}#mermaid-svg-lBDohdZsjWPb7fbu #sequencenumber{fill:#333;}#mermaid-svg-lBDohdZsjWPb7fbu #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-lBDohdZsjWPb7fbu .messageText{fill:#333;stroke:none;}#mermaid-svg-lBDohdZsjWPb7fbu .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-lBDohdZsjWPb7fbu .labelText,#mermaid-svg-lBDohdZsjWPb7fbu .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-lBDohdZsjWPb7fbu .loopText,#mermaid-svg-lBDohdZsjWPb7fbu .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-lBDohdZsjWPb7fbu .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-lBDohdZsjWPb7fbu .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-lBDohdZsjWPb7fbu .noteText,#mermaid-svg-lBDohdZsjWPb7fbu .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-lBDohdZsjWPb7fbu .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-lBDohdZsjWPb7fbu .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-lBDohdZsjWPb7fbu .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-lBDohdZsjWPb7fbu .actorPopupMenu{position:absolute;}#mermaid-svg-lBDohdZsjWPb7fbu .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-lBDohdZsjWPb7fbu .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-lBDohdZsjWPb7fbu .actor-man circle,#mermaid-svg-lBDohdZsjWPb7fbu line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-lBDohdZsjWPb7fbu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 调用 MCP 工具(tool_use)JSON-RPC 请求返回 -32042 Elicitation 请求\n(需要额外信息:目标路径)触发 handleElicitation 回调显示交互表单\n「请输入目标路径:」填写并确认提供请求的额外信息正常执行并返回结果工具调用成功
六、官方 MCP 注册表预取
main.tsx 启动时有一行不太引人注意但很有意思的代码:
typescript
prefetchOfficialMcpUrls()
Claude Code 维护一个官方 MCP Server 列表,里面包含由 Anthropic 或主流服务提供的 MCP Server(src/services/mcp/officialRegistry.ts)。启动时就预取这个列表,是为了让 /mcp add 命令能立刻弹出「推荐的 MCP Server」列表,不需要现场等待网络请求。
这个设计背后的思路是:用户在使用 /mcp add 时,处于「想要扩展能力」的高意图状态,任何加载延迟都会打断这个体验。提前预热,让用户在需要的那一刻看到「丝滑」的响应,而不是一个菊花转圈。
七、MCP Server 的审批机制
Claude Code 不会在未经用户确认的情况下静默连接任何 MCP Server。src/services/mcpServerApproval.tsx 实现了 MCP Server 的信任审批流程:
- 首次连接某个 MCP Server 时,会显示服务器信息和它暴露的工具列表,请用户确认是否信任
- 审批结果存在本地配置里,下次启动不需要重新确认
- 远程管理的 MCP Server(通过
remoteManagedSettings下发的)有单独的审批通道,企业可以统一配置
这个审批机制的存在是有必要的------MCP Server 本质上是在用户机器上运行的代码,能访问文件系统、执行网络请求、操作用户数据。不加审批地允许任意 MCP Server 连接,相当于无条件信任任何第三方脚本。
学习完本篇,大家对以下问题应该有了清晰的认识:
- MCP 是一套开放的工具暴露标准,让任何服务都能以统一方式接入 Claude
- 三种传输层(Stdio、SSE、Streamable HTTP)对应不同部署场景,上层接口统一
- OAuth 鉴权对用户尽量透明,包括自动 token 刷新和首次授权引导
- Elicitation 是 MCP Server 向 Claude 主动询问信息的机制,双向通信
- MCP Server 首次连接需要用户明确审批,不静默信任
接下来,进入最后一篇------多智能体协调 ,AgentTool 和 Coordinator Mode 是如何让多个 Claude 实例协同工作的。