Claude Code 源码揭秘:MCP 协议集成全拆解,1 个协议 7 种传输,AI 编程工具如何连接整个开发工具链
🚩 2026 年「术哥无界」系列实战文档 X 篇原创计划 第 75 篇,Claude Code 源码揭秘系列第 7 篇
大家好,欢迎来到 术哥无界 | ShugeX | 运维有术。
我是术哥,一名专注于 AI 编程、AI 智能体、Agent Skills、MCP、云原生、AIOps、Milvus 向量数据库的技术实践者与开源布道者!
Talk is cheap, let’s explore。无界探索,有术而行。
本系列已更新至第 7 篇,往期精彩:
-
第 1 篇:5 个 Agent 设计模式拆解 — Agent 调度、Repl、工具路由、权限链、Speculation -
第 2 篇:Buddy 宠物系统 — 情感化 UI 交互设计 -
第 3 篇:Skills 系统 — AI 如何学会你的工作流 -
第 4 篇:三级压缩系统 — 上下文管理的工程艺术 -
第 5 篇:Speculation 预判执行 — AI 的分支预测 -
第 6 篇:权限系统与 YOLO Classifier — AI 如何给自己当保安

图 1:Claude Code MCP 架构全景:从配置发现到连接管理,从 Elicitation 交互到安全权限的完整生态
AI 编程工具有一个绕不开的最后一公里问题:不是 AI 不够聪明,是它碰不到你的工具。
你能读代码、能写代码、能理解架构,但你需要调用 GitHub API、需要操作 Kubernetes 集群、需要连接数据库、需要触发 CI/CD 流水线——这些事情,传统 AI 只能告诉你怎么做,没法帮你做。就像一个只带脑子没带手的顾问。
MCP(Model Context Protocol)就是 Anthropic 给出的答案:定义一套 AI Agent 与外部工具之间的标准化通信协议。而 Claude Code 不只是实现了 MCP 客户端——它围绕这个协议搭建了一套完整的生态系统:多层级配置、7 种传输方式、自动重连、OAuth 认证、Elicitation 双向交互……翻完 services/mcp/ 目录下几千行源码,这套集成的工程密度相当高。
今天这篇,就来完整拆解 Claude Code 的 MCP 协议集成。
1. MCP 是什么:AI Agent 的 USB 协议
协议定义
MCP 是 Anthropic 定义的 AI-Agent-to-Tool 通信协议。如果打个比方:MCP 就是 AI Agent 世界的 USB 协议。
在 USB 出现之前,键盘用 PS/2 接口,打印机用并口,鼠标用串口,每个外设一种线。USB 统一了这一切:一套协议,所有设备通用。MCP 做的是同一件事——在 MCP 之前,每个 AI 工具集成都是定制开发:GitHub 有 GitHub 的 API,Jira 有 Jira 的 API,Slack 有 Slack 的 SDK。MCP 统一了接入方式:AI Agent 只需要实现一个 MCP 客户端,就能和任何实现了 MCP Server 的工具通信。
底层协议是 JSON-RPC 2.0,支持三种核心能力:
|
|
|
|
|---|---|---|
| Tools |
|
|
| Resources |
|
|
| Prompts |
|
|
传输方式也做了标准化:stdio(子进程)、SSE(Server-Sent Events)、HTTP(Streamable HTTP)、WebSocket,以及 Claude Code 自己扩展的 SDK、InProcess、claudeai-proxy。
Claude Code 中的 MCP 架构
Claude Code 的 MCP 实现不是简单的客户端封装,而是分成了清晰的 5 层:
配置层(config.ts / officialRegistry.ts) → 连接层(useManageMCPConnections.ts / client.ts) → 交互层(elicitationHandler.ts) → 安全层(auth.ts / channelPermissions.ts) → 工具层(MCPTool.ts / vscodeSdkMcp.ts)
每一层都有独立的责任和复杂度。接下来逐层拆解。
2. 配置与发现:6 层优先级 + 官方注册表
多层级配置系统
config.ts 有 1578 行,核心入口是 getClaudeCodeMcpConfigs()。它负责合并所有层级的 MCP 配置,层级从低到高:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
~/.claude
|
|
|
|
.mcp.json
|
|
|
|
|
|
|
|
|
企业配置是独占模式:doesEnterpriseMcpConfigExist() 返回 true 时,只有企业配置生效。这个设计很明确:企业管控优先于个人偏好。
配置去重机制
多层级配置带来一个现实问题:同一个 MCP 服务器可能在好几个层级里都配了。dedupPluginMcpServers() 和 dedupClaudeAiMcpServers() 负责去重,基于签名(URL 或 command 数组)判断是否重复。去重策略是手动配置优先:用户显式配置的版本覆盖自动发现的版本。
环境变量展开
expandEnvVars() 支持两种语法:
${VAR}# 直接引用${VAR:-default}# 带默认值
这个细节看着不起眼,但在企业环境里很关键:数据库连接串、API Key 这些敏感信息不会明文写在 .mcp.json 里,而是通过环境变量注入。
官方注册表
officialRegistry.ts 从 api.anthropic.com/mcp-registry/v0/servers 获取官方 MCP 服务器列表。启动时 prefetchOfficialMcpUrls() 以 fire-and-forget 方式预取,不阻塞主流程。isOfficialMcpUrl() 用来判断一个 URL 是否属于官方注册的服务器——这和后面的安全策略挂钩:官方服务器和第三方服务器的信任等级不同。
企业策略过滤
isMcpServerAllowedByPolicy() 支持按名称、命令、URL 三种维度匹配黑白名单。企业的 allowedMcpServers / deniedMcpServers 可以精确控制员工能用哪些 MCP 工具。这又是企业场景的现实需求:不是所有 SaaS 工具都允许接入公司内部数据。
3. 连接管理:7 种传输 + 2 阶段加载
这是整个 MCP 集成里代码量较大、设计密度较高的部分。useManageMCPConnections.ts 有 1141 行,client.ts 有 1343+ 行。两个文件加起来接近 2500 行,管理着 MCP 连接的完整生命周期。
传输层矩阵
Claude Code 实现了 7 种传输方式,覆盖从本地到远程、从子进程到进程内的所有场景:
|
|
|
|
|---|---|---|
| stdio | StdioClientTransport |
|
| SSE | SSEClientTransport |
|
| HTTP | StreamableHTTPClientTransport |
|
| WebSocket | WebSocketTransport |
|
| SDK |
|
|
| claudeai-proxy |
|
|
| InProcess | InProcessTransport |
|
InProcessTransport 值得多说两句。它用 createLinkedTransportPair() 创建一对配对的传输端点,send() 的消息通过 queueMicrotask 异步投递到对端的 onmessage。Chrome MCP 和 Computer Use 就是通过这种方式集成的:不走网络,不走子进程,直接在进程内调用。零延迟,零序列化开销。
// 源码路径:services/mcp/InProcessTransport.ts// 核心逻辑:配对传输,进程内直接通信send(message) → queueMicrotask → 对端 onmessage
两阶段加载策略
连接管理采用了两阶段加载:
Phase 1 — 本地配置(快速,纯文件读取):启动时立即加载本地所有层级的 MCP 配置,建立连接。
Phase 2 — claude.ai 远程配置(可能较慢,需要网络请求):异步拉取 Web 端的 MCP 配置,加载完成后补充或覆盖本地配置。
这个设计很务实:不让远程配置拖慢启动速度。用户打开 Claude Code 就能先用本地配置的工具,远程配置后台慢慢加载。
连接状态机
一个 MCP 连接从发现到可用,经历的状态变迁:
discovered → pending → connecting → connected ↓ (error/onclose) reconnecting → connected ↓ (max retries) failed ↓ (auth required) needs-auth ↓ (user disable) disabled
自动重连只针对 SSE/HTTP/WS 等远程传输。stdio 和 sdk 不重连——子进程挂了就是挂了,重连没有意义。
重连参数:
// 源码路径:services/mcp/useManageMCPConnections.tsconst MAX_RECONNECT_ATTEMPTS = 5const INITIAL_BACKOFF_MS = 1000// 初始退避 1 秒const MAX_BACKOFF_MS = 30000// 最大退避 30 秒
指数退避,5 次上限。第一次 1 秒后重试,第二次 2 秒,第三次 4 秒……超过 5 次标记为 failed。这和网络协议的重连策略一模一样,不算新奇,但该有的都有了。

