Codex connects to MCP servers through one file, ~/.codex/config.toml, and one command, codex mcp add. Whatever you add there shows up in the Codex CLI, the Codex IDE extension and the ChatGPT desktop app, because all three read the same configuration. Use a URL for a remote server or a command for a local one. Codex handles OAuth on its own when the server asks for it.
"Codex MCP" used to mean two different things. One was Codex as an MCP client, using other people's tools. The other was Codex as an MCP server, run with codex mcp-server so another agent could drive it. OpenAI removed the second one. The command has been gone since Codex CLI 0.154.0, released September 9, 2026, but several of the pages ranking for this query still teach it. We cover that change near the end.
Everything below was run on October 15, 2026 against Codex CLI 0.162.1. The remote example is our own server, https://mcp.dialmcp.com/mcp, so the output is real rather than paraphrased from docs. Our Codex client setup notes hold the short version. This page covers the rest: what Codex does underneath, the config options worth knowing, how approvals work, and the error messages that send people searching.
The short version
Remote server (Streamable HTTP):
codex mcp add dialmcp --url https://mcp.dialmcp.com/mcp
Local server (stdio), with an environment variable:
codex mcp add everything --env FOO=bar -- npx -y @modelcontextprotocol/server-everything
Then check it from the shell with codex mcp list, or type /mcp inside the Codex terminal UI. If the server uses OAuth and the browser did not open, run codex mcp login <name> (add --no-browser on a remote machine). The full set of subcommands is small: list, get, add, remove, login and logout.
Where Codex keeps MCP configuration
Codex stores every MCP server as a [mcp_servers.<name>] table in config.toml. The global file is ~/.codex/config.toml, or $CODEX_HOME/config.toml if you have moved it. You can also put a .codex/config.toml inside a repository to scope servers to that project. OpenAI's documentation says this works in trusted projects only, and that matched our test: in a directory Codex had never been told to trust, codex mcp list ignored the project file completely and printed no warning. If a project server is missing, check trust before you check your TOML.
The shared file is the useful part. Add a server from the CLI and it appears in the IDE extension and the ChatGPT desktop app on the same machine, because they are all front ends to the same Codex host. ChatGPT on the web is the exception. It does not read local Codex configuration at all. Its MCP tools come from plugins and connectors, which is a separate setup covered in our ChatGPT MCP connectors guide.
Adding a remote MCP server from the CLI
A remote server is an MCP endpoint reachable over HTTPS using the Streamable HTTP transport. Here is the complete output from adding ours, on a machine with no browser available:
$ codex mcp add dialmcp --url https://mcp.dialmcp.com/mcp
Added global MCP server 'dialmcp'.
Detected OAuth support. Starting OAuth flow…
Authorize `dialmcp` by opening this URL in your browser:
https://mcp.dialmcp.com/authorize?response_type=code&client_id=i4tVp5q1NGrVyHsw
&code_challenge_method=S256&redirect_uri=http://127.0.0.1:45249/callback/SnTi2XH0tVcp
&resource=https://mcp.dialmcp.com/mcp …
(The URL is trimmed and decoded so it is readable.) The only thing written to config.toml was this:
[mcp_servers.dialmcp]
url = "https://mcp.dialmcp.com/mcp"
No token or client ID was stored in the file. Codex saves OAuth credentials separately, and auth = "oauth" is already the default, so you never have to write it.
A few things happened between those output lines, and knowing them makes failures easier to read:
- Discovery. Codex probed the URL and got a
401whoseWWW-Authenticateheader points to the server's protected resource metadata. That document names the authorization server. That is the "Detected OAuth support" line. - Client registration. Codex supports two ways to identify itself: Client ID Metadata Documents (CIMD) and Dynamic Client Registration (DCR). It chooses CIMD only when the authorization server advertises support for it. Ours does not, so Codex registered through DCR, and the
client_idin the URL is the one our server just issued. A second login registered a fresh client with a different ID. - The callback. Codex listens on
127.0.0.1on a random port, with a path ending in an ID derived from the server URL. Across both of our runs the port changed (45249, then 45245) but the path stayed/callback/SnTi2XH0tVcp. That only matters if you run an authorization server that pre-registers exact redirect URIs. RFC 8252 requires it to accept any loopback port. - The resource parameter. Codex sends
resource=with the server's URL, so the authorization server can issue a token restricted to that one server.
Our MCP authorization guide walks through that whole sequence at the spec level. Our OAuth reference shows what our own endpoints return at each step.
On a remote box or over SSH
If Codex runs somewhere that cannot open your browser, use --no-browser:
$ codex mcp login dialmcp --no-browser
Authorize the MCP server by opening this URL in your browser:
https://mcp.dialmcp.com/authorize?…
After signing in, copy the full URL from your browser's address bar.
If the callback page cannot load, paste that URL here anyway.
Callback URL (input hidden):
Open the link on your laptop and sign in. The final redirect to 127.0.0.1 will fail to load, because nothing on your laptop is listening, and that is fine. Copy the address bar and paste it into the waiting prompt. If you close the prompt first, you get Error: No OAuth callback URL received before input closed and have to start over.
Servers that use a static token instead
Some remote servers want an API key rather than OAuth. Keep the key out of the file by naming an environment variable, either with --bearer-token-env-var MY_TOKEN on codex mcp add or with bearer_token_env_var in TOML. For other headers there are http_headers (static values) and env_http_headers (values read from the environment). Only use these when the server's own docs say to. An OAuth server like ours does not want a bearer token, and setting one just swaps a working sign-in for a 401.
Adding a local stdio server
A stdio server is a process Codex starts on your machine and talks to over standard input and output. Everything after -- is the command:
$ codex mcp add everything --env FOO=bar -- npx -y @modelcontextprotocol/server-everything
Added global MCP server 'everything'.
That produced two tables in config.toml:
[mcp_servers.everything]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-everything"]
[mcp_servers.everything.env]
FOO = "bar"
Note that --env writes the value into the file in plain text. For secrets, use env_vars = ["MY_TOKEN"] instead, which forwards a variable from your shell without copying its value. codex mcp list masks env values (FOO=*****), but the file itself does not. Other stdio keys: cwd for the working directory, and args if you are writing the TOML by hand.
Here is what codex mcp list showed with both servers configured. Stdio and HTTP servers get separate tables:
Name Command Args Env Cwd Status Auth
everything npx -y @modelcontextprotocol/server-everything FOO=***** - enabled Unsupported
Name Url Bearer Token Env Var Status Auth
dialmcp https://mcp.dialmcp.com/mcp - enabled Not logged in
"Unsupported" in the Auth column is normal for stdio, since OAuth only applies to HTTP servers. It also appears for an HTTP server that does not use OAuth. "Not logged in" means Codex knows the server needs OAuth and has no credentials yet. codex mcp list --json gives the same data with an auth_status field, which is handy in scripts.
The same setup in the IDE extension and the ChatGPT desktop app
If you would rather click than type, both graphical clients have a form that writes the same configuration:
- IDE extension: gear menu, MCP servers, Add server. Enter a name, choose STDIO or Streamable HTTP, give the command or URL, save, then select Restart extension.
- ChatGPT desktop app: Settings, MCP servers, Add server, the same fields, save, then Restart. Type
/mcpin the composer to see what is connected.
In both, an OAuth server shows an Authenticate button until you sign in. Because the configuration is shared, a server you add in one client appears in the others.
config.toml options worth knowing
The CLI only sets the basics. These are the per-server keys we would actually reach for, with defaults from OpenAI's docs and the Codex source:
| Key | Default | What it does |
|---|---|---|
startup_timeout_sec | 10 | How long the server gets to start and answer. Raise it for slow npx first runs. |
tool_timeout_sec | 60 | How long one tool call may run before Codex gives up. |
enabled | true | Set false to switch a server off without deleting it. |
required | false | Set true to make Codex fail at startup if this server cannot initialize. |
enabled_tools / disabled_tools | all tools | Allow list and deny list. The deny list is applied after the allow list. |
default_tools_approval_mode | auto | When Codex asks before calling a tool. See the next section. |
tools.<name>.approval_mode | (server default) | Per-tool override of the line above. |
There is also a top-level mcp_optional_startup_grace_ms (default 1000). It controls how long Codex waits for servers that are not required before building the first tool list, so a slow server can miss your first turn without anything being wrong.
One timeout check is worth doing for any server with long-running tools. DialMCP's get_call can block for up to 50 seconds while a call finishes (its wait_seconds argument is capped there), which fits inside Codex's 60-second default. If a server you use documents a longer blocking call, raise tool_timeout_sec for that server only.
How Codex decides when to ask before a tool runs
OpenAI's docs list the four approval modes but say little about how the default one behaves. The Codex source (codex-rs/core/src/mcp_tool_call.rs, read on October 15) answers it:
promptasks every time (subject to the session settings covered below).approvenever asks.writesasks unless the tool is markedreadOnlyHint: true.auto, the default, reads the tool's MCP annotations. A tool marked destructive always needs approval. A tool marked read-only never does. For anything else, a hint the server left out is treated as the cautious value: not read-only, possibly destructive, open-world. So a tool with no annotations at all needs approval.
That last rule is the one that surprises people. Many servers ship without annotations, and under the default every unannotated tool prompts. That is not a bug. Codex cannot tell a harmless lookup from something with side effects unless the server says so. If you trust a server's read-only tools, the fix is to approve them by name rather than turning approvals off for the whole server:
[mcp_servers.dialmcp]
url = "https://mcp.dialmcp.com/mcp"
default_tools_approval_mode = "prompt"
[mcp_servers.dialmcp.tools.get_call]
approval_mode = "approve"
[mcp_servers.dialmcp.tools.list_calls]
approval_mode = "approve"
That is the setup we use for our own server. place_call dials a real phone number, and end_call hangs one up. Neither should ever run without a human seeing it first. Checking on a call or listing past calls changes nothing, so those two can run on their own.
Two session-level settings sit above these per-server modes, and they can quietly override them. With approval_policy = "never" and full access (no sandbox), Codex auto-approves MCP calls, even for a tool set to prompt. With never under a sandbox, it rejects them instead. And approvals_reviewer = "auto_review" sends prompts to a reviewer agent rather than to you. If you want a person to see every place_call, keep the default on-request policy with the user reviewer.
"codex mcp-server" no longer exists
Until September, you could run Codex itself as an MCP server: codex mcp-server started a stdio server exposing codex and codex-reply tools, so another agent (or the OpenAI Agents SDK) could hand Codex a coding task. The pull request that removed it, openai/codex#42993, was merged on September 5, 2026 and first shipped in Codex CLI 0.154.0 on September 9, whose release notes say: "The deprecated codex mcp-server entry point is no longer available." The standalone codex-mcp-server binary went with it.
On a current build the subcommand is not merely deprecated. It is not parsed as a subcommand at all. On 0.162.1, codex mcp-server with input piped in exits with Error: stdin is not a terminal, because Codex treats the words as a prompt for its interactive UI. If an integration broke with that error after an upgrade, this is why.
OpenAI's replacement is codex app-server. Its migration note is blunt about the trade: the app server speaks its own JSON-RPC protocol, it "isn't an MCP server or a drop-in replacement for an MCP client," and it is experimental and not supported for production workloads. So anything that drove Codex over MCP has to be rewritten, not repointed. If you need time to migrate, 0.153.4 (September 4) was the last release that still includes codex mcp-server. Pinning it keeps the old command working, though OpenAI no longer supports that path. Expect the older docs pages and blog posts that still show codex mcp-server to stay in search results for a while.
None of this affects Codex as a client. codex mcp, the subcommand this whole page is about, is still supported.
Troubleshooting: what the messages actually mean
| You see | What is going on |
|---|---|
Error: command is required on an --url add | You passed --env with --url. --env is for stdio servers only, and the message does not say so. Drop it, or use env_http_headers in TOML. |
Not logged in in codex mcp list | The server wants OAuth and Codex has no credentials. Run codex mcp login <name>. |
| A server you added in a repo does not show up | The project is not trusted, so .codex/config.toml is ignored with no warning. |
| Your old config for a name is gone | codex mcp add with an existing name replaces it and just prints "Added". Use codex mcp get <name> first if you are unsure. |
No MCP server named 'x' found. | Typo in codex mcp remove. It still exits 0, so do not rely on the exit code in scripts. |
| A server worked yesterday and now asks you to reconnect | Its OAuth token expired and could not be refreshed. Since 0.155.0 Codex reports expired credentials accurately and tells you how to reconnect. Run codex mcp login <name> again. |
| Tools missing on the first turn, present later | The server took longer than the startup grace period. Raise mcp_optional_startup_grace_ms (or set it to 0 to wait each server's full startup_timeout_sec), or set required = true for that server. |
For problems inside the server rather than in Codex, take Codex out of the loop and connect with the MCP Inspector. If the Inspector cannot list the tools, Codex will not be able to either.
Example: letting Codex place a phone call
Codex is a coding agent, but its MCP support works for any tool. Ours is a good test of the approval model because the side effect is real. After codex mcp add dialmcp --url https://mcp.dialmcp.com/mcp, sign-in happens in the browser: enter a US or Canadian mobile number, type the SMS code, on first sign-in pick the first name the assistant should use, and click Allow. Calls go out from that verified number.
Then ask in plain language, for example "call the hardware store at +1 415 555 0123 and ask whether they cut keys on Sundays". Codex prepares a place_call request with the number and an objective. Under the config above, with the default on-request approval policy, it stops to show you that request before anything dials. Once you approve, the call runs in the background, usually for 2 to 5 minutes. The voice agent says it is an AI calling on your behalf and that the call is recorded, and it gets through phone menus and hold queues. Codex polls get_call until the call ends and hands back the transcript and outcome. The server, not the prompt, enforces the limits: US and Canadian numbers only, 8am to 9pm at the destination's local time, and per-user rate limits that come back as error codes Codex can relay. Every tool and field is listed in our tool reference.
Want Codex to make phone calls? DialMCP is a hosted remote MCP server that places real calls from your own verified number and returns a transcript and a structured outcome. Run codex mcp add dialmcp --url https://mcp.dialmcp.com/mcp and sign in with OAuth. US and Canada, free during launch.
FAQ
How do I add an MCP server to Codex?
Run codex mcp add with a name and either --url for a remote server or -- followed by a command for a local one, for example codex mcp add dialmcp --url https://mcp.dialmcp.com/mcp. Codex writes an [mcp_servers.name] table to ~/.codex/config.toml. You can also edit that file directly, or use Add server under MCP servers in the IDE extension or the ChatGPT desktop app.
Where is the Codex MCP config file?
In ~/.codex/config.toml, or $CODEX_HOME/config.toml if you have set CODEX_HOME. A project can also have its own .codex/config.toml, but Codex only reads it in trusted projects. The Codex CLI, the IDE extension and the ChatGPT desktop app all share this configuration.
Does Codex support remote MCP servers with OAuth?
Yes. Codex supports Streamable HTTP servers with OAuth, including Client ID Metadata Documents and Dynamic Client Registration, as well as bearer tokens read from an environment variable. When you add an OAuth server, Codex detects it and opens the sign-in. If no browser is available, run codex mcp login with --no-browser and paste back the callback URL.
What happened to codex mcp-server?
OpenAI removed it. The command and the standalone codex-mcp-server binary were deleted in openai/codex pull request 42993 and are absent from Codex CLI 0.154.0 onward, released September 9, 2026. The replacement, codex app-server, uses its own JSON-RPC protocol rather than MCP and is marked experimental. Codex as an MCP client is unaffected.
Why does Codex ask for approval before every MCP tool call?
Under the default auto approval mode, Codex reads each tool's MCP annotations. Any hint a tool leaves out is read cautiously, so a tool with no annotations counts as possibly destructive and needs approval, and many servers publish no annotations at all. You can approve specific tools with tools.<name>.approval_mode = "approve" in config.toml rather than turning approvals off for the whole server.