从Claude Code泄露源码看工程架构:第八章 —— MCP 接入层设计

MCP 框架设计解析

1. 引言

MCP(Multi-Connection Protocol)框架的设计旨在提供一种统一且高效的连接管理机制,支持多种传输协议(如 SSE、WebSocket 和 HTTP),并确保鉴权和重连的灵活性及安全性。本文将深入探讨其核心组件和技术细节。


2. 统一入口设计

在 client.ts 文件中,MCP 的统一入口函数为 connectToServer(),它被装饰器 memoize 包装以避免重复连接和优化资源使用。此函数确保所有协议的会话都通过同一路径进行管理。

2.1 关键特性

  • 唯一性:相同的服务器配置不会创建多个连接实例。
  • 缓存机制:利用 memoize 缓存已建立的连接,提高性能并避免重复工作。
  • 资源节约与状态一致性:减少不必要的网络握手和认证流程。
export const connectToServer = memoize(
  async (
    name: string,
    serverRef: ScopedMcpServerConfig,
    serverStats?: {
      totalServers: number;
      stdioCount: number;
      sseCount: number;
      httpCount: number;
      sseIdeCount: number;
      wsIdeCount: number;
    },
  ): Promise
<MCPServerConnection> => { ... }
);

3. 认证缓存策略

认证缓存 TTL 参数被设置为 15 60 1000 毫秒,即 15 分钟。此设计在用户体验、陈旧风险和401响应处理成本之间取得了平衡。

3.1 权衡因素

维度过短 TTL (如 1 分钟)过长 TTL (如 1 小时)合适的 TTL (15分钟)
重新认证频率频繁,用户体验差稀少适中
陈旧风险很低较高可控
401 处理成本每次都要完整流程可能错过 token 刷新合理折中
257:const MCP_AUTH_CACHE_TTL_MS = 15 * 60 * 1000 // 15 min
...
282:  const entry = cache[serverId];
283:  if (!entry) {
284:    return false;
285:  }
286:  return Date.now() - entry.timestamp < MCP_AUTH_CACHE_TTL_MS;
287:}

4. SSE 长连接处理

针对 SSE(Server-Sent Events),MCP 实现了特殊机制来区分长连接中的超时选项,确保其在网络不稳定时仍能保持稳定状态。

4.1 关键代码片段

619:      if (serverRef.type === 'sse') {
620:        const authProvider = new ClaudeAuthProvider(name, serverRef);
624:        const combinedHeaders = await getMcpServerHeaders(name, serverRef);
627:        const transportOptions: SSEClientTransportOptions = {
628:          authProvider,
632:          fetch: wrapFetchWithTimeout(
633:            wrapFetchWithStepUpDetection(createFetchWithInit(), authProvider),
634:          ),
635:          requestInit: {
636:            headers: {
637:              'User-Agent': getMCPUserAgent(),
638:              ...combinedHeaders,
639:            },
640:          },
641:        };
643:        // IMPORTANT: Always set eventSourceInit with a fetch that does NOT use the timeout wrapper.
648:        transportOptions.eventSourceInit = {
649:          fetch: async (url: string | URL, init?: RequestInit) => {
657:            Accept: 'text/event-stream',
658:          },
659:        });
670:      };
673:      transport = new SSEClientTransport(new URL(serverRef.url), transportOptions);

5. WebSocket 客户端适配

MCP 实现了 WebSocket 的授权头脱敏与运行时环境的灵活适配。

5.1 脱敏逻辑

