How to add MCP servers to Codex (config.toml and codex mcp add)
Add MCP servers to Codex with codex mcp add or an mcp_servers block in config.toml: stdio and HTTP transports, OAuth, bearer tokens and tool limits.
Checked against Codex CLI 0.163.0
To add an MCP server to Codex, run codex mcp add <name> -- <command> for a local server or codex mcp add <name> --url <url> for a remote one, and Codex writes the entry into ~/.codex/config.toml for you. Reach for the file directly when you need the fields the flags do not cover: tool allow and deny lists, startup and per-tool timeouts, and per-tool approval modes. Either way the server is defined as an [mcp_servers.<server-name>] table, and the ChatGPT desktop app, the Codex CLI and the IDE extension all read the same configuration, so you add a server once and get it in all three.
#Where does Codex keep MCP server configuration?
Two files, with different trust rules:
- User config:
~/.codex/config.toml. This is the onecodex mcp addwrites to and the one most servers belong in. - Project config:
.codex/config.toml. The documentation is explicit that this one "works only in trusted projects". A project-level server is therefore not a way to ship an MCP server to anyone who clones a repository: in an untrusted project the block is inert.
That trust rule is the single most useful thing to know before you start, because it fails quietly. A project config in a project Codex does not trust does not error; the server simply is not there.
#What is the fastest way to add a server?
For a local server that Codex launches as a process, pass the launch command after --:
codex mcp add context7 -- npx -y @upstash/context7-mcpThe general form, including environment variables, is:
codex mcp add <server-name> --env VAR1=VALUE1 --env VAR2=VALUE2 -- <stdio server-command>For a remote server, pass --url instead of a command. The two are mutually exclusive:
codex mcp add example --url https://mcp.example.comThe rest of the subcommands cover the lifecycle:
| Command | What it does |
|---|---|
codex mcp add <name> | Registers a server, with -- <command...> for stdio or --url <value> for streamable HTTP |
codex mcp list | Lists configured servers. --json gives machine-readable output |
codex mcp get <name> | Shows one server's configuration. --json prints the raw config entry |
codex mcp remove <name> | Deletes a stored server definition |
codex mcp login <name> | Starts an OAuth login, for streamable HTTP servers that support OAuth only. Takes --scopes scope1,scope2 |
codex mcp logout <name> | Removes stored OAuth credentials for a streamable HTTP server |
codex mcp --help lists every MCP command, and inside the codex TUI, /mcp shows the servers active in the current session. That last one is the check to run after any change: it tells you what Codex actually loaded, rather than what you wrote.
#Which transport should you use, stdio or streamable HTTP?
Pick by where the server runs. A server you install and launch locally is stdio; a service you reach at an address is streamable HTTP.
| stdio | Streamable HTTP | |
|---|---|---|
| Required field | command | url |
| Where it runs | A local process Codex starts | A service reached over the network |
| Add it with | codex mcp add <name> -- <command...> | codex mcp add <name> --url <value> |
| Passing secrets | env, env_vars | bearer_token_env_var, http_headers, env_http_headers, http_headers_helper |
| Interactive sign-in | Not applicable | codex mcp login <name> |
| Other fields | args, cwd, experimental_environment | auth, which is oauth by default or chatgpt |
The stdio fields in full: command is required, args passes arguments, env sets environment variables, env_vars allows and forwards variables that already exist, cwd sets the working directory, and experimental_environment set to remote starts the server through a remote executor where one is available.
Note the difference between env and env_vars, because it is easy to reach for the wrong one. env sets a value you write in the config file. env_vars names variables already in your environment and forwards them, which is what you want for a secret you do not wish to commit.
#How do you write the TOML by hand?
A basic stdio server, showing both environment mechanisms:
[mcp_servers.context7]command = "npx"args = ["-y", "@upstash/context7-mcp"]env_vars = ["LOCAL_TOKEN"][mcp_servers.context7.env]MY_ENV_VAR = "MY_ENV_VALUE"A streamable HTTP server with a bearer token and a static header:
[mcp_servers.figma]url = "https://mcp.figma.com/mcp"bearer_token_env_var = "FIGMA_OAUTH_TOKEN"http_headers = { "X-Figma-Region" = "us-east-1" }A server with tool filtering, timeouts and a per-tool approval mode:
[mcp_servers.chrome_devtools]url = "http://localhost:3000/mcp"enabled_tools = ["open", "screenshot"]disabled_tools = ["screenshot"] # applied after enabled_toolsdefault_tools_approval_mode = "prompt"startup_timeout_sec = 20tool_timeout_sec = 45enabled = true[mcp_servers.chrome_devtools.tools.open]approval_mode = "approve"output_token_limit = 30000Read that third block carefully, because it is the documentation's own illustration of an ordering rule rather than a configuration to copy. screenshot appears in enabled_tools and again in disabled_tools, and because the deny list is applied after the allow list, the result is that screenshot is off.
#How do you authenticate a remote server?
Four mechanisms, and they have a defined precedence.
- Bearer token: set
bearer_token_env_varto the name of an environment variable holding the token. On the command line,--bearer-token-env-var ENV_VARdoes the same. - Static headers:
http_headersfor names and values written in the config, orenv_http_headersto map header names to environment variable names. - A header helper:
http_headers_helpernames a local command that prints a JSON object of headers, which is how you refresh a short-lived credential. - OAuth:
codex mcp login <server-name>. Theauthfield isoauthby default, withchatgptas the other value.
Precedence matters when you combine them: explicit bearer tokens and OAuth credentials take precedence over an Authorization header supplied by a helper. If you expected the helper's header to win, it will not.
For registering an OAuth client at add time, --oauth-client-id sets the client identifier and requires --url; --oauth-client-secret reads a confidential client's secret from an environment variable; and --oauth-resource adds an OAuth resource parameter during login, also requiring --url:
codex mcp add example --url https://mcp.example.com --oauth-client-id my-client --oauth-client-secret "$MCP_CLIENT_SECRET"--oauth-client-registration accepts cimd or dcr for a single login and defaults to auto.
The behaviour to plan around: if no credential source resolves, Codex connects without authentication. That is the documented fallback, and it means a typo in an environment variable name does not stop the server from loading. It produces an anonymous connection that looks healthy.
#How do you limit what a server can do?
The fields that decide how much of a server reaches the model, and how much it can do without asking:
| Field | Default | What it controls |
|---|---|---|
enabled_tools | Unset | An allow list of tool names |
disabled_tools | Unset | A deny list, applied after enabled_tools |
default_tools_approval_mode | Unset | auto, prompt, writes or approve for the server's tools |
tools.<tool>.approval_mode | Unset | The same choice for one named tool |
tools.<tool>.output_token_limit | Unset | A cap on one tool's output |
startup_timeout_sec | 10 | How long Codex waits for the server to initialize |
tool_timeout_sec | 60 | How long a single tool call may take |
enabled | Enabled | Set to false to switch a server off without deleting it |
required | Not required | true makes Codex startup fail if the server cannot initialize |
default_tools_approval_mode is the field worth setting deliberately on anything that can write. An allow list controls what the model can see; the approval mode controls what it can do unattended. For the wider argument about giving an agent tools versus giving it instructions, see MCP vs agent skills.
#What breaks first?
In rough order of how often each one catches people:
- A project config in an untrusted project.
.codex/config.tomlapplies only in trusted projects, and an untrusted one gives you no server and no complaint. Check/mcpin the TUI, orcodex mcp list, rather than assuming the file was read. - A tool in both lists.
disabled_toolsis applied afterenabled_tools, so a name in both is disabled. If a tool you explicitly allowed is missing, look for it in the deny list. - An unauthenticated connection you thought was authenticated. With no resolvable credential, Codex connects anyway. A misspelled
bearer_token_env_var, or an environment variable that is not exported in the shell Codex was started from, lands here. - A
startup_timeout_secof 10 against a coldnpx. The default is 10 seconds. A stdio server whose launcher downloads a package on first run can exceed that, and the honest fix is raising the timeout for that server rather than retrying until it happens to be warm. required = trueon a flaky server. It does what it says: Codex startup fails when that server cannot initialize. Useful for a server your workflow genuinely depends on, and a self-inflicted outage on one that is merely nice to have.
#How do you install or update the Codex CLI?
If you do not have it yet, the documented installers are:
curl -fsSL https://chatgpt.com/codex/install.sh | shnpm install -g @openai/codexbrew install --cask codexOn Windows, from a new PowerShell window:
powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"Homebrew users update with brew upgrade --cask codex. The configuration in this guide was checked against the Codex CLI release that openai/codex marks as latest, 0.163.0, published 15 October 2026; the repository also carries 0.164.0-alpha prereleases, which are not the stable line.
Next, the Codex platform hub covers the rest of the tool, what MCP is explains the protocol these servers speak, and Codex vs Claude Code compares the two agents if you are choosing between them. If you are adding somebody else's MCP server rather than your own, treat it as third-party code with access to whatever you authenticate it against, and work through the skill security audit first. ClawDocx does not vet, endorse or certify any third-party MCP server, including the ones named in the documentation examples above.
Frequently asked questions
- Where does Codex look for MCP server configuration?
- In two files. The user config is ~/.codex/config.toml, and a project config at .codex/config.toml works only in trusted projects. The ChatGPT desktop app, the Codex CLI and the IDE extension all share that configuration, so a server you add once is available in all three.
- Do you have to edit TOML by hand to add an MCP server to Codex?
- No. Run codex mcp add <name> -- <command> for a local stdio server, or codex mcp add <name> --url <url> for a streamable HTTP one, and Codex writes the entry for you. Hand-editing is for the fields the flags do not cover, such as tool filters, timeouts and per-tool approval modes.
- How do you sign in to an MCP server that uses OAuth?
- Run codex mcp login <server-name>, which starts an OAuth login for a streamable HTTP server that supports it. Add --scopes scope1,scope2 to request specific scopes. codex mcp logout <server-name> removes the stored credentials again.
- What happens if no credential is configured for a remote MCP server?
- Codex connects without authentication. It does not refuse to start the server or warn you that the connection is anonymous, so an unauthenticated connection to a server you believed was authenticated looks exactly like a working one until a tool call fails or returns the wrong data.
- How do you stop an MCP server from exposing a particular tool?
- Use enabled_tools as an allow list and disabled_tools as a deny list. disabled_tools is applied after enabled_tools, so a tool named in both ends up disabled. For a tool you want to keep but gate, set tools.<tool>.approval_mode instead.