图 2:MCP 连接生命周期状态机:从 discovered 到 connected/failed/disabled 的完整变迁路径
批量更新优化
MCP_BATCH_FLUSH_MS = 16ms——一个容易被忽略但很精巧的优化。16ms 是一帧的时间(60fps)。当多个 MCP 连接的状态在同一帧内发生变化时,不会逐个触发 React 状态更新,而是在 16ms 窗口内合并成一次批量更新。这对有十几个 MCP 服务器同时连接的场景很关键:避免状态更新风暴导致 UI 卡顿。
动态启停
toggleMcpServer 支持运行时动态启用/禁用 MCP 服务器,不需要重启 Claude Code。禁用的服务器进入 disabled 状态,工具列表被清空,不再参与后续的工具发现和调用。
超时设计的有意思之处
请求超时 60 秒(MCP_REQUEST_TIMEOUT_MS = 60000),这个很正常。但工具调用超时设了 100,000,000 毫秒(约 27.8 小时):
// 源码路径:services/mcp/client.tsconst DEFAULT_MCP_TOOL_TIMEOUT_MS = 100_000_000 // ~27.8 hours
乍一看有点离谱,但仔细想想合理:MCP 工具可以是任何东西,包括长时间运行的任务(比如跑测试套件、训练模型、执行大型数据库迁移)。如果超时设太短,这些合理的长任务会被误杀。27.8 小时基本上等于”不限制”——但留了一个上限,防止真正的死循环。
4. Elicitation:工具也可以跟用户聊天
这是整个 MCP 集成里让我觉得设计密度相当高的功能。
什么是 Elicitation
传统的工具调用模型是单向的:AI 调用工具 → 工具返回结果。但现实里很多工具需要和用户交互:OAuth 授权需要用户在浏览器里点确认,支付工具需要用户选择支付方式,部署工具可能需要用户确认目标环境。
Elicitation 就是解决这个问题的:MCP 服务器可以主动向用户提问,用户回答后,工具拿到答案继续执行。
两种模式
|
|
|
|
|---|---|---|
| form |
|
|
| url |
|
|
三阶段流程
阶段 1:MCP 服务器发送 ElicitRequest ↓阶段 2:Claude Code 通过 AppState.elicitation.queue 弹出 UI ↓ (Hook 预处理:runElicitationHooks) ↓阶段 3:用户响应 → Hook 后处理 → 返回 ElicitResult → MCP 服务器
elicitationHandler.ts(313 行)实现了完整的三阶段流程。几个关键细节:
Hook 系统:runElicitationHooks() 可以在 UI 展示前做预处理,甚至程序化自动响应——不需要用户干预。runElicitationResultHooks() 可以在返回前修改用户的响应。这套 Hook 机制意味着 Elicitation 可以被完全自动化:对于已知的安全操作,Hook 直接返回预设答案,用户全程无感。
URL 模式的完成确认:URL 模式下,用户被导向外部页面完成操作(比如 OAuth)。但问题是:Claude Code 怎么知道用户已经操作完了?答案是 ElicitationCompleteNotificationSchema——MCP 服务器在检测到用户操作完成后,主动发送完成通知。还有一个 onWaitingDismiss 回调,处理用户在等待期间关闭了弹窗的情况。
// 源码路径:services/mcp/elicitationHandler.ts// 三阶段核心流程(简化)MCP Server → ElicitRequest → runElicitationHooks() // 预处理,可程序化自动响应 → UI 展示(form/url) → 用户响应 → runElicitationResultHooks() // 后处理,可修改响应 → ElicitResult → MCP Server

