MCP Integration
此内容尚不支持你的语言。
Claude Code has first-class support for the Model Context Protocol (MCP) — an open standard for connecting AI assistants to external tools and data sources. MCP tools are treated as peers to built-in tools, sharing the same dispatch, permission, and hook systems.
Architecture Overview
Section titled “Architecture Overview”graph TD A[MCP Configuration] --> B[useManageMCPConnections] B --> C{Transport Type} C --> D[stdio - local process] C --> E[sse - Server-Sent Events] C --> F[http - Streamable HTTP] C --> G[ws - WebSocket] C --> H[sdk - SDK control] C --> I[sse-ide / ws-ide - IDE] C --> J[claudeai-proxy]
D --> K[MCP Client] E --> K F --> K G --> K H --> K I --> K J --> K
K --> L[ListTools] L --> M[MCPTool wrappers] M --> N[Merged with built-in tools] N --> O[ToolUseContext.options.tools]
K --> P[ListResources] P --> Q[ListMcpResources / ReadMcpResource tools]MCP Server Configuration
Section titled “MCP Server Configuration”MCP servers are configured in settings.json at user, project, or enterprise scope. The configuration is defined in src/services/mcp/types.ts:
Transport Types
Section titled “Transport Types”export const TransportSchema = z.enum([ 'stdio', // Local subprocess 'sse', // Server-Sent Events (HTTP/2) 'sse-ide', // IDE-specific SSE 'http', // Streamable HTTP 'ws', // WebSocket 'sdk', // SDK control transport]);Configuration Schemas
Section titled “Configuration Schemas”stdio — Launch a local process:
const McpStdioServerConfigSchema = z.object({ type: z.literal('stdio').optional(), command: z.string().min(1), args: z.array(z.string()).default([]), env: z.record(z.string(), z.string()).optional(),});sse/http — Connect to a remote server:
const McpSSEServerConfigSchema = z.object({ type: z.literal('sse'), url: z.string(), headers: z.record(z.string(), z.string()).optional(), headersHelper: z.string().optional(), oauth: z.object({ clientId: z.string().optional(), callbackPort: z.number().optional(), authServerMetadataUrl: z.string().url().optional(), }).optional(),});Configuration Scope
Section titled “Configuration Scope”Each server config carries a scope that determines its trust level:
export type ConfigScope = 'local' | 'user' | 'project' | 'dynamic' | 'enterprise' | 'claudeai' | 'managed';
export type ScopedMcpServerConfig = McpServerConfig & { scope: ConfigScope; pluginSource?: string; // For plugin-provided servers};Connection Lifecycle
Section titled “Connection Lifecycle”Server Connection States
Section titled “Server Connection States”stateDiagram-v2 [*] --> pending: Config detected pending --> connected: Client connects successfully pending --> failed: Connection error pending --> needs_auth: OAuth required (401) pending --> disabled: User disabled failed --> pending: Reconnect attempt needs_auth --> pending: OAuth flow completed connected --> failed: Connection lost connected --> pending: Reconnect (tool list changed) disabled --> pending: User re-enabledexport type MCPServerConnection = | ConnectedMCPServer // type: 'connected' | FailedMCPServer // type: 'failed' | NeedsAuthMCPServer // type: 'needs-auth' | PendingMCPServer // type: 'pending' | DisabledMCPServer // type: 'disabled'Connected Server Shape
Section titled “Connected Server Shape”export type ConnectedMCPServer = { client: Client; // MCP SDK Client instance name: string; type: 'connected'; capabilities: ServerCapabilities; serverInfo?: { name: string; version: string }; instructions?: string; // Server-provided instructions (capped at 2048 chars) config: ScopedMcpServerConfig; cleanup: () => Promise<void>;};Reconnection with Exponential Backoff
Section titled “Reconnection with Exponential Backoff”const MAX_RECONNECT_ATTEMPTS = 5;const INITIAL_BACKOFF_MS = 1000;const MAX_BACKOFF_MS = 30000;On disconnection:
- Set server state to
pendingwithreconnectAttemptcounter - Wait
min(INITIAL_BACKOFF_MS * 2^attempt, MAX_BACKOFF_MS)milliseconds - Attempt reconnection
- If failed and
attempt < MAX_RECONNECT_ATTEMPTS, increment and retry - If all attempts exhausted, set state to
failed
Tool Registration
Section titled “Tool Registration”When an MCP server connects, its tools are fetched and wrapped:
// src/services/mcp/client.ts (conceptual flow)async function fetchToolsForClient( server: ConnectedMCPServer,): Promise<Tool[]> { const result: ListToolsResult = await server.client.listTools();
return result.tools.map(mcpTool => { const normalizedName = buildMcpToolName(server.name, mcpTool.name);
return { ...MCPTool, // Base MCPTool template name: normalizedName, // e.g., "mcp__github__create_issue" isMcp: true, mcpInfo: { serverName: server.name, toolName: mcpTool.name }, inputJSONSchema: mcpTool.inputSchema, shouldDefer: mcpTool._meta?.['anthropic/deferLoading'] ?? false, alwaysLoad: mcpTool._meta?.['anthropic/alwaysLoad'] ?? false,
async description() { return truncate(mcpTool.description, MAX_MCP_DESCRIPTION_LENGTH); },
async call(args, context, canUseTool, parentMessage, onProgress) { const result = await server.client.callTool({ name: mcpTool.name, arguments: args, }, CallToolResultSchema, { timeout: getMcpToolTimeoutMs(), signal: context.abortController.signal, onprogress: (progress) => { onProgress?.({ toolUseID: context.toolUseId!, data: { type: 'mcp_progress', ...progress }, }); }, }); return { data: formatMcpResult(result) }; }, }; });}Tool Naming Convention
Section titled “Tool Naming Convention”MCP tools are namespaced to prevent conflicts:
mcp__{serverName}__{toolName}Example: A GitHub MCP server named github with a tool create_issue becomes mcp__github__create_issue.
The normalization logic in src/services/mcp/normalization.ts handles:
- Special characters in server/tool names
- Name length limits
- Collision detection
Unified Tool Dispatch
Section titled “Unified Tool Dispatch”MCP tools and built-in tools share the same dispatch pipeline:
graph TD A[API Response: tool_use block] --> B[findToolByName] B --> C{Is MCP tool?} C -- yes --> D[MCPTool.call → client.callTool] C -- no --> E[BuiltinTool.call → local execution] D --> F[Format MCP result] E --> G[Format built-in result] F --> H[tool_result message] G --> HThe same pipeline applies to:
- Input validation: MCP tools validate against
inputJSONSchema - Permission checks: Same
checkPermissionsflow - PreToolUse/PostToolUse hooks: Same hook system
- Result formatting: Same
mapToolResultToToolResultBlockParam - Concurrent execution: MCP tools are concurrent-safe by default (read-only from Claude Code’s perspective)
MCP-Specific Permission Handling
Section titled “MCP-Specific Permission Handling”MCP tools use behavior: 'passthrough' for checkPermissions, meaning they always require the standard permission flow:
async checkPermissions(): Promise<PermissionResult> { return { behavior: 'passthrough', message: 'MCPTool requires permission.', };}The actual permission decision goes through the full chain: hooks → rule-based → user prompt.
Resource Support
Section titled “Resource Support”MCP servers can expose resources (files, data) alongside tools:
export type ServerResource = Resource & { server: string };Two built-in tools handle resources:
- ListMcpResources (
src/tools/ListMcpResourcesTool/): Lists available resources from all connected servers - ReadMcpResource (
src/tools/ReadMcpResourceTool/): Reads a specific resource by URI
MCP Result Handling
Section titled “MCP Result Handling”MCP tool results can contain various content types. The client processes them:
// Content types from MCPtype MCPToolResult = { content: Array< | { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string } | { type: 'resource'; resource: { uri: string; text?: string } } >; isError?: boolean; _meta?: Record<string, unknown>; structuredContent?: Record<string, unknown>;};Large results are truncated to prevent context overflow:
const MAX_MCP_TOOL_RESULT_SIZE = 100_000; // characters
function truncateMcpContentIfNeeded(content: MCPToolResult): MCPToolResult { if (!mcpContentNeedsTruncation(content)) return content; // Truncate and add "output truncated" marker}Session-Expired Error Handling
Section titled “Session-Expired Error Handling”MCP sessions can expire (HTTP 404 + JSON-RPC code -32001). The client handles this transparently:
export function isMcpSessionExpiredError(error: Error): boolean { const httpStatus = 'code' in error ? error.code : undefined; if (httpStatus !== 404) return false; return ( error.message.includes('"code":-32001') || error.message.includes('"code": -32001') );}On session expiry:
- Clear the cached client connection
- Get a fresh client via
ensureConnectedClient - Retry the tool call
Server Notifications
Section titled “Server Notifications”Connected MCP servers can push notifications that trigger tool/resource refreshes:
server.client.setNotificationHandler(ToolListChangedNotificationSchema, () => { // Re-fetch tools from server void fetchToolsForClient(server);});
server.client.setNotificationHandler(ResourceListChangedNotificationSchema, () => { // Re-fetch resources from server void fetchResourcesForClient(server);});
server.client.setNotificationHandler(PromptListChangedNotificationSchema, () => { // Re-fetch prompts from server});This enables dynamic tool registration — an MCP server can add/remove tools at runtime and Claude Code picks up the changes automatically.
OAuth Authentication
Section titled “OAuth Authentication”Remote MCP servers (SSE, HTTP) can require OAuth:
export class ClaudeAuthProvider { // Implements the MCP SDK's AuthProvider interface // Handles OAuth authorization code flow // Stores tokens in secure storage (macOS Keychain, etc.)}The auth flow:
- Server returns 401
- Client creates
ClaudeAuthProviderwith the server’s OAuth config - Provider discovers the authorization server metadata URL
- Opens browser for user authorization
- Receives callback with authorization code
- Exchanges code for tokens
- Stores tokens and retries connection
Auth state is cached for 15 minutes to avoid repeated auth flows:
const MCP_AUTH_CACHE_TTL_MS = 15 * 60 * 1000;