跳转至

Deep Dive | src/services/mcp/client.ts 3348 行 — MCP Client 完整实现

重要性:⭐⭐⭐⭐(MCP 协议核心——连接 / 工具 / 资源 / 命令 / 重连 / 鉴权 全实现) 真实位置src/services/mcp/client.ts3348 行角色: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 判别器

export function isMcpSessionExpiredError(error: Error): boolean {
  // 行 193
  // instanceof 或错误信息匹配
}

判别函数——上层代码可判断是否需要重新连接。


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)

async function createNodeWsClient(url: string): Promise<WsClientLike> { ... }

Node WebSocket 客户端工厂——某些 MCP 服务器用 WS 协议。

5.3 wrapFetchWithTimeout(行 492)

export function wrapFetchWithTimeout(baseFetch: FetchLike): FetchLike {
  // 给 fetch 加 60s 超时
}

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)

export const fetchResourcesForClient = memoizeWithLRU(
  async (client) => { ... }
);

获取 client 的所有 resources

7.3 fetchCommandsForClient(行 2033-2116)

export const fetchCommandsForClient = memoizeWithLRU(
  async (client) => { ... }
);

获取 client 的所有 commands(MCP 的"斜杠命令")。

7.4 MCP_FETCH_CACHE_SIZE = 20

const 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

export function getMcpServerConnectionBatchSize(): number { ... }

批量大小——env 可配置。


9. 资源预取

export function prefetchAllMcpResources(...): void {
  // 行 2408
  // 启动后预取所有 MCP resources
}

预取——首屏后并行(不阻塞)。


10. 结果转换

export async function transformResultContent(...): Promise<...> {
  // 行 2478
  // 转换 MCP 工具结果为 LLM 友好的格式
}

async function persistBlobToTextBlock(...): Promise<...> {
  // 把 binary blob 写入临时文件,转成文本块
}

2 个 helper: - transformResultContent —— 标准化 MCP 结果 - persistBlobToTextBlock —— 大 blob 持久化(避免消息超限)


11. IDE RPC

export async function callIdeRpc(...): Promise<...> {
  // 行 2116
  // 调用 IDE 的 MCP 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. 阅读建议

  1. 看 3 个 Error 类(行 152-220)—— 错误处理设计
  2. 看 15 min auth 缓存(行 257-322)—— UX 优化
  3. 看 connectToServer 入口(行 595)—— 协议核心
  4. 跳看 3 个 fetch 函数(行 1743-2116)—— 缓存策略
  5. 看 getMcpToolsCommandsAndResources(行 2226)—— 批量入口

18. 与其他深度拆解的关系

文件 关系
mcp/types.ts 类型定义
mcp/auth.ts OAuth 实现
mcp/officialRegistry.ts 官方 MCP 注册表
mcp/client/*.ts 具体 transport 实现
mcp/tools/... MCP 工具列表 / 资源列表

19. 阅读清单

  1. ✅ 看 3 个 Error 类(行 152-220)
  2. ✅ 看 15 min auth 缓存(行 257-322)
  3. ✅ 看 connectToServer 入口(行 595)
  4. ✅ 看 3 个 fetch 函数(行 1743-2116)
  5. ✅ 看 getMcpToolsCommandsAndResources(行 2226)
  6. 📌 对照 topics/deep-dive-mcp-auth.md 看 OAuth
  7. 📌 对照 topics/mcp-protocol-deep-dive.md 看协议

20. 练习任务

  1. 数 transport 支持(grep)—— 4 种
  2. 数 memoize 缓存层(grep)—— 2 层
  3. 数 feature('X')(grep)—— 看 MCP client 的 DCE 范围
  4. 找一个 reconnect 路径(grep reconnect)—— 解读重连逻辑
  5. 手写一个最简 MCP client(~50 行)—— 用 stdio 启一个 python mcp server
  6. 思考:MCP 协议的"4 种 transport"为什么必要?什么时候该用哪个?