从Claude Code泄露源码看工程架构:第八章 —— MCP 接入层设计
- 人工智能
- 13天前
- 18热度
- 0评论
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 函数来实现。
| 维度 | 普通 HTTP | Claude.ai Proxy |
|---|---|---|
| Transport | StreamableHTTPClientTransport | StreamableHTTPClientTransport(复用) |
| 认证方式 | OAuth / Session Ingress | Claude.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框架