职责概述

解决的问题:CC 内置了 30 多个工具,但用户总会有新需求——连接公司的数据库、调用内部 API、操作特定格式的文件。不可能每次都改源码。MCP(Model Context Protocol)就是 CC 的"应用商店"机制,让第三方可以开发插件工具,CC 自动发现和调用。

应用场景:① 安装一个 MCP 插件后,CC 突然能查 PostgreSQL 了 ② 团队共享的内部工具(如部署脚本)打包成 MCP Server 给全组用 ③ 第三方开发者发布工具到市场,用户一键安装 ④ 企业内网服务通过 OAuth 认证安全接入。

一句话理解:就像手机的 App Store——手机出厂自带一些功能,但你想加新功能就装 App。MCP 就是 CC 的 App 安装协议。

架构设计

React 编排层
MCPConnectionManager.tsx
useManageMCPConnections.ts
useMergedTools.ts
MCP 客户端引擎
client.ts(connectToServer)
config.ts(配置加载与策略过滤)
auth.ts(OAuth 2.0 / ClaudeAuthProvider)
传输层
StdioClientTransport
SSEClientTransport
HTTPStreamableTransport
WebSocketTransport
InProcessTransport
SdkControlTransport
认证与安全
ClaudeAuthProvider (OAuth 2.0 + PKCE)
xaa.ts(企业 XAA 免浏览器认证)
xaaIdpLogin.ts(IdP OIDC 登录)
headersHelper.ts(动态 Header 注入)
通知与交互
channelNotification.ts(Channel 消息推送)
channelPermissions.ts(权限远程确认)
elicitationHandler.ts(Elicitation 交互)
claudeai.ts(claude.ai 代理服务器)

服务端架构

Direct Connect 服务端
server/types.ts(会话状态模型)
server/createDirectConnectSession.ts
server/directConnectManager.ts(WebSocket 会话管理)

核心数据流

连接生命周期

1. 配置加载
getClaudeCodeMcpConfigs() 从 .mcp.json、用户设置、企业策略、claude.ai API 加载服务器配置,按策略过滤并去重
2. 初始化 Pending
useManageMCPConnections 将所有服务器标记为 pending 状态,注册到 AppState.mcp.clients
3. 批量连接
getMcpToolsCommandsAndResources() 分 local/remote 两组并发调用 connectToServer(),本地并发度低(进程开销),远程并发度高
4. 传输握手
根据 type 字段选择传输实现(stdio/SSE/HTTP/WS/Sdk/InProcess),完成 OAuth 认证或 XAA 认证
5. 能力发现
并发获取 tools、commands(prompts)、resources、skills,注册 list_changed 通知处理器
6. 状态同步
通过 updateServer() 批量更新 AppState(16ms 窗口合并),触发 UI 刷新与工具池重组

认证决策流

认证入口
connectToServer() 根据 config.type 判断传输类型
远程服务器?
sse/http 类型创建 ClaudeAuthProvider,检查 config.oauth.xaa 标志
XAA 路径
performMCPXaaAuth(): PRM 发现 → AS 元数据 → id_token 换 ID-JAG → ID-JAG 换 access_token(无需浏览器)
OAuth 路径
performMCPOAuthFlow(): 发现 → 注册 → 授权 URL → 回调 → Token 交换,token 持久化到系统 Keychain

关键类型与接口

服务器配置联合类型

// services/mcp/types.ts:23-26
export const TransportSchema = lazySchema(() =>
  z.enum(['stdio', 'sse', 'sse-ide', 'http', 'ws', 'sdk']),
)

// services/mcp/types.ts:124-135 — 七种配置的联合类型
export const McpServerConfigSchema = lazySchema(() =>
  z.union([
    McpStdioServerConfigSchema(),   // 本地进程
    McpSSEServerConfigSchema(),     // SSE 远程(支持 OAuth)
    McpSSEIDEServerConfigSchema(),  // IDE 内部 SSE
    McpWebSocketIDEServerConfigSchema(), // IDE WebSocket
    McpHTTPServerConfigSchema(),    // HTTP 流式传输
    McpWebSocketServerConfigSchema(),    // 通用 WebSocket
    McpSdkServerConfigSchema(),     // SDK 进程内
    McpClaudeAIProxyServerConfigSchema(), // claude.ai 代理
  ]),
)

连接状态联合类型

