21-MCP服务
21. MCP 服务所属分组服务层概述MCPModel Context Protocol模型上下文协议是 Anthropic 推出的开放协议用于让 LLM 客户端以统一方式接入外部工具、资源与 prompt。Claude Code 在services/mcp/目录下实现了完整的 MCP 客户端栈能够同时管理本地 stdio 子进程、远程 SSE/HTTP/WebSocket 服务、IDE 扩展、Claude.ai 托管代理、以及进程内 SDK 服务等多种传输方式并把它们统一暴露为mcp__server__tool形式的工具供主循环调用。MCP 服务的复杂度远超普通 HTTP 客户端它需要处理协议握手capabilities 协商、OAuth 2.1 授权码流程含 PKCE、Dynamic Client Registration、Step-Up 检测、Token 刷新与吊销、企业级 Cross-App AccessXAA / SEP-990无浏览器场景下的 token 交换、会话过期重连、工具结果截断与二进制持久化、多源配置合并local / user / project / enterprise / claudeai / plugin / managed等大量边界情况。Claude Code 把这些复杂度封装在connectToServer/getMcpToolsCommandsAndResources/callMCPToolWithUrlElicitationRetry等入口背后使上层只需关心 Tool 抽象。本篇聚焦 MCP 客户端的连接管理、配置解析、OAuth 认证三大主线并通过types.ts理解其类型模型。源码位置[client.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/services/mcp/client.ts) — MCP 客户端核心connectToServer/getMcpToolsCommandsAndResources/callMCPToolWithUrlElicitingRetry[config.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/services/mcp/config.ts) — 多源配置合并与.mcp.json写入[auth.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/services/mcp/auth.ts) —ClaudeAuthProvider、OAuth 流程、token 刷新与 Step-Up 检测[types.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/services/mcp/types.ts) — Zod schema 与连接状态类型[claudeai.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/services/mcp/claudeai.ts) — Claude.ai 托管 MCP 服务器拉取[utils.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/services/mcp/utils.ts) — 工具/命令过滤工具函数[xaa.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/services/mcp/xaa.ts) — Cross-App Access token 交换[MCPConnectionManager.tsx](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/services/mcp/MCPConnectionManager.tsx) — React 层连接管理器核心实现分析1. 类型模型types.tstypes.ts用 Zod schema 定义了所有支持的 MCP server 配置形态。TransportSchema列举了 6 种传输stdio/sse/sse-ide/http/ws/sdk外加claudeai-proxy与ws-ide两种内部专用类型每种都有独立的 schemaMcpStdioServerConfigSchema——command args env最常用的本地子进程模式McpSSEServerConfigSchema/McpHTTPServerConfigSchema——url headers headersHelper oauth远程服务可附带 OAuth 配置McpSSEIDEServerConfigSchema/McpWebSocketIDEServerConfigSchema—— IDE 扩展专用带ideName与ideRunningInWindowsMcpSdkServerConfigSchema——name进程内 SDK 服务如 Chrome MCPMcpClaudeAIProxyServerConfigSchema——url idClaude.ai 托管的代理服务。ConfigScopeSchema定义了 7 种配置来源local、user、project、dynamic、enterprise、claudeai、managed这个 scope 决定了配置优先级与可见性。ScopedMcpServerConfig在McpServerConfig上加了scope与可选的pluginSource后者用于插件去重。连接状态用一组 discriminated union 表示ConnectedMCPServer/FailedMCPServer/NeedsAuthMCPServer/PendingMCPServer/DisabledMCPServerUI 据此渲染不同的状态徽标。2. 配置合并config.tsgetAllMcpConfigs是 MCP 配置的统一入口它按优先级合并多个来源managed企业下发managed-mcp.json→user全局~/.claude.json→project.mcp.json→claudeai远程拉取→dynamic运行时动态注册→plugin插件提供→enterprise设置同步。每个 server config 都被打上对应的scope标签。.mcp.json的写入采用了临时文件 fsync atomic rename模式以保证安全consttempPath${mcpJsonPath}.tmp.${process.pid}.${Date.now()}consthandleawaitopen(tempPath,w,existingMode??0o644)awaithandle.writeFile(jsonStringify(config,null,2),{encoding:utf8})awaithandle.datasync()// ... chmod rename这种写法避免了写入中途崩溃导致配置文件损坏并保留了原文件的权限位如0o600。config.ts还实现了 plugin 去重通过computeDedupSignature为每个 server 生成一个签名stdio 用commandargs远程用unwrapCcrProxyUrl(url)同一签名的 server 只保留一个避免插件与用户手动配置重复注册同一个 MCP 服务。unwrapCcrProxyUrl会剥离 CCRClaude Code Remote代理路径把/v2/session_ingress/shttp/mcp/...?mcp_url原始 URL还原成原始 vendor URL使签名能跨直连 vs CCR 代理匹配。3. 连接管理client.tsconnectToServer是 MCP 客户端的核心被memoize包装以避免重复连接。函数内部根据serverRef.type分支选择 Transportstdio——new StdioClientTransport({ command, args, env })会启动子进程并通过 stderr 监听日志sse——new SSEClientTransport(url, { authProvider, fetch, eventSourceInit })注意eventSourceInit的 fetch 不能套 timeout 包装因为 SSE 是长连接http——new StreamableHTTPClientTransport(url, { authProvider, fetch })是 MCP 推荐的流式 HTTP 传输ws / ws-ide—— 自实现WebSocketTransport支持 proxy 与 mTLSsdk—— 进程内 SDK通过InProcessTransport直接内存通信无需序列化claudeai-proxy—— 通过createClaudeAiProxyFetch包装 fetch注入 session ingress JWT。每个 transport 在连接后都会调用client.request({ method: initialize }, ...)完成协议握手协商capabilitiestools / resources / prompts / logging并把 server info 与 instructions 缓存到ConnectedMCPServer。连接关闭时client.onclose会清理三层缓存fetchToolsForClient.cache/fetchResourcesForClient.cache/fetchCommandsForClient.cache并删除connectToServer.cache中对应的 key确保下次访问时重新连接。对 stdio 服务还会显式发送SIGINT信号并等待 500ms 优雅退出否则 Docker 容器等场景下子进程可能无法触发 graceful shutdown。getMcpToolsCommandsAndResources是上层的批量入口它先把所有 server 分成 localstdio/sdk低并发和 remotesse/http/ws高并发两组用p-map并行连接并对最近 15 分钟内返回 401 的 server 跳过连接直接标记为needs-auth并附上createMcpAuthTool避免每次启动都打一遍无意义的 OAuth 探测。4. 工具与资源拉取fetchToolsForClient调用tools/list拉取工具列表把每个 MCP tool 转换成 Claude Code 内部的Tool对象return{...MCPTool,name:skipPrefix?tool.name:fullyQualifiedName,mcpInfo:{serverName:client.name,toolName:tool.name},isMcp:true,searchHint:tool._meta?.[anthropic/searchHint],alwaysLoad:tool._meta?.[anthropic/alwaysLoad]true,isConcurrencySafe:()tool.annotations?.readOnlyHint??false,isReadOnly:()tool.annotations?.readOnlyHint??false,isDestructive:()tool.annotations?.destructiveHint??false,isOpenWorld:()tool.annotations?.openWorldHint??false,// ...}这里关键设计是把 MCP 标准的annotationsreadOnlyHint / destructiveHint / openWorldHint映射到 Claude Code 的权限/调度接口isConcurrencySafe决定是否可与其他工具并行执行isDestructive决定是否需要额外确认isOpenWorld影响 auto-mode 分类器。tool._meta[anthropic/searchHint]与anthropic/alwaysLoad是 Anthropic 私有扩展分别用于工具搜索与强制加载。工具描述被截断到MAX_MCP_DESCRIPTION_LENGTH 2048字符避免 OpenAPI 生成的 MCP server 把几十 KB 的文档塞进 prompt。工具名通过buildMcpToolName(serverName, toolName)拼成mcp__server__tool形式server 名先经normalizeNameForMCP处理去特殊字符、空格转下划线。5. OAuth 认证auth.tsClaudeAuthProvider实现了 MCP SDK 的OAuthClientProvider接口是远程 MCP server 鉴权的核心。它支持完整的 OAuth 2.1 PKCE 流程Discovery—— 调用discoverAuthorizationServerMetadata从 server 的/.well-known/oauth-authorization-server拉取 AS 元数据authorization_endpoint、token_endpoint、registration_endpoint 等DCRDynamic Client Registration—— 如果 AS 不支持client_id_metadata_document_supportedCIMD则调用register动态注册客户端获取clientId/clientSecret存到 secure storageAuthorization Code PKCE—— 生成code_verifier与code_challenge构造 authorization URL 并通过本地 HTTP serverbuildRedirectUri找可用端口接收回调Token Exchange—— 用code code_verifier换取access_token refresh_tokenRefresh——tokens()方法在 access_token 过期时用 refresh_token 自动刷新刷新失败按原因分类invalid_grant/transient_retries_exhausted/metadata_discovery_failed触发不同的清理逻辑Step-Up——wrapFetchWithStepUpDetection检测 403insufficient_scope调用markStepUpPending标记需要升级授权下次tokens()会故意省略 refresh_token强制 SDK 走完整的 authorization 流程RFC 6749 §6 禁止通过 refresh 提升 scope。normalizeOAuthErrorBody是一个有意思的兼容层有些 OAuth server如 Slack在 token 失效时返回 HTTP 200 {error:invalid_grant}而 SDK 只在!response.ok时才解析错误导致 Zod 校验失败被当成request_failed。这个 wrapper 会 peek 2xx 响应体若匹配OAuthErrorResponseSchema则改写成 400 响应并把 Slack 的非标准错误码invalid_refresh_token/expired_refresh_token/token_expired归一化为invalid_grant使 token 失效逻辑能正确触发。6. XAACross-App Accessxaa.ts实现了企业场景下的无浏览器认证SEP-990。它通过两步 token 交换获得 MCP access_tokenRFC 8693 Token Exchange—— 在企业 IdP 用 id_token 换取 ID-JAGIdentity JWT Authorization GrantRFC 7523 JWT Bearer Grant—— 在 MCP 的 AS 用 ID-JAG 换取 access_token。这样企业用户在 IdP 已登录后无需再打开浏览器走 MCP 的 OAuth 同意页即可直接访问 MCP 服务。xaaIdpLogin.ts负责 IdP 侧的 OIDC discovery、client secret 管理与 id_token 缓存。7. Claude.ai 托管 MCPclaudeai.ts的fetchClaudeAIMcpConfigsIfEligible从BASE_API_URL/v1/mcp_servers拉取用户在 Claude.ai 网页端配置的 MCP 服务列表使用mcp-servers-2025-12-04beta header。它直接检查user:mcp_serversscope 而非isClaudeAISubscriber()因为非交互模式下设置ANTHROPIC_API_KEY会让isClaudeAISubscriber()返回 false但 OAuth token 仍可用。返回结果被 memoize 以保证一次 CLI 会话只拉取一次。关键设计要点传输抽象统一6 种 transport claudeai-proxy 都被connectToServer收敛到同一个ConnectedMCPServer类型上层fetchToolsForClient/callMCPToolWithUrlElicitatingRetry无需感知传输差异三层缓存隔离connectToServer/fetchToolsForClient/fetchResourcesForClient各自独立 memoize连接断开时三层缓存联动清理既避免重复握手又保证重连后立即拉到新工具列表OAuth 兼容性打补丁normalizeOAuthErrorBody修正 Slack 等 server 的 200error 非标准行为NONSTANDARD_INVALID_GRANT_ALIASES归一化错误码wrapFetchWithStepUpDetection处理 scope 不足——这些兼容层让 Claude Code 能对接各类不严格遵守 RFC 的 OAuth server配置多源 签名去重7 种 ConfigScope 按优先级合并plugin 与用户手动配置通过computeDedupSignature去重CCR 代理 URL 被unwrapCcrProxyUrl还原后再签名避免同一服务被注册两次stdio 进程优雅退出cleanup 时显式发SIGINT并轮询process.kill(pid, 0)检测进程存活500ms 内未退出才升级信号兼顾 Docker 容器的 graceful shutdown 需求与 CLI 响应性。与其他模块的关系Tool 系统MCPTool是所有 mcp 工具共享的 Tool 实现fetchToolsForClient把每个 MCP tool 包装成带mcpInfo的 Tool 注入主循环权限系统channelPermissions.ts/channelAllowlist.ts决定 MCP 工具是否需要用户确认依赖isDestructive/isOpenWorld等 annotationanalytics 服务logEvent(tengu_mcp_*)贯穿连接、工具调用、OAuth 流程是 MCP 可靠性归因的主要数据源oauth 服务MCP_CLIENT_METADATA_URL、getOauthConfig来自constants/oauth.ts与第一方 OAuth 共享配置secureStorageOAuth token、client secret 通过getSecureStorage()持久化到 macOS Keychain / Windows Credential Manager / Linux libsecretcompact 服务MCP 工具结果可能很大truncateMcpContentIfNeeded与persistBinaryContent把大输出落地到磁盘避免撑爆 contextplugins 服务getPluginMcpServers让插件能注册 MCP server与用户配置走相同的去重逻辑IDE 集成sse-ide/ws-ide传输与 IDE 扩展VSCode、JetBrains通信maybeNotifyIDEConnected在连接成功后通知 IDE。小结MCP 服务是 Claude Code 扩展性的基石通过一套统一的传输抽象与 OAuth 认证框架它把接入任意外部工具这件事简化成了写一个 MCP server 并在.mcp.json里登记。其实现细节体现了大量工程经验——从 OAuth 兼容性补丁到 stdio 优雅退出从三层缓存联动到 CCR 代理 URL 还原每一处都对应着真实场景中踩过的坑。理解 MCP 服务之后再看mcp__xxx__yyy形式的工具调用就会清晰得多那背后是一次完整的握手、一次 OAuth token 刷新、一次tools/list拉取、以及一次tools/callRPC全部由services/mcp/默默承担。