图 3:Elicitation 三阶段交互流程:从 MCP Server 发起请求,到 Hook 预处理、UI 展示、用户响应、Hook 后处理,再到最终返回结果
设计哲学
Elicitation 打破了传统”AI 调用工具、工具返回结果”的单向模型。它让工具变成了有状态、有交互的参与者。这个设计的意义在于:MCP 服务器不需要把所有需要的输入都提前声明在参数里——它可以在执行过程中按需获取。
说到底,Elicitation 让 MCP 从”函数调用”升级成了”对话式调用”。工具不再是被动的执行者,而是可以主动和用户沟通的协作者。
5. 安全与权限:OAuth + Channel + 白名单三重门
MCP 连接涉及外部工具,安全问题不可能回避。Claude Code 在这一层投入的代码量不小:auth.ts 有 1426+ 行,channelPermissions.ts 有 240 行,channelAllowlist.ts 有 76 行。
OAuth 认证完整实现
ClaudeAuthProvider 实现了完整的 OAuth 2.0 流程,不是阉割版:
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
认证发现也做了自动化:按照 RFC 9728 → RFC 8414 的顺序自动发现 OAuth 服务器元数据。这意味着 MCP 服务器只需要声明自己的 OAuth 端点,Claude Code 就能自动完成整个认证流程。
// 源码路径:services/mcp/auth.ts → ClaudeAuthProvider// 认证发现链RFC 9728 → RFC 8414 → 自动发现 OAuth Server Metadata// 支持 authServerMetadataUrl 显式配置覆盖
还有个有意思的细节:normalizeOAuthErrorBody() 专门处理 Slack 等非标准 OAuth 服务器的错误响应。现实世界不是所有服务都严格遵循 OAuth 规范,这种兼容性处理体现了实战经验。
Token 存储用了 macOS Keychain,基于 getServerKey() 生成唯一键(名称 + 配置哈希)。认证缓存 TTL 是 15 分钟(MCP_AUTH_CACHE_TTL_MS = 15 * 60 * 1000)。
XAA 认证
XAA(Cross-App Access)是跨应用访问的认证机制。当一个 MCP 服务器需要访问另一个应用的资源时,XAA 提供了安全的跨应用认证通道。这个场景在微服务架构和 SaaS 集成中很常见。
Channel 权限系统
Channel 是 MCP 服务器与 Claude Code 之间的消息通道。channelPermissions.ts 实现了权限中继:
// 源码路径:services/mcp/channelPermissions.ts → shortRequestId// 生成 5 字母 ID:a-z 减 l,25^5 ≈ 9.8M 空间// 还有 ID_AVOID_SUBSTRINGS 过滤不雅词汇
这个 ID 生成器有个细节:字母表去掉了 l(小写 L),因为 l 和 1 容易混淆。25 个字母的 5 次方约 980 万种组合,对于权限请求 ID 来说空间足够大。
白名单门控
channelAllowlist.ts 检查 MCP 插件是否在 GrowthBook 白名单中,isChannelsEnabled() 是全局开关。两层门控:全局开关控制 Channel 功能是否开启,白名单控制具体哪个插件可以使用 Channel。