// services/mcp/types.ts:180-227
export type MCPServerConnection =
  | ConnectedMCPServer   // { type: 'connected', client, capabilities, tools... }
  | FailedMCPServer      // { type: 'failed', error? }
  | NeedsAuthMCPServer   // { type: 'needs-auth' }
  | PendingMCPServer     // { type: 'pending', reconnectAttempt? }
  | DisabledMCPServer    // { type: 'disabled' }

InProcessTransport(进程内传输)

// services/mcp/InProcessTransport.ts:11-49
class InProcessTransport implements Transport {
  private peer: InProcessTransport | undefined
  async send(message: JSONRPCMessage): Promise<void> {
    if (this.closed) throw new Error('Transport is closed')
    queueMicrotask(() => {
      this.peer?.onmessage?.(message)
    })
  }
}

// 创建成对的传输通道
export function createLinkedTransportPair(): [Transport, Transport]

Channel 权限回调接口

// services/mcp/channelPermissions.ts:46-61
export type ChannelPermissionCallbacks = {
  onResponse(requestId: string,
    handler: (response: ChannelPermissionResponse) => void): () => void
  resolve(requestId: string, behavior: 'allow' | 'deny',
    fromServer: string): boolean
}

工具名称规范化

// services/mcp/mcpStringUtils.ts:39-41
export function getMcpPrefix(serverName: string): string {
  return `mcp__${normalizeNameForMCP(serverName)}__`
}
// 例: "slack" 服务器的工具 "send_message" → "mcp__slack__send_message"

设计模式与亮点

多传输层抽象

connectToServer()(client.ts:595)是一个巨大的 memoize 函数,根据 serverRef.type 分发到六种传输实现。每种传输的认证逻辑统一通过 ClaudeAuthProvider 处理(SSE/HTTP),或通过专用通道(stdio 无需认证、SDK 通过控制消息桥接)。这种设计让添加新传输类型只需增加一个 else-if 分支。

批量状态更新 + 微任务合并

updateServer()(useManageMCPConnections.ts:297)将单个服务器的状态变更推入 pendingUpdatesRef,通过 16ms 的 setTimeout 窗口合并多个几乎同时到达的更新为一次 setAppState 调用。这避免了 N 个 MCP 服务器同时连接完成时触发 N 次 React 重渲染。

两阶段配置加载

MCP 配置分两阶段加载(useManageMCPConnections.ts:858-1024):Phase 1 先连接 Claude Code 本地配置的服务器(快速),Phase 2 再异步获取 claude.ai 组织配置的服务器(可能慢),两者并行执行不互相阻塞。还通过 dedupClaudeAiMcpServers() 按签名去重,避免重复连接。

XAA 企业免浏览器认证

xaa.ts 实现了三层 OAuth 链:PRM 发现(RFC 9728)→ Token Exchange(RFC 8693: id_token 换 ID-JAG)→ JWT Bearer Grant(RFC 7523: ID-JAG 换 access_token)。企业用户只需一次 IdP 浏览器登录,后续所有 MCP 服务器认证均为静默完成。

指数退避重连

远程传输(SSE/HTTP/WS)断开后,onclose 处理器(useManageMCPConnections.ts:333)启动最多 5 次的重连尝试,退避时间从 1s 到 30s 指数增长。重连前检查服务器是否被禁用,支持中途取消。

Channel 消息推送

通过 notifications/claude/channel 通知机制,MCP 服务器可以主动向 Claude 推送消息(如 Telegram/iMessage/Discord 入站消息)。五层门控(capability → runtime → auth → policy → allowlist)确保安全性。

认证缓存策略:MCP 认证结果通过 mcpAuthCacheisMcpAuthCached() 缓存到磁盘。对于返回 401 的服务器有 15 分钟的 TTL,避免反复探测不可用的认证端点。ClaudeAuthProvider 将 token 持久化到系统 Keychain(macOS)或加密存储,跨 CLI 会话复用。

开发者实践指南

添加新的 MCP 传输类型

services/mcp/types.ts 中定义新的配置 Schema,在 client.tsconnectToServer() 函数中添加新的 else-if 分支创建传输实例。注意:

理解 MCP 工具调用链

MCP 工具调用流程:Tool.call() → ensureConnectedClient() → callMCPToolWithUrlElicitationRetry() → callMCPTool() → client.request()。如果遇到 McpSessionExpiredError,自动清除连接缓存并重试一次。

调试 MCP 连接问题

使用 --debug 标志启动 Claude Code,所有 MCP 操作会通过 logMCPDebug() 输出详细日志。关注 "SSE transport initialized"、"Received tools/list_changed"、"reconnection attempt" 等关键消息。

