Overview

MCP (Model Context Protocol) extends agent-code with tools and resources from external servers. agent-code acts as an MCP client, connecting to one or more MCP servers.

Connection Lifecycle

1. Startup: read mcp_servers from config
2. For each server:
   a. Spawn subprocess (stdio) or connect HTTP (SSE)
   b. Send initialize request (protocol version, capabilities)
   c. Receive initialize response (server capabilities)
   d. Send tools/list request
   e. Register discovered tools as mcp__<server>__<tool> in the tool pool
   f. Send resources/list request (if supported)
   g. Register discovered resources
3. Ready: MCP tools available to the LLM alongside built-in tools

Transports

Stdio

The default transport. agent-code spawns the server as a subprocess and communicates via JSON-RPC over stdin/stdout.

[mcp_servers.github]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
env = { GITHUB_TOKEN = "ghp_..." }

The subprocess inherits the configured env variables. Stderr is captured for debugging.

SSE (Server-Sent Events)

For servers that expose an HTTP endpoint:

[mcp_servers.remote]
url = "http://localhost:8080"

Uses HTTP POST for requests and SSE for responses/notifications.

Tool Proxying

When the LLM calls an MCP tool, the request flows through McpProxy:

LLM: tool_use { name: "mcp__github__create_issue", input: {...} }
    │
    ▼
McpProxy.call()
    ├── Parse server name and tool name from the namespaced name
    ├── Find the MCP client for "github"
    ├── Send tools/call JSON-RPC request
    ├── Wait for response
    └── Return tool result to conversation

Tool names are namespaced as mcp__<server>__<tool> to prevent collisions between servers and with built-in tools.

Resource Access

MCP servers can expose resources (database schemas, file listings, documentation). Two tools handle this:

ToolPurpose
ListMcpResourcesBrowse available resources across all connected servers
ReadMcpResourceRead a specific resource by URI

The LLM uses these tools when it needs context from external systems.

Security

Allowlist / Denylist

[security]
mcp_server_allowlist = ["github", "filesystem"]   # Only these can connect
mcp_server_denylist = ["untrusted"]                # These are blocked

If allowlist is non-empty, only listed servers are connected. denylist is checked regardless.

Permission Integration

MCP tool calls go through the same permission system as built-in tools. The namespaced tool name (mcp__github__create_issue) can be matched by permission rules:

[[permissions.rules]]
tool = "mcp__github__*"
action = "allow"

Trust Boundary

MCP servers run with the user's permissions. They can access the filesystem, network, and any service the user can. The security boundary is the same as running a shell command — use allowlists to restrict which servers connect.

Error Handling

ErrorBehavior
Server fails to startWarning logged, server skipped, agent continues without it
Connection lostTool calls to that server return error results
Tool call failsError message returned as tool result, LLM can retry or adjust
TimeoutTransport-level timeout, error result returned

JSON-RPC Protocol

MCP uses JSON-RPC 2.0 over the chosen transport:

// Request
{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "create_issue", "arguments": {...}}}

// Response
{"jsonrpc": "2.0", "id": 1, "result": {"content": [{"type": "text", "text": "Issue created: #42"}]}}

Source: services/mcp/client.rs (client), services/mcp/transport.rs (stdio/SSE), services/mcp/types.rs (JSON-RPC types), tools/mcp_proxy.rs (tool proxy), tools/mcp_resources.rs (resource access)