Skip to content

Codex connects to MCP (Model Context Protocol) servers declared as [mcp_servers.<name>] tables in config.toml: stdio servers that Codex launches as a local process, and streamable HTTP servers reached by URL with a bearer token or OAuth. The codex mcp command adds, inspects, and authenticates servers, and every server should pass a smoke test before its output is trusted.

This page is for the developer setting up Codex on their own machine, and for the tech lead who wants the same servers on every teammate’s machine. You asked Codex to fix the top Sentry error and it answered that it cannot reach external services. You asked for a Next.js middleware and got an API that changed two majors ago. Both are context problems, and connecting the right MCP server takes one command. The work that takes longer is scoping what each server may do and proving that it actually ran.

  • A working first server (Context7), checked with codex mcp list, /mcp, and a scripted smoke test.
  • Copy-paste config for the five servers most teams start with: Context7, Playwright, GitHub, Figma, and Sentry.
  • Per-server tool allow lists, approval modes, and output limits, so a server cannot flood the context or write without asking.
  • Secrets kept in environment variables and out of any file you commit.
  • A codex exec check that fails CI when a required server does not start.

Before you start, finish Codex configuration: this page assumes a working ~/.codex/config.toml and a signed-in Codex CLI (checked against codex-cli 0.157.1; the codex mcp subcommands and flags used on this page were checked again against 0.162.1 on 2026-10-12).

Codex supports two transports. The fields you set decide which one a table uses, and Codex rejects fields that belong to the other transport when it loads the config.

TransportCodex starts it byRequired keyAuth optionsTypical servers
stdioRunning command with args as a child processcommandenv_vars forwarded from your shell, envContext7 (npm), Playwright, Chrome DevTools, local GitHub in Docker
Streamable HTTPConnecting to urlurlbearer_token_env_var, env_http_headers, OAuth via codex mcp loginGitHub remote, Figma, Sentry, Context7 remote

Where the config lives matters more than it first appears:

  • ~/.codex/config.toml (under CODEX_HOME) is your personal file. codex mcp add writes here and only here (0.157.1). The CLI, the IDE extension, and the Codex desktop app read it when they share the same CODEX_HOME.
  • .codex/config.toml in the repository is the team file. Codex loads it only when you have marked the project as trusted; in an untrusted checkout it is loaded but disabled.

Codex 0.157.1 still talks to servers with the pre-2026-07-28 MCP protocol: support for the 2026-07-28 spec is behind the mcp_2026_07_28 feature flag, which codex features list shows as under development and off. The MCP overview covers what changed in the spec.

Context7 is the best first server: it needs no account, and the result is visible in one prompt.

  1. Add the server. Everything after -- is the command Codex runs:

    Terminal window
    codex mcp add context7 -- npx -y @upstash/context7-mcp

    Codex prints Added global MCP server 'context7'. and writes this table to ~/.codex/config.toml:

    [mcp_servers.context7]
    command = "npx"
    args = ["-y", "@upstash/context7-mcp"]
  2. Confirm Codex parsed it. codex mcp list shows every configured server; codex mcp get context7 --json prints the effective settings for one:

    Terminal window
    codex mcp list
    codex mcp get context7 --json
  3. Start Codex and type /mcp in the composer. It lists the configured MCP tools; /mcp verbose adds details. If context7 shows no tools, go to When Codex MCP servers break.

  4. Paste the prompt below and check that the answer names a Context7 library ID.

The prompt asks for the library ID and a quoted snippet on purpose. An answer without them came from training data, not from the server, and that is the failure you are trying to rule out.

Configure the servers most teams start with

Section titled “Configure the servers most teams start with”

Each block below is either a codex mcp add command or the equivalent config.toml table; use one or the other. Package versions were checked on npm on 2026-09-26: @upstash/context7-mcp 4.1.1 and @playwright/mcp 0.0.82.

Version-specific library docs. The API key is optional and raises rate limits. To use the remote server with a key, read it from an environment variable:

[mcp_servers.context7]
url = "https://mcp.context7.com/mcp"
bearer_token_env_var = "CONTEXT7_API_KEY"

More prompts and the skills-based alternative: Context7.

Add a server that needs a pre-registered OAuth client

Section titled “Add a server that needs a pre-registered OAuth client”