图 4:MCP 安全权限体系:OAuth 认证层、Channel 权限层、白名单门控的三重防护
你在项目中用过类似的 OAuth 动态注册 + 权限中继方案吗?欢迎在评论区聊聊。
6. 工具集成:MCPTool 桥接一切
MCPTool:统一适配器
MCPTool.ts 只有 77 行,但它做的事很关键:把 MCP 工具转换为 Claude Code 内部的 Tool 接口。
// 源码路径:services/mcp/MCPTool.ts// 核心属性{ isMcp: true, // 标记为 MCP 工具 inputSchema: z.object({}).passthrough(), // 允许任意输入 checkPermissions: passthrough, // 权限由上层系统控制}
passthrough() schema 意味着输入校验不在这一层做——MCP 工具的参数校验由 MCP 服务器自己负责。Claude Code 只负责转发。这是一个合理的职责划分:每个 MCP 服务器最清楚自己需要什么参数。
工具调用完整链路
当 Claude 决定调用一个 MCP 工具时,请求走过这样一条链路:
Claude(LLM 输出 tool_use) → MCPTool.call() → MCP Client(参数序列化) → Transport 层(stdio/SSE/HTTP/WS/...) → MCP Server(执行具体操作) → Result(返回结果) ← Truncation(结果截断,防止超出上下文窗口) ← Permission Check(权限检查) ← 返回给 Claude
整条链路中最容易出问题的是 Truncation 环节:MCP 工具的返回结果可能很大(比如一整个数据库查询结果),需要截断后才能塞回 Claude 的上下文窗口。这个截断逻辑在 MCP Client 层处理。