架构师决策指南

连接管理的性能权衡

connectToServer 使用 memoize 缓存连接(client.ts:595),避免重复建立连接。代价是缓存失效策略复杂:服务器配置变更时需要通过 clearServerCache() 显式清除。当前通过 getServerCacheKey() 生成 name-jsonStringify(config) 作为缓存键,配置变更会导致新键值、旧连接泄漏直到进程退出。

本地 vs 远程并发度

服务器连接分为 local(stdio/sdk)和 remote(SSE/HTTP/WS)两组独立并发控制。本地受 getMcpServerConnectionBatchSize() 限制(进程创建开销),远程受 getRemoteMcpServerConnectionBatchSize() 限制。两组通过 Promise.all 并行执行,互不阻塞。

Channel 安全模型

Channel 推送是 MCP 体系中最复杂的安全边界。五层门控链在 gateChannelServer()(channelNotification.ts:191)中实现:

  1. capability — 服务器必须声明 experimental['claude/channel']
  2. runtime — GrowthBook 特性开关 tengu_harbor 必须为 true
  3. auth — 必须有 claude.ai OAuth token(API key 用户被阻断)
  4. policy — Teams/Enterprise 必须在 managed settings 中启用 channelsEnabled: true
  5. allowlist — 插件必须在 GrowthBook tengu_harbor_ledger 白名单中
已知风险:被攻陷的 Channel 服务器可以通过 notifications/claude/channel/permission 事件伪造权限确认。这是可接受的风险——被攻陷的服务器本身已有无限对话注入能力,权限确认只是加速了攻击而非扩展了攻击面。

Elicitation 交互架构

Elicitation 是 MCP 协议的交互式确认机制,允许服务器向用户弹出表单或 URL 确认。通过 registerElicitationHandler()(elicitationHandler.ts:68)注册到 MCP Client。请求先经过 hook 链(可程序化响应),未处理的请求排队到 AppState.elicitation.queue 供 UI 渲染。URL 模式的 Elicitation 支持"等待完成"通知(ElicitationCompleteNotificationSchema),用于 OAuth 回调等异步场景。

可视化处理拓扑图

MCP 系统是一个从多源配置发现到运行时工具调用的完整生命周期管理管线。配置从 5 个作用域汇聚、经过策略过滤后并发连接,每条连接根据 Transport 类型走不同的创建路径,最终将远程工具统一封装为 Claude Code 内部的 Tool 对象。

多作用域配置加载getClaudeCodeMcpConfigs() — config.ts:1071
.mcp.jsonproject scope
local 覆盖.claude/mcp.json
用户级~/.claude/mcp.json
企业策略managed policy
插件提供plugin MCP
addScopeToServers + dedupPluginMcpServersconfig.ts:69 标记作用域 → config.ts:223 URL签名去重
filterMcpServersByPolicyconfig.ts:536 — 企业白名单/黑名单过滤 + expandEnvVars()
被拒绝
disabled不创建网络连接
通过 ↓
合并后的有效服务器列表Record<name, ScopedMcpServerConfig>

Phase 2 — 并发连接与 Transport 创建

getMcpToolsCommandsAndResources()client.ts:2226 — 分组并发连接
本地服务器组(低并发)stdio / sdk 类型
processBatched(items, localBatch)
StdioClientTransport
SdkControlTransport
远程服务器组(高并发)sse / http 类型
processBatched(items, remoteBatch)
SSEClientTransport
StreamableHTTPClientTransport
connectToServer() — memoizedclient.ts:595 — 缓存即连接池,相同参数返回同一 Promise
OAuth 认证ClaudeAuthProvider
auth.ts — token 获取/刷新
失败
handleRemoteAuthFailureclient.ts:340 → type: 'failed'
成功 ↓
ConnectedMCPServerclient.listTools() → 发现工具 → Tool 对象封装

Phase 3 — 工具发现与运行时调用

工具发现:client.listTools()每个 MCP 工具 → 封装为 Claude Code Tool 对象
Tool 对象封装client.ts:1786 — 注入 call(), checkPermissions(), isReadOnly()
isIncludedMcpTool() 过滤client.ts:569 — include/exclude 配置过滤
React 批量状态更新useManageMCPConnections.ts:207 — 16ms 窗口 flush
运行时工具调用 — Tool.call()client.ts:1833 — 模型发起 MCP 工具调用
ensureConnectedClient()client.ts:1688 — 确认连接活跃
callMCPToolWithUrlElicitationRetry()client.ts:2813 — URL 认证重试 + Elicitation
processMCPResult() + transformResultContent()client.ts:2720/2478 — MCP 格式 → ContentBlock