Some OAuth servers hand you a client ID and a client secret instead of registering Codex automatically. codex mcp add takes both (codex mcp add --help in 0.162.1, checked 2026-10-12):

Terminal window
read -rs MCP_CLIENT_SECRET # paste the secret from the server's admin page, then press Enter
codex mcp add my-server --url https://mcp.example.com/mcp \
--oauth-client-id CLIENT_ID \
--oauth-client-secret "$MCP_CLIENT_SECRET"

Replace CLIENT_ID with the identifier the server issued. read -rs takes the secret without echoing it, so it never lands in your shell history. --oauth-client-secret still takes the value as a command-line argument (codex mcp add --help), so other processes on the machine can see it in the process list while the command runs. The flag arrived in 0.158.0 (2026-09-28), documented in the release notes as being for pre-registered OAuth clients. The same help lists --oauth-client-registration (auto, cimd or dcr, applied to the immediate login only) and --oauth-resource (an OAuth resource parameter for that login).

To authenticate again later, run codex mcp login NAME in a terminal, which also accepts --no-browser, --scopes SCOPE,SCOPE and --oauth-client-registration. Inside a session, /mcp login <name> starts the login (0.161.0, 2026-10-07); the 0.162.1 command list describes /mcp as list MCP tools; use /mcp verbose or /mcp login <name>.

Every server you add puts its tools in reach of the model. Codex has searched MCP tools on demand by default since 0.143.0 (2026-07-08), so an idle server costs little. A server’s tool output does land in context, though, and a write tool runs with whatever permissions its token has. Scope both per server:

[mcp_servers.github]
url = "https://api.githubcopilot.com/mcp/"
bearer_token_env_var = "GITHUB_PAT_TOKEN"
enabled_tools = ["pull_request_read", "actions_list", "get_job_logs", "create_pull_request"]
default_tools_approval_mode = "prompt" # ask before every tool call from this server
startup_timeout_sec = 30 # default 30
tool_timeout_sec = 120 # default 300
[mcp_servers.github.tools.get_job_logs]
approval_mode = "auto" # back to the default: no prompt if the server marks the tool read-only
output_token_limit = 8000 # cap one huge CI log
  • enabled_tools is an allow list; disabled_tools removes tools after it is applied.

  • default_tools_approval_mode sets the server-wide policy, and [mcp_servers.<name>.tools.<tool>] overrides it for one tool. The four values, as codex-rs/core/src/mcp_tool_call.rs implements them in 0.157.1, key off the annotations the server publishes for each tool:

    • auto (the default) skips the prompt for tools the server marks read-only and asks for tools marked destructive or open-world, or not annotated at all.
    • prompt asks before every call.
    • writes asks before every call to a tool that is not marked read-only.
    • approve never asks.

    These apply when Codex can ask. With approval_policy = "never" (for example in unattended codex exec runs), a call that would prompt is rejected instead, unless the sandbox is full access, in which case it runs without asking (checked in 0.157.1). For CI, give the servers a job depends on auto or an explicit per-tool approve.

  • output_token_limit caps a single tool’s output, which is the setting that stops a log dump from pushing your task out of context. Reducing MCP token cost covers the wider trade-off.

  • enabled = false turns a server off without deleting its table. codex mcp remove <name> deletes it.

Tool names differ per server and version. Read the real ones from /mcp verbose before you write an allow list, because a misspelled name silently removes that tool.

Codex offers three ways to pass a secret without writing it into the file:

