Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

MCP Integration

How agents discover, connect to, and manage MCP (Model Context Protocol) servers for extensible tool access.

Overview

AgentMCP RoleSDK UsedTransportsDiscoveryAuto-Restart
GooseAll tools via MCPrmcp (Rust)stdio, SSE, streamable-HTTPEager at connectYes (via extension lifecycle)
Grok BuildAlongside built-insrmcp 2.1 (Rust)stdio, SSE, streamable-HTTPEager + search_tool/use_tool meta-toolsYes (3 retries stdio, backoff ladder HTTP)
Qwen CodeAlongside built-ins@modelcontextprotocol/sdk (TS)stdio, SSE, streamable-HTTPDeferred (schemas hidden until tool-search)Yes (polling health 30s)
CodexClient + Serverrmcp (Rust)stdio, streamable-HTTPPrewarmed + cachedBest-effort background reconnect
ClineAlongside built-ins@modelcontextprotocol/sdk (TS)stdio, SSE, streamable-HTTPEager per-serverNo auto-respawn (stdio); backoff (HTTP)
OpenCode/KilocodeAlongside built-ins@modelcontextprotocol/sdk (TS)stdio, SSEEager at connectManual restart

Core files: repos/qwen-code/packages/core/src/tools/tool-search.ts, mcp-client.ts, mcp-client-manager.ts, mcp-tool.ts

Unique Feature: On-Demand Schema Loading

MCP tool schemas are genuinely deferred — hidden from the model until ToolSearch reveals them:

  1. ToolRegistry.getFunctionDeclarations() filters out any tool with shouldDefer && !alwaysLoad && !revealed
  2. The model only sees deferred tool names via a startup reminder (getDeferredToolSummary())
  3. When the model uses tool-search, matching tools are revealed via registry.revealDeferredTool(name) + geminiClient.setTools() re-sync
  4. However, discoverTools() still eagerly calls tools/list on every server at connection time — only the schema exposure to the model is deferred, not the underlying RPC

Transport Priority

createTransport() (lines 2035-2240): in-process SDK → httpUrl (streamable HTTP) → url (SSE) → command (stdio)

Health Monitoring

McpClientManager runs a polling health monitor:

  • Default 30s interval
  • 3 consecutive failures → reconnect
  • 5s reconnect delay
  • Separate per-call reconnect path with regex-matched connection errors

Namespacing

mcp__<serverName>__<toolName> via generateValidName(). Collisions with built-ins force the MCP tool to its fully-qualified name.

Config

MCPServerConfig in packages/core/src/config/config.ts:750-803:

  • Fields: command/args/env/cwd (stdio), url (SSE), httpUrl (streamable HTTP), headers, timeout, trust, includeTools/excludeTools
  • Scoped: project/workspace/system
  • OAuth: full browser flow, RFC 9728 discovery, keychain storage, proactive probing

Trust Layers (Multiple, Composable)

  • Settings-level glob: mcp.allowed/mcp.excluded
  • Per-server: includeTools/excludeTools
  • trust: boolean → auto-allow only if trusted server AND trusted workspace folder
  • Project/workspace-scoped servers held behind explicit pending-approval gate

Codex — Both MCP Client AND Server

Core files: repos/codex/codex-rs/codex-mcp/, codex-rmcp-client/, mcp-server/

Unique Feature: Self-as-MCP-Server

codex mcp-server (binary codex-mcp-server) exposes Codex itself as an MCP server:

  • Two tools: codex (start session) and codex-reply (continue thread)
  • Approval round-trips (execCommandApproval/applyPatchApproval) sent back to calling client
  • Documented as “experimental” in codex-rs/docs/codex_mcp_interface.md

Transport Config

#![allow(unused)]
fn main() {
pub enum McpServerTransportConfig {
    Stdio { command, args, env, env_vars, cwd },
    StreamableHttp { url, bearer_token_env_var, http_headers, env_http_headers },
}
}

No standalone SSE config — SSE only as the streaming mechanism inside Streamable HTTP.

Discovery

Hybrid: mcp_prewarm.rs proactively prewarms connections/tool lists at session start (non-blocking); tool_catalog_cache.rs caches tools/list results, invalidated on events (OAuth login, server recovery).

Config Format

TOML [mcp_servers.<name>] with rich fields: startup_timeout_sec, tool_timeout_sec, enabled, required (fails session if server won’t start), per-tool tools.<name>.approval_mode, auth (oauth|chatgpt). Programmatic edits via codex mcp add/remove with TOML-document surgery preserving formatting.

Auth

Full OAuth2/PKCE, browser or silent flow, RFC 8707 resource indicators, OS-keyring storage with .credentials.json fallback. Bearer tokens only accepted via env-var reference (raw literals rejected).


Grok Build — Unified Permission Pipeline + Config Import

Core files: repos/grok-build/crates/codegen/xai-grok-mcp/src/servers.rs, xai-grok-config-types/src/mcp.rs

Architecture