Phase 4 — 健康监控与自动重连

连接健康监控循环 — client.ts:1240-1570
onclose / onerror 处理器:
① 连接断开 → 错误计数 +1
② 连续错误 > 3 → closeTransportAndRejectPending()
③ clearServerCache() → 清空 memoize 缓存条目
④ 下次工具调用 → connectToServer() 自动重建
⑤ Session 过期 (404 + -32001) → isMcpSessionExpiredError() 检测 → 自动重连
缓存即状态,清缓存即重连——无需显式连接池状态机
reconnectWithBackoff()useManageMCPConnections.ts:371 — 指数退避重连
核心设计:MCP 系统使用 Memoize-as-Connection-Pool 模式——connectToServermemoize 包装,缓存存在即已连接,clearServerCache() 即重连。Transport 类型通过工厂分支创建(stdio/sse/http),但上层代码统一通过 ConnectedMCPServer 接口操作,完全屏蔽底层差异。16ms React 批量更新机制确保多服务器并发连接不会造成 UI 抖动。

核心处理流程详解

MCP(Model Context Protocol)系统是从配置发现到工具调用的完整生命周期管理。其核心链路横跨配置加载(config.ts)、连接管理(client.ts)、React 编排(useManageMCPConnections.ts)和运行时工具调用四大模块。以下是从应用启动到工具执行的完整处理链路:

1. 多作用域配置加载与合并
getClaudeCodeMcpConfigs()(config.ts:1071)从 5 个作用域读取配置:project(.mcp.json)、local(项目本地覆盖)、user(~/.claude/mcp.json)、enterprise(托管策略)和 plugin(插件提供的 MCP 服务器)。通过 addScopeToServers()(config.ts:69)标记每个服务器的来源作用域,再用 dedupPluginMcpServers()(config.ts:223)基于 URL 签名去重,防止同一服务器重复连接 — config.ts:1071-1251
2. 策略过滤与启停管理
合并后的配置经过 filterMcpServersByPolicy()(config.ts:536)过滤。企业策略通过白名单(allowedMcpServers)或黑名单(deniedMcpServers)控制。环境变量通过 expandEnvVars()(config.ts:556)展开。禁用的服务器不生成网络连接,仅标记为 type: 'disabled' — config.ts:536-551
3. 批量并发连接(Transport 层)
getMcpToolsCommandsAndResources()(client.ts:2226)将服务器分为本地(stdio/sdk,低并发)和远程(sse/http,高并发)两组。对每个服务器调用 connectToServer()(client.ts:595,memoized)。根据类型创建不同 Transport:stdio 使用 StdioClientTransport,SSE 使用 SSEClientTransport,HTTP 使用 StreamableHTTPClientTransport,IDE 通过特殊管道通信 — client.ts:2226-2403
4. OAuth 认证与 Token 管理
远程服务器需要 OAuth 认证。ClaudeAuthProvider(auth.ts)实现 OAuthClientProvider 接口,管理 token 的获取、刷新和撤销。认证失败时 handleRemoteAuthFailure()(client.ts:340)返回 failed 状态连接。认证缓存通过 setMcpAuthCacheEntry()(client.ts:293)持久化到磁盘,避免重复认证流程 — client.ts:340-361, auth.ts
5. 工具发现与 Tool 对象封装
连接成功后,通过 MCP 协议的 client.listTools() 发现可用工具。每个 MCP 工具被封装为 Claude Code 的 Tool 对象(client.ts:1786-1832),注入 call()checkPermissions()isReadOnly() 等方法。isIncludedMcpTool()(client.ts:569)基于 include/exclude 配置过滤工具 — client.ts:1786-1832
6. React 状态批量更新
useManageMCPConnections(useManageMCPConnections.ts:143)通过 16ms 时间窗口的批量更新机制(MCP_BATCH_FLUSH_MS = 16),将多个服务器的连接结果合并为单次 setAppState() 调用。每个服务器的连接回调(onConnectionAttempt)进入 pending 队列,通过 setTimeout(flush, 16) 延迟刷入状态 — useManageMCPConnections.ts:207-270
7. 运行时工具调用与结果转换
模型调用 MCP 工具时,封装的 Tool 对象执行 call()(client.ts:1833)。先通过 ensureConnectedClient()(client.ts:1688)确认连接活跃,再调用 callMCPToolWithUrlElicitationRetry()(client.ts:2813)处理 URL 认证重试。结果通过 processMCPResult()(client.ts:2720)和 transformResultContent()(client.ts:2478)将 MCP 格式转换为 Claude API 的 ContentBlock 格式 — client.ts:1833-1971
8. 连接健康监控与自动重连
每个连接注册增强的 onerroronclose 处理器(client.ts:1266-1570)。连续错误超过 3 次触发 closeTransportAndRejectPending(),清空 memoize 缓存使下次调用自动重连。HTTP 传输层的 session 过期(404 + JSON-RPC -32001)由 isMcpSessionExpiredError()(client.ts:193)检测,自动清除缓存重建连接 — client.ts:1240-1570
关键处理逻辑:整个 MCP 系统最核心的设计是"连接缓存 + 延迟重连"模式。connectToServermemoize 包装(client.ts:595),相同参数返回同一个 Promise。当连接断开时,onclose 处理器调用 clearServerCache()(client.ts:1648)清空特定服务器的缓存条目,使下一次工具调用自动触发重连。这种设计避免了维护复杂的连接池状态机——缓存即状态,清缓存即重连。