# stdio: forward named variables from your shell to the server process
[mcp_servers.github_local]
command = "docker"
args = ["run", "-i", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", "ghcr.io/github/github-mcp-server"]
env_vars = ["GITHUB_PERSONAL_ACCESS_TOKEN"]
# HTTP: send the value of an env var as `Authorization: Bearer <value>`
[mcp_servers.context7]
url = "https://mcp.context7.com/mcp"
bearer_token_env_var = "CONTEXT7_API_KEY"
# HTTP: any header, filled from an env var (here SENTRY_AUTH_HEADER="Sentry-Bearer sntrys_...")
[mcp_servers.sentry_token]
url = "https://mcp.sentry.dev/mcp"
env_http_headers = { "Authorization" = "SENTRY_AUTH_HEADER" }

Three things to avoid. codex mcp add --env KEY=VALUE writes the value itself into config.toml, so use it only for non-secret settings. A literal bearer_token key is rejected for HTTP servers (0.157.1), so old snippets that set one fail to load. And OAuth credentials from codex mcp login go to the OS keyring when one is available, otherwise to a file under CODEX_HOME, which you should treat like a private key. Securing MCP servers covers token scoping.

For a team, commit the servers every teammate needs to .codex/config.toml in the repository, with only environment-variable names in it, and document the variables in AGENTS.md or the README. Each developer then trusts the project once and exports their own tokens. Two shortcuts help with migration: /import in the Codex TUI brings MCP servers, settings, and plugins over from Claude Code and Cursor (since 0.145.0), and codex plugin list shows vendor plugins that bundle a server’s config with skills.

How do you verify MCP-backed work without reading every line?

Section titled “How do you verify MCP-backed work without reading every line?”

An MCP server changes what the agent knows, not how you check the result. Verify three things, in this order.

1. The server is up. For unattended runs, mark the servers a job depends on as required. With required = true, codex exec exits with an error when that server fails to initialize, instead of carrying on without it:

[mcp_servers.context7]
command = "npx"
args = ["-y", "@upstash/context7-mcp"]
required = true

2. The tool was actually called. codex exec --json prints one event per line, and each MCP call appears as an item.completed event whose item has type mcp_tool_call, with server, tool, and status (completed or failed). This smoke test fails when no Context7 call completed:

Terminal window
codex exec --json --ephemeral \
"Use the context7 MCP server to resolve the library ID for React. Reply with the ID only." \
| jq -r 'select(.type=="item.completed" and .item.type=="mcp_tool_call")
| [.item.server, .item.tool, .item.status] | @tsv' \
| grep -q $'^context7\t.*\tcompleted$' && echo "context7 OK" || { echo "context7 NOT CALLED"; false; }

The final false fails a CI step without closing an interactive shell. Run it after onboarding a teammate, after a Codex upgrade, and in CI before any job that relies on the server. Non-interactive Codex covers codex exec in pipelines.

3. The change is correct. Docs from Context7 or a stack trace from Sentry do not make the code right. The gate is still your tests, type checker, and linter, plus a failing test written before the fix. Ask for that explicitly:

The reviewer then checks the evidence — the issue ID, the failing-then-passing test, the green suite — rather than reading the diff line by line.

SymptomLikely causeRecovery
Codex reports a config error after you edit a tableA key from the other transport (for example env_vars on a url server, or a literal bearer_token), or a value of the wrong typeFix the key; codex mcp get <name> --json should parse cleanly
A setting has no effectA misspelled or unknown key, which Codex 0.157.1 ignores silently (for example headers instead of http_headers)For transport keys (url, bearer_token_env_var, env_http_headers, http_headers), timeouts and tool lists, check codex mcp get <name> --json; that output does not show required, approval modes or output_token_limit, so compare those names against the Codex config reference
Server listed but shows no tools in /mcpThe command failed: npx not on PATH, Docker not running, a missing env varRun the exact command and args in a terminal; export the variables named in env_vars before starting Codex
“startup timeout” on first runnpx or docker pull downloads the package inside the 30-second defaultPre-fetch once (npx -y @upstash/context7-mcp --help), or raise startup_timeout_sec
Tool calls time out on large pages or logsSlow tool, 300-second defaultRaise tool_timeout_sec for that server only; cap output with output_token_limit
Works in the terminal, not in the IDE extension or appDifferent CODEX_HOME, or a variable exported only in your interactive shellPoint both at one CODEX_HOME; put exports in a file your GUI session also loads, then restart the app
Team servers missing in a fresh cloneThe project is not trusted, so .codex/config.toml is loaded but disabledTrust the project when Codex asks, then restart the session
OAuth login never completesCorporate proxy or a firewall blocks the local callbackSet mcp_oauth_callback_port to an allowed port, or run codex mcp login <name> --no-browser and paste the callback URL
Two parallel sessions break PlaywrightBoth use one persistent browser profileAdd --isolated to the Playwright args

The MCP connection issues page covers transport-level debugging that applies to every client, and Codex error recovery covers what to do when a task goes wrong midway.

Did this guide help you?

One tap, no sign-in.

not at all a lot