Built-in tools and MCP tools merge into one ToolBridge → ToolRegistry. No separate dispatch path post-registration. Also exposes search_tool/use_tool meta-tools for indirect discovery/invocation.

Unique Feature: Config Import from Other Tools

Auto-imports MCP configs from: .claude.json (Claude Code), .cursor/mcp.json (Cursor), standard .mcp.json — merged in priority order.

Lifecycle

  • start_mcp_server(): spawns via tokio::process::Command with kill_on_drop(true)
  • Stderr drained to ~/.grok/logs/mcp/<server>.stderr.log
  • Liveness: 500ms poller (rmcp 2.1 RunningService lacks a “closed” future)
  • Restart-on-crash (stdio): 3 attempts, backoff 1s/+4s/+16s, then “parked” (tools unregistered)
  • HTTP/SSE: in-place transport reset with 8-step backoff ladder (accommodates rolling redeploys)

Permission Integration

MCP tool calls route through the exact same AccessKind::MCPTool{name, input} pipeline as built-ins. Even auto/YOLO mode classifier-checks MCP calls rather than blanket-approving.

Resilience Patches

  • Custom NDJSON transport (ResilientRwTransport): works around rmcp’s default of replying -32600 on malformed lines (which some servers echo back into a loop)
  • SSE-flood backoff: patches rmcp 2.1.0 bug where SSE reconnects fire immediately without backoff

Auth

Full OAuth 2.0 via rmcp’s auth feature: Dynamic Client Registration (RFC 7591), cross-process dedup via filesystem lock + generation counter, loopback callback server, plaintext credential store at ~/.grok/mcp_credentials.json (0600 perms).


Cline — UI-Driven Per-Tool Approval

Core files: repos/cline/apps/vscode/src/services/mcp/McpHub.ts, sdk/packages/core/src/extensions/mcp/tools.ts

Architecture

McpHub (1909 lines) owns full lifecycle: reads/validates settings, connects, tears down, restarts, watches settings file, handles auto-approval and OAuth.

Tool Exposure

The SDK generates one first-class dynamic tool per MCP tool (named ${serverName}__${toolName}, hashed/truncated for OpenAI’s 64-char limit) — not a single generic use_mcp_tool(server,tool,args) wrapper.

Config

cline_mcp_settings.json, Zod-validated. Supports both legacy flat fields and newer nested {transport:{type,...}} shape, normalized via .transform(). Per-server: autoApprove: string[], disabled, timeout.

Lifecycle/Hot-Reload

watchMcpSettingsFile() uses chokidar with awaitWriteFinish/atomic, computing content fingerprint to skip self-triggered reconnect loops. Crash handling differs by transport:

  • stdio: no auto-respawn (marks disconnected, requires manual restart)
  • streamableHttp: exponential-backoff reconnect (6 attempts, 2000*2^n ms)
  • SSE: relies on ReconnectingEventSource built-in retry

Auto-Approval

Per-tool checkbox in webview UI, enforced via isToolAutoApproved() which parses serverName__toolName convention — gated behind master switch autoApprovalSettings.actions.useMcp.

Resources & Prompts

Fully supported beyond tools: readResource(), getPrompt(), plus list-side schemas surfaced in dedicated UI rows.


Design Patterns

1. Converging on Official SDKs

All agents use official MCP SDKs (@modelcontextprotocol/sdk for TS, rmcp for Rust) rather than hand-rolling JSON-RPC. The Rust SDK is less battle-tested — Grok Build had to patch around SSE reconnect storms and NDJSON parsing brittleness.

2. Namespace Convention: server__tool

Universal pattern: <serverName>__<toolName> (double underscore). Cline additionally hashes/truncates to 64 chars for OpenAI compatibility.

3. Eager vs Deferred Discovery

StrategyAgentsTradeoff
Eager (list all at connect)Goose, Grok Build, ClineMore tokens in system prompt; immediate availability
Deferred (schemas hidden until searched)Qwen CodeSaves context window; adds one extra tool call per discovery
Hybrid (eager + meta-tools for indirect access)Grok Build, CodexBest of both; search_tool for overflow

4. Restart Policies Reflect Design Philosophy

AgentStdio RestartHTTP/SSE RestartPhilosophy
Grok Build3 retries, backoff, then park8-step backoff ladderMaximize uptime
Qwen CodeHealth monitor (30s/3 failures)SameMaximize uptime
CodexBest-effort backgroundSameBackground resilience
ClineNo auto-respawnBackoff reconnectUser control

5. Permission Integration Spectrum

  • Unified (Grok Build): MCP calls flow through the same classifier/policy as built-in tools
  • Layered (Qwen Code, Cline): MCP-specific trust flags (trust, autoApprove) alongside general approval
  • Config-level (Codex): per-tool approval_mode in TOML

6. Config Portability

Only Grok Build auto-imports configs from other tools (Claude Code, Cursor, .mcp.json). Others require manual re-configuration per tool.