设计精华

1. Memoize-as-Connection-Pool 模式

不维护显式的连接池,而是利用 memoize 的缓存特性作为隐式连接池。key 由服务器名和配置对象计算(getServerCacheKey(),client.ts:581),值是连接 Promise。连接断开时只需 cache.delete(key),下次调用自然重建。这消除了连接状态机(connecting/connected/disconnected/reconnecting)的复杂性——缓存存在即已连接,不存在即需连接。

Memoized 连接函数(client.ts:595-607)
connectToServer 通过 memoize 包装,相同参数返回相同 Promise
export const connectToServer = memoize(
  async (name: string, serverRef: ScopedMcpServerConfig, ...): Promise<MCPServerConnection> => {
    // 创建 transport、执行 OAuth、发现工具...
    // 返回 ConnectedMCPServer 或 FailedMCPServerConnection
  }
)
// 断开时清空缓存 → 下次调用自动重连
clearServerCache(name, serverRef)
设计洞察:这种模式将连接生命周期管理从"命令式状态机"简化为"声明式缓存失效"。不亚于 React Query 的 staleTime 思想——用缓存策略替代状态管理。全局 clearPluginCache()(pluginLoader.ts:3225)也使用了相同模式管理插件加载。

2. 分层去重:签名 + URL 双重保障

MCP 配置来自 5 个不同作用域,同一个服务器可能被多处配置。getMcpServerSignature()(config.ts:202)为每个配置计算签名(基于 URL 或 command 数组)。在插件层,dedupPluginMcpServers()(config.ts:223)阻止插件注册已存在的 MCP 服务器;在 claude.ai 层,dedupClaudeAiMcpServers()(config.ts:281)阻止 claude.ai connector 与手动配置冲突。两层去重确保同一后端服务只有一个活跃连接。

设计洞察:去重的关键不是"名称相同"而是"签名相同"。用户可能在 .mcp.json 中把 GitHub MCP 命名为 gh,在 claude.ai 中命名为 GitHub,但只要 URL 相同就会被识别为重复。这种内容语义去重比名称去重更健壮。

3. SSE 长连接的超时分离策略

SSE 传输层有两个 fetch:EventSource 的长连接 fetch 和普通 API 请求的 fetch。wrapFetchWithTimeout()(client.ts:492)仅包装 API 请求 fetch(60 秒超时),EventSource 的 fetch 保持无超时。如果错误地给 EventSource 也加了超时,SSE 流会在 60 秒后断开,导致推送通知丢失。这个分离通过 transportOptions.eventSourceInittransportOptions.fetch 分别配置实现 — client.ts:630-671

超时分离(client.ts:627-671)
API 请求使用带超时的 fetch,EventSource 连接使用无限期 fetch
// API 请求:带超时
transportOptions.fetch = wrapFetchWithTimeout(
  wrapFetchWithStepUpDetection(createFetchWithInit(), authProvider),
)
// EventSource 长连接:不设超时
transportOptions.eventSourceInit = {
  fetch: async (url, init) => fetch(url, { ...init, ...proxyOptions })
}

4. 批量更新的时间窗口合并

