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.

bash
codex --print-config | rg "mcp_servers"

Output:

text
[mcp_servers.filesystem]
[mcp_servers.github]
[mcp_servers.postgres]

config.toml structure

toml
[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

KeyTypeDefaultDescription
commandstring(required)Executable to launch the server
argsarray[]Command-line arguments
envtable{}Extra environment variables for the server process
enabledbooltrueDisable a server without removing its config
enabled_toolsarray[] (all)Whitelist specific tool names; empty = allow all
startup_timeout_secint30Seconds to wait for the server to start
tool_timeout_secint60Per-call timeout in seconds
default_tools_approval_modestring"auto""auto" (never prompt) or "manual" (always prompt)
supports_parallel_tool_callsbooltrueWhether 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)

toml
[mcp_servers.filesystem]
command = "npx"
args    = ["-y", "@modelcontextprotocol/server-filesystem", "/home/alice/projects"]
enabled = true

Output: (none — TOML config)

HTTP/SSE server

toml
[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

toml
[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.

toml
[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:

bash
codex mcp list

Output:

text
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):

bash
codex mcp test filesystem

Output:

text
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:

bash
codex mcp logs filesystem --follow

Output:

text
[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:

bash
codex mcp show filesystem

Output:

text
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:

text
/mcp

Output (inline in TUI):

text
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):

text
/mcp restart filesystem

Output (inline in TUI):

text
Stopping filesystem… ok
Starting filesystem… ok
4 tools available

Disable a server for the rest of the session only:

text
/mcp disable github

Output (inline in TUI):

text
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.

toml
[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_policy is "never", server tools run without prompts regardless of default_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):

toml
[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).

toml
[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:

toml
[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:

toml
# /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:

toml
# ~/.codex/config.toml
[projects."/home/alice/myproject"]
trust_level = "trusted"

Output: (none — TOML config)


Servernpm packageWhat it adds
Filesystem@modelcontextprotocol/server-filesystemRead/write files outside cwd
GitHub@modelcontextprotocol/server-githubGitHub Issues, PRs, code search
Postgres@modelcontextprotocol/server-postgresQuery a PostgreSQL database
Brave Search@modelcontextprotocol/server-brave-searchLive web search
Puppeteer@modelcontextprotocol/server-puppeteerBrowser automation
Fetch@modelcontextprotocol/server-fetchHTTP requests to external URLs

Install and run any of these:

bash
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:

bash
codex mcp serve --port 8765

Output:

text
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:

toml
[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

toml
[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

toml
[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

  1. npx -y re-downloads on every cold start. First-time launches of an npx -y @modelcontextprotocol/server-* server can take 5–15 seconds. Bump startup_timeout_sec to 60 if you see "MCP server timed out" errors on slow networks. Once cached, subsequent runs start in under a second.

  2. stdio MCP servers must NOT write to stdout for anything other than protocol messages. A stray print() or console.log() corrupts the JSON-RPC stream and disconnects the server. Always write debug output to stderr (which Codex captures into logs/).

  3. enabled_tools = [] means "all tools enabled," not "no tools enabled." This is a frequent footgun. To disable all tools from a server, use enabled = false instead.

  4. Project-level MCP config requires trust_level = "trusted". A new repo's .codex/config.toml is silently ignored. Run codex projects list to confirm.

  5. default_tools_approval_mode = "manual" is overridden by --ask-for-approval never. The CLI flag wins. Use per-tool tool_approvals if you need certain tools to always prompt regardless of the session policy.

  6. 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 to danger-full-access. Configure the server's allowed paths explicitly.

  7. env = { … } clobbers, doesn't merge. Any variable not listed under env is inherited from the parent process. Listed variables are set to the configured value; there is no "remove" syntax.

  8. HTTP-only MCP servers must be wrapped. Codex's MCP transport is stdio. Use mcp-remote or 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.

toml
[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:

bash
codex --sandbox read-only --ask-for-approval never "Summarise the architecture of this repo."

Output:

text
[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.

toml
# 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:

text
/mcp disable brave-search

Output (inline in TUI):

text
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:

bash
codex mcp test --config /tmp/test-mcp.toml my-new-server

Output:

text
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:

bash
ln -sf ~/dotfiles/codex/config.toml ~/.codex/config.toml

Output: (none — creates symlink)