---
title: "MCP Integration — Claude Code Internals — Vlad Arbatov"
description: "Server lifecycle, tool proxying, OAuth, resources, elicitation"
image: "https://arbatov.dev/share-claude.png"
---

# MCP (Model Context Protocol) Integration

## Overview

Claude Code implements sophisticated MCP integration for tool extension: server discovery, connection management, tool proxying, resource handling, OAuth authentication, and graceful error recovery.

---

## Server Configuration & Scopes

- **Local** (`.mcp.json` in current directory)
- **User** (`~/.claude/mcp.json`)
- **Project** (`.mcp.json` in project root) — requires approval
- **Dynamic** (plugin-provided servers)
- **Enterprise/Managed** (admin-controlled)

**Transport Types:**
- `stdio` (local subprocess)
- `sse` (Server-Sent Events)
- `http` (Streamable HTTP)
- `ws` (WebSocket)
- `sdk` (in-process SDK servers)
- `claudeai-proxy` (claude.ai proxy servers)

---

## Connection Lifecycle

### Startup (services/mcp/client.ts:595-1641)

- Servers split by type: local (concurrency=3), remote (concurrency=20)
- Transports initialized with appropriate auth/headers
- Connection timeout: 30 seconds (configurable via MCP_TIMEOUT)
- Failed servers generate `NeedsAuthMCPServer` or `FailedMCPServer` states

### Auto-Reconnection (services/mcp/useManageMCPConnections.ts:310-431)

- Triggered on `client.onclose` for remote transports (sse/http/ws)
- Exponential backoff: 1s, 2s, 4s, 8s, 16s (max 5 attempts)
- Stdio/sdk servers don't auto-reconnect
- Clears keychain cache on reconnect for fresh credentials

### Graceful Shutdown (services/mcp/client.ts:1450-1570)

Three-phase:
1. SIGINT (graceful), wait 15 seconds
2. SIGTERM, wait 400ms
3. SIGKILL (force)

---

## Tool Execution & Proxying

### Tool Registration

- Tool names: `mcp__servername__toolname`
- Schema: `z.object({}).passthrough()` (accepts any MCP args)
- Marked with `isMcp: true` flag

### Call Flow (services/mcp/client.ts:2813-3245)

1. `callMCPToolWithUrlElicitationRetry` — handles 3 retry attempts for URL elicitations
2. Sets up 30-second progress logging for long-running tools
3. Custom timeout (default ~27.8 hours via MCP_TOOL_TIMEOUT)
4. Calls `client.callTool()` with abort signal
5. Checks `result.isError` flag
6. Processes through `processMCPResult()`

### Large Output Handling (services/mcp/client.ts:2720-2799)

- Checks if content exceeds token limit via `mcpContentNeedsTruncation()`
- **Disabled**: Truncate in-memory
- **Enabled**: Persist to disk, return file path instructions
- Skips persistence for image content

### Error Cases

| Error | Detection | Recovery |
|-------|-----------|----------|
| 401 Unauthorized | `McpAuthError` | Trigger auth tool |
| 404 + -32001 | Session expired | Clear cache, reconnect |
| -32000 "Connection closed" | HTTP transport | Treat as session expired |
| -32042 | URL elicitation required | Retry up to 3 times with user interaction |

---

## Resource Handling

### ListMcpResourcesTool

- Lists all resources or filtered by server
- LRU-cached `fetchResourcesForClient()` (max 20 entries)
- Invalidated on close or `resources/list_changed` notification

### ReadMcpResourceTool

- Reads specific resource by URI
- Binary blob: decodes base64, writes to disk, returns path
- Text content returned inline

---

## Authentication & OAuth

### OAuth Flow (services/mcp/auth.ts)

**ClaudeAuthProvider** implements MCP SDK's OAuthClientProvider:
- Token discovery, refresh, platform keychain integration
- Detects step-up (403) and retries with force-refresh
- 60-second timeout for OAuth requests

**Token Storage:**
- `~/.claude/mcp-tokens.json` (encrypted on macOS via keychain)
- Per-server client secret in platform keychain
- 15-minute cache TTL for "needs-auth" state

### McpAuthTool (tools/McpAuthTool/McpAuthTool.ts)

Created when MCP server returns 401:
- Only supports sse/http/claudeai-proxy (not stdio)
- Initiates `performMCPOAuthFlow`
- Returns authorization URL immediately
- Background continuation reconnects and swaps tools into AppState

### Cross-App Access (XAA) (services/mcp/xaa.ts)

For servers with `oauth.xaa: true`:
1. Acquires ID token from configured IdP
2. Exchanges at server's token endpoint
3. Server returns MCP tokens (access + optional refresh)
4. Stores in keychain

---

## Elicitation Handling (services/mcp/elicitationHandler.ts)

MCP servers can request user interaction:

**Form Mode:**
- Server provides fields with types (string, number, boolean, etc.)
- User enters values in dialog or via hooks
- Server receives ElicitResult with user input

**URL Mode:**
- Server provides OAuth/login URL and unique elicitationId
- User opens URL, completes auth flow
- Server sends `notifications/elicitation_complete`
- CC retries tool call after notification

---

## Plugin Integration (utils/plugins/mcpPluginIntegration.ts)

Plugins provide MCP servers via:
1. **Manifest entry**: Static config in plugin.json
2. **.mcp.json file**: Dynamic config in plugin directory
3. **MCPB archive**: DXT manifest with user configuration
4. **Variable substitution**: `${USER_VAR_NAME}` replaced at connection time

---

## Tool Caching (services/mcp/client.ts:1743-2080)

```typescript
fetchToolsForClient = memoizeWithLRU(
  async (client) => client.client.request({method: 'tools/list'}),
  (client) => client.name,
  {max: 20}
)
```

- Name normalization: `my-tool` → `my_tool`
- Description capped at 2048 chars
- IDE tools whitelisted (only executeCode and getDiagnostics)

---

## SDK MCP Servers (In-Process)

**Transport Bridge** (services/mcp/SdkControlTransport.ts):
- `SdkControlClientTransport`: CLI-side
- `SdkControlServerTransport`: SDK-side
- Message correlation via request IDs
- Parallel connection setup

---

## Project Server Approval (services/mcpServerApproval.tsx)

Project-scoped servers require user approval:
- Single server: MCPServerApprovalDialog
- Multiple servers: MCPServerMultiselectDialog
- Approval cached per project + server pair
- Stored in `~/.claude/.approval-cache`