React 的状态更新是同步的——每个 setAppState 触发一次渲染。当 10 个 MCP 服务器同时连接完成时,不加控制会产生 10 次渲染。useManageMCPConnectionsMCP_BATCH_FLUSH_MS = 16(一帧时间)的时间窗口收集所有更新,在单次 setAppState 中批量应用(useManageMCPConnections.ts:216-270)。16ms 足够收集同一"事件循环 tick"中的多个回调,又短到用户感知不到延迟。

设计洞察:16ms 是 requestAnimationFrame 的一帧。这不是巧合——它将 React 渲染节奏对齐到浏览器/终端的刷新率,既保证了视觉流畅性,又避免了不必要的中间状态渲染。批量更新还通过新旧 state 引用相等性检查(if (newState === prevState) return prevState)跳过无变化的更新。

Agent 实践借鉴 — 客服 Agent 外部系统集成设计

场景映射:客服 agent 在外部系统集成上的真实问题

客服 agent 需要连接 6+ 外部系统,每个系统协议不同、稳定性不同、接入方式也不同。典型痛点包括:

借鉴 CC + 客服改造

1. Memoize 连接池(CC 的核心模式,客服场景最该抄的)

CC 的 connectToServermemoize 包装,缓存存在=已连接,缓存清除=重连。这个模式在客服场景非常合适——连接 6 个外部系统不需要维护 6 个状态机:

// 借鉴 CC 的 Memoize-as-Connection-Pool → 客服系统连接池
// 核心思想:不维护显式连接池,用缓存特性管理连接

interface ExternalSystem {
  name: string               // 'crm' | 'erp' | 'payment' | 'logistics' | 'kb' | 'sms'
  protocol: 'rest' | 'grpc' | 'mq' | 'soap'
  config: SystemConfig
}

// 用 memoize 实现隐式连接池——缓存存在=已连接
const connectToExternalSystem = memoize(
  async (systemName: string, config: SystemConfig): Promise<SystemConnection> => {
    const adapter = createTransportAdapter(config)  // 工厂分支
    const connection = await adapter.connect(config)
    const health = await connection.healthCheck()
    if (!health.ok) throw new Error(`${systemName} 健康检查失败`)
    return { status: 'connected', connection, adapter }
  }
)

// 连接断开时:清缓存 = 重连(无需显式状态机)
function onSystemConnectionLost(systemName: string, config: SystemConfig) {
  clearConnectionCache(systemName, config)
  // 下次工具调用 connectToExternalSystem() 自动重建
  // 借鉴 CC 的 onclose 处理器:连续错误超限则降级
  logWarning(`外部系统 ${systemName} 断开,下次调用自动重连`)
}

// 降级策略:连接失败不影响其他系统
async function callWithFallback(
  systemName: string,
  operation: string,
  params: any,
  fallbackValue?: any
): Promise<any> {
  try {
    const conn = await connectToExternalSystem(systemName, getConfig(systemName))
    return await conn.connection.call(operation, params)
  } catch (error) {
    logError(`${systemName}.${operation} 调用失败`, error)
    if (fallbackValue !== undefined) return fallbackValue
    throw new ServiceUnavailableError(systemName, operation)
  }
}

2. 统一 Transport 适配器(CC 6 种 → 客服 2-3 种)

CC 的 6 种 Transport(stdio/SSE/HTTP/WS/SDK/InProcess)统一为 Transport 接口。客服场景只需要 REST/gRPC/MQ 三种,但同样需要统一接口:

// 借鉴 CC 的 Transport 接口 → 客服场景的统一适配器
interface ServiceTransport {
  connect(config: SystemConfig): Promise<Connection>
  send(request: ServiceRequest): Promise<ServiceResponse>
  close(): Promise<void>
  healthCheck(): Promise<HealthStatus>
}

// REST 适配器——大多数客服系统走 REST
class RestTransport implements ServiceTransport {
  private client: HttpClient | undefined

  async connect(config: SystemConfig): Promise<Connection> {
    this.client = createHttpClient(config.baseUrl, {
      timeout: config.timeout || 5000,
      retries: config.retries || 2,
      headers: { 'Authorization': `Bearer ${config.apiKey}` },
    })
    return { type: 'rest', client: this.client }
  }

  async send(request: ServiceRequest): Promise<ServiceResponse> {
    return this.client!.request({
      method: request.method,
      path: request.path,
      body: request.params,
    })
  }

