Model Context Protocol (MCP) 让 Claude Code 能连接外部工具服务器,无限扩展能力。
一、MCP 是什么 Model Context Protocol 是 Anthropic 提出的开放协议,定义了 LLM 应用与外部工具/资源服务器之间的标准通信方式。
Claude Code (MCP Client) ←→ MCP Server (外部工具提供者) ├─ 数据库查询服务器 ├─ GitHub API 服务器 ├─ Slack 集成服务器 ├─ 文件系统服务器 └─ 自定义业务逻辑
二、MCP 集成架构 services/mcp/ ├── client.ts — MCP 客户端管理 (连接/断开/获取工具) ├── config.ts — MCP 配置解析 (服务器列表) ├── types.ts — MCP 类型定义 ├── auth.ts — MCP 认证 ├── claudeai.ts — Claude.ai 平台的 MCP 配置 ├── officialRegistry.ts — 官方 MCP 服务器注册表 ├── channelAllowlist.ts — 频道白名单 ├── channelPermissions.ts — 频道权限 ├── elicitationHandler.ts — URL elicitation 处理 ├── InProcessTransport.ts — 进程内传输 ├── MCPConnectionManager.tsx — 连接管理 UI 组件 ├── normalization.ts — 工具名标准化 ├── utils.ts — 工具函数 └── xaa.ts / xaaIdpLogin.ts — XAA 身份认证
三、MCP 服务器配置 3.1 配置来源 { "mcpServers" : { "github" : { "command" : "npx" , "args" : [ "-y" , "@modelcontextprotocol/server-github" ] , "env" : { "GITHUB_TOKEN" : "${GITHUB_TOKEN}" } } , "postgres" : { "command" : "npx" , "args" : [ "-y" , "@modelcontextprotocol/server-postgres" ] , "env" : { "DATABASE_URL" : "postgresql://..." } } } }
3.2 配置解析 export function parseMcpConfig (configPath : string ): McpServerConfig [] { } function expandEnvVars (value : string ): string { return value.replace (/\$\{(\w+)\}/g , (_, name ) => { return process.env [name] || '' ; }); }
四、MCP 客户端生命周期 应用启动 │ ▼ [1] 解析 MCP 配置 → 获取服务器列表 │ ▼ [2] 连接服务器 → spawn 子进程 / WebSocket │ ├─ stdio 传输 (本地服务器) │ ├─ SSE 传输 (远程服务器) │ └─ In-Process 传输 (内嵌服务器) │ ▼ [3] 获取工具列表 → tools/list │ ▼ [4] 获取资源列表 → resources/list │ ▼ [5] 获取命令列表 → prompts/list (可选) │ ▼ [运行时] 工具调用 → tools/call → 返回结果 │ ▼ [退出] 断开所有连接 → 清理子进程
4.1 连接管理 export async function connectToServer ( config : McpServerConfig , ): Promise <MCPServerConnection > { const transport = config.command ? createStdioTransport (config.command , config.args , config.env ) : createSSETransport (config.url ); const client = new MCPClient (transport); await client.connect (); const capabilities = await client.getServerCapabilities (); return { name : config.name , client, capabilities, transport, }; } export async function getMcpToolsCommandsAndResources ( clients : MCPServerConnection [], ): Promise <{ tools : Tool []; commands : Command []; resources : ServerResource []; }> { const allTools : Tool [] = []; const allCommands : Command [] = []; for (const client of clients) { const serverTools = await fetchToolsForClient (client); allTools.push (...serverTools); const serverCommands = await fetchPromptsForClient (client); allCommands.push (...serverCommands); } return { tools : allTools, commands : allCommands, resources : allResources }; }
function mcpToolToClaudeCodeTool ( mcpTool : MCPToolDefinition , client : MCPServerConnection , ): Tool { return buildTool ({ name : `mcp__${client.name} __${mcpTool.name} ` , description : mcpTool.description , inputSchema : convertMcpSchemaToZod (mcpTool.inputSchema ), async *call (input, context ) { const result = await client.client .callTool (mcpTool.name , input); yield { type : 'result' , content : result.content }; }, isReadOnly : () => false , needsPermissions : () => true , }); }
五、MCP 资源
六、频道系统 (Channels) MCP 服务器可以通过 “频道” 机制管理:
type ChannelEntry = | { kind : 'plugin' ; name : string ; marketplace : string ; dev ?: boolean } | { kind : 'server' ; name : string ; dev ?: boolean };
七、官方 MCP 注册表 export async function prefetchOfficialMcpUrls ( ): Promise <void > { }
八、Elicitation 处理 type ElicitationRequestEvent = { serverId : string ; url : string ; message : string ; params : ElicitRequestURLParams ; };
九、JSON-RPC 2.0 协议帧格式详解 MCP 完全构建于 JSON-RPC 2.0 协议之上,理解三种消息类型是读懂整个通信链路的基础。
9.1 三种消息类型 import type { JSON RPCMessage } from '@modelcontextprotocol/sdk/types.js' { "jsonrpc" : "2.0" , "id" : 1 , "method" : "tools/list" , "params" : {} } { "jsonrpc" : "2.0" , "id" : 1 , "result" : { "tools" : [ { "name" : "search" , "description" : "..." , "inputSchema" : {...} } ] } } { "jsonrpc" : "2.0" , "id" : 1 , "error" : { "code" : -32001 , "message" : "Session not found" } } { "jsonrpc" : "2.0" , "method" : "notifications/tools/progress" , "params" : { "progress" : 0.5 , "total" : 1.0 , "message" : "Processing..." } }
9.2 MCP 专有错误码 export function isMcpSessionExpiredError (error : Error ): boolean { const httpStatus = 'code' in error ? (error as Error & { code ?: number }).code : undefined if (httpStatus !== 404 ) return false return ( error.message .includes ('"code":-32001' ) || error.message .includes ('"code": -32001' ) ) } import { ErrorCode } from '@modelcontextprotocol/sdk/types.js' if (error instanceof McpError && error.code === ErrorCode .UrlElicitationRequired ) { }
9.3 协议握手流程 Claude Code(Client) MCP Server | | |-- initialize {capabilities} -------->| | clientInfo: { name, version } | | capabilities: { roots, elicitation } | | | |<-- initialize result -----------------| | serverInfo: { name, version } | | capabilities: { tools, resources } | | instructions: "..." | | | |-- initialized (notification) ------->| | | |-- tools/list ----------------------->| |<-- tools/list result ----------------|
CC 在建立连接时声明的能力(client.ts:998):
const client = new Client ( { name : 'claude-code' , title : 'Claude Code' , version : MACRO .VERSION }, { capabilities : { roots : {}, elicitation : {}, }, }, ) client.setRequestHandler (ListRootsRequestSchema , async () => ({ roots : [{ uri : `file://${getOriginalCwd()} ` }], }))
十、三种传输层完整对比 10.1 传输层架构一览 Transport 接口(SDK 定义): start(): Promise<void> send(message: JSONRPCMessage): Promise<void> close(): Promise<void> onmessage?: (message: JSONRPCMessage) => void onclose?: () => void onerror?: (error: Error) => void
CC 实现/使用了以下传输类型:
传输类型
类
使用场景
子进程
stdio
StdioClientTransport(SDK)
本地命令行 MCP 服务器
是
SSE
SSEClientTransport(SDK)
远程服务器(旧协议)
否
HTTP
StreamableHTTPClientTransport(SDK)
远程服务器(新规范)
否
WebSocket
WebSocketTransport(CC 实现)
IDE 扩展
否
InProcess
InProcessTransport(CC 实现)
同进程 MCP 服务器
否
SdkControl
SdkControlClientTransport(CC 实现)
SDK 进程内 MCP 服务器
否
10.2 stdio 传输 最常见的本地传输方式,CC 作为父进程 spawn 子进程,通过 stdin/stdout 通信:
transport = new StdioClientTransport ({ command : finalCommand, args : finalArgs, env : { ...subprocessEnv (), ...serverRef.env , } as Record <string , string >, stderr : 'pipe' , }) const stderrHandler = (data : Buffer ) => { if (stderrOutput.length < 64 * 1024 * 1024 ) { stderrOutput += data.toString () } } stdioTransport.stderr .on ('data' , stderrHandler) process.kill (childPid, 'SIGINT' ) process.kill (childPid, 'SIGTERM' )
10.3 SSE(Server-Sent Events)传输 旧版 MCP 规范使用的传输,采用长连接 SSE 流接收消息,POST 请求发送消息:
const transportOptions : SSEClientTransportOptions = { authProvider, fetch : wrapFetchWithTimeout ( wrapFetchWithStepUpDetection (createFetchWithInit (), authProvider), ), requestInit : { headers : { 'User-Agent' : getMCPUserAgent (), ...combinedHeaders }, }, } transportOptions.eventSourceInit = { fetch : async (url, init) => { const tokens = await authProvider.tokens () const authHeaders = tokens ? { Authorization : `Bearer ${tokens.access_token} ` } : {} return fetch (url, { ...init, headers : { ...authHeaders, ...combinedHeaders, Accept : 'text/event-stream' }, }) }, }
10.4 Streamable HTTP 传输(新规范) MCP 2025-03-26 规范引入的新传输,单端点,POST 请求既可返回 JSON 也可返回 SSE 流:
const MCP_STREAMABLE_HTTP_ACCEPT = 'application/json, text/event-stream' export function wrapFetchWithTimeout (baseFetch : FetchLike ): FetchLike { return async (url, init) => { const method = (init?.method ?? 'GET' ).toUpperCase () if (method === 'GET' ) return baseFetch (url, init) const headers = new Headers (init?.headers ) if (!headers.has ('accept' )) { headers.set ('accept' , MCP_STREAMABLE_HTTP_ACCEPT ) } const controller = new AbortController () const timer = setTimeout (c => c.abort (new DOMException (...)), 60000 , controller) timer.unref ?.() } }
10.5 InProcessTransport(同进程传输) 用于在同一进程内运行 MCP 服务器(如 Claude-in-Chrome、Computer Use),避免 spawn 325MB 子进程:
class InProcessTransport implements Transport { private peer : InProcessTransport | undefined private closed = false async send (message : JSON RPCMessage): Promise <void > { if (this .closed ) throw new Error ('Transport is closed' ) queueMicrotask (() => { this .peer ?.onmessage ?.(message) }) } async close (): Promise <void > { if (this .closed ) return this .closed = true this .onclose ?.() if (this .peer && !this .peer .closed ) { this .peer .closed = true this .peer .onclose ?.() } } } export function createLinkedTransportPair ( ): [Transport , Transport ] { const a = new InProcessTransport () const b = new InProcessTransport () a._setPeer (b) b._setPeer (a) return [a, b] } const { createLinkedTransportPair } = await import ('./InProcessTransport.js' )const context = createChromeContext (serverRef.env )inProcessServer = createClaudeForChromeMcpServer (context) const [clientTransport, serverTransport] = createLinkedTransportPair ()await inProcessServer.connect (serverTransport)transport = clientTransport
十一、认证机制深度解析 11.1 认证架构总览 CC 的 MCP 认证系统由四个文件协作实现:
auth.ts — ClaudeAuthProvider (OAuthClientProvider 实现)、OAuth 流程编排 xaa.ts — XAA (Cross-App Access) 企业级无浏览器认证 xaaIdpLogin.ts — IdP OIDC 登录(XAA 的第一步) oauthPort.ts — OAuth 回调端口选择和 redirect_uri 构建
11.2 ClaudeAuthProvider:OAuthClientProvider 实现 ClaudeAuthProvider 是 CC 对 MCP SDK OAuthClientProvider 接口的完整实现,负责令牌的持久化和刷新:
export class ClaudeAuthProvider implements OAuthClientProvider { get clientMetadata (): OAuthClientMetadata { return { client_name : `Claude Code (${this .serverName} )` , redirect_uris : [this .redirectUri ], grant_types : ['authorization_code' , 'refresh_token' ], response_types : ['code' ], token_endpoint_auth_method : 'none' , scope : getScopeFromMetadata (this ._metadata ), } } get clientMetadataUrl (): string | undefined { return process.env .MCP_OAUTH_CLIENT_METADATA_URL ?? MCP_CLIENT_METADATA_URL } } export function getServerKey ( serverName : string , serverConfig : McpSSEServerConfig | McpHTTPServerConfig , ): string { const configJson = jsonStringify ({ type : serverConfig.type , url : serverConfig.url , headers : serverConfig.headers || {}, }) const hash = createHash ('sha256' ).update (configJson).digest ('hex' ).substring (0 , 16 ) return `${serverName} |${hash} ` }
11.3 令牌获取与刷新逻辑(tokens() 方法) tokens() 是整个认证体系的核心,被 MCP SDK 在每次请求前调用:
async tokens (): Promise <OAuthTokens | undefined > { if (isXaaEnabled () && this .serverConfig .oauth ?.xaa && !tokenData?.refreshToken && (!tokenData?.accessToken || (tokenData.expiresAt - Date .now ()) / 1000 <= 300 )) { if (!this ._refreshInProgress ) { this ._refreshInProgress = this .xaaRefresh ().finally (() => { this ._refreshInProgress = undefined }) } const refreshed = await this ._refreshInProgress if (refreshed) return refreshed } const expiresIn = (tokenData.expiresAt - Date .now ()) / 1000 const needsStepUp = this ._pendingStepUpScope !== undefined && this ._pendingStepUpScope .split (' ' ).some (s => !currentScopes.includes (s)) if (expiresIn <= 300 && tokenData.refreshToken && !needsStepUp) { if (!this ._refreshInProgress ) { this ._refreshInProgress = this .refreshAuthorization (tokenData.refreshToken ) .finally (() => { this ._refreshInProgress = undefined }) } const refreshed = await this ._refreshInProgress if (refreshed) return refreshed } return { access_token : tokenData.accessToken , refresh_token : needsStepUp ? undefined : tokenData.refreshToken , expires_in : expiresIn, scope : tokenData.scope , token_type : 'Bearer' , } }
11.4 OAuth 流程(oauthPort.ts + auth.ts) export async function findAvailablePort ( ): Promise <number > { const configuredPort = getMcpOAuthCallbackPort () if (configuredPort) return configuredPort const { min, max } = REDIRECT_PORT_RANGE for (let attempt = 0 ; attempt < 100 ; attempt++) { const port = min + Math .floor (Math .random () * (max - min + 1 )) const testServer = createServer () } } export function buildRedirectUri (port = 3118 ): string { return `http://localhost:${port} /callback` }
OAuth 流程全程(performMCPOAuthFlow,auth.ts:847):
1. 选择回调端口(随机 / 配置 / 默认 3118) 2. 创建 ClaudeAuthProvider(handleRedirection=true) 3. 调用 sdkAuth(provider) → 触发 redirectToAuthorization 4. 打开浏览器,用户完成授权 5. 本地 HTTP 服务器(127.0.0.1:port)接收 /callback 回调 6. 验证 OAuth state(CSRF 防护) 7. 调用 sdkAuth(provider, { authorizationCode }) 完成令牌交换 8. 令牌保存到 secureStorage(macOS Keychain / Linux Secret Service)
11.5 Step-up 认证(wrapFetchWithStepUpDetection) 当 MCP 服务器返回 403 insufficient_scope 时触发:
export function wrapFetchWithStepUpDetection ( baseFetch : FetchLike , provider : ClaudeAuthProvider , ): FetchLike { return async (url, init) => { const response = await baseFetch (url, init) if (response.status === 403 ) { const wwwAuth = response.headers .get ('WWW-Authenticate' ) if (wwwAuth?.includes ('insufficient_scope' )) { const match = wwwAuth.match (/scope=(?:"([^"]+)"|([^\s,]+))/ ) const scope = match?.[1 ] ?? match?.[2 ] if (scope) { provider.markStepUpPending (scope) } } } return response } }
11.6 XAA(Cross-App Access)企业无浏览器认证 XAA 是面向企业部署的认证方案(SEP-990),整合链路:
xaa.ts 实现的 4 层协议链(Layer-2 操作 + Layer-3 编排): Layer-2: discoverProtectedResource() → RFC 9728: GET /.well-known/oauth-protected-resource → 返回 { resource, authorization_servers[] } Layer-2: discoverAuthorizationServer() → RFC 8414: GET /.well-known/oauth-authorization-server → 验证 issuer 匹配(防止 mix-up 攻击) → 确保 token_endpoint 使用 HTTPS Layer-2: requestJwtAuthorizationGrant() → RFC 8693 Token Exchange: id_token → ID-JAG → POST {grant_type: "token-exchange", subject_token: id_token, ...} 到 IdP Layer-2: exchangeJwtAuthGrant() → RFC 7523 JWT Bearer: ID-JAG → access_token → POST {grant_type: "jwt-bearer", assertion: id-jag} 到 AS Layer-3: performCrossAppAccess() — 编排以上四步
export async function performCrossAppAccess ( serverUrl : string , config : XaaConfig , serverName = 'xaa' , ): Promise <XaaResult > { const prm = await discoverProtectedResource (serverUrl, { fetchFn }) let asMeta : AuthorizationServerMetadata | undefined for (const asUrl of prm.authorization_servers ) { const candidate = await discoverAuthorizationServer (asUrl, { fetchFn }) if (candidate.grant_types_supported && !candidate.grant_types_supported .includes (JWT_BEARER_GRANT )) { continue } asMeta = candidate break } const authMethod = authMethods?.includes ('client_secret_post' ) && !authMethods.includes ('client_secret_basic' ) ? 'client_secret_post' : 'client_secret_basic' const jag = await requestJwtAuthorizationGrant ({...}) const tokens = await exchangeJwtAuthGrant ({...}) return { ...tokens, authorizationServerUrl : asMeta.issuer } }
十二、SdkControlTransport 机制 12.1 架构背景 SDK MCP 服务器运行在 SDK 进程内(而非 CLI 进程),不能通过 stdout/stdin 直接通信。SdkControlTransport 通过控制消息桥接两个进程:
CLI 进程 SDK 进程 ┌─────────────────────────┐ ┌─────────────────────────────────┐ │ MCP Client │ │ SDK MCP Server (in-process) │ │ ↕ (JSON-RPC) │ │ ↕ (JSON-RPC) │ │ SdkControlClientTransport│<--->│ SdkControlServerTransport │ │ ↕ (control messages) │ │ ↕ │ │ StructuredIO │ │ Query (stdin/stdout 控制通道) │ └─────────────────────────┘ └─────────────────────────────────┘ CLI stdout/stdin ←──────────────────────────────────────────
12.2 CLI 侧:SdkControlClientTransport export class SdkControlClientTransport implements Transport { constructor ( private serverName : string , private sendMcpMessage : (serverName: string , message: JSONRPCMessage) => Promise <JSON RPCMessage>, ) {} async send (message : JSON RPCMessage): Promise <void > { if (this .isClosed ) throw new Error ('Transport is closed' ) const response = await this .sendMcpMessage (this .serverName , message) if (this .onmessage ) { this .onmessage (response) } } }
12.3 SDK 侧:SdkControlServerTransport export class SdkControlServerTransport implements Transport { constructor ( private sendMcpMessage : (message: JSONRPCMessage) => void , ) {} async send (message : JSON RPCMessage): Promise <void > { if (this .isClosed ) throw new Error ('Transport is closed' ) this .sendMcpMessage (message) } }
12.4 使用流程 export async function setupSdkMcpClients ( sdkMcpConfigs : Record <string , McpSdkServerConfig >, sendMcpMessage : (serverName: string , message: JSONRPCMessage) => Promise <JSON RPCMessage>, ) { for (const [name, config] of Object .entries (sdkMcpConfigs)) { const transport = new SdkControlClientTransport (name, sendMcpMessage) const client = new Client ({ name : 'claude-code' , ... }, { capabilities : {} }) await client.connect (transport) const capabilities = client.getServerCapabilities () if (capabilities?.tools ) { const sdkTools = await fetchToolsForClient (connectedClient) } } }
与 InProcessTransport 的关键区别:
特性
InProcessTransport
SdkControlTransport
跨进程
否(同进程)
是(CLI ↔ SDK 两进程)
通信机制
queueMicrotask
stdout/stdin 控制消息
使用场景
Chrome MCP、Computer Use
SDK 集成 MCP 服务器
消息路由
直接对端引用
通过 server_name 路由
十三、错误处理与重连策略 13.1 错误分类与处理矩阵 const isTerminalConnectionError = (msg : string ): boolean => { return ( msg.includes ('ECONNRESET' ) || msg.includes ('ETIMEDOUT' ) || msg.includes ('EPIPE' ) || msg.includes ('EHOSTUNREACH' ) || msg.includes ('ECONNREFUSED' ) || msg.includes ('Body Timeout Error' ) || msg.includes ('terminated' ) || msg.includes ('SSE stream disconnected' ) || msg.includes ('Failed to reconnect SSE stream' ) ) }
13.2 错误计数与重连触发 let consecutiveConnectionErrors = 0 const MAX_ERRORS_BEFORE_RECONNECT = 3 let hasTriggeredClose = false const closeTransportAndRejectPending = (reason : string ) => { if (hasTriggeredClose) return hasTriggeredClose = true logMCPDebug (name, `Closing transport (${reason} )` ) void client.close ().catch (...) } client.onerror = (error : Error ) => { if (isMcpSessionExpiredError (error)) { closeTransportAndRejectPending ('session expired' ) return } if (error.message .includes ('Maximum reconnection attempts' )) { closeTransportAndRejectPending ('SSE reconnection exhausted' ) return } if (isTerminalConnectionError (error.message )) { consecutiveConnectionErrors++ if (consecutiveConnectionErrors >= MAX_ERRORS_BEFORE_RECONNECT ) { consecutiveConnectionErrors = 0 closeTransportAndRejectPending ('max consecutive terminal errors' ) } } else { consecutiveConnectionErrors = 0 } }
13.3 memoize 缓存与重连机制 client.onclose = () => { const key = getServerCacheKey (name, serverRef) fetchToolsForClient.cache .delete (name) fetchResourcesForClient.cache .delete (name) fetchCommandsForClient.cache .delete (name) connectToServer.cache .delete (key) } export const connectToServer = memoize ( async (name, serverRef, serverStats) => { }, getServerCacheKey, )
13.4 会话过期与重试(HTTP Streamable 传输特有) const MAX_SESSION_RETRIES = 1 for (let attempt = 0 ; ; attempt++) { try { const connectedClient = await ensureConnectedClient (client) return await callMCPToolWithUrlElicitationRetry ({...}) } catch (error) { if (error instanceof McpSessionExpiredError && attempt < MAX_SESSION_RETRIES ) { logMCPDebug (client.name , `Retrying tool '${tool.name} ' after session recovery` ) continue } throw error } }
13.5 并发批处理(pMap) async function processBatched<T>(items, concurrency, processor) { await pMap (items, processor, { concurrency }) } await Promise .all ([ processBatched (localServers, getMcpServerConnectionBatchSize (), processServer), processBatched (remoteServers, getRemoteMcpServerConnectionBatchSize (), processServer), ])
十四、工具名称标准化(normalization.ts) 14.1 核心规则 const CLAUDEAI_SERVER_PREFIX = 'claude.ai ' export function normalizeNameForMCP (name : string ): string { let normalized = name.replace (/[^a-zA-Z0-9_-]/g , '_' ) if (name.startsWith (CLAUDEAI_SERVER_PREFIX )) { normalized = normalized.replace (/_+/g , '_' ).replace (/^_|_$/g , '' ) } return normalized }
14.2 工具名称构建(mcpStringUtils.ts) MCP 工具的全限定名(Fully Qualified Name)采用三段式结构:
export function buildMcpToolName (serverName : string , toolName : string ): string { return `mcp__${normalizeNameForMCP(serverName)} __${normalizeNameForMCP(toolName)} ` } export function mcpInfoFromString (toolString : string ): { serverName : string ; toolName : string | undefined } | null { const parts = toolString.split ('__' ) const [mcpPart, serverName, ...toolNameParts] = parts if (mcpPart !== 'mcp' || !serverName) return null const toolName = toolNameParts.length > 0 ? toolNameParts.join ('__' ) : undefined return { serverName, toolName } } export function getToolNameForPermissionCheck (tool ): string { return tool.mcpInfo ? buildMcpToolName (tool.mcpInfo .serverName , tool.mcpInfo .toolName ) : tool.name }
14.3 skip-prefix 模式(SDK 集成) const skipPrefix = client.config .type === 'sdk' && isEnvTruthy (process.env .CLAUDE_AGENT_SDK_MCP_NO_PREFIX ) return { name : skipPrefix ? tool.name : fullyQualifiedName, mcpInfo : { serverName : client.name , toolName : tool.name }, isMcp : true , }
十五、权限模型——MCP 工具进入 CC 权限系统 15.1 权限检查接口实现 每个 MCP 工具在 fetchToolsForClient 中获得 checkPermissions 实现:
async checkPermissions ( ) { return { behavior : 'passthrough' as const , message : 'MCPTool requires permission.' , suggestions : [ { type : 'addRules' as const , rules : [ { toolName : fullyQualifiedName, ruleContent : undefined , }, ], behavior : 'allow' as const , destination : 'localSettings' as const , }, ], } }
15.2 工具注解(Annotations)与权限行为 MCP 工具通过 annotations 字段声明安全属性,CC 据此调整权限逻辑:
isConcurrencySafe ( ) { return tool.annotations ?.readOnlyHint ?? false }, isReadOnly ( ) { return tool.annotations ?.readOnlyHint ?? false }, isDestructive ( ) { return tool.annotations ?.destructiveHint ?? false }, isOpenWorld ( ) { return tool.annotations ?.openWorldHint ?? false },
15.3 IDE 工具白名单 const ALLOWED_IDE_TOOLS = ['mcp__ide__executeCode' , 'mcp__ide__getDiagnostics' ]function isIncludedMcpTool (tool : Tool ): boolean { return ( !tool.name .startsWith ('mcp__ide__' ) || ALLOWED_IDE_TOOLS .includes (tool.name ) ) }
15.4 需要认证时的特殊工具 当 MCP 服务器需要 OAuth 认证时,CC 注入 McpAuthTool 代替正常工具列表:
if (client.type === 'needs-auth' ) { onConnectionAttempt ({ client, tools : [createMcpAuthTool (name, config)], commands : [], }) return }
十六、channelAllowlist / channelPermissions 完整机制 16.1 频道系统总览 Channel(频道)是 CC 对接第三方通信平台(Telegram、iMessage、Discord 等)的机制。频道本质上是特殊的 MCP 服务器,通过 --channels 命令行参数启用:
CC 启动参数:--channels plugin:telegram@marketplace └── 频道插件标识符(plugin:name@marketplace)
16.2 频道白名单(channelAllowlist.ts) export function getChannelAllowlist ( ): ChannelAllowlistEntry [] { const raw = getFeatureValue_CACHED_MAY_BE_STALE<unknown >('tengu_harbor_ledger' , []) return ChannelAllowlistSchema ().safeParse (raw).data ?? [] } export function isChannelsEnabled ( ): boolean { return getFeatureValue_CACHED_MAY_BE_STALE ('tengu_harbor' , false ) } export function isChannelAllowlisted (pluginSource : string | undefined ): boolean { if (!pluginSource) return false const { name, marketplace } = parsePluginIdentifier (pluginSource) if (!marketplace) return false return getChannelAllowlist ().some ( e => e.plugin === name && e.marketplace === marketplace, ) }
16.3 频道权限中继(channelPermissions.ts) 频道的核心能力:用户可以通过 Telegram/Discord 等平台审批 CC 的权限请求:
export function isChannelPermissionRelayEnabled ( ): boolean { return getFeatureValue_CACHED_MAY_BE_STALE ('tengu_harbor_permissions' , false ) } export const PERMISSION_REPLY_RE = /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/i export function shortRequestId (toolUseID : string ): string { let candidate = hashToId (toolUseID) for (let salt = 0 ; salt < 10 ; salt++) { if (!ID_AVOID_SUBSTRINGS .some (bad => candidate.includes (bad))) { return candidate } candidate = hashToId (`${toolUseID} :${salt} ` ) } return candidate } export function filterPermissionRelayClients<T>(clients, isInAllowlist) { return clients.filter ( c => c.type === 'connected' && isInAllowlist (c.name ) && c.capabilities ?.experimental ?.['claude/channel' ] !== undefined && c.capabilities ?.experimental ?.['claude/channel/permission' ] !== undefined , ) }
16.4 权限中继工作流 export function createChannelPermissionCallbacks ( ): ChannelPermissionCallbacks { const pending = new Map <string , (response : ChannelPermissionResponse ) => void >() return { onResponse (requestId, handler ) { const key = requestId.toLowerCase () pending.set (key, handler) return () => { pending.delete (key) } }, resolve (requestId, behavior, fromServer ) { const key = requestId.toLowerCase () const resolver = pending.get (key) if (!resolver) return false pending.delete (key) resolver ({ behavior, fromServer }) return true }, } }
16.5 输入预览截断(防止长工具输入泛洪频道) export function truncateForPreview (input : unknown ): string { try { const s = jsonStringify (input) return s.length > 200 ? s.slice (0 , 200 ) + '…' : s } catch { return '(unserializable)' } }
十七、MCP 认证缓存策略 17.1 needs-auth 缓存(client.ts) const MCP_AUTH_CACHE_TTL_MS = 15 * 60 * 1000 let writeChain = Promise .resolve ()function setMcpAuthCacheEntry (serverId : string ): void { writeChain = writeChain.then (async () => { const cache = await getMcpAuthCache () cache[serverId] = { timestamp : Date .now () } await writeFile (cachePath, jsonStringify (cache)) authCachePromise = null }) }
17.2 工具/资源 LRU 缓存 const MCP_FETCH_CACHE_SIZE = 20
以上章节(九至十七)基于 CC 源码实测,所有代码片段均来自实际实现,无推断内容。核心文件路径:src/services/mcp/client.ts(3349行)、src/services/mcp/auth.ts(2466行)、src/services/mcp/InProcessTransport.ts、src/services/mcp/SdkControlTransport.ts、src/services/mcp/normalization.ts、src/services/mcp/mcpStringUtils.ts、src/services/mcp/channelAllowlist.ts、src/services/mcp/channelPermissions.ts、src/services/mcp/oauthPort.ts、src/services/mcp/xaa.ts。