How MCP Server Support is Implemented in OpenAI Codex

Codex implements MCP server support through a layered Rust architecture that parses user configurations from ~/.codex/config.toml, manages persistent JSON-RPC connections via McpConnectionManager, and integrates external tools into the TUI with sandbox-state negotiation and human-in-the-loop approvals.

The Model Context Protocol (MCP) allows Codex to extend its capabilities by connecting to external "MCP servers"—sandboxed processes that expose tools via a standardized protocol. According to the openai/codex source code, this integration is handled through a coordinated stack of configuration parsers, connection managers, protocol clients, and UI components written in Rust.

MCP Server Architecture Stack

Codex’s MCP implementation is organized into four distinct layers that handle everything from user configuration to UI rendering.

User Configuration Layer

Users declare MCP servers in ~/.codex/config.toml under the [mcp_servers] section. The configuration parser maps these entries into McpServerConfig and McpServerTransportConfig structs, which define the command, arguments, and transport type (stdio, HTTP, or UDS) for each server.

When Codex starts, McpConnectionManager::new reads these configurations and initiates the connection lifecycle for each defined server.

Connection Management Layer

The McpConnectionManager (located in codex-rs/core/src/mcp_connection_manager.rs) serves as the central coordinator. For each configured server, it creates a single RmcpClient instance, performs the MCP handshake, and negotiates capabilities. The manager also tracks startup progress using McpStartupStatus and aggregates the complete list of available tools from all connected servers.

Protocol Transport Layer

The low-level MCP JSON-RPC protocol is implemented in codex-rs/rmcp-client/src/rmcp_client.rs by the RmcpClient struct. This client handles:

  • OAuth authentication and session initialization
  • Tool discovery via list_tools
  • Tool invocation via call_tool
  • Elicitation requests (human-in-the-loop approvals) via send_elicitation
  • Resource listing and management

The client exposes async helpers that the connection manager uses to forward requests from the LLM to the appropriate MCP server.

UI Integration Layer

The TUI renders MCP-specific components in two key locations:

  1. codex-rs/tui/src/history_cell.rs — Displays the "MCP Tools" section and renders tool execution results in the conversation history.
  2. codex-rs/tui/src/bottom_pane/mcp_server_elicitation.rs — Implements AskForApproval, the UI component that presents elicitation requests when an MCP server requires human approval before executing a sensitive operation.

Connection Lifecycle and Capability Negotiation

The MCP connection follows a strict lifecycle managed by the McpConnectionManager.

1. Initialization and Handshake

When Codex launches, the manager iterates through the server configurations and calls RmcpClient::new followed by initialize for each. This establishes the JSON-RPC connection and negotiates protocol capabilities.

2. Sandbox-State Negotiation

After the handshake, the manager advertises the codex/sandbox-state capability (defined by constants MCP_SANDBOX_STATE_CAPABILITY and MCP_SANDBOX_STATE_METHOD). It constructs a SandboxPolicy struct from the user’s configuration and sends it to the server via send_request(MCP_SANDBOX_STATE_METHOD, ...).

The server confirms the policy is active by returning an empty result, ensuring all subsequent tool executions adhere to the defined sandbox restrictions (e.g., writable roots, network access).

3. Tool Discovery and Qualification

Once capabilities are negotiated, the manager calls client.list_tools to retrieve the server’s tool catalog. These tools are then processed by the qualify_tools function, which:

  1. Namespaces tools using the delimiter __ with the format: mcp__<server_name>__<tool_name>
  2. Sanitizes names for the Responses API
  3. Truncates names exceeding 64 bytes, appending a SHA-1 suffix to maintain uniqueness

The qualified tool list is cached for the session using the codex_apps_tools_cache_key identifier.

4. Tool Invocation and Elicitation

When the model requests an MCP tool (e.g., mcp__bash__shell), the TUI routes the request to McpConnectionManager::call_tool. The manager forwards the call to the appropriate RmcpClient.

If the tool requires human approval (based on the server’s .rules configuration), the server returns an elicitation request. The UI renders this via AskForApproval, and the user’s decision is sent back to the server using client.send_approval, allowing the command to proceed or be blocked.

Configuring an MCP Server in Codex

To add an external tool server, define it in your Codex configuration file:


# ~/.codex/config.toml

[features]
shell_tool = false                     # disable the built-in shell tool

