# MCP in Codex

Codex connects MCP servers through [mcp_servers.<id>] tables in config.toml, user-wide or per trusted project, over stdio or streamable HTTP, with per-server tool allow lists and approval modes.

Source: https://ai-sw-factory.mellicci.dev/fundamentals/mcp/codex

This page covers Codex CLI; the desktop app and IDE extension share the same MCP configuration.

## At a glance

| | Codex |
|---|---|
| Term | MCP server, one `[mcp_servers.<id>]` table in `config.toml` |
| Configured in | `~/.codex/config.toml` (user) or `.codex/config.toml` (project, trusted projects only); `codex mcp add` writes the user file |
| Loads / runs | Servers start with Codex; `startup_timeout_sec` defaults to 10 s, and `required = true` makes a failed start fatal |
| Scope & precedence | CLI flags, then project files (closest wins), profile, user, cloud-managed, system. Untrusted projects skip `.codex/` layers |

## Build the scenario

Scope: the `github` server lives in the project's `.codex/config.toml`, so the whole team gets it. The token stays in `GITHUB_MCP_TOKEN`; the file only names the variable.

| Step | What you write or run | Where |
|---|---|---|
| 1 | Export the token | Your shell: `export GITHUB_MCP_TOKEN=...` |
| 2 | Define the server (or run `codex mcp add`, below) | `ticket-service/.codex/config.toml` |
| 3 | Confirm it is connected | `codex mcp list`, then `/mcp` in the TUI |
| 4 | Ask for the work | `codex "Implement issue #42"` |

`.codex/config.toml`:

```toml
[mcp_servers.github]
url = "https://api.githubcopilot.com/mcp/"
bearer_token_env_var = "GITHUB_MCP_TOKEN"
default_tools_approval_mode = "writes"
startup_timeout_sec = 20
```

`codex mcp add github --url https://api.githubcopilot.com/mcp/ --bearer-token-env-var GITHUB_MCP_TOKEN` registers the same server, but in `~/.codex/config.toml` (user scope). It has no flag for approval mode or timeouts; edit the file for those.

Run `codex mcp list` (add `--json` for scripts) to see the entry, and `/mcp` inside the TUI to see servers and tools for the session (`/mcp verbose` adds diagnostics). When you ask Codex to implement issue #42, the model picks the server's issue tools; with `writes`, calls to tools marked read-only run without a prompt, while other tools prompt you first.

The documentation doesn't show a read-only or issues-only switch for GitHub's server. `enabled_tools` can restrict it, but you need the server's actual tool names (check `/mcp`). The URL is GitHub's remote endpoint, not from the Codex docs.

## Specifics

### Config files and scopes

| Location | Scope | Note |
|---|---|---|
| `~/.codex/config.toml` | User, all projects | Written by `codex mcp add` |
| `.codex/config.toml` | Project | Trusted projects only; closest to the working directory wins |
| `--config` / `-c` | One run | Highest precedence |
| `/etc/codex/config.toml` | System | Lowest of the files |
| `plugins.<plugin>.mcp_servers.<server>` | Plugin-bundled servers | You control only on/off and tool policy |

The desktop app and IDE extension read the same files. The documentation doesn't say how a same-named server merges across layers.

### Local and remote servers

| | stdio | Streamable HTTP |
|---|---|---|
| Required key | `command` | `url` |
| Other keys | `args`, `env`, `env_vars`, `cwd` | `auth`, `bearer_token_env_var`, `http_headers`, `env_http_headers`, `http_headers_helper` |
| Add command | `codex mcp add <name> --env K=V -- <command...>` | `codex mcp add <name> --url <url>` |
| Timeouts | `startup_timeout_sec` (10), `tool_timeout_sec` (60) | Same |

Other per-server keys: `enabled`, `required`, `tools.<tool>.output_token_limit`. `mcp_optional_startup_grace_ms` (default 1000) sets how long Codex waits for optional servers when building the first tool list.

### Authentication and secrets

| Method | Setup | Note |
|---|---|---|
| Bearer token from environment | `bearer_token_env_var = "GITHUB_MCP_TOKEN"` | Sent in `Authorization`; the file holds only the variable name |
| Header from environment | `env_http_headers = { "X-Name" = "ENV_VAR" }` | For non-bearer headers |
| Static header | `http_headers` | Value sits in the file; avoid for secrets |
| Stdio secrets | `env_vars = ["NAME"]` forwards from Codex's environment; `env` sets fixed values | |
| OAuth | `codex mcp login <name>` (`--scopes`), `codex mcp logout <name>` | Streamable HTTP servers that support OAuth; `auth = "oauth"` is the default |

Configured bearer tokens and headers are tried before `auth`. If nothing resolves, Codex connects without authentication. `mcp_oauth_credentials_store` picks `auto`, `file` or `keyring` for OAuth credentials.

### Tool allow and deny

| Key | Effect |
|---|---|
| `enabled_tools` | Allow list of tool names |
| `disabled_tools` | Deny list, applied after `enabled_tools` |
| `default_tools_approval_mode` | `auto`, `prompt`, `writes` or `approve`; `writes` prompts for tools not marked read-only |
| `tools.<tool>.approval_mode` | Same values, overrides the server default for one tool |

Independent of these modes, destructive MCP tool calls always require approval when the tool advertises a destructive annotation, unless it also advertises a read annotation. `approval_policy.granular.mcp_elicitations = true` lets MCP elicitation prompts surface instead of being auto-rejected. Under auto-review, side-effecting MCP calls are the ones the reviewer evaluates.

### Context cost and inspection

| Need | How |
|---|---|
| See servers and tools | `/mcp`; `/mcp verbose` for diagnostics; `codex mcp list` / `get <name>` outside the TUI |
| Shrink the tool list | `enabled_tools` / `disabled_tools`, or `enabled = false` |
| Cap one tool's output | `tools.<tool>.output_token_limit` |

The documentation doesn't state how many tokens tool definitions use.

Codex can no longer run as an MCP server: `codex mcp-server` was removed in favor of the app server, and `mcp-server.md` says the app server isn't an MCP server.

## Gotchas

- Project `.codex/config.toml` is ignored in untrusted projects; if `/mcp` doesn't show `github`, check trust first.
- `codex mcp add` writes the user file, not the project file. Hand-edit `.codex/config.toml` for project scope.
- The documentation says that when no credential source resolves, Codex connects without authentication. An unset `GITHUB_MCP_TOKEN` may therefore show up as 401 errors at tool-call time, not as a startup failure; check `/mcp verbose`. The docs do not spell out this exact case.
- `writes` relies on the server marking tools read-only. A server that doesn't annotate its tools gets prompts for every call.
- `disabled_tools` wins over `enabled_tools` when both name a tool.
