《MCP 协议设计与实现》完整目录
- 前言
- 第1章 为什么需要 MCP
- 第02章 架构总览:Host-Client-Server 模型
- 第03章 JSON-RPC 与消息格式
- 第04章 生命周期与能力协商
- 第05章 Tool:让 Agent 调用世界
- 第6章 Resource:结构化的上下文注入
- 第7章 Prompt:可复用的交互模板
- 第8章 TypeScript Server 实现剖析
- 第09章 TypeScript Client 实现剖析
- 第10章 Python Server 实现剖析
- 第11章 Python Client 实现剖析
- 第12章 STDIO 传输:本地进程通信
- 第13章 Streamable HTTP:远程流式传输
- 第14章 SSE 与 WebSocket
- 第15章 OAuth 2.1 认证框架
- 第16章 服务发现与客户端注册
- 第17章 sampling
- 第18章 Elicitation、Roots 与配置管理
- 第19章 Claude Code 的 MCP 客户端:12 万行的实战(当前)
- 第20章 从零构建一个生产级 MCP Server
- 第21章 设计模式与架构决策
第19章 Claude Code 的 MCP 客户端:12 万行的实战
"The proof of a protocol is not in its specification, but in the 120,000 lines of code that someone actually ships with it."
:::tip 本章要点
- 理解 Claude Code 作为 MCP 客户端的整体架构及其与 Claude Desktop 的本质区别
- 掌握 Server 发现机制:从 claude_desktop_config.json 到动态注册
- 深入 OAuth 授权流程在 CLI 环境下的特殊处理
- 理解工具延迟加载(Deferred Loading)如何解决大规模 MCP 集成的性能问题
- 认识 MCP 工具如何与 Claude Code 内置工具在统一接口下共存 :::
19.1 为什么要研究 Claude Code
在前面的章节中,我们从协议规范和 SDK 源码的角度理解了 MCP 的方方面面。但规范是抽象的,SDK 提供的是积木,真正的挑战在于用这些积木搭建一个面向百万用户的产品。
Claude Code 是 Anthropic 官方的命令行 AI 编程工具。截至 2026 年初,其代码库中与 MCP 相关的代码量超过 12 万行------这不仅是目前最大规模的 MCP 客户端实现,也是 MCP 协议本身的"试验场":许多协议特性是在 Claude Code 的实践中被发现需要、被提出、最终被纳入规范的。
研究 Claude Code 的 MCP 实现,不是为了模仿它的每一行代码,而是为了理解一个成熟的 MCP 客户端需要解决哪些协议规范没有明说的工程问题。
19.2 架构全景
Claude Code 的 MCP 客户端架构可以分为五个层次:
统一内置工具 + MCP 工具"] end subgraph "MCP 客户端层" SD["Server Discovery
配置发现"] SM["Server Manager
生命周期管理"] OA["OAuth Handler
认证管理"] DL["Deferred Loader
延迟加载"] end subgraph "传输层" ST["Stdio Transport"] HT["Streamable HTTP Transport"] end subgraph "MCP Servers" S1["本地 Server
(stdio)"] S2["远程 Server
(HTTP)"] S3["内置 Server
(filesystem, etc.)"] end CLI --> AG REPL --> AG AG --> TM TM --> SM SM --> SD SM --> OA SM --> DL SM --> ST SM --> HT ST --> S1 HT --> S2 ST --> S3 style AG fill:#3b82f6,color:#fff,stroke:none style TM fill:#8b5cf6,color:#fff,stroke:none style SM fill:#ec4899,color:#fff,stroke:none style SD fill:#f59e0b,color:#fff,stroke:none style OA fill:#10b981,color:#fff,stroke:none style DL fill:#6366f1,color:#fff,stroke:none
与 Claude Desktop 不同,Claude Code 是一个 CLI-first 的应用。这意味着它面临一些独特的挑战:
- 没有持久的 GUI 窗口来展示 Elicitation 表单
- OAuth 回调需要临时启动本地 HTTP Server 来接收
- 终端环境对并发连接数和资源占用更敏感
- 用户期望启动速度极快,不能等待所有 Server 初始化
19.3 Server 发现与配置
19.3.1 配置文件层级
Claude Code 使用与 Claude Desktop 兼容的配置格式,但引入了更灵活的层级系统:
bash
~/.claude/claude_desktop_config.json # 全局配置
./.claude/claude_desktop_config.json # 项目级配置
./.mcp.json # MCP 专用配置(简化格式)
一个典型的配置文件:
json
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "ghp_..."
}
},
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"]
},
"remote-db": {
"url": "https://db-mcp.example.com/mcp",
"headers": {
"Authorization": "Bearer ${MCP_DB_TOKEN}"
}
}
}
}
配置合并策略是深度合并,项目级覆盖全局。同名 Server 在项目级配置中出现时完全替换全局配置中的同名条目。
19.3.2 环境变量替换
注意上面 ${MCP_DB_TOKEN} 的用法------Claude Code 支持在配置文件中引用环境变量。这是一个实用的安全措施:密钥不应硬编码在配置文件中,尤其是项目级配置可能被提交到 Git 仓库。
19.3.3 Server 启动与健康检查
Server Manager 在启动时并不立即初始化所有 Server。它采用按需启动策略:
- 解析配置文件,构建 Server 注册表
- 对 stdio 类型的 Server,在首次被引用时才 spawn 子进程
- 对 HTTP 类型的 Server,在首次请求时才建立连接
- 每个 Server 启动后执行 MCP 初始化握手(
initialize→initialized) - 缓存 Server 的能力声明,用于后续的功能路由
如果一个 Server 在启动或初始化过程中失败,Server Manager 会记录错误但不影响其他 Server------这是"优雅降级"的核心体现。
19.4 OAuth 在 CLI 中的实现
19.4.1 无浏览器环境的挑战
对于远程 MCP Server,认证通常通过 OAuth 完成。在有 GUI 的桌面应用中,这很自然------弹出浏览器,完成授权,回调到应用。但在 CLI 环境中,这需要一些巧妙的处理。
Claude Code 的 OAuth 流程:
关键实现细节:
- 临时 HTTP Server 只监听
127.0.0.1的随机端口,最大程度降低安全风险 - 使用 PKCE(Proof Key for Code Exchange)防止 authorization code 被截获
- Token 存储在操作系统的密钥链中(macOS Keychain / Linux Secret Service)
- Refresh token 在过期前自动刷新,用户无感
19.4.2 Token 生命周期管理
OAuth Handler 维护一个 token 缓存,键是 Server URL + 用户标识。每次向远程 Server 发送请求前,都会检查 token 是否即将过期(提前 5 分钟刷新),确保请求不会因为 token 过期而意外失败。
19.5 工具延迟加载(Deferred Loading)
19.5.1 问题的规模
一个典型的 Claude Code 用户可能配置了 5-10 个 MCP Server,每个 Server 暴露 3-20 个工具。这意味着在最坏情况下,系统需要管理上百个工具。如果在每次对话开始时都把所有工具的完整定义(包括 JSON Schema)发送给 LLM,会造成:
- 上下文浪费:工具描述可能占用数千 token,挤占真正有用的对话内容
- 决策干扰:LLM 面对过多工具时,选择正确工具的准确率会下降
- 启动延迟:等待所有 Server 返回工具列表会拖慢首次交互
19.5.2 延迟加载策略
Claude Code 实现了一个精巧的分层加载机制:
第一层:工具名称 + 简要描述。对话开始时,只向 LLM 提供所有可用工具的名称和一行描述。这类似于一个"目录",让 LLM 知道有哪些能力可用,但不占用太多 context。
第二层:完整工具定义。当 LLM 决定使用某个工具时,Claude Code 才获取该工具的完整 JSON Schema,包括详细的参数描述、示例、约束条件等。
这种模式在用户体验中表现为:用户在系统提示中看到类似"以下延迟工具可通过 ToolSearch 获取:Read, Edit, Grep..."的提示,当 Agent 判断需要使用某个工具时,它先调用 ToolSearch 获取完整定义,然后才发起实际的工具调用。
typescript
// 概念性代码,展示延迟加载的核心思路
class DeferredToolLoader {
private summaries: Map<string, ToolSummary> = new Map();
private fullDefinitions: Map<string, Tool> = new Map();
// 对话开始时只加载摘要
async loadSummaries(): Promise<ToolSummary[]> {
const results: ToolSummary[] = [];
for (const server of this.servers) {
const tools = await server.listTools();
for (const tool of tools) {
const summary = { name: tool.name, description: tool.description?.slice(0, 80) };
this.summaries.set(tool.name, summary);
results.push(summary);
}
}
return results;
}
// LLM 需要时才获取完整定义
async getFullDefinition(toolName: string): Promise<Tool | null> {
if (this.fullDefinitions.has(toolName)) {
return this.fullDefinitions.get(toolName)!;
}
// 找到对应的 Server 并获取完整工具信息
const server = this.findServerForTool(toolName);
if (!server) return null;
const tools = await server.listTools();
const tool = tools.find(t => t.name === toolName);
if (tool) {
this.fullDefinitions.set(toolName, tool);
}
return tool ?? null;
}
}
19.5.3 与内置工具的统一
Claude Code 有大量内置工具------Read(读文件)、Edit(编辑文件)、Bash(执行命令)、Grep(搜索)等。MCP 工具需要与这些内置工具在同一个接口下暴露给 LLM。
Tool Manager 维护一个统一的工具注册表:
当 LLM 发起工具调用时,Tool Manager 根据工具名称路由到正确的执行器------内置工具直接在进程内执行,MCP 工具则通过对应的 Server 连接转发 tools/call 请求。对 LLM 来说,两者完全透明。
19.5.4 命名空间与冲突处理
MCP 工具的命名采用 serverName:toolName 的命名空间格式。如果两个 Server 都暴露了名为 search 的工具,它们会被注册为 github:search 和 jira:search,避免冲突。
但如果 MCP 工具名与内置工具名冲突,内置工具优先。这确保了核心功能不会被第三方 Server 意外覆盖------又一个"安全优先"的设计选择。
19.6 Elicitation 与 Sampling 的 CLI 适配
19.6.1 终端中的表单渲染
当 MCP Server 发起 Form Mode Elicitation 时,Claude Code 需要在终端中渲染表单。这比 GUI 应用困难得多------终端没有原生的表单控件。
Claude Code 的做法是将 Elicitation 请求转化为交互式的终端提示:
ini
[MCP Server: github] 请求你提供信息:
请提供你的 GitHub 用户名
用户名 (必填): _
邮箱 (email 格式): _
[Enter] 提交 [Esc] 取消 [Tab] 下一字段
对于枚举类型,使用箭头键选择的列表:
markdown
选择部署环境:
> staging
production
development
19.6.2 Sampling 的隐含实现
Claude Code 对 Sampling 请求的处理尤为独特------它不需要"借用"外部 LLM,因为它本身就运行着 Claude。当 MCP Server 发送 sampling/createMessage 请求时,Claude Code 直接将请求注入到当前的 LLM 对话流中,利用自己的 Claude 模型来响应。
这带来了一个有趣的递归:Server 通过 Client 借用 LLM 的能力,而这个 LLM 正是驱动 Client 的同一个模型。实际上,这使得 MCP Server 可以利用 Claude 的推理能力来增强自己的工具执行,同时保持了协议层面的解耦。
19.7 连接管理与容错
19.7.1 Server 生命周期状态机
每个 Server 连接都有明确的状态:
css
[未启动] → [启动中] → [初始化中] → [就绪] → [运行中]
↓ ↓ ↓ ↓
[启动失败] [初始化失败] [断开] [错误]
↓
[重连中] → [就绪]
Server Manager 为每个状态定义了明确的行为:
- 启动失败:记录错误,标记为不可用,不重试(避免反复 spawn 失败进程)
- 初始化失败:可能是版本不兼容,记录 Server 报告的版本信息,帮助用户诊断
- 断开:对 stdio Server,检测子进程是否退出;对 HTTP Server,检测连接是否中断。自动尝试重连
- 错误:工具调用返回错误不影响连接状态,但连续多次错误可能触发健康检查
19.7.2 超时策略
不同操作的超时时间经过精心调优:
| 操作 | 超时时间 | 理由 |
|---|---|---|
| Server 启动 | 30 秒 | npm 安装可能较慢 |
| 初始化握手 | 10 秒 | 协议交互应快速完成 |
| 工具列表 | 5 秒 | 元数据查询不应耗时 |
| 工具调用 | 可配置,默认 120 秒 | 复杂操作可能耗时较长 |
| Elicitation 响应 | 300 秒 | 等待用户输入 |
19.7.3 资源管理
Claude Code 作为 CLI 工具,用户期望它在退出时干净地释放所有资源。Server Manager 在关闭时:
- 向所有 Server 发送关闭通知
- 等待正在进行的工具调用完成(最多 5 秒)
- 关闭所有传输连接
- 终止所有 stdio 子进程
- 清理临时文件和端口占用
19.8 12 万行代码的启示
Claude Code 的 MCP 实现规模之大,背后是真实的工程复杂性:
配置管理约占 15%------多层级配置合并、环境变量替换、配置验证、迁移升级。
传输与连接约占 25%------stdio 子进程管理、HTTP 连接池、重连逻辑、超时处理、OAuth 全流程。
工具管理约占 20%------延迟加载、命名空间、与内置工具融合、权限控制、工具描述缓存。
Elicitation/Sampling 适配约占 15%------终端 UI 渲染、表单验证、OAuth URL 处理、Sampling 请求路由。
测试与错误处理约占 25%------每个边界条件、每个竞态条件、每个错误路径都需要测试。12 万行中有相当一部分是测试代码。
这个比例揭示了一个重要事实:MCP 客户端的核心复杂性不在协议本身,而在于将协议适配到具体的产品形态中。CLI 有 CLI 的挑战,桌面应用有桌面应用的挑战,Web 应用有 Web 应用的挑战。MCP 协议的抽象层级恰到好处------它定义了"做什么",但把"怎么做"的自由留给了实现者。
19.9 对其他客户端的借鉴
从 Claude Code 的实践中,我们可以提炼出对所有 MCP 客户端实现者的建议:
- 按需连接,延迟加载。不要在启动时初始化所有 Server,用户可能只用其中一两个。
- 配置分层,安全优先。支持全局和项目级配置,密钥通过环境变量或系统密钥链管理。
- 统一工具接口。MCP 工具和内置工具对 LLM 应该透明无差异,命名空间解决冲突。
- 优雅降级。单个 Server 的故障不应影响整个系统,错误信息应帮助用户定位问题。
- 资源意识。CLI 工具对启动速度和内存占用敏感,Web 应用对并发连接数敏感,移动端对电量消耗敏感------了解你的运行环境。
19.10 本章小结
Claude Code 是 MCP 协议在真实产品中最大规模的落地案例。通过研究它的架构,我们看到了:
- Server 发现通过分层配置文件实现,支持全局、项目级、环境变量三个维度
- OAuth 认证在 CLI 环境下通过临时本地 HTTP Server + PKCE 实现,兼顾安全与易用
- 工具延迟加载通过"摘要 + 按需获取"的两层策略,解决了大规模工具集成的 context 压力
- 内置工具与 MCP 工具通过统一的 Tool Manager 融合,对 LLM 透明
- 12 万行代码中真正的复杂性在于将协议适配到具体产品形态,而非协议本身
这些经验告诉我们:设计一个好的 MCP 客户端,既要深入理解协议规范,更要理解你的用户在什么场景下、用什么方式与 AI 工具交互。协议提供了骨架,产品思维才能填充血肉。
在下一章,我们将角色互换------从消费 MCP 的 Client 转向生产 MCP 的 Server,从零开始构建一个生产级 MCP Server。