Deep Dive | src/services/mcp/client.ts 3348 行 — MCP Client 完整实现¶
重要性:⭐⭐⭐⭐(MCP 协议核心——连接 / 工具 / 资源 / 命令 / 重连 / 鉴权 全实现) 真实位置:
src/services/mcp/client.ts(3348 行) 角色:MCP(Model Context Protocol)客户端的完整实现——stdin/stdout、SSE、Streamable HTTP、OAuth、reconnect、LRU 缓存 关联:topics/deep-dive-mcp-auth.md(OAuth 鉴权)、topics/deep-dive-mcp-protocol.md(协议规范)
1. 文件全景¶
mcp/client.ts (3348 行)
│
├── 行 1-150 :imports + 3 个 feature-gated 懒加载
│
├── 行 152-220 :3 个 Error class
│ ├── McpAuthError (export)
│ ├── McpSessionExpiredError
│ └── McpToolCallError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
│
├── 行 224-255 :3 个 timeout 常量 + 3 个 helper
│ ├── DEFAULT_MCP_TOOL_TIMEOUT_MS (100s)
│ ├── MAX_MCP_DESCRIPTION_LENGTH (2048)
│ ├── getMcpToolTimeoutMs()
│ ├── computerUseWrapper (CHICAGO_MCP)
│ └── isComputerUseMCPServer (CHICAGO_MCP)
│
├── 行 257-322 :15 min Auth Cache
│ ├── MCP_AUTH_CACHE_TTL_MS (15 min)
│ ├── McpAuthCacheData type
│ ├── getMcpAuthCachePath / getMcpAuthCache
│ ├── isMcpAuthCached / setMcpAuthCacheEntry
│ └── clearMcpAuthCache (export)
│
├── 行 323-370 :mcpBaseUrlAnalytics + handleRemoteAuthFailure
│
├── 行 372-450 :createClaudeAiProxyFetch + WsClientLike + createNodeWsClient
│
├── 行 449-490 :IMAGE_MIME_TYPES + getConnectionTimeoutMs + MCP_REQUEST_TIMEOUT_MS
│
├── 行 492-560 :wrapFetchWithTimeout + getMcpServerConnectionBatchSize + getRemoteMcpServerConnectionBatchSize
│
├── 行 563-595 :isLocalMcpServer + ALLOWED_IDE_TOOLS + isIncludedMcpTool + getServerCacheKey
│
├── 行 595-1648 :**connectToServer (memoize)** — **~1050 行核心实现**
│ ├── Transport 协商 (stdio/sse/streamable-http/ws)
│ ├── 鉴权 / OAuth
│ ├── Tool 列表
│ ├── 资源列表
│ ├── 命令列表
│ ├── Reconnect
│ └── Error 处理
│
├── 行 1648-1725:clearServerCache + ensureConnectedClient + areMcpConfigsEqual
│
├── 行 1726-1740:MCP_FETCH_CACHE_SIZE + mcpToolInputToAutoClassifierInput
│
├── 行 1743-2000:**fetchToolsForClient (memoizeWithLRU)** — ~260 行
│
├── 行 2000-2033:**fetchResourcesForClient (memoizeWithLRU)**
│
├── 行 2033-2116:**fetchCommandsForClient (memoizeWithLRU)**
│
├── 行 2116-2218:callIdeRpc + reconnectMcpServerImpl
│
├── 行 2218-2408:processBatched + **getMcpToolsCommandsAndResources** — 批量连接入口
│
├── 行 2408-2478:prefetchAllMcpResources
│
├── 行 2478-2632:transformResultContent + persistBlobToTextBlock
│
├── 行 2632+ :MCPResultType + ~10 个其他 helpers
核心洞察: - connectToServer 是 1050 行——MCP 协议本身复杂 - 3 个 memoizeWithLRU 缓存——避免重复连接 - 3 个 timeout + 1 个 15min auth 缓存——全方位保护
2. 2 个 Error class(行 152-220)¶
export class McpAuthError extends Error { ... }
class McpSessionExpiredError extends Error { ... }
export class McpToolCallError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS extends TelemetrySafeError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS { ... }
3 个错误类:
- McpAuthError —— 鉴权失败(export,外部可 catch)
- McpSessionExpiredError —— session 过期(不 export——内部用)
- McpToolCallError —— 工具调用失败(带 telemetry-safe 标识——确保错误信息可安全上报)
命名约定:_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS 后缀——作者手动验证该字段不含代码/路径,可安全上报到 telemetry。
2.1 isMcpSessionExpiredError 判别器¶
判别函数——上层代码可判断是否需要重新连接。
3. 3 个 Timeout 常量¶
const DEFAULT_MCP_TOOL_TIMEOUT_MS = 100_000_000 // 100s
const MAX_MCP_DESCRIPTION_LENGTH = 2048
const MCP_REQUEST_TIMEOUT_MS = 60000 // 60s
| 常量 | 值 | 用途 |
|---|---|---|
DEFAULT_MCP_TOOL_TIMEOUT_MS |
100s | 工具调用默认超时 |
MAX_MCP_DESCRIPTION_LENGTH |
2048 | 工具描述最大长度(截断) |
MCP_REQUEST_TIMEOUT_MS |
60s | MCP 请求超时 |
2 个 helper:
- getMcpToolTimeoutMs() —— 从 env 读
- getConnectionTimeoutMs() —— 连接超时
4. 15 min Auth 缓存(行 257-322)¶
const MCP_AUTH_CACHE_TTL_MS = 15 * 60 * 1000 // 15 min
type McpAuthCacheData = Record<string, { timestamp: number }>
function getMcpAuthCachePath(): string { ... }
function getMcpAuthCache(): Promise<McpAuthCacheData> { ... }
async function isMcpAuthCached(serverId: string): Promise<boolean> { ... }
function setMcpAuthCacheEntry(serverId: string): void { ... }
export function clearMcpAuthCache(): void { ... }
5 个函数 + 1 个 TTL 常量:
| API | 作用 |
|---|---|
getMcpAuthCachePath |
缓存文件路径 |
getMcpAuthCache |
读缓存 |
isMcpAuthCached |
检查是否在 15min 内已认证 |
setMcpAuthCacheEntry |
标记已认证 |
clearMcpAuthCache (export) |
清空缓存(注销时) |
15 分钟 = "短时记忆"——避免每 5 分钟重新 OAuth 一次。
5. 3 个 Fetch / WebSocket 工具¶
5.1 createClaudeAiProxyFetch(行 372)¶
export function createClaudeAiProxyFetch(innerFetch: FetchLike): FetchLike {
// 把 fetch 包一层,通过 Claude AI 代理
}
Proxy 模式——某些 MCP 服务器需要通过 Claude AI 的代理访问。
5.2 createNodeWsClient(行 436)¶
Node WebSocket 客户端工厂——某些 MCP 服务器用 WS 协议。
5.3 wrapFetchWithTimeout(行 492)¶
60s fetch 超时——避免挂起。
6. connectToServer — 1050 行核心(行 595-1648)¶
memoize 的连接函数——同一 server config 复用同一连接。
6.1 Transport 协商¶
支持 4 种 transport:
| Transport | 用途 |
|---|---|
| stdio | 本地进程(subprocess) |
| SSE | Server-Sent Events |
| Streamable HTTP | HTTP 流式 |
| WebSocket | 双向 WS |
6.2 连接流程¶
1. 选择 transport
2. 建立连接
3. 协商鉴权(OAuth / API key)
4. 列出 tools / resources / commands
5. 缓存结果
6. 失败重试(reconnect)
6.3 Reconnect 机制¶
reconnectMcpServerImpl(行 2137)—— 显式 reconnect。
6.4 鉴权失败处理¶
handleRemoteAuthFailure(行 340)—— 远程 MCP 服务器鉴权失败处理。
6.5 计算机使用包装¶
const computerUseWrapper = feature('CHICAGO_MCP') ? require(...) : null;
const isComputerUseMCPServer = feature('CHICAGO_MCP') ? ... : () => false;
CHICAGO_MCP —— Anthropic 内部的 computer use MCP 包装。
6.6 IDE 工具白名单¶
const ALLOWED_IDE_TOOLS = ['mcp__ide__executeCode', 'mcp__ide__getDiagnostics']
function isIncludedMcpTool(tool: Tool): boolean { ... }
白名单——只允许 IDE 的 2 个工具(executeCode + getDiagnostics)。
7. 3 个 memoizeWithLRU(行 1743-2116)¶
7.1 fetchToolsForClient(行 1743-2000, ~260 行)¶
export const fetchToolsForClient = memoizeWithLRU(
async (client, options) => { ... },
// LRU cache size
);
获取 client 的所有 tools——LRU 缓存避免重复请求。
7.2 fetchResourcesForClient(行 2000-2033)¶
获取 client 的所有 resources。
7.3 fetchCommandsForClient(行 2033-2116)¶
获取 client 的所有 commands(MCP 的"斜杠命令")。
7.4 MCP_FETCH_CACHE_SIZE = 20¶
20 个 client 的 LRU——超过则 LRU 淘汰。
8. 批量连接入口¶
8.1 getMcpToolsCommandsAndResources(行 2226-2408, ~180 行)¶
主入口——批量连接所有 MCP servers,收集 tools + commands + resources。
export async function getMcpToolsCommandsAndResources(...) {
// 1. processBatched 连接所有 servers
// 2. 收集 tools (with computer-use 包装)
// 3. 收集 commands
// 4. 收集 resources
// 5. 排序 + 缓存
}
8.2 processBatched(行 2218)¶
async function processBatched<T>(items: T[], batchSize: number, fn: (item: T) => Promise<T>): Promise<T[]> {
// 分批处理(避免一次性打开太多连接)
}
分批——避免 100 个 MCP 服务器一次性连接(资源耗尽)。
8.3 getMcpServerConnectionBatchSize¶
批量大小——env 可配置。
9. 资源预取¶
预取——首屏后并行(不阻塞)。
10. 结果转换¶
export async function transformResultContent(...): Promise<...> {
// 行 2478
// 转换 MCP 工具结果为 LLM 友好的格式
}
async function persistBlobToTextBlock(...): Promise<...> {
// 把 binary blob 写入临时文件,转成文本块
}
2 个 helper:
- transformResultContent —— 标准化 MCP 结果
- persistBlobToTextBlock —— 大 blob 持久化(避免消息超限)
11. IDE RPC¶
IDE 集成——通过 MCP 调用 IDE 功能。
12. 关键设计模式¶
12.1 memoize + memoizeWithLRU 双层缓存¶
// 第一层:connectToServer (memoize) — 一个 server config → 一个 client
export const connectToServer = memoize(...)
// 第二层:fetchToolsForClient (memoizeWithLRU) — 一个 client → tools 列表
export const fetchToolsForClient = memoizeWithLRU(...)
双层缓存: - 第一层避免重复连接 - 第二层避免重复拉取 tool 列表
12.2 多种 transport 支持¶
stdio / SSE / Streamable HTTP / WebSocket——4 种 transport,统一抽象。
12.3 15 min auth 缓存¶
避免每 5 分钟重新 OAuth——UX 优化。
12.4 失败 reconnect¶
reconnectMcpServerImpl + 内部 reconnect 逻辑——网络抖动容错。
12.5 批量连接 + 分批¶
getMcpToolsCommandsAndResources + processBatched——避免 100 个服务器同时连。
12.6 预取策略¶
prefetchAllMcpResources 在启动后并行——不阻塞首屏。
12.7 Computer Use 包装¶
CHICAGO_MCP feature gate 包装——Anthropic 内部使用。
12.8 IDE 工具白名单¶
ALLOWED_IDE_TOOLS —— 只允许 2 个 IDE 工具(executeCode + getDiagnostics)。
12.9 Telemetry-safe 错误¶
McpToolCallError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS —— 作者手动验证该字段不含敏感信息,可安全上报。
13. 复杂度分析¶
| 维度 | 数字 |
|---|---|
| 总行数 | 3348 |
| 错误类 | 3 |
| 主要函数 | connectToServer (1050 行) + 3 个 fetch + 1 个批量入口 |
| Transport | 4 种 (stdio/SSE/Streamable HTTP/WS) |
| 缓存层 | 2 层 (memoize + LRU) |
| Auth cache TTL | 15 min |
| Tool 超时 | 100s |
| 请求超时 | 60s |
| IDE 工具白名单 | 2 个 |
14. 性能特征¶
14.1 连接时间¶
- 首次连接:~500ms-2s(subprocess 启动 + 协议握手)
- 复用连接:< 1ms(缓存命中)
- Reconnect:~500ms-1s
14.2 Tool 列表¶
- 拉取:~50-200ms(每个 server)
- 缓存命中:< 1ms
14.3 工具调用¶
- stdio subprocess:~10-100ms(IPC)
- HTTP/SSE:~100ms-5s(网络 + 服务器)
14.4 优化¶
- 2 层缓存
- 批量 + 分批
- 预取
- 15 min auth 缓存
15. 与其他文件的关系¶
mcp/client.ts
├──→ @modelcontextprotocol/sdk (MCP 协议实现)
├──→ ./types.js (MCP 类型定义)
├──→ ./auth.js (OAuth / PKCE)
├──→ ./client/*.js (transport 实现)
├──→ ../analytics/ (telemetry)
└──→ ide.js (IDE RPC)
mcp/client.ts 是"门面"——具体 transport / auth 在子目录。
16. 关键洞察¶
16.1 1050 行 connectToServer 是"协议复杂度"¶
MCP 协议本身就复杂——4 种 transport + OAuth + reconnect + error handling。
16.2 2 层缓存是"性能必需"¶
不缓存会每次都重新连接所有 MCP 服务器——慢得不可用。
16.3 15 min auth 缓存 = UX¶
不长不短——避免每 5 分钟重 OAuth。
16.4 Telemetry-safe 命名约定¶
_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS —— 作者主动声明"已验证可上报"。
16.5 Computer Use 是"双轨"¶
CHICAGO_MCP feature gate 包装——Anthropic 内部用 Claude 操作电脑。
16.6 IDE 工具白名单是"安全"¶
只允许 2 个工具——executeCode + getDiagnostics,防止 IDE MCP 暴露其他能力。
16.7 reconnect 显式 API¶
reconnectMcpServerImpl——上层代码可显式触发重连。
16.8 transformResultContent 是"LLM 友好"¶
把 MCP 原始结果转成 Claude API 能用的 content blocks。
17. 阅读建议¶
- 看 3 个 Error 类(行 152-220)—— 错误处理设计
- 看 15 min auth 缓存(行 257-322)—— UX 优化
- 看 connectToServer 入口(行 595)—— 协议核心
- 跳看 3 个 fetch 函数(行 1743-2116)—— 缓存策略
- 看 getMcpToolsCommandsAndResources(行 2226)—— 批量入口
18. 与其他深度拆解的关系¶
| 文件 | 关系 |
|---|---|
mcp/types.ts |
类型定义 |
mcp/auth.ts |
OAuth 实现 |
mcp/officialRegistry.ts |
官方 MCP 注册表 |
mcp/client/*.ts |
具体 transport 实现 |
mcp/tools/... |
MCP 工具列表 / 资源列表 |
19. 阅读清单¶
- ✅ 看 3 个 Error 类(行 152-220)
- ✅ 看 15 min auth 缓存(行 257-322)
- ✅ 看 connectToServer 入口(行 595)
- ✅ 看 3 个 fetch 函数(行 1743-2116)
- ✅ 看 getMcpToolsCommandsAndResources(行 2226)
- 📌 对照 topics/deep-dive-mcp-auth.md 看 OAuth
- 📌 对照 topics/mcp-protocol-deep-dive.md 看协议
20. 练习任务¶
- 数 transport 支持(grep)—— 4 种
- 数 memoize 缓存层(grep)—— 2 层
- 数 feature('X')(grep)—— 看 MCP client 的 DCE 范围
- 找一个 reconnect 路径(grep
reconnect)—— 解读重连逻辑 - 手写一个最简 MCP client(~50 行)—— 用 stdio 启一个 python mcp server
- 思考:MCP 协议的"4 种 transport"为什么必要?什么时候该用哪个?