735:      } else if (serverRef.type === 'ws') {
...
742:        const wsHeaders = { ... };
749:        const wsHeadersForLogging = mapValues(wsHeaders, (value, key) =>
750:          key.toLowerCase() === 'authorization' ? '[REDACTED]' : value,
751:        );

5.2 运行时适配

767:      if (typeof Bun !== 'undefined') {
770:        wsClient = new globalThis.WebSocket(serverRef.url, {...});
776:      } else {
779:        wsClient = await createNodeWsClient(serverRef.url, {...});
782:      }

6. HTTP 协议的 OAuth 和 Session Ingress 协调

对于使用 HTTP 的连接,MCP 实现了 OAuth 和 Session Ingress 之间复杂的协调机制。

6.1 关键代码片段

784:      } else if (serverRef.type === 'http') {
...
801:        const authProvider = new ClaudeAuthProvider(name, serverRef);
821:        const transportOptions: StreamableHTTPClientTransportOptions = { ... };
826:        fetch: wrapFetchWithTimeout(
827:          wrapFetchWithStepUpDetection(createFetchWithInit(), authProvider),
828:        );
...

通过上述设计,MCP 框架成功地解决了多协议连接的复杂性问题,并确保了系统的鲁棒性和安全性。这种统一且灵活的设计模式为高性能和安全的应用开发提供了坚实的基础。


总结

本文详细解析了 MCP 框架的核心组件和技术细节,包括认证缓存策略、SSE 长连接处理、WebSocket 的运行时适配及 HTTP 协议的 OAuth 和 Session Ingress 协调机制。通过这些设计,MCP 成功地简化了多协议连接管理,并提高了系统的整体性能和安全性。此框架为构建高效且安全的应用程序提供了强大的工具集。

第四类传输:身份代理

在处理特定服务器类型时,例如 Claude.ai Proxy,架构会有所不同以适应特定的身份验证机制和 URL 生成方式。

文件位置 :services/mcp/client.ts:868-904

868:      } else if (serverRef.type === 'claudeai-proxy') {
874:        const tokens = getClaudeAIOAuthTokens()
875:        if (!tokens) {
876:          throw new Error('No claude.ai OAuth token found')
877:        }
879:        const oauthConfig = getOauthConfig()
880:        const proxyUrl = `${oauthConfig.MCP_PROXY_URL}${oauthConfig.MCP_PROXY_PATH.replace('{server_id}', serverRef.id)}`
885:        const fetchWithAuth = createClaudeAiProxyFetch(globalThis.fetch)
888:        const transportOptions: StreamableHTTPClientTransportOptions = {
890:          fetch: wrapFetchWithTimeout(fetchWithAuth),
891:          requestInit: {
892:            ...proxyOptions,
893:            headers: {
894:              'User-Agent': getMCPUserAgent(),
895:              'X-Mcp-Client-Session-Id': getSessionId(),
896:            },
897:          },
898:        }
900:        transport = new StreamableHTTPClientTransport(
901:          new URL(proxyUrl),
902:          transportOptions,
903:        )

此部分的主要目标是使用 OAuth tokens 通过 Claude.ai Proxy 进行身份验证。在认证时,优先考虑使用 OAuth token 而非 session ingress token。这可以通过配置 fetchWithAuth 函数来实现。

维度普通 HTTPClaude.ai Proxy
TransportStreamableHTTPClientTransportStreamableHTTPClientTransport(复用)
认证方式OAuth / Session IngressClaude.ai OAuth Tokens
URL 生成直接使用 serverRef.url拼接 proxy URL + server_id
特殊 Header-X-Mcp-Client-Session-Id

通过这种方式,可以确保针对特定服务器类型的身份验证机制被正确处理,并且与普通 HTTP 请求具有不同的配置和行为。

第五类传输:子进程管理与 In-Process 优化

9.1 重型 Server 的 In-Process 特判

对于某些类型的服务器(如 Chrome MCP Server 和 Computer Use MCP Server),架构会进行特殊处理,以避免启动较大的子进程。这些优化措施有助于减少资源消耗和提高性能。

文件位置 :services/mcp/client.ts:905-959

905:      } else if (
906:        (serverRef.type === 'stdio' || !serverRef.type) &&
907:        isClaudeInChromeMCPServer(name)
908:      ) {
909:        // Run the Chrome MCP server in-process to avoid spawning a ~325 MB subprocess
...
923:        transport = clientTransport
924:        logMCPDebug(name, `In-process Chrome MCP server started`)
925:      } else if (
926:        feature('CHICAGO_MCP') &&
927:        (serverRef.type === 'stdio' || !serverRef.type) &&
928:        isComputerUseMCPServer!(name)
929:      ) {
930:        // Run the Computer Use MCP server in-process
...
942:        transport = clientTransport
943:        logMCPDebug(name, `In-process Computer Use MCP server started`)
944:      } else if (serverRef.type === 'stdio' || !serverRef.type) {
945:        const finalCommand =
946:          process.env.CLAUDE_CODE_SHELL_PREFIX || serverRef.command
947:        const finalArgs = process.env.CLAUDE_CODE_SHELL_PREFIX
948:          ? [[serverRef.command, ...serverRef.args].join(' ')]
949:          : serverRef.args
950:        // 创建子进程并启动服务器
维度影响
功能正确性未必坏
资源成本显著增加
Chrome MCP Server每次连接启动 ~325 MB 子进程
系统负载多个重型 server 同时运行时内存压力巨大
启动速度变慢

9.2 Stderr 监控与 64MB 上限保护

为了防止因错误处理不当而导致的日志文件过大,系统会监控子进程的 stderr 输出,并设置一个 64 MB 的日志上限。

影响分析 :通过限制 stderr 日志大小并适时清理过大的日志输出,可以有效地避免资源耗尽的情况发生。这有助于保持系统的稳定性和可靠性。

假设实验:修改影响评估

实验一:将 SSE 源的 eventSourceInit.fetch 也套上普通超时包装

修改位置 :client.ts:648-670

维度影响
长连接稳定性定期被误杀
错误表象"网络不稳定"
根本原因接入层定时切断流
排查难度极难定位

实验二:HTTP 分支不区分 hasOAuthTokens

修改位置 :client.ts:833-836

场景后果
OAuth + Session Ingress 共存优先级混乱,不稳定认证
鉴权语义不稳定:有时正确,有时错误
UI 表现很难从 UI 直接看出问题

实验三:一律使用 stdio 起重型 MCP server

修改方案 :移除 client.ts:907-943 的两个特判分支

通过将所有服务器类型统一处理为 stdio 会带来资源消耗的显著增加,尤其是在启动较大体积的服务器时。

设计原则总结

原则一:统一入口,保留差异

所有协议从单个入口点进入,并且针对不同传输特性进行适当调整。

原则二:认证优先级明确

OAuth tokens 优先于 session ingress token 使用。

原则三:长连接特殊对待

EventSource 和 WebSocket 连接在接入层有不同的处理方式,以确保稳定性及性能优化。

原则四:性能意识内建

对于体积较大的服务器,使用 in-process 方法进行优化可以有效降低资源消耗。

原则五:边界清晰

明确区分各个模块的功能范围,并且只负责各自的职责。


> 🔗 相关阅读MCP框架