[mcp_servers.bash]                     # logical name used in tool prefixes

command = "npx"
args = ["-y", "@openai/codex-shell-tool-mcp"]

# optional: transport = {type = "stdio"}   # default, can also be HTTP/UDS

With this configuration, the shell tool becomes available as mcp__bash__shell and appears in the TUI under the 🔌 MCP Tools section. The experimental @openai/codex-shell-tool-mcp package demonstrates a complete implementation that patches Bash to intercept execve(2) calls and applies the sandbox policy received from Codex.

Programmatic MCP Interactions

Beyond configuration, the Rust API allows direct interaction with MCP servers during execution.

Invoking Tools via the Connection Manager

// Inside a TUI turn, after the model requests `mcp__bash__shell`
let request = CallToolResult {
    tool_name: "mcp__bash__shell".to_string(),
    arguments: serde_json::json!({ "command": "ls -l /home/user" }),
    // … other fields omitted for brevity
};

let response = codex_app.mcp_connection_manager()
    .call_tool(request)
    .await?;   // async call to the appropriate RmcpClient

println!("MCP output:\n{}", response.result);

If the server’s rules specify action = "prompt", the UI automatically renders an approval prompt before executing the command.

Updating Sandbox Policies Dynamically

use codex_protocol::protocol::SandboxPolicy;
use codex_rmcp_client::RmcpClient;

// Build a policy that blocks network access and limits write roots
let policy = SandboxPolicy {
    policy_type: "workspace-write".into(),
    writable_roots: vec!["/home/user/projects".into()],
    network_access: false,
    exclude_tmpdir_env_var: false,
    exclude_slash_tmp: false,
};

// Send the update to every configured MCP server
for manager in codex_app.mcp_connection_manager().servers() {
    manager.client().send_request(
        codex_rs::core::mcp_connection_manager::MCP_SANDBOX_STATE_METHOD,
        serde_json::json!({ "sandboxPolicy": policy })
    ).await?;
}

This policy update instructs the MCP server to apply the new restrictions to all subsequent process spawns.

Summary

  • Configuration: Users define servers in ~/.codex/config.toml under [mcp_servers], parsed into McpServerConfig structs.
  • Connection Management: McpConnectionManager in codex-rs/core/src/mcp_connection_manager.rs creates and monitors RmcpClient instances for each server.
  • Protocol Handling: codex-rs/rmcp-client/src/rmcp_client.rs implements the MCP JSON-RPC protocol, including OAuth, tool invocation, and elicitation.
  • Tool Qualification: The qualify_tools function namespaces tools as mcp__<server>__<tool>, sanitizes API names, and enforces a 64-byte limit with SHA-1 hashing.
  • Sandbox Negotiation: The manager advertises codex/sandbox-state and transmits SandboxPolicy configurations to control server-side execution environments.
  • UI Integration: history_cell.rs displays tools while mcp_server_elicitation.rs handles human approval flows via AskForApproval.

Frequently Asked Questions

How do I configure an MCP server in Codex?

Add an entry to the [mcp_servers] section of your ~/.codex/config.toml file. Specify the command and arguments needed to launch the server (e.g., npx -y @openai/codex-shell-tool-mcp). You can optionally set the transport type to stdio, http, or uds depending on how the server communicates.

What is the codex/sandbox-state capability?

The codex/sandbox-state capability is a Codex-specific MCP extension defined by MCP_SANDBOX_STATE_CAPABILITY and MCP_SANDBOX_STATE_METHOD. It allows Codex to transmit a SandboxPolicy to the MCP server after the initial handshake, dictating which directories are writable and whether network access is permitted for tools like the sandboxed Bash shell.

How are MCP tool names formatted in Codex?

MCP tools are qualified using the pattern mcp__<server_name>__<tool_name>, with the delimiter __ separating the namespace components. If the resulting name exceeds 64 bytes, the qualify_tools function truncates it and appends a SHA-1 hash suffix to maintain uniqueness while satisfying API constraints.

Where is the MCP connection lifecycle managed in the codebase?

The connection lifecycle—including client creation, handshake, capability negotiation, and tool aggregation—is managed by the McpConnectionManager struct in codex-rs/core/src/mcp_connection_manager.rs. This file also defines the codex/sandbox-state constants and the qualify_tools sanitization logic.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →