The MCP Connection Manager is responsible for managing connections to multiple Model Context Protocol (MCP) servers, aggregating their tools and resources, and routing operations to the appropriate server. It handles server lifecycle (startup, initialization, failure tracking), tool name qualification to avoid collisions, and coordination of elicitation requests between servers and the UI.
For information about MCP server configuration (transport types, timeouts, scopes), see MCP Server Configuration. For details on OAuth flows and authentication, see OAuth Authentication for MCP. For the command-line interface to manage servers, see MCP CLI Commands.
The McpConnectionManager serves as the central coordinator for all MCP server connections within a Codex session. Its primary responsibilities include:
| Responsibility | Description |
|---|---|
| Connection Lifecycle | Lazy initialization of servers via AsyncManagedClient, tracking startup status |
| Tool Aggregation | Collecting tools from all servers and qualifying names with mcp__<server>__<tool> |
| Request Routing | Dispatching call_tool, list_resources, and other operations to the correct server |
| Elicitation Handling | Forwarding approval requests from servers to the UI and routing responses back |
| Sandbox Coordination | Notifying servers of sandbox policy changes via custom protocol extension |
| Caching | Optimizing Codex Apps server tool discovery with disk-based caching |
Sources: codex-rs/core/src/mcp_connection_manager.rs1-82
Architecture: McpConnectionManager Component Hierarchy
The manager owns a HashMap<String, AsyncManagedClient> keyed by server name. Each AsyncManagedClient wraps a lazy initialization future that produces a ManagedClient, which in turn holds an Arc<RmcpClient> and cached tool metadata.
Sources: codex-rs/core/src/mcp_connection_manager.rs509-663
Diagram: MCP Server Initialization Sequence
The McpConnectionManager::new method spawns initialization tasks for all enabled servers concurrently. Each task:
RmcpClient (stdio or streamable HTTP transport)initialize() with timeout (default 10 seconds)enabled_tools, disabled_tools)McpStartupUpdateEvent for UI feedbackManagedClient or errorSources: codex-rs/core/src/mcp_connection_manager.rs545-664 codex-rs/core/src/mcp_connection_manager.rs1059-1219
| Field | Type | Purpose |
|---|---|---|
client | Shared<BoxFuture<Result<ManagedClient>>> | Shared future allowing multiple awaiters during startup |
startup_snapshot | Option<Vec<ToolInfo>> | Cached tools for immediate availability (Codex Apps only) |
startup_complete | Arc<AtomicBool> | Flag indicating initialization finished |
The AsyncManagedClient uses a shared future pattern to allow concurrent access during initialization. If a startup snapshot exists (from cache), listed_tools() returns it immediately while initialization completes in the background.
Sources: codex-rs/core/src/mcp_connection_manager.rs389-490
Diagram: Tool Name Qualification and Collision Handling
MCP_TOOL_NAME_DELIMITER = "__"
MAX_TOOL_NAME_LENGTH = 64
The delimiter __ is chosen because OpenAI's Responses API requires tool names matching ^[a-zA-Z0-9_-]+$. The qualification process:
"mcp__<server>__<tool>" (e.g., "mcp__github__search_issues")[a-zA-Z0-9_-] with _Sources: codex-rs/core/src/mcp_connection_manager.rs83-191 codex-rs/core/src/mcp_connection_manager.rs106-121
Each server's ToolFilter applies enabled_tools and disabled_tools configuration:
The filter is applied in filter_tools() after tools are listed from the server but before qualification.
Sources: codex-rs/core/src/mcp_connection_manager.rs1238-1260
Iterates over all AsyncManagedClient instances, awaits their tools (or uses startup snapshot), and merges into a single map with qualified names as keys. This map is used by the tool registry to expose MCP tools to the model.
Sources: codex-rs/core/src/mcp_connection_manager.rs724-734
ManagedClient by server nameRmcpClient::call_tool(tool, arguments, timeout)rmcp::model::CallToolResult to codex_protocol::mcp::CallToolResultTool timeouts default to DEFAULT_TOOL_TIMEOUT (120 seconds) but can be overridden per-server via tool_timeout_sec config.
Sources: codex-rs/core/src/mcp_connection_manager.rs916-950
| Method | Purpose |
|---|---|
list_all_resources() | Aggregates resources from all servers into HashMap<String, Vec<Resource>> |
list_all_resource_templates() | Aggregates templates from all servers |
list_resources(server, params) | Lists resources from specific server with pagination |
read_resource(server, params) | Reads a specific resource by URI from a server |
Resource operations support pagination via PaginatedRequestParams with cursor tokens for servers that return large result sets.
Sources: codex-rs/core/src/mcp_connection_manager.rs781-999
MCP servers can request user input via elicitations (form-based or URL-based). The manager:
RmcpClient via SendElicitation callbackAskForApproval::Never or rejects MCP elicitations, auto-declineEvent::ElicitationRequest to UI via tx_eventresolve_elicitation()Diagram: Elicitation Request Flow
The make_sender() method creates a closure that implements the SendElicitation trait, capturing the event channel and request map. This closure is passed to RmcpClient::initialize().
Sources: codex-rs/core/src/mcp_connection_manager.rs249-334 codex-rs/core/src/mcp_connection_manager.rs675-684
Codex implements a custom MCP protocol extension to notify servers when sandbox policies change:
| Constant | Value | Purpose |
|---|---|---|
MCP_SANDBOX_STATE_CAPABILITY | "codex/sandbox-state" | Server capability flag |
MCP_SANDBOX_STATE_METHOD | "codex/sandbox-state/update" | Notification method name |
When notify_sandbox_state_change() is called (typically after turn start or policy updates), the manager:
AsyncManagedClient instancesserver_supports_sandbox_state_capability, sends custom requestThis allows MCP servers to adjust their behavior based on the active sandbox constraints.
Sources: codex-rs/core/src/mcp_connection_manager.rs492-507 codex-rs/core/src/mcp_connection_manager.rs1008-1034
The codex-apps MCP server (hosted by OpenAI) exposes a large number of connector tools that change infrequently. To avoid startup latency, Codex caches these tools on disk and loads them immediately while initialization completes asynchronously.
Cache file location: ~/.codex/cache/codex_apps_tools/<sha1_of_key>.json
| Function | Metrics | Behavior |
|---|---|---|
load_cached_codex_apps_tools() | codex.mcp.tools.list.duration_ms (cache=hit/miss) | Loads from disk, validates schema version |
list_tools_for_client_uncached() | codex.mcp.tools.fetch_uncached.duration_ms | Bypasses cache, always fetches fresh |
write_cached_codex_apps_tools_if_needed() | codex.mcp.tools.cache_write.duration_ms | Atomically writes to temp file then renames |
The cache is invalidated whenever the user's account information changes (account ID or user ID).
Sources: codex-rs/core/src/mcp_connection_manager.rs97-149 codex-rs/core/src/mcp_connection_manager.rs1262-1439
Bypasses the in-memory cache and forces a fresh fetch from the server, then updates the disk cache. Used by the app-server to support explicit refresh requests from IDEs.
Sources: codex-rs/core/src/mcp_connection_manager.rs740-777
The manager emits McpStartupUpdateEvent for each server as it transitions through states, then emits a single McpStartupCompleteEvent when all servers have finished (successfully or not).
Checks if any servers marked required: true in configuration failed to initialize. The session uses this to abort turns if required tools are unavailable.
Sources: codex-rs/core/src/mcp_connection_manager.rs697-720
The manager is constructed with HashMap<String, McpServerConfig> from the resolved configuration. Key fields:
| Config Field | Usage in Manager |
|---|---|
enabled | Only servers with enabled: true are initialized |
transport | Passed to make_rmcp_client() (Stdio vs StreamableHttp) |
startup_timeout_sec | Per-server timeout for initialization (default 10s) |
tool_timeout_sec | Per-operation timeout for tool calls (default 120s) |
enabled_tools | Whitelist filter applied to server's tools |
disabled_tools | Blacklist filter applied after whitelist |
scopes | OAuth scopes for authentication (see OAuth Authentication) |
Sources: codex-rs/core/src/mcp_connection_manager.rs545-664
The test suite uses several helper binaries:
| Binary | Transport | Purpose |
|---|---|---|
test_stdio_server | stdio | Echo tool, image tool, resource serving |
test_streamable_http_server | HTTP | Same tools as stdio, tests OAuth and HTTP transport |
rmcp_test_server | stdio | Minimal echo-only server for basic tests |
These are compiled as part of codex-rmcp-client and used in integration tests.
Sources: codex-rs/core/tests/suite/rmcp_client.rs49-189 codex-rs/rmcp-client/src/bin/test_stdio_server.rs1-40 codex-rs/rmcp-client/src/bin/test_streamable_http_server.rs1-50
Initialization errors are captured and converted to user-friendly messages:
McpOAuthLoginSupport and suggest codex mcp loginIf a server fails to initialize:
list_all_tools()required_startup_failures() reports the issue for required serversSources: codex-rs/core/src/mcp_connection_manager.rs1440-1496
Diagram: Typical Session Integration Flow
Refresh this wiki