skip to content

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.

10 min read 42 snippets deep dive

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#

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)#

[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_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):

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


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:

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#

  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.

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