Where Codex’s MCP config lives, the [mcp_servers] TOML format, the codex mcp CLI, and how to fix the common failures — plus how to put a policy gate in front of every tool call.
QUICK ANSWERQuick answer: ~/.codex/config.toml, one [mcp_servers.<name>] table per server. Note the snake_case — mcp_servers, not mcpServers.
| Scope | Path | Notes |
|---|---|---|
| User | ~/.codex/config.toml | The primary config. Set CODEX_HOME to relocate the whole .codex directory. |
| Project | .codex/config.toml | At the project root — trusted projects only. |
The Codex CLI and IDE extension share the same configuration.
One TOML table per server: [mcp_servers.<name>]. The transport is chosen by which keys are present — command means stdio, url means streamable HTTP. Mixing keys from both is rejected.
| Key | What it does |
|---|---|
command | Executable for a stdio server (required for stdio). |
args | Array of arguments passed to the command. |
env | Inline table of environment variables — Codex controls the child env, so pass secrets explicitly. |
url | Endpoint for a streamable HTTP server (required for HTTP). |
bearer_token_env_var | Name of an env var whose value is sent as Authorization: Bearer …. |
http_headers | Inline table of extra HTTP headers. |
enabled | Set false to switch a server off without deleting it. |
startup_timeout_sec | Startup limit in seconds — raise for slow npx cold starts. |
tool_timeout_sec | Per-tool-call limit in seconds. |
enabled_tools / disabled_tools | Allowlist / blocklist of tool names exposed to the agent. |
[mcp_servers.context7]
command = "npx"
args = ["-y", "@upstash/context7-mcp"]
env = { LOCAL_TOKEN = "secret" }
startup_timeout_sec = 20
tool_timeout_sec = 60 REMOTE SERVER (HTTP):
[mcp_servers.linear] url = "https://mcp.linear.app/mcp" bearer_token_env_var = "LINEAR_TOKEN"
| Transport | Status | How to declare it |
|---|---|---|
| stdio | Supported | Declare command (+ args, env). |
| Streamable HTTP | Supported | Declare url; auth via bearer_token_env_var, http_headers, or OAuth (codex mcp login). |
| SSE | Not supported | No dedicated SSE transport — use streamable HTTP for remote servers. |
Stdio servers take a ---separated command; HTTP servers take --url.
# Local stdio server codex mcp add context7 --env LOCAL_TOKEN=secret -- npx -y @upstash/context7-mcp # Remote HTTP server with bearer auth codex mcp add linear --url https://mcp.linear.app/mcp \ --bearer-token-env-var LINEAR_TOKEN
Add a [mcp_servers.<name>] table to ~/.codex/config.toml. Manage with codex mcp list, codex mcp get <name> and codex mcp remove <name>.
codex mcp login <name> runs the OAuth flow for an HTTP server; codex mcp logout clears it.
codex mcp list — every configured server should appear (add --json for machine output).codex mcp get <name> shows the resolved config for one server.Check the table name: it must be [mcp_servers.<name>] — snake_case, not mcpServers. A typo means the section is ignored without an error.
You’ve mixed transports — url, bearer_token_env_var and http_headers can’t sit in a table that also has command. One transport per server.
Raise startup_timeout_sec — slow npx cold starts are the usual cause. Long-running tools need tool_timeout_sec instead.
Codex controls the child process environment. Pass what the server needs explicitly via env (stdio) or bearer_token_env_var / env_http_headers (HTTP).
Inline tables (env = { KEY = "value" }) must stay on one line, and strings need quotes. A TOML linter catches most of it.
A standard Stripe MCP wired into Codex. Every call hits Stripe directly with no policy gate.
[mcp_servers.stripe] command = "npx" args = ["-y", "@stripe/mcp-server"] [mcp_servers.stripe.env] STRIPE_SECRET_KEY = "sk_live_..."
Register Stripe in PolicyLayer, mint a grant, point Codex at the proxy URL over streamable HTTP. PolicyLayer evaluates every call against the grant’s policy before it reaches Stripe — and the live key leaves your config.
[mcp_servers.stripe] url = "https://proxy.policylayer.com/mcp/<server-uuid>/" [mcp_servers.stripe.http_headers] Authorization = "Bearer <grant-token>"
CLI ALTERNATIVE:
codex mcp add stripe \ --url "https://proxy.policylayer.com/mcp/<server-uuid>/" \ --bearer-token-env-var POLICYLAYER_GRANT
The <server-uuid> appears on each server's detail page. The <grant-token> shows once at mint time. See the Quick start for the full setup walk-through — and if you operate under SOC 2, HIPAA, GDPR or similar, the compliance hub maps how gateway-mediated MCP traffic evidences each framework.
Allow list_customers. Allow create_charge, but deny any charge over 50000 cents. Deny everything else.
{
"version": "1",
"default": "deny",
"tools": {
"list_customers": {},
"create_charge": {
"deny_if": [
{
"conditions": [
{ "path": "args.amount", "op": "gt", "value": 50000 }
]
}
]
}
}
} See Writing policies for the policy format, operators, and quota shapes.
In ~/.codex/config.toml as [mcp_servers.<name>] TOML tables, or project-scoped in .codex/config.toml for trusted projects. The CODEX_HOME env var relocates the user directory, and codex mcp add writes the table for you.
Yes — streamable HTTP, declared with a url key. Auth options: bearer_token_env_var for token auth, http_headers for custom headers, or codex mcp login for OAuth.
Usually a section-name typo. The table must be [mcp_servers.<name>] in snake_case; a JSON-style mcpServers key or a misspelt section is ignored without an error.
Raise startup_timeout_sec on the server table — npx-based servers can be slow on first run while the package downloads. tool_timeout_sec separately bounds individual tool calls.
Yes — enabled_tools allowlists and disabled_tools blocklists tool names per server. For argument-level rules, rate limits and audit logs on top, route the server through a policy gateway like PolicyLayer.
Route your MCP traffic through PolicyLayer. Every tool call is checked against your policy before it runs: allow, deny, or require approval. Per-identity grants. Full audit log. Live in minutes.
Instant setup, no code required.
46,500+ MCP servers and 515,000+ tools scanned and risk-classified.