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)