Connect MCP to Codex CLI Without Guesswork
You add one MCP server to Codex, start a session, and the tool still isn’t there.
The config file looks plausible. The server command works in your terminal. The URL opens in a browser. Yet Codex either doesn’t connect, connects without tools, or waits long enough to make you question the entire setup.
I treat that as a layered integration problem, not a config-file mystery. Codex has to load the right configuration, start or reach the server, complete the transport and authentication flow, pass the relevant permission checks, and finally expose the tools to the session. If you’re changing all of them at once, you’re debugging noise.
If you’re staring at config.toml wondering which line to change first, start with the first failure you can actually observe. Not the most suspicious-looking line.
Confirm what Codex can connect to today
The current Codex CLI has a stable codex mcp command for managing MCP server entries. The official command reference lists add, get, list, login, logout, and remove. Codex CLI command reference
That gives you a useful first test:
codex mcp listThen inspect one entry:
codex mcp get <name>If the server isn’t listed, don’t debug OAuth. The session can’t use a server that isn’t in the configuration layer you’re actually running.
Separate Codex as MCP client from Codex as MCP server
This distinction causes more confusion than it should.
For the workflow in this guide, Codex is the MCP client. It connects to another MCP server and calls that server’s tools.
Older guides can muddy this by describing Codex itself as an MCP server. The current official CLI reference says the codex mcp-server command and standalone codex-mcp-server binary have been removed.
So if a setup guide tells you to start codex mcp-server, stop there. That's stale guidance for the current CLI.
Not the same feature.
Check whether the config should be global or project-scoped
Codex reads user configuration from ~/.codex/config.toml. A project can also have .codex/config.toml, and project-scoped configuration is loaded only for trusted projects. The documented layering puts project configuration above user profile and user configuration, while CLI overrides are also applied as a higher-precedence layer.
That creates a simple decision rule:
If the MCP server should follow you across projects, start with ~/.codex/config.toml. If it belongs to one repository, use that project's .codex/config.toml.
If a project entry isn’t loading, check trust before editing TOML again. An untrusted project skips project-scoped .codex/ configuration.
Add the smallest server that can prove discovery
I prefer a boring first server. You don’t need your production ticketing system, a complicated OAuth provider, and six custom headers to prove that MCP works.
The official configuration reference exposes separate fields for stdio commands and Streamable HTTP URLs. It also supports enabled, enabled_tools, disabled_tools, environment variables, HTTP headers, OAuth settings, startup timeouts, and per-tool approval modes.
Start with one server and as few moving parts as possible.
Use a stdio example when local startup is the unknown
For a local server, the configuration shape is roughly:
[mcp_servers.my-tool]
command = "your-server-command"
args = ["your", "arguments"][mcp_servers.my-tool.env]
API_KEY = "value"The CLI can also register a stdio server directly:
codex mcp add my-tool -- your-server-command your-argumentsThe current command reference describes add <name> -- <command...> for a stdio launcher and supports --env KEY=VALUE.
Your first question is simple: can Codex launch the process?
If the executable isn’t available in the environment Codex inherits, the server won’t reach the MCP handshake. Running the command successfully in a separate shell doesn’t prove that Codex sees the same executable path.
Use an absolute executable path when necessary.
For example:
which uvxThen, if required:
command = "/absolute/path/to/uvx"Green. Move on.
Use HTTP when remote auth or headers are the unknown
For a Streamable HTTP server, the configuration is different:
[mcp_servers.remote-tool]
url = "https://service.example.com/mcp/"
bearer_token_env_var = "MCP_TOKEN"Or add the server from the CLI:
codex mcp add remote-tool --url https://service.example.com/mcp/The important detail is that bearer_token_env_var names an environment variable. It isn't the bearer token itself. The official configuration reference describes it as the environment variable used to source the bearer token.
So this:
bearer_token_env_var = "MCP_TOKEN"requires MCP_TOKEN to exist where Codex can access it.
If the server supports OAuth, use:
codex mcp login remote-toolThe current CLI reference lists login <name> specifically for OAuth authentication to Streamable HTTP servers.
Don’t add a bearer token just because the server is remote. Use the authentication mechanism the server actually supports.
Read the first failure by layer
Once the basic entry exists, resist the urge to change three settings at once.
Get Felix Anderson’s stories in your inbox
Join Medium for free to get updates from this writer.
I use this order:
- Configuration — Did Codex load the intended server entry?
- Startup — Can the local process launch, or can Codex reach the HTTP endpoint?
- Transport — Does the MCP connection establish successfully?
- Authentication — Are the required credentials present and accepted?
- Permissions — Is the tool allowed to run?
- Tool exposure — Does the session actually see the tool?
That order is deliberate. A permission change can’t repair a server that never started.
Server startup and discovery failures
For stdio, inspect the executable, arguments, working directory, and environment first. The official config reference exposes command, args, cwd, and env specifically for MCP stdio servers.
If the server starts slowly, don’t immediately assume the transport is broken. Codex’s documented default MCP startup timeout is 10 seconds; startup_timeout_sec can override it.
That gives you a clean experiment:
[mcp_servers.slow-tool]
command = "your-command"
startup_timeout_sec = 30If increasing the timeout changes the result, you’ve learned something. If it doesn’t, put the timeout back and investigate the next layer.
For HTTP, start with reachability and URL correctness. Then check the authentication mechanism.
Not yet? Good. Don’t touch permissions.
Transport, authentication, and permission failures
Transport errors and authentication errors can look similar from the outside because both prevent useful tool calls.
For HTTP, check the URL first. Then verify the token environment variable or run the OAuth login flow. Codex also exposes HTTP header configuration and OAuth-related settings for MCP servers.
Authentication succeeding doesn’t automatically mean every tool can run.
The current config reference supports a server-level default_tools_approval_mode with auto, prompt, writes, or approve, plus per-tool approval overrides. It also supports enabled_tools and disabled_tools.
So there are two different questions:
Can Codex see this tool?
and
Can Codex call this tool under the current approval rules?
Treat them separately.
Inspect tool exposure before changing the config
Once the server connects, open a Codex session and run:
/mcpThe current CLI documentation describes /mcp as the session command for listing configured MCP tools, with verbose available for server details.
This is one of the most useful checkpoints in the entire process.
If the server isn’t shown, you’re still debugging configuration or connection.
If the server is shown but no tools appear, look at the server’s returned tool list and its authentication state.
If the tools appear but calls fail, investigate permissions, tool-specific approval, server-side errors, or timeout behavior.
Codex’s current configuration reference sets the default MCP tool-call timeout at 60 seconds, with tool_timeout_sec available for overrides.
A timeout is a different failure from discovery. Keep it that way.
Decide what belongs in user, project, or plugin scope
Scope should follow ownership.
A personal utility that you use across repositories belongs naturally in user configuration. A repository-specific MCP server belongs in project configuration, provided the project is trusted.
Plugins add another layer. The current configuration reference includes plugin-specific MCP server settings, including whether a bundled server is enabled, which tools are allowed, and default approval behavior.
My rule is straightforward:
If changing the server should affect every Codex project, use user scope. If the server exists because of one repository, use project scope. If the server is part of a packaged extension, keep its configuration with the plugin unless you have a reason to override it.
Don’t solve a scope problem by copying the same MCP entry into every file you can find. That makes the next failure harder to explain.
Leave the reader with a repeatable debug checklis
When MCP breaks, I don’t start by rewriting config.toml. I start by locating the first layer that fails.
- Run
codex mcp listand confirm the server entry exists. - Run
codex mcp get <name>and inspect what Codex actually has configured. - Confirm whether the entry belongs in user or trusted project scope.
- For stdio, verify the executable, arguments, working directory, and environment.
- For HTTP, verify the endpoint before debugging authentication.
- If OAuth is required, use
codex mcp login <name>. - Start a session and run
/mcp. - Separate “server isn’t visible” from “tool is visible but the call is denied.”
- Only then change approval, tool allowlists, or timeout settings.
- Re-run the same check after each change.
I would keep that sequence next to the terminal. It turns MCP debugging from configuration archaeology into a small set of observable decisions.
The useful result isn’t merely “the server connects.” It’s knowing which layer made it fail when it doesn’t. Once you have that boundary, the next configuration change has a reason behind it.
And that’s the part worth keeping.