  async healthCheck(): Promise<HealthStatus> {
    try {
      await this.client!.get('/health')
      return { ok: true }
    } catch { return { ok: false } }
  }

  close() { this.client = undefined }
}

// MQ 适配器——物流系统走消息队列
class MessageQueueTransport implements ServiceTransport {
  private consumer: MQConsumer | undefined

  async connect(config: SystemConfig): Promise<Connection> {
    this.consumer = await createConsumer(config.queueUrl, config.queueName)
    return { type: 'mq', consumer: this.consumer }
  }

  async send(request: ServiceRequest): Promise<ServiceResponse> {
    // 发送查询消息 → 等待响应消息(带超时)
    const correlationId = generateId()
    return waitForResponse(this.consumer!, correlationId, config.responseTimeout)
  }
  // ... healthCheck, close
}

3. 外部 API → 内部 Tool 封装

CC 把每个 MCP 工具封装为 Tool 对象。客服场景把每个外部系统 API 封装为统一的 Tool:

// 借鉴 CC 的 wrapMcpTool → 客服场景的外部 API → Tool 封装
interface CustomerServiceTool {
  name: string                 // 如 "query_order"
  description: string          // 如 "查询订单状态"
  requiredSystem: string       // 如 "erp"
  requiredPermission: string   // 如 "order:read"
  inputSchema: object          // 参数 schema
  execute(params: any): Promise<ToolResult>
}

// 封装 CRM 的"查客户"接口为统一 Tool
function createQueryCustomerTool(crmSystem: string): CustomerServiceTool {
  return {
    name: 'query_customer',
    description: '查询客户信息(CRM系统)',
    requiredSystem: crmSystem,
    requiredPermission: 'customer:read',
    inputSchema: {
      type: 'object',
      properties: {
        phone: { type: 'string', description: '客户手机号' },
        customerId: { type: 'string', description: '客户ID' },
      },
    },
    async execute(params) {
      // 连接 + 调用 + 结果转换
      const conn = await connectToExternalSystem(
        crmSystem, getConfig(crmSystem)
      )
      const raw = await conn.connection.call('getCustomer', {
        phone: params.phone,
        customerId: params.customerId,
      })
      // 原始 API 返回 → 统一 ToolResult 格式
      return {
        success: true,
        data: {
          name: raw.customer_name,
          phone: raw.phone_number,
          vipLevel: raw.vip_level,
          tags: raw.tags,
        },
        summary: `客户 ${raw.customer_name},${raw.vip_level}会员`,
      }
    },
  }
}

// agent 调用时无感知底层是 REST 还是 gRPC
// const result = await toolRegistry.get('query_customer').execute({ phone: '138xxxx' })

4. 连接健康检查 + 自动重连

// 借鉴 CC 的 onclose/onerror + reconnectWithBackoff
// 健康检查循环——每 30 秒检查所有外部系统连接
async function startHealthMonitor(systems: ExternalSystem[]): Promise<void> {
  setInterval(async () => {
    for (const system of systems) {
      const cacheKey = getCacheKey(system.name, system.config)
      const cached = connectionCache.get(cacheKey)
      if (!cached) continue  // 未连接,跳过

      const health = await cached.adapter.healthCheck().catch(() => ({ ok: false }))
      if (!health.ok) {
        // 借鉴 CC:清缓存 → 下次调用自动重建
        connectionCache.delete(cacheKey)
        logWarning(`${system.name} 健康检查失败,已清除缓存,下次调用自动重连`)
        // 通知 agent 该系统暂时不可用
        notifySystemStatus(system.name, 'degraded')
      }
    }
  }, 30_000)
}

// 指数退避重连——借鉴 CC 的 reconnectWithBackoff
async function reconnectWithBackoff(
  systemName: string,
  config: SystemConfig,
  maxAttempts: number = 5,
): Promise<SystemConnection> {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      clearConnectionCache(systemName, config)
      const conn = await connectToExternalSystem(systemName, config)
      notifySystemStatus(systemName, 'healthy')
      return conn
    } catch (error) {
      const delay = Math.min(1000 * Math.pow(2, attempt), 30_000)
      logWarning(`${systemName} 重连失败(第${attempt}次),${delay}ms后重试`)
      await sleep(delay)
    }
  }
  throw new Error(`${systemName} 重连${maxAttempts}次后仍然失败`)
}

落地清单

