MCP Protocol Integration: How AI Tools Connect to Everything
A deep dive into Claude Code's MCP integration — Server lifecycle management, dynamic tool generation, Resource/Prompt support, and inter-Agent Server inheritance
The Problem
Claude Code can query databases, access APIs, and manipulate Figma — these capabilities aren't hardcoded but dynamically loaded through MCP.
When you configure a Slack Server in .mcp.json, Claude Code automatically connects to it, discovers all the tools it provides (search messages, send messages, list channels), then registers those tools as callable Tools for Claude, as if they were built-in. When you say "send a message in the #general channel," Claude chooses to call mcp__slack__send_message, serializes the arguments as JSON, sends them through a stdio pipe to the Slack Server process, waits for the response, and presents the result to you.
This mechanism is far more than simple RPC calls. It involves:
- A unified abstraction over multiple transport protocols (stdio, SSE, HTTP Streamable, WebSocket, SDK in-process)
- Connection pooling with memoize caching to avoid redundant handshakes
- Dynamic tool discovery: after a Server declares its capabilities, Claude Code automatically converts MCP Tools to the internal
Toolinterface - JSON Schema pass-through: MCP tools bypass Zod parsing, passing
inputSchemadirectly to the API as JSON Schema - Integration of two auxiliary primitives: Resources and Prompts
- Handling of edge cases like OAuth authentication, URL Elicitation, and Session expiration reconnection
- MCP Server sharing and isolation between Agent subprocesses
This article starts with a protocol overview and progressively dives into Claude Code's MCP integration implementation.
MCP Protocol Overview
The Model Context Protocol (MCP) is an open protocol proposed by Anthropic to standardize how AI models interact with external tools and data sources. Its core design philosophy is: the model doesn't need to know implementation details of tools — it only needs the tool's name, description, and input Schema.
MCP defines five core concepts:
| Concept | Role | Description |
|---|---|---|
| Client | Consumer | The MCP client within the Claude Code process, responsible for connecting to Servers, discovering tools, and initiating calls |
| Server | Provider | An independent process or remote service that exposes tools/resources/prompts |
| Tool | Executable action | A function provided by a Server, with a name, description, and JSON Schema-defined input parameters |
| Resource | Context data | Read-only data provided by a Server (file contents, API responses, etc.) that can be injected into conversation context |
| Prompt | Preset template | A prompt template provided by a Server that users can invoke as a slash command |
Transport Layer Diversity
The transport types supported by Claude Code go well beyond the basic specification. The full type list can be seen from the Schema definition in types.ts:
Each transport type corresponds to a different use case:
| Transport | Scenario | Characteristics |
|---|---|---|
stdio | Local CLI tools | Spawns a subprocess, communicates via stdin/stdout |
sse | Remote HTTP services | Server-Sent Events, supports OAuth |
http | Streamable HTTP | New transport from MCP 2025-03-26 spec |
ws | WebSocket | Bidirectional real-time communication |
sse-ide | IDE extensions | Used internally by VS Code/JetBrains |
sdk | In-process Server | Agent SDK scenarios, no subprocess needed |
The sdk type uses an elegant InProcessTransport:
createLinkedTransportPair() creates a pair of interconnected Transports — one for the Client and one for the Server. send() uses queueMicrotask internally to deliver messages asynchronously, avoiding stack overflow from synchronous RPC calls — this is critical in high-frequency tool call scenarios.
Server Configuration and Discovery
Configuration Hierarchy
MCP Server configuration comes from multiple layers, each with a different scope:
The ConfigScope type for each configuration source is defined as:
The project scope comes from the .mcp.json file at the project root — this is the most common configuration method. Since project configurations may contain malicious Servers, Claude Code introduces an approval mechanism:
Note the security boundary: auto-approval in --dangerously-skip-permissions mode only checks hasSkipDangerousModePermissionPrompt() — this function deliberately excludes projectSettings, preventing malicious repositories from self-approving bypass mode through project configuration.
Name Normalization
The MCP protocol requires tool names to match ^[a-zA-Z0-9_-]{1,64}$. Since Server names may contain spaces, dots, and other special characters (especially claude.ai Servers), normalization is needed:
The fully qualified tool name format is mcp__<serverName>__<toolName>:
This naming convention has a known limitation: if a Server name itself contains __, parsing will break. The code comments explicitly document this — in practice, this scenario is extremely rare.
Server Lifecycle Management
Connection Flow
connectToServer is the entry function for the entire MCP integration. It's wrapped with memoize, using name + JSON(config) as the cache key to ensure Servers with identical configurations aren't connected redundantly:
The connection result is a union type that precisely expresses five possible states:
ConnectedMCPServer holds the core state of a connection:
Batch Connection Strategy
Claude Code doesn't connect to all Servers at once. It distinguishes between local Servers (stdio/sdk) and remote Servers, using different concurrency limits for each:
Local Servers default to concurrency of 3 (high process startup overhead), while remote Servers default to 20 (network connections only). In getMcpToolsCommandsAndResources, both groups are processed in parallel:
Cleanup and Reconnection
Each stdio Server connection registers a cleanup function to the global cleanup registry, ensuring all subprocesses are properly terminated when the process exits:
Cleanup is especially important for stdio Servers — it first sends SIGTERM, waits for the process to exit, and escalates to SIGKILL if it times out:
When the cache is invalidated, clearServerCache clears all associated caches:
Note that four independent caches are cleared here — the connection cache and three data fetch caches. If only the connection cache were cleared while retaining the fetch caches, a reconnection would use stale tool lists.
Session Expiration Reconnection
For HTTP Streamable transport, the MCP spec defines a Session expiration mechanism (HTTP 404 + JSON-RPC error code -32001). Claude Code has dedicated detection logic:
When a Session expiration is encountered during a tool call, it automatically retries once:
Dynamic Tool Generation
This is the most critical part of the MCP integration: how MCP Server-declared tools are converted into Claude Code's internal Tool interface.
The MCPTool Template
MCPTool.ts defines a "template" object containing the base behavior shared by all MCP tools:
Note the design pattern here: MCPTool uses z.object({}).passthrough() as its inputSchema — this is a Zod Schema that "accepts any object," because the actual Schema for MCP tools is passed through via inputJSONSchema.
fetchToolsForClient: From Server to Tool
fetchToolsForClient is the core function for tool discovery. It calls the MCP protocol's tools/list method, then maps each MCP Tool to an internal Tool object:
This code reveals several key design decisions:
1. Object spread override pattern: { ...MCPTool, ...overrides } uses the template object as a base, overriding field by field. This is more flexible than inheritance and better aligned with TypeScript's structural type system.
2. MCP Annotations mapping: The MCP 2025-03-26 spec introduced Tool Annotations (readOnlyHint, destructiveHint, openWorldHint), which Claude Code directly maps to corresponding methods on the internal Tool interface.
3. Description length limit: MAX_MCP_DESCRIPTION_LENGTH = 2048. Some OpenAPI auto-generated MCP Servers produce tool descriptions of 15-60KB; without limits, this would waste enormous amounts of tokens.
4. IDE tool filtering: The isIncludedMcpTool function filters out non-whitelisted IDE tools, only allowing executeCode and getDiagnostics.
Tool Call Chain
When the model decides to call an MCP tool, the call chain proceeds as follows:
The retry logic within the call method operates at two levels:
- Session expiration retry: Up to 1 attempt, clears the connection cache and re-obtains the Client
- URL Elicitation retry: Up to 3 attempts, handles MCP -32042 error code (Server requests user to open a URL for authorization)
JSON Schema vs Zod: Dual-Track Parameter Validation
This is an interesting design divergence in Claude Code's tool system. Built-in tools use Zod Schema, while MCP tools use JSON Schema — two systems running in parallel.
The Zod Path for Built-in Tools
Built-in tools (like Read, Write, Bash) define inputSchema as a Zod Schema:
Zod Schemas are automatically converted to JSON Schema before being sent to the API.
JSON Schema Pass-Through for MCP Tools
MCP tools bypass the Zod layer entirely. The Tool interface specifically defines an inputJSONSchema field:
In fetchToolsForClient, the MCP tool's inputSchema (JSON Schema from the Server) is directly assigned to inputJSONSchema:
Why not convert JSON Schema to Zod? Three reasons:
- Performance: Runtime conversion from JSON Schema to Zod has overhead, and MCP Servers may provide complex nested Schemas
- Fidelity: Certain JSON Schema features (
patternProperties,additionalProperties,oneOfcombinations) have no direct Zod equivalents - Unnecessary: The Claude API itself accepts JSON Schema, so no intermediate conversion is needed
This is why MCPTool's inputSchema is a permissive z.object({}).passthrough() — it performs no actual validation at runtime, and the real Schema is passed through to the API via inputJSONSchema.
Resource and Prompt Integration
Resource: Context Data Injection
MCP Resources allow Servers to expose read-only data. Claude Code provides two built-in tools for this:
ListMcpResourcesTool: Lists all Resources provided by MCP ServersReadMcpResourceTool: Reads the content of a specific Resource
The Resource type definition extends the MCP SDK type with Server attribution:
In getMcpToolsCommandsAndResources, Resource tools are only added when a Server has declared the resources capability, and they're added only once globally:
The resourceToolsAdded flag ensures that even if 10 Servers all declare the Resource capability, the List and Read tools are only registered once — they can access Resources from all Servers.
Resource fetching also uses LRU caching and concurrent processing. prefetchAllMcpResources prefetches tools, commands, and Resources from all Servers at startup, avoiding delays during the first conversation.
Prompt: Preset Command Templates
MCP Prompts are converted to Claude Code Commands (slash commands). When a Server declares the prompts capability, fetchCommandsForClient calls prompts/list to get the list:
The distinction between Prompts and Skills is subtle: MCP Prompts set isMcp: true, while MCP Skills (discovered from skill:// Resources) set loadedFrom: 'mcp'. This distinction affects capability display in the /mcp menu:
Inter-Agent MCP Server Sharing and Isolation
Claude Code's multi-Agent architecture (main thread + sub-Agents) introduces the question of MCP Server sharing.
Sharing Mechanism
After the main thread connects to MCP Servers, sub-Agents inherit the parent process's connections through ToolUseContext.options.mcpClients:
Sub-Agents don't need to reconnect to MCP Servers — they share the connections already established by the parent process. This is because connectToServer's memoize cache is shared globally within the process.
Isolation Mechanism
However, Server configuration changes don't automatically propagate. excludeStalePluginClients is responsible for detecting stale connections:
Staleness detection uses SHA-256 hash comparison of configurations, excluding the scope field (since scope is metadata that doesn't affect connection parameters).
Change Notifications
The MCP protocol supports Server-side push change notifications. useManageMCPConnections listens for three notification types:
Upon receiving a notification, Claude Code clears the corresponding fetch cache and re-fetches data. This allows Servers to dynamically add/remove tools at runtime — for example, a database Server might update available query tools after the user switches database connections.
Reconnection with Exponential Backoff
Disconnection reconnection uses an exponential backoff strategy:
The interval between each reconnection attempt doubles, up to a 30-second cap, with a maximum of 5 attempts.
OAuth and Elicitation
Authentication Cache
For remote Servers requiring OAuth (SSE, HTTP), Claude Code maintains an authentication cache to avoid repeated probing:
Cache writes are serialized through a promise chain, preventing concurrent read-modify-write race conditions:
URL Elicitation
The MCP spec's -32042 error code indicates the Server needs the user to open a URL to complete authorization. Claude Code has a complete handling flow for this:
Elicitation handling operates at three layers:
- Hooks first:
runElicitationHooksallows custom logic to handle it automatically - SDK/Print mode: Delegates to structured IO via the
handleElicitationcallback - REPL mode: Displays a UI dialog through the AppState queue
Large Result Handling
MCP tools may return large amounts of data. processMCPResult implements a tiered handling strategy:
Handling strategy priority:
- Small results: Return directly
- Large image results: Truncate (maintain viewability)
- Large text results: Persist to file, return Read instructions for the model to fetch on demand
- Persistence failure: Fall back to truncation
The inferCompactSchema function generates a compact Schema description for structured content, helping the model understand the output format:
Claude.ai Proxy Connections
Claude Code can access MCP Servers configured on claude.ai, using a special claudeai-proxy transport type. Proxy connections need to handle OAuth token refresh:
Note the use of sentToken — the code deliberately records the token used when sending the request, rather than re-reading the current token after the 401 response. This is because a concurrent connector may have already refreshed the token via handleOAuth401Error in the meantime; re-reading would get the new token, which when passed to handleOAuth401Error would be judged as "token unchanged" and skip the refresh.
Timeouts and Request Wrapping
Tool Call Timeout
The default timeout for MCP tool calls is approximately 27.8 hours (effectively "infinite"):
This seemingly outrageous value is intentional — some MCP tools (like database migrations, large-scale analyses) genuinely need long execution times. Users can customize this via the MCP_TOOL_TIMEOUT environment variable.
Per-Request Timeout Wrapping
Unlike the tool timeout, each HTTP request has a 60-second timeout. wrapFetchWithTimeout creates an independent AbortController for each request:
A code comment reveals an interesting Bun runtime detail: internal timers created by AbortSignal.timeout() are only released during GC, causing each request to leak approximately 2.4KB of native memory. Hence the switch to manually managing lifecycle with setTimeout + clearTimeout.
Serialized State and CLI Integration
MCP state can be serialized to JSON for the /mcp command and CLI state exports:
The normalizedNames mapping solves a practical problem: users reference original names in permission rules, but the system internally uses normalized names. This mapping ensures permission checks can correctly associate the two.
Portable Patterns and Best Practices
Configuration Organization Recommendations
Based on Claude Code's configuration hierarchy design, the following organization is recommended:
| Scenario | Recommended Config Location | Reason |
|---|---|---|
| Team-shared project tools | .mcp.json (project scope) | Version-controlled with code, new members get it automatically |
| Personal preference tools | ~/.claude.json (user scope) | Available across projects |
| CI/CD environments | --mcp-server argument (dynamic scope) | Temporary injection without modifying config files |
| Enterprise compliance tools | managed config (enterprise scope) | Organization-wide management, users cannot modify |
Tool Design Principles
From Claude Code's MCP integration code, several MCP Server tool design principles can be distilled:
- Use annotations: Declare
readOnlyHintto enable concurrent tool execution; declaredestructiveHintto trigger additional confirmation - Control description length: Descriptions over 2048 characters will be truncated — put core usage information first
- Support pagination: Large results will be truncated or persisted to files — provide pagination parameters so the model can fetch on demand
- Use searchHint: Help ToolSearch find lazily-loaded tools via
_meta['anthropic/searchHint'] - Use alwaysLoad: For critical tools the model must see in the first turn, set
_meta['anthropic/alwaysLoad']to skip ToolSearch
Security Boundaries
The MCP integration's security model deserves special attention:
- Project Server approval: Servers in
.mcp.jsonrequire explicit user approval (unless in non-interactive mode with projectSettings enabled) - Permission isolation: MCP tools use
passthroughpermission mode, meaning each call is checked against global permission rules - Namespace isolation: The
mcp__prefix ensures MCP tools don't conflict with built-in tools (can be optionally disabled in SDK mode) - Description truncation: Prevents malicious Servers from injecting prompts via overly long descriptions
- Unicode sanitization:
recursivelySanitizeUnicoderemoves control characters that could interfere with model behavior
Architecture Summary
Claude Code's MCP integration is a carefully designed extensible architecture. It cleanly separates "connection management" from "tool adaptation": client.ts handles establishing and maintaining connections to MCP Servers, MCPTool.ts provides the tool template, and fetchToolsForClient bridges the two — from a Server's raw capability declarations to Tool objects callable by Claude, passing through name normalization, description truncation, Schema pass-through, permission configuration, and a series of other transformations.
Core principles of the overall design:
- Lazy discovery: Only connect to Servers and fetch tool lists when needed, using memoize caching to avoid redundant operations
- Graceful degradation: Connection failures don't block the entire system — only that Server's tools become unavailable; Servers requiring authentication register an
McpAuthToolto guide users through authentication - Dual-track Schema: Built-in tools use Zod for type safety and validation, while MCP tools use JSON Schema pass-through for flexibility and efficiency
- Defensive design: From Unicode sanitization to description truncation, from configuration hash comparison to authentication cache serialization, edge case considerations are evident throughout
For projects looking to build similar extension systems, the MCP integration provides a referenceable pattern: use template objects + object spread for dynamic tool registration, memoize caching for connection lifecycle management, union types for precise connection state expression, and configuration hierarchies for flexible scope management.