图 5:MCP 工具调用完整链路:从 Claude 的 tool_use 输出,经过 MCPTool 适配、Transport 传输、MCP Server 执行,再经过 Truncation 和 Permission Check 返回
VSCode SDK 双向通信
vscodeSdkMcp.ts(112 行)实现了 Claude Code 与 VSCode 扩展的双向 MCP 通信:
-
notifyVscodeFileUpdated():文件变更时通知 VSCode -
setupVscodeSdkMcp():设置通知处理器,包括log_event(从 VSCode 接收事件)和experiment_gates(向 VSCode 发送实验开关)
这不是单向的”AI 使用工具”,而是双向的协同:VSCode 可以向 Claude Code 推送事件,Claude Code 也可以向 VSCode 发送控制指令。
MCP 资源(Resources)
除了工具调用,MCP 还支持资源读取。ResourceListChangedNotificationSchema 监听资源列表变更,ReadMcpResourceTool 提供资源读取能力。这允许 MCP 服务器暴露只读资源(如配置文件、数据库 Schema)给 Claude,而不需要经过工具调用。
连接错误处理
client.ts 定义了完整的错误类型体系:
|
|
|
|---|---|
McpAuthError |
|
McpSessionExpiredError |
|
McpToolCallError |
|
还有终端错误检测:ECONNRESET、ETIMEDOUT、EPIPE、EHOSTUNREACH 等网络错误被识别为致命错误,连续 3 次(MAX_ERRORS_BEFORE_RECONNECT = 3)触发重新连接。connectToServer 使用 memoize 缓存,避免重复建立连接。fetchToolsForClient 和 fetchCommandsForClient 也有缓存,同一连接的工具列表和命令列表不会重复拉取。
总结
拆完整个 MCP 集成,有三个观察。
MCP 的本质是 AI Agent 的 USB 协议。标准化接入方式带来的好处和 USB 一样:一次实现,到处使用。Claude Code 围绕 MCP 搭建的生态系统——多层级配置、7 种传输、自动重连、OAuth 认证——不是简单的客户端封装,而是一个完整的连接管理框架。这个框架的复杂度反映了一个现实:生产级的 AI Agent 工具集成,远比调一个 API 复杂得多。
Elicitation 是杀手级功能。它打破了工具调用的单向模型,让 MCP 服务器可以主动和用户交互。OAuth 授权、参数确认、外部操作确认——这些场景没有 Elicitation 根本做不了。从工程角度看,Hook 系统让 Elicitation 可以被完全自动化,这对用户体验很关键:普通用户看到弹窗交互,高级用户配置 Hook 实现无感操作。
对 AI Agent 开发者的启示:Claude Code 的 MCP 实现展示了一个成熟的 AI Agent 工具集成应该长什么样。不是简单的 SDK 封装,而是配置管理、连接生命周期、安全认证、用户交互每一层都要考虑到。特别是那个 27.8 小时的工具调用超时——它说明 AI Agent 调用的工具可能是任何东西,超时策略不能用传统 API 的思维来设计。
从趋势看,MCP 这类标准化协议会成为 AI Agent 生态的基础设施。就像 USB 让外设即插即用,MCP 让工具即插即用。Claude Code 的实现给出了一个相当完整的参考。
好啦,谢谢你观看我的文章,如果喜欢可以点赞转发给需要的朋友,我们下一期再见!敬请期待!
扫码关注,获取更多 AI 工具的实战经验和最佳实践。不错过每一篇干货!

夜雨聆风