
🚩 2026 年「术哥无界」系列实战文档 X 篇原创计划 第 75 篇,Claude Code 源码揭秘系列第 7 篇
大家好,欢迎来到 术哥无界 | ShugeX | 运维有术。
我是术哥,一名专注于 AI 编程、AI 智能体、Agent Skills、MCP、云原生、AIOps、Milvus 向量数据库的技术实践者与开源布道者!
Talk is cheap, let's explore。无界探索,有术而行。

图 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 协议集成。
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 | 工具调用 | 执行 SQL 查询、创建 GitHub Issue |
Resources | 资源读取 | 读取数据库 Schema、获取文件内容 |
Prompts | 提示模板 | 预定义的 Prompt 模板 |
传输方式也做了标准化:stdio(子进程)、SSE(Server-Sent Events)、HTTP(Streamable HTTP)、WebSocket,以及 Claude Code 自己扩展的 SDK、InProcess、claudeai-proxy。
Claude Code 的 MCP 实现不是简单的客户端封装,而是分成了清晰的 5 层:
配置层(config.ts / officialRegistry.ts)
→ 连接层(useManageMCPConnections.ts / client.ts)
→ 交互层(elicitationHandler.ts)
→ 安全层(auth.ts / channelPermissions.ts)
→ 工具层(MCPTool.ts / vscodeSdkMcp.ts)每一层都有独立的责任和复杂度。接下来逐层拆解。
config.ts 有 1578 行,核心入口是 getClaudeCodeMcpConfigs()。它负责合并所有层级的 MCP 配置,层级从低到高:
优先级 | 来源 | 说明 |
|---|---|---|
1(低) | claude.ai 连接器 | 远程 Web 平台配置 |
2 | 插件 MCP 服务器 | 来自插件系统 |
3 | 全局用户配置 |
|
4 | 项目配置 |
|
5 | 本地配置 | Gitignore 的项目级配置 |
6(高) | 企业配置 | 独占模式,启用后其他层级全部失效 |
企业配置是独占模式: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 工具都允许接入公司内部数据。
这是整个 MCP 集成里代码量较大、设计密度较高的部分。useManageMCPConnections.ts 有 1141 行,client.ts 有 1343+ 行。两个文件加起来接近 2500 行,管理着 MCP 连接的完整生命周期。
Claude Code 实现了 7 种传输方式,覆盖从本地到远程、从子进程到进程内的所有场景:
传输方式 | 实现 | 适用场景 |
|---|---|---|
stdio |
| 本地命令行工具,子进程通信 |
SSE |
| 远程 HTTP 流式推送 |
HTTP |
| 标准 HTTP 请求/响应 |
WebSocket |
| 实时双向通信 |
SDK | 内部传输 | 插件集成 |
claudeai-proxy | 通过 claude.ai 代理 | Web 端远程访问 |
InProcess |
| 进程内直接调用,零网络开销 |
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.ts
const MAX_RECONNECT_ATTEMPTS = 5
const 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.ts
const DEFAULT_MCP_TOOL_TIMEOUT_MS = 100_000_000 // ~27.8 hours乍一看有点离谱,但仔细想想合理:MCP 工具可以是任何东西,包括长时间运行的任务(比如跑测试套件、训练模型、执行大型数据库迁移)。如果超时设太短,这些合理的长任务会被误杀。27.8 小时基本上等于"不限制"——但留了一个上限,防止真正的死循环。
这是整个 MCP 集成里让我觉得设计密度相当高的功能。
传统的工具调用模型是单向的:AI 调用工具 → 工具返回结果。但现实里很多工具需要和用户交互:OAuth 授权需要用户在浏览器里点确认,支付工具需要用户选择支付方式,部署工具可能需要用户确认目标环境。
Elicitation 就是解决这个问题的:MCP 服务器可以主动向用户提问,用户回答后,工具拿到答案继续执行。
模式 | 行为 | 典型场景 |
|---|---|---|
form | 弹出表单让用户填写 | 工具需要用户输入参数 |
url | 打开 URL 让用户完成操作 | OAuth 授权、外部服务确认 |
阶段 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 从"函数调用"升级成了"对话式调用"。工具不再是被动的执行者,而是可以主动和用户沟通的协作者。
MCP 连接涉及外部工具,安全问题不可能回避。Claude Code 在这一层投入的代码量不小:auth.ts 有 1426+ 行,channelPermissions.ts 有 240 行,channelAllowlist.ts 有 76 行。
ClaudeAuthProvider 实现了完整的 OAuth 2.0 流程,不是阉割版:
能力 | 说明 |
|---|---|
动态客户端注册(DCR) | 不需要预先注册 client_id |
PKCE | 防止授权码拦截攻击 |
Token 刷新 | Access Token 过期后自动续期 |
Token 撤销 | RFC 7009 标准 |
Step-up 认证 | 检测 403 insufficient_scope,触发权限升级 |
认证发现也做了自动化:按照 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(Cross-App Access)是跨应用访问的认证机制。当一个 MCP 服务器需要访问另一个应用的资源时,XAA 提供了安全的跨应用认证通道。这个场景在微服务架构和 SaaS 集成中很常见。
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 动态注册 + 权限中继方案吗?欢迎在评论区聊聊。
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 返回
vscodeSdkMcp.ts(112 行)实现了 Claude Code 与 VSCode 扩展的双向 MCP 通信:
notifyVscodeFileUpdated():文件变更时通知 VSCodesetupVscodeSdkMcp():设置通知处理器,包括 log_event(从 VSCode 接收事件)和 experiment_gates(向 VSCode 发送实验开关)这不是单向的"AI 使用工具",而是双向的协同:VSCode 可以向 Claude Code 推送事件,Claude Code 也可以向 VSCode 发送控制指令。
除了工具调用,MCP 还支持资源读取。ResourceListChangedNotificationSchema 监听资源列表变更,ReadMcpResourceTool 提供资源读取能力。这允许 MCP 服务器暴露只读资源(如配置文件、数据库 Schema)给 Claude,而不需要经过工具调用。
client.ts 定义了完整的错误类型体系:
错误类型 | 说明 |
|---|---|
| 认证失败 |
| 会话过期 |
| 工具调用错误 |
还有终端错误检测: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 的实现给出了一个相当完整的参考。
好啦,谢谢你观看我的文章,如果喜欢可以点赞转发给需要的朋友,我们下一期再见!敬请期待!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。