cheat sheet
Codex MCP Servers
Add and configure Model Context Protocol (MCP) servers in Codex CLI — config.toml [mcp_servers.*] reference, the codex mcp subcommand, per-server tool approval modes, and the /mcp runtime command.
Codex MCP Servers
What it is
Codex CLI supports the Model Context Protocol (MCP) to extend the agent with external tools — filesystem access, databases, APIs, browser automation, and more. MCP servers are configured in ~/.codex/config.toml under [mcp_servers.<id>] tables. Unlike Claude Code (where MCP is configured with codex mcp add), Codex uses pure TOML configuration.
How Codex finds servers
Codex enumerates MCP servers by walking the merged config: it reads every [mcp_servers.<id>] table in the global ~/.codex/config.toml, then layers project-level <project>/.codex/config.toml on top (project keys win for the same <id>). Servers marked enabled = false are kept in the config but skipped at startup. There is no separate mcp.json file.
codex --print-config | rg "mcp_servers"
Output:
[mcp_servers.filesystem]
[mcp_servers.github]
[mcp_servers.postgres]
config.toml structure
[mcp_servers.<server-id>]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/home/alice/work"]
env = { MCP_TOKEN = "secret" }
enabled = true
enabled_tools = [] # empty = all tools enabled
startup_timeout_sec = 30
tool_timeout_sec = 60
default_tools_approval_mode = "auto" # "auto" | "manual"
supports_parallel_tool_calls = true
Output: (none — TOML config)
All [mcp_servers.*] keys
| Key | Type | Default | Description |
|---|---|---|---|
command | string | (required) | Executable to launch the server |
args | array | [] | Command-line arguments |
env | table | {} | Extra environment variables for the server process |
enabled | bool | true | Disable a server without removing its config |
enabled_tools | array | [] (all) | Whitelist specific tool names; empty = allow all |
startup_timeout_sec | int | 30 | Seconds to wait for the server to start |
tool_timeout_sec | int | 60 | Per-call timeout in seconds |
default_tools_approval_mode | string | "auto" | "auto" (never prompt) or "manual" (always prompt) |
supports_parallel_tool_calls | bool | true | Whether Codex can call this server's tools in parallel |
Adding MCP servers
The fastest way to add a server is to drop a [mcp_servers.<id>] block into ~/.codex/config.toml and restart Codex. The server process is launched lazily on the first session that needs it, and lives for the duration of that session. For HTTP/SSE servers the launch is the same — Codex still spawns the process; it just speaks HTTP over the spawned process's stdin/stdout-bridged loopback port. There is no daemon mode.
stdio server (most common)
[mcp_servers.filesystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/home/alice/projects"]
enabled = true
Output: (none — TOML config)
HTTP/SSE server
[mcp_servers.my-api]
command = "python3"
args = ["-m", "my_mcp_server", "--port", "8080"]
env = { MY_API_KEY = "abc123" }
startup_timeout_sec = 60
Output: (none — TOML config)
Disable a server temporarily
[mcp_servers.filesystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/home/alice/projects"]
enabled = false
Output: (none — TOML config)
Remote (SSE / streamable HTTP) server
For a remote MCP server (one exposed by a SaaS API), point Codex at a small shim that proxies stdio to HTTP. The community mcp-remote package wraps any URL into the stdio transport Codex expects.
[mcp_servers.linear]
command = "npx"
args = ["-y", "mcp-remote", "https://mcp.linear.app/sse"]
env = { LINEAR_API_KEY = "lin_oauth_..." }
startup_timeout_sec = 60
Output: (none — TOML config)
Disabling a server temporarily
Set enabled = false to keep the config block around but skip the launch. Faster than commenting the whole table out, and round-trips through codex --print-config cleanly.
Managing servers with codex mcp
The codex mcp subcommand is read-only — it lists, inspects, and tests, but does not edit config. To add or remove servers, edit config.toml directly (Codex's design choice: no hidden state).
List configured MCP servers:
codex mcp list
Output:
filesystem enabled npx -y @modelcontextprotocol/server-filesystem /home/alice/projects
my-api disabled python3 -m my_mcp_server --port 8080
Test a server (start it, list its tools, then exit):
codex mcp test filesystem
Output:
Starting filesystem… ok
Tools:
read_file(path: string) -> string
write_file(path: string, content: string) -> void
list_directory(path: string) -> string[]
search_files(pattern: string, dir?: string) -> string[]
Server exited cleanly.
Tail an MCP server's stderr for debugging:
codex mcp logs filesystem --follow
Output:
[mcp:filesystem] listening on stdio
[mcp:filesystem] tool call: read_file {"path": "/home/alice/notes.md"}
[mcp:filesystem] tool result: 1428 bytes
Show details for a specific server:
codex mcp show filesystem
Output:
Name: filesystem
Command: npx -y @modelcontextprotocol/server-filesystem /home/alice/projects
Status: enabled
Tools: read_file, write_file, list_directory, move_file, search_files
Runtime inspection with /mcp
/mcp is a TUI-only slash command that prints the live state of MCP servers — which ones are running, which tools they expose, and recent tool-call counts. It is the fastest way to verify your config without leaving the session. Inside an active session, use /mcp to see which servers are connected and what tools they expose:
/mcp
Output (inline in TUI):
Connected MCP servers:
filesystem [connected]
read_file(path: string) → string
write_file(path: string, content: string) → void
list_directory(path: string) → array
search_files(pattern: string, dir?: string) → array
Restart a server without leaving the session (useful after editing its config):
/mcp restart filesystem
Output (inline in TUI):
Stopping filesystem… ok
Starting filesystem… ok
4 tools available
Disable a server for the rest of the session only:
/mcp disable github
Output (inline in TUI):
github disabled for this session.
Tool approval modes
Per-server tool approval is controlled by default_tools_approval_mode. The setting governs whether a tool call from this server pauses for user confirmation before executing. The default is "auto" (run immediately, in keeping with the session's overall approval_policy); "manual" forces a per-call prompt even when the session's policy says never.
[mcp_servers.filesystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/home/alice/projects"]
default_tools_approval_mode = "manual" # Prompt before every tool call
The global approval_policy in config.toml and the per-server default_tools_approval_mode interact:
- If the session's
approval_policyis"never", server tools run without prompts regardless ofdefault_tools_approval_mode. "manual"forces a prompt for each call even if the global policy is"on-request".
Per-tool approval override (more granular than the per-server mode):
[mcp_servers.filesystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/home/alice/projects"]
default_tools_approval_mode = "auto"
[mcp_servers.filesystem.tool_approvals]
write_file = "manual" # write_file always prompts even though server default is auto
move_file = "manual"
Whitelisting specific tools
enabled_tools is a small but powerful safety knob: rather than disabling a server, you can run it with a curated subset of its tools exposed. Good for filesystem servers (read-only mode) and database servers (omit drop_table).
[mcp_servers.filesystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/home/alice/projects"]
enabled_tools = ["read_file", "list_directory"] # write_file and others disabled
Output: (none — TOML config)
Inverse pattern — disabled_tools keeps the whitelist implicit but blocks specific dangerous tools:
[mcp_servers.postgres]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"]
disabled_tools = ["execute_query", "alter_table"]
Output: (none — TOML config)
Project-level MCP config
Project-level MCP is the canonical pattern for "this repo needs Postgres MCP pointed at its local dev DB." The project's .codex/config.toml is merged on top of the user-level config — including [mcp_servers.*] tables — so per-repo servers come online automatically when you launch Codex in that directory, and vanish when you leave. When a project directory is trusted, .codex/config.toml inside it overlays the user config. This lets you declare project-specific MCP servers without polluting your global config:
# /home/alice/myproject/.codex/config.toml
[mcp_servers.project-db]
command = "python3"
args = ["-m", "mcp_postgres_server"]
env = { DATABASE_URL = "postgresql://localhost/mydb" }
Output: (none — TOML config)
Trust the project first:
# ~/.codex/config.toml
[projects."/home/alice/myproject"]
trust_level = "trusted"
Output: (none — TOML config)
Popular MCP servers
| Server | npm package | What it adds |
|---|---|---|
| Filesystem | @modelcontextprotocol/server-filesystem | Read/write files outside cwd |
| GitHub | @modelcontextprotocol/server-github | GitHub Issues, PRs, code search |
| Postgres | @modelcontextprotocol/server-postgres | Query a PostgreSQL database |
| Brave Search | @modelcontextprotocol/server-brave-search | Live web search |
| Puppeteer | @modelcontextprotocol/server-puppeteer | Browser automation |
| Fetch | @modelcontextprotocol/server-fetch | HTTP requests to external URLs |
Install and run any of these:
npx -y @modelcontextprotocol/server-github
Output: (none — starts server process; Codex connects automatically)
Codex as an MCP server (other direction)
Codex itself can act as an MCP server, exposing its own capabilities to another MCP client. This is the foundation of the "Codex inside Codex" sub-agent pattern (see subagents). Launch the server with:
codex mcp serve --port 8765
Output:
Codex MCP server listening on stdio (port 8765 bridged)
Exposed tools: codex_exec, codex_resume, codex_apply
A consumer can then add it as a regular MCP server:
[mcp_servers.inner-codex]
command = "codex"
args = ["mcp", "serve"]
Output: (none — TOML config)
Secrets handling
Embedding tokens directly in config.toml is convenient but lands secrets in plain text. Codex supports two safer patterns:
Reference an env var
[mcp_servers.github]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
env = { GITHUB_TOKEN = "${env:GITHUB_TOKEN}" }
Output: (none — TOML config; ${env:NAME} is resolved at server start)
Reference a file
[mcp_servers.github]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
env = { GITHUB_TOKEN = "${file:~/.config/secrets/github_token}" }
Output: (none — TOML config; file contents trimmed of trailing newlines)
Common pitfalls
-
npx -yre-downloads on every cold start. First-time launches of annpx -y @modelcontextprotocol/server-*server can take 5–15 seconds. Bumpstartup_timeout_secto60if you see "MCP server timed out" errors on slow networks. Once cached, subsequent runs start in under a second. -
stdio MCP servers must NOT write to stdout for anything other than protocol messages. A stray
print()orconsole.log()corrupts the JSON-RPC stream and disconnects the server. Always write debug output to stderr (which Codex captures intologs/). -
enabled_tools = []means "all tools enabled," not "no tools enabled." This is a frequent footgun. To disable all tools from a server, useenabled = falseinstead. -
Project-level MCP config requires
trust_level = "trusted". A new repo's.codex/config.tomlis silently ignored. Runcodex projects listto confirm. -
default_tools_approval_mode = "manual"is overridden by--ask-for-approval never. The CLI flag wins. Use per-tooltool_approvalsif you need certain tools to always prompt regardless of the session policy. -
MCP servers don't share file handles with Codex. A filesystem MCP server with
args = ["/home/alice/work"]can only access that subtree, even if the Codex sandbox is set todanger-full-access. Configure the server's allowed paths explicitly. -
env = { … }clobbers, doesn't merge. Any variable not listed underenvis inherited from the parent process. Listed variables are set to the configured value; there is no "remove" syntax. -
HTTP-only MCP servers must be wrapped. Codex's MCP transport is stdio. Use
mcp-remoteor write a small Python shim to bridge an SSE/streamable-HTTP server.
Real-world recipes
Read-only Git inspection MCP
A safe MCP setup for "summarise this repo" sessions — filesystem with reads only, plus a git MCP that exposes log/diff but not write operations.
[mcp_servers.fs-readonly]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/home/alice/myproject"]
enabled_tools = ["read_file", "list_directory", "search_files"]
[mcp_servers.git]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-git", "/home/alice/myproject"]
disabled_tools = ["commit", "push", "reset"]
Output: (none — TOML config)
Then:
codex --sandbox read-only --ask-for-approval never "Summarise the architecture of this repo."
Output:
[agent uses fs-readonly + git MCP to summarise without writing anything]
Per-project Postgres MCP
Drop a .codex/config.toml into a repo whose dev DB is local. Codex picks it up automatically once the project is trusted.
# myproject/.codex/config.toml
[mcp_servers.dev-db]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost:5432/myproject_dev"]
default_tools_approval_mode = "manual"
disabled_tools = ["execute_query"]
Output: (none — project-scoped TOML)
Disable a noisy MCP server for one session
If a server is generating too many tokens of background context, disable it for the current TUI session without editing config:
/mcp disable brave-search
Output (inline in TUI):
brave-search disabled for this session.
Validate a new server before adding it globally
Before adding a server to config.toml, smoke-test it standalone:
codex mcp test --config /tmp/test-mcp.toml my-new-server
Output:
Starting my-new-server… ok
Tools:
do_thing(arg: string) -> string
Server exited cleanly.
Mirror MCP config across machines
Keep a single source of truth in dotfiles, symlinked into ~/.codex:
ln -sf ~/dotfiles/codex/config.toml ~/.codex/config.toml
Output: (none — creates symlink)