必须抄的:
1. Memoize 连接池——这个太实用了,不维护状态机,用缓存特性管理连接。连接 6 个外部系统完全够用。
2. 统一 Transport 接口——REST/gRPC/MQ 统一为 ServiceTransport 接口,新增协议只需加适配器。
3. Tool 封装——外部 API 封装为 CustomerServiceTool,agent 调用时不感知底层协议差异。
4. 连接隔离——单个系统连接失败不影响其他系统,降级而非报错。
不需要抄的:
1. OAuth PKCE 认证——客服系统用内部认证(API Key / 内网 Token),不需要 OAuth 浏览器授权流程。
2. 6 种传输方式——客服场景 REST + gRPC + MQ 三种覆盖 95% 场景,不需要 SSE/WebSocket/InProcess。
3. Elicitation 交互确认——不需要外部系统弹确认框,客服操作确认走内部审批流程。
4. Channel 消息推送——客服场景不需要外部系统主动推送消息到 agent。
常见坑: 1. 自建连接池状态机——不要为每个外部系统维护 connecting/connected/disconnected/reconnecting 状态机。6 个系统就是 6 个状态机,状态组合爆炸。用 Memoize + clearCache 模式,"缓存存在=已连接,不存在=需连接",零状态管理。 2. 没有降级策略——物流系统挂了,整个客服 agent 也挂了?必须有降级:连接失败时返回缓存数据或提示"物流系统暂不可用,请稍后查询",而不是直接报错中断服务。 3. 新系统接入影响老系统——新增短信平台连接时,初始化代码异常导致整个 agent 启动失败。所有外部系统连接必须并行初始化、独立错误处理,一个失败不影响其他。

代码索引

文件行数说明
services/mcp/client.ts~3349MCP 客户端核心:connectToServer、callMCPTool、工具封装、连接缓存
services/mcp/auth.ts~2466OAuth 2.0 认证:ClaudeAuthProvider、Token 持久化/刷新/撤销
services/mcp/config.ts~1579配置加载:多作用域读取、策略过滤、签名去重、启停管理
services/mcp/useManageMCPConnections.ts~1142React Hook:连接生命周期、批量更新、重连、Channel 注册
services/mcp/MCPConnectionManager.tsx~73React Context:提供 reconnectMcpServer / toggleMcpServer 给子组件
services/mcp/types.ts~258所有 MCP 类型定义:配置 Schema、连接状态、CLI 状态
services/mcp/InProcessTransport.ts~64进程内传输对:linked pair,用于无子进程的 MCP 通信
services/mcp/SdkControlTransport.ts~137SDK 传输桥:CLI ↔ SDK 进程的控制消息转发
services/mcp/channelNotification.ts~317Channel 通知:五层门控、消息包装、会话匹配
services/mcp/channelPermissions.ts~241Channel 权限:短 ID 生成、远程确认回调、allowlist 过滤
services/mcp/elicitationHandler.ts~314Elicitation 处理:表单/URL 模式、hook 集成、完成通知
services/mcp/claudeai.ts~165claude.ai 代理:从组织 API 获取 MCP 服务器配置
services/mcp/xaa.ts~512XAA 认证:PRM 发现、Token Exchange、JWT Bearer Grant
services/mcp/xaaIdpLogin.ts~488XAA IdP 登录:OIDC 授权码 + PKCE、id_token 缓存
services/mcp/oauthPort.ts~79OAuth 回调端口:动态端口分配、redirect URI 构建
services/mcp/headersHelper.ts~139动态 Header:通过外部脚本获取认证 Header
services/mcp/officialRegistry.ts~73官方 MCP 注册表:预取白名单 URL
services/mcp/vscodeSdkMcp.ts~113VSCode MCP 双向通信:文件变更通知、实验门控同步
services/mcp/normalization.ts~23名称规范化:MCP 工具名 ↔ API 兼容格式
services/mcp/mcpStringUtils.ts~107字符串工具:工具名解析、前缀生成、权限匹配
services/mcp/envExpansion.ts~39环境变量展开:${VAR} 和 ${VAR:-default} 语法
services/mcp/channelAllowlist.ts~77Channel 白名单:GrowthBook 驱动的插件许可列表
services/mcp/utils.ts~575工具函数:过期客户端检测、Agent frontmatter 提取
server/types.ts~58服务端类型:ServerConfig、SessionState、SessionIndex
server/directConnectManager.ts~214WebSocket 会话管理:消息收发、权限请求/响应、中断
server/createDirectConnectSession.ts~89创建 Direct Connect 会话:POST /sessions、响应验证