Codex as an MCP Client
Connect external tools over stdio and streamable HTTP
- Explain what MCP gives a coding agent and when to reach for it over a built-in tool or a skill
- Distinguish the two transports Codex supports — stdio (a local process) and streamable HTTP (a URL) — and pick the right one
- Register, list, inspect, and remove MCP servers with `codex mcp add/list/get/remove` and authenticate HTTP servers with `codex mcp login/logout`
- Write a correct `[mcp_servers.<id>]` block in `config.toml` for both stdio and HTTP servers, including `env`, timeouts, and headers
- Gate which tools a server may use with `enabled`, `required`, `enabled_tools`, `disabled_tools`, and approval modes
- Inspect active servers and their status from the TUI with `/mcp`
Codex's built-in tools stop at the edge of your repo — MCP (the Model Context Protocol) is how you give it more: live docs, a database, Figma, your issue tracker. This lesson teaches Codex as an MCP *client*: the two supported transports (stdio and streamable HTTP), registering servers with `codex mcp add`, the management subcommands, the `[mcp_servers]` config schema with timeouts and tool gating, and listing what's active with `/mcp`.
- 1The mental model: a universal adapter for tools
- 2Two transports: stdio and streamable HTTP
- 3Registering servers with `codex mcp add`
- 4Listing, inspecting, and authenticating servers
- 5The `config.toml` schema: stdio and HTTP servers
- 6Tool gating: allow-lists, deny-lists, and approval modes
- 7Seeing what's live: the `/mcp` slash command
The mental model: a universal adapter for tools
Out of the box, Codex can read your files, search the code, run commands, and edit. Powerful — but every one of those tools points inward, at the repo in front of it. The moment a task needs something the repo doesn't contain — the current docs for a fast-moving library, a row from your production database, a Figma frame, a Jira ticket — Codex is blind.
MCP (the Model Context Protocol) is the universal adapter that fixes that. It's an open standard for connecting a model to external tools, resources, and prompts. Instead of OpenAI hand-building an integration for every service on earth, anyone can ship an MCP server that exposes their service, and any MCP client — Codex included — can plug into it. One protocol, many providers: the M×N integration problem collapses to M+N.
In this relationship Codex plays the client: it connects out to servers other people wrote and calls their tools as if they were its own. (Codex can also run the other way — as a server that other agents drive — but that's the next lesson. Here we're strictly the consumer.)
Think of it like USB-C for AI tooling. You don't rewire your laptop for every peripheral; you expose one port, and any compliant device speaks to it. MCP is that port for Codex.
Key insight
Why this is the high-leverage move
Built-in tools make Codex good inside your repo. MCP makes it good about your whole stack — connect your DB, your docs, your design tool, and Codex can reason across all of them in one turn instead of asking you to paste things in.
Note
Client now, server later
This lesson covers Codex as an MCP client (consuming external servers). Running codex mcp-server so other tools can drive Codex is a separate, opposite direction — covered in the next lesson.
Two transports: stdio and streamable HTTP
Codex can talk to an MCP server over exactly two transports, and choosing between them is your first decision:
| Transport | What it is | How Codex connects | Auth |
|---|---|---|---|
| stdio | A server that runs as a local process, started by a command | Codex spawns the process and speaks JSON-RPC over stdin/stdout | Environment variables (--env / env) |
| Streamable HTTP | A server you reach at an address (a URL) | Codex sends requests to the URL | Bearer token, custom headers, or OAuth |
The rule of thumb: stdio for local tools (a CLI you npx/uvx into existence, a script on your machine), streamable HTTP for hosted services (a vendor running the server for you at https://…).
One accuracy note that catches people coming from other clients: the older SSE (HTTP+SSE) transport is not listed as supported by Codex — only stdio and streamable HTTP. If a server only advertises a legacy SSE endpoint, you'll want its stdio or streamable-HTTP variant. (Under the hood Codex's MCP stack is built on the Rust rmcp crate; you don't interact with that directly, but it's why streamable HTTP is a first-class, default path now rather than something you opt into.)
Tip
Picking a transport in one question
"Does this tool live on my machine or on someone's server?" Local → stdio (a launch command). Remote/hosted → streamable HTTP (a URL, usually with a token or OAuth).
Watch out
No SSE
The legacy HTTP+SSE transport is not in Codex's supported list. Don't assume a server's SSE URL will work — use its streamable-HTTP endpoint or run it locally over stdio.
Registering servers with `codex mcp add`
The fastest way to add a server is the codex mcp command group, which writes entries into ~/.codex/config.toml for you. You don't have to hand-edit TOML to get started.
stdio server — everything after the -- separator is the launcher command and its arguments:
codex mcp add <server-name> --env VAR1=VALUE1 --env VAR2=VALUE2 -- <stdio server-command>The official, copy-pasteable example wires up Context7 (a live-docs server) via npx:
codex mcp add context7 -- npx -y @upstash/context7-mcpHere context7 is the name you'll see in /mcp, and npx -y @upstash/context7-mcp is the command Codex will spawn. Pass --env KEY=VALUE (repeatable) for any environment variables the process needs.
Streamable HTTP / URL server — use --url instead of a -- launcher:
codex mcp add figma --url https://mcp.figma.com/mcpHTTP servers usually need authentication. The codex mcp add flags that matter for that:
| Flag | Applies to | Purpose |
|---|---|---|
--env KEY=VALUE (repeatable) | stdio | Environment variables for the spawned process |
--url https://… | HTTP | Register an HTTP server instead of a stdio launcher |
--bearer-token-env-var ENV_VAR | HTTP | Name of the env var holding the server's bearer token |
--oauth-client-id CLIENT_ID | HTTP | OAuth client ID for the server |
--oauth-resource RESOURCE | HTTP | OAuth resource (RFC 8707) parameter |
Note the bearer-token flag takes the name of an environment variable, not the token itself — Codex reads the secret from your environment so it never lands in config.toml in plaintext.
Watch out
Pass the env var NAME, not the secret
--bearer-token-env-var FIGMA_TOKEN tells Codex to read the token from $FIGMA_TOKEN at runtime. Never put the literal token on the command line or in config — keep it in your environment (or a secrets manager) and reference it by name.
Tip
Discover subcommands live
Run codex mcp --help to see the current subcommand set and flags. The CLI is the source of truth for your installed version — confirm specifics at developers.openai.com/codex/cli/reference.
Listing, inspecting, and authenticating servers
Once servers are registered, the rest of the codex mcp subcommands let you manage them without touching the config file:
| Subcommand | Syntax | What it does |
|---|---|---|
add | codex mcp add <name> -- <command…> or … --url <value> | Register a server (stdio launcher or HTTP URL) |
list | codex mcp list [--json] | List all configured servers (--json for machine-readable output) |
get | codex mcp get <name> [--json] | Show one server's full configuration |
remove | codex mcp remove <name> | Delete a stored server definition |
login | codex mcp login <name> [--scopes scope1,scope2] | Start an OAuth login for a streamable HTTP server |
logout | codex mcp logout <name> | Clear stored OAuth credentials for a server |
A typical lifecycle looks like:
codex mcp add context7 -- npx -y @upstash/context7-mcp # register
codex mcp list # confirm it's there
codex mcp get context7 --json # inspect its config
codex mcp login figma --scopes files:read # OAuth into an HTTP server
codex mcp remove context7 # tear it downFor remote servers that use OAuth, codex mcp login <name> runs the browser login flow and stores the credentials; codex mcp logout <name> revokes them locally. Codex respects the scopes the server advertises, or falls back to the --scopes you pass (or the scopes in config).
Tip
`--json` is for scripts
codex mcp list --json and codex mcp get <name> --json emit machine-readable output — handy for auditing which servers (and which tools) are wired into a teammate's or CI's Codex setup.
The `config.toml` schema: stdio and HTTP servers
The CLI is convenient, but every server ultimately lives as a [mcp_servers.<id>] table in ~/.codex/config.toml (global) — or in a project-scoped .codex/config.toml for a trusted project. Knowing the schema lets you review, version, and fine-tune entries by hand.
stdio server — a command (required) plus optional args, environment, and timeouts:
[mcp_servers.context7]
command = "npx" # required: the launcher
args = ["-y", "@upstash/context7-mcp"]
startup_timeout_sec = 10 # default 10s to initialize the server
tool_timeout_sec = 60 # default 60s per tool call
[mcp_servers.context7.env]
MY_ENV_VAR = "MY_ENV_VALUE" # env forwarded to the processStreamable HTTP server — a url plus auth and headers:
[mcp_servers.figma]
url = "https://mcp.figma.com/mcp"
bearer_token_env_var = "FIGMA_OAUTH_TOKEN" # token read from this env var
http_headers = { "X-Figma-Region" = "us-east-1" } # static headers per request
# env_http_headers = { ... } # headers populated from env vars
# scopes = ["files:read"] # OAuth scopes to request
# oauth_resource = "..." # RFC 8707 resource parameterThe key fields to know by transport:
| Field | Transport | Meaning |
|---|---|---|
command | stdio | Required. The launcher command |
args | stdio | Arguments passed to the command |
env | stdio | Environment variables forwarded to the process |
startup_timeout_sec | stdio | Time to initialize before giving up — default 10s |
tool_timeout_sec | both | Per-tool-call timeout — default 60s |
url | HTTP | The streamable-HTTP endpoint |
bearer_token_env_var | HTTP | Env var that sources the bearer token |
http_headers | HTTP | Static headers sent on each request |
scopes / oauth_resource | HTTP | OAuth scopes and resource parameter |
Both timeouts are overridable per server, so a slow-starting server (a heavy local process) or a long-running tool can be given more room.
Note
Two timeouts, two jobs
startup_timeout_sec (default 10s) bounds how long Codex waits for the server to come up; tool_timeout_sec (default 60s) bounds each individual tool call. A server that's slow to boot but fast per call needs the first raised, not the second.
Watch out
Project-scoped config needs trust
A .codex/config.toml checked into a repo only takes effect in a trusted project. Don't expect an MCP server defined in a freshly-cloned, untrusted repo to silently activate — Codex gates project-level config behind trust.
Tool gating: allow-lists, deny-lists, and approval modes
An MCP server can expose many tools, and you rarely want all of them, all the time. Codex gives you per-server controls that work for both stdio and HTTP servers:
| Key | Type | Effect |
|---|---|---|
enabled | boolean | Turn a server off without deleting its config |
required | boolean | When true, Codex fails startup/resume if this server can't initialize |
enabled_tools | array | Allow-list — only these tool names are exposed |
disabled_tools | array | Deny-list — removed after the allow-list is applied |
default_tools_approval_mode | auto|prompt|approve | Default approval behavior for the server's tools |
tools.<tool>.approval_mode | auto|prompt|approve | Per-tool override of the default |
The ordering matters: disabled_tools is applied after enabled_tools. So the final tool set is "everything in the allow-list, minus anything in the deny-list" — the deny-list always wins.
[mcp_servers.db]
command = "my-db-mcp"
enabled = true
required = false
enabled_tools = ["query", "describe_table"] # only these two
disabled_tools = ["query"] # ...minus query -> only describe_table remains
default_tools_approval_mode = "prompt" # ask before running its tools
[mcp_servers.db.tools.describe_table]
approval_mode = "auto" # but let this safe one run unpromptedTwo behaviors worth internalizing. First, required = true is a sharp tool: it turns a flaky or misconfigured server into a hard startup failure, which is what you want in CI (fail loud) but not on your laptop (where you'd rather keep working). Second, read-only MCP tools that advertise the readOnlyHint are allowed to run in parallel, so well-behaved read tools don't serialize your turn.
Example
Allow-list a database server to read-only
Expose a database MCP server but enabled_tools = ["query", "describe_table"] and set default_tools_approval_mode = "prompt", then promote only describe_table to auto. Codex can explore schema freely but must ask before each query — least privilege, applied at the tool level.
Watch out
`required = true` will block your startup
A required server that can't initialize aborts Codex's startup and resume. Great for CI determinism; painful interactively. Reserve it for servers a session genuinely cannot run without.
Seeing what's live: the `/mcp` slash command
After all the configuration, you need a way to confirm what Codex actually loaded. In the codex TUI, type:
/mcp/mcp lists your active MCP servers and their status — which connected, which failed, and the tools they expose. It's the first thing to check when a server you expected isn't showing up, or when a tool call mysteriously isn't available.
A quick triage loop when an MCP tool isn't working:
/mcpin the TUI — is the server listed and healthy? If it's missing or errored, the problem is connection/config, not the model.codex mcp get <name>— is the command, URL, or token env var what you expect?- Auth — for HTTP servers, did
codex mcp login <name>succeed, and is the token env var actually set in this shell? - Gating — did
enabled_tools/disabled_toolsaccidentally filter out the tool you wanted? Remember the deny-list is applied last. - Timeouts — a heavy server may be tripping the 10s
startup_timeout_sec; raise it.
Because MCP server config is just part of Codex's config, the servers you set up are shared across Codex surfaces that read the same config.toml — set them up once and they follow you.
Tip
Start every debugging session with `/mcp`
Before assuming the model is ignoring a tool, run /mcp. Nine times out of ten a 'missing' tool is a server that failed to start, an unset token env var, or a tool filtered out by your allow/deny lists — all visible right there.
Try it: Wire a live-docs MCP server into Codex and gate it
Goal: feel Codex reach outside the repo, then lock the connection down. 1) Add a stdio server. Register the Context7 live-docs server: codex mcp add context7 -- npx -y @upstash/context7-mcp. 2) Confirm it registered. Run codex mcp list and codex mcp get context7 --json; note the command and args Codex stored in ~/.codex/config.toml. 3) Verify it's live. Start codex, type /mcp, and confirm the server shows as active with its tools listed. If it isn't, work the triage loop: check get, check the spawn command, check timeouts. 4) Use it. Ask Codex a question that needs current library docs (e.g. "Using up-to-date docs, show the current way to define a tool with the OpenAI Agents SDK") and watch it call the MCP tool rather than guess from training data. 5) Gate it. Open config.toml and add tool gating to the [mcp_servers.context7] block: set default_tools_approval_mode = "prompt" so Codex must ask before each call, then restart and observe the approval prompt. 6) (Optional) Try an HTTP server. If you have access to a hosted MCP server, add it with codex mcp add <name> --url <url>, authenticate via codex mcp login <name>, and confirm it in /mcp. 7) Clean up. codex mcp remove context7. Write three sentences: which transport you used and why, what /mcp told you, and how the approval mode changed the agent's behavior. This builds the core instinct that MCP is how you extend Codex past the repo — under explicit, gated control.
Key takeaways
- 1MCP is the universal adapter that lets Codex (the client) consume external tools, resources, and prompts — turning M×N integrations into M+N, and extending the agent past the edge of your repo.
- 2Codex supports exactly two transports: stdio (a local process started by a command) and streamable HTTP (a URL). The legacy SSE transport is not on the supported list.
- 3Register servers with `codex mcp add <name> -- <command>` (stdio) or `--url <value>` (HTTP); manage them with `list`, `get`, `remove`, and authenticate HTTP servers with `login`/`logout` — all writing to `~/.codex/config.toml`.
- 4In config, stdio servers need `command` (required) plus `args`/`env` and tunable `startup_timeout_sec` (10s) and `tool_timeout_sec` (60s); HTTP servers use `url`, `bearer_token_env_var`, `http_headers`, and OAuth `scopes`. Pass secrets by env-var name, never inline.
- 5Gate tools per server with `enabled`, `required`, `enabled_tools` (allow-list), `disabled_tools` (deny-list applied after), and approval modes `auto`/`prompt`/`approve` at server-default or per-tool granularity.
- 6Run `/mcp` in the TUI to list active servers and their status — your first stop for debugging a missing or failing MCP tool.
Quiz
Lock in what you learned
Check your understanding
0 / 4 answered
1.You want Codex to use a database tool that runs as a local Node process you start with `npx`, and separately a Figma integration hosted by Figma at `https://mcp.figma.com/mcp`. Which transports apply?
2.You run `codex mcp add figma --url https://mcp.figma.com/mcp --bearer-token-env-var FIGMA_TOKEN`. Where does Codex get the bearer token, and what ends up in config.toml?
3.A server's config sets `enabled_tools = ["query", "describe_table", "run_migration"]` and `disabled_tools = ["run_migration"]`. Which tools does Codex expose?
4.An MCP tool you configured isn't showing up in a Codex session. What's the most direct first step to diagnose it?
Go deeper
Hand-picked sources to keep learning
Canonical page for Codex as an MCP client: transports, `codex mcp add`, the `/mcp` command, and verbatim TOML examples.
The full `codex mcp` subcommand set (add, list, get, remove, login, logout) and their flags. Confirm specifics for your installed version here.
Complete `[mcp_servers]` schema — stdio and HTTP keys, timeouts, tool gating, and the global MCP OAuth options.
The open-source Rust CLI — releases, changelog, and issues. The MCP stack is built on the `rmcp` crate.
Track volatile details: CLI version, `rmcp` version, OAuth-in-`codex mcp add`, and parallel read-only tool changes.
The opposite direction — running Codex itself as an MCP server. Useful preview for the next lesson.