# Set up MCP servers in Codex

URL: https://developertoolkit.ai/en/codex/quick-start/mcp-setup/
Description: Connect Codex to MCP servers with codex mcp add and config.toml, scope their tools and secrets, and prove each server works before trusting its output.

Codex connects to MCP (Model Context Protocol) servers declared as `[mcp_servers.<name>]` tables in `config.toml`: stdio servers that Codex launches as a local process, and streamable HTTP servers reached by URL with a bearer token or OAuth. The `codex mcp` command adds, inspects, and authenticates servers, and every server should pass a smoke test before its output is trusted.

This page is for the developer setting up Codex on their own machine, and for the tech lead who wants the same servers on every teammate's machine. You asked Codex to fix the top Sentry error and it answered that it cannot reach external services. You asked for a Next.js middleware and got an API that changed two majors ago. Both are context problems, and connecting the right MCP server takes one command. The work that takes longer is scoping what each server may do and proving that it actually ran.

## What a working Codex MCP setup gives you

- A working first server (Context7), checked with `codex mcp list`, `/mcp`, and a scripted smoke test.
- Copy-paste config for the five servers most teams start with: Context7, Playwright, GitHub, Figma, and Sentry.
- Per-server tool allow lists, approval modes, and output limits, so a server cannot flood the context or write without asking.
- Secrets kept in environment variables and out of any file you commit.
- A `codex exec` check that fails CI when a required server does not start.

Before you start, finish [Codex configuration](/en/codex/quick-start/configuration/): this page assumes a working `~/.codex/config.toml` and a signed-in Codex CLI (checked against `codex-cli 0.157.1`; the `codex mcp` subcommands and flags used on this page were checked again against 0.162.1 on 2026-10-12).

## How does Codex connect to an MCP server?

Codex supports two transports. The fields you set decide which one a table uses, and Codex rejects fields that belong to the other transport when it loads the config.

| Transport | Codex starts it by | Required key | Auth options | Typical servers |
| --- | --- | --- | --- | --- |
| stdio | Running `command` with `args` as a child process | `command` | `env_vars` forwarded from your shell, `env` | Context7 (npm), Playwright, Chrome DevTools, local GitHub in Docker |
| Streamable HTTP | Connecting to `url` | `url` | `bearer_token_env_var`, `env_http_headers`, OAuth via `codex mcp login` | GitHub remote, Figma, Sentry, Context7 remote |

Where the config lives matters more than it first appears:

- **`~/.codex/config.toml`** (under `CODEX_HOME`) is your personal file. `codex mcp add` writes here and only here (0.157.1). The CLI, the IDE extension, and the Codex desktop app read it when they share the same `CODEX_HOME`.
- **`.codex/config.toml` in the repository** is the team file. Codex loads it only when you have marked the project as trusted; in an untrusted checkout it is loaded but disabled.

Codex 0.157.1 still talks to servers with the pre-2026-07-28 MCP protocol: support for the 2026-07-28 spec is behind the `mcp_2026_07_28` feature flag, which `codex features list` shows as under development and off. The [MCP overview](/en/ecosystem/mcp/) covers what changed in the spec.

## Add your first server and prove it works

Context7 is the best first server: it needs no account, and the result is visible in one prompt.

1. Add the server. Everything after `--` is the command Codex runs:

   ```bash
   codex mcp add context7 -- npx -y @upstash/context7-mcp
   ```

   Codex prints `Added global MCP server 'context7'.` and writes this table to `~/.codex/config.toml`:

   ```toml
   [mcp_servers.context7]
   command = "npx"
   args = ["-y", "@upstash/context7-mcp"]
   ```

2. Confirm Codex parsed it. `codex mcp list` shows every configured server; `codex mcp get context7 --json` prints the effective settings for one:

   ```bash
   codex mcp list
   codex mcp get context7 --json
   ```

3. Start Codex and type `/mcp` in the composer. It lists the configured MCP tools; `/mcp verbose` adds details. If `context7` shows no tools, go to [When Codex MCP servers break](#when-codex-mcp-servers-break).

4. Paste the prompt below and check that the answer names a Context7 library ID.

   **Copy-paste prompt: prove Context7 is live**

   ```text
   Use the context7 MCP server to look up how Next.js middleware redirects an
   unauthenticated request to /login. Before writing any code, tell me the exact
   Context7 library ID and version you resolved, and quote the one doc snippet you
   are relying on. Then write middleware.ts for that version and nothing else.
   ```

The prompt asks for the library ID and a quoted snippet on purpose. An answer without them came from training data, not from the server, and that is the failure you are trying to rule out.

## Configure the servers most teams start with

Each block below is either a `codex mcp add` command or the equivalent `config.toml` table; use one or the other. Package versions were checked on npm on 2026-09-26: `@upstash/context7-mcp` 4.1.1 and `@playwright/mcp` 0.0.82.

**Context7**

Version-specific library docs. The API key is optional and raises rate limits. To use the remote server with a key, read it from an environment variable:

```toml
[mcp_servers.context7]
url = "https://mcp.context7.com/mcp"
bearer_token_env_var = "CONTEXT7_API_KEY"
```

More prompts and the skills-based alternative: [Context7](/en/ecosystem/mcp/context7/).

**Playwright**

Browser automation over the accessibility tree, for reproducing UI bugs and checking a flow after a change.

```bash
codex mcp add playwright -- npx @playwright/mcp@latest --isolated
```

`--isolated` keeps the browser profile in memory. Without it, two Codex sessions in parallel worktrees fight over one profile. Playwright's README now suggests that coding agents may do better with its CLI plus skills, because accessibility snapshots cost context; see [Playwright MCP](/en/ecosystem/mcp/playwright-mcp/).

**GitHub**

GitHub's official server, for PRs, issues, and Actions logs. The remote server takes a fine-grained personal access token from an environment variable:

```bash
export GITHUB_PAT_TOKEN=github_pat_...   # in your shell profile, never in config.toml
codex mcp add github --url https://api.githubcopilot.com/mcp/ --bearer-token-env-var GITHUB_PAT_TOKEN
```

To run it locally in Docker instead, forward the variable name the image expects, `GITHUB_PERSONAL_ACCESS_TOKEN`:

```toml
[mcp_servers.github]
command = "docker"
args = ["run", "-i", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", "ghcr.io/github/github-mcp-server"]
env_vars = ["GITHUB_PERSONAL_ACCESS_TOKEN"]
```

The npm package `@modelcontextprotocol/server-github` is deprecated on npm; do not use it. Toolset scoping and read-only URLs are on [GitHub MCP](/en/ecosystem/mcp/github-mcp/).

**Figma**

Design context, screenshots, and Code Connect mappings from Figma's remote server, with OAuth:

```bash
codex mcp add figma --url https://mcp.figma.com/mcp
```

When `codex mcp add --url` detects OAuth support on the server, it starts the browser login immediately. If it prints "may or may not require login", run `codex mcp login figma`. Figma limits Starter plans, and View or Collab seats on paid plans, to "up to 6 tool calls per month" (Figma's MCP guide, read September 2026), so give a Dev or Full seat to whoever runs design-to-code. See [Figma and design system MCP](/en/ecosystem/mcp/design-tools-mcp/).

**Sentry**

Issues, events, and traces from Sentry's hosted server, with OAuth. Codex speaks streamable HTTP natively, so you do not need an `mcp-remote` bridge:

```bash
codex mcp add sentry --url https://mcp.sentry.dev/mcp
codex mcp login sentry   # only if the add step did not start the login
```

If you authenticate with a token instead of OAuth, Sentry expects the header `Authorization: Sentry-Bearer <token>`, not `Bearer`. Set it with `env_http_headers`, as shown in [Keep secrets out of config.toml](#keep-secrets-out-of-configtoml). The stdio package `@sentry/mcp-server` (0.42.0) targets self-hosted Sentry.

### Add a server that needs a pre-registered OAuth client

Some OAuth servers hand you a client ID and a client secret instead of registering Codex automatically. `codex mcp add` takes both (`codex mcp add --help` in 0.162.1, checked 2026-10-12):

```bash
read -rs MCP_CLIENT_SECRET   # paste the secret from the server's admin page, then press Enter
codex mcp add my-server --url https://mcp.example.com/mcp \
  --oauth-client-id CLIENT_ID \
  --oauth-client-secret "$MCP_CLIENT_SECRET"
```

Replace `CLIENT_ID` with the identifier the server issued. `read -rs` takes the secret without echoing it, so it never lands in your shell history. `--oauth-client-secret` still takes the value as a command-line argument (`codex mcp add --help`), so other processes on the machine can see it in the process list while the command runs. The flag arrived in 0.158.0 (2026-09-28), documented in the release notes as being for pre-registered OAuth clients. The same help lists `--oauth-client-registration` (`auto`, `cimd` or `dcr`, applied to the immediate login only) and `--oauth-resource` (an OAuth resource parameter for that login).

To authenticate again later, run `codex mcp login NAME` in a terminal, which also accepts `--no-browser`, `--scopes SCOPE,SCOPE` and `--oauth-client-registration`. Inside a session, `/mcp login <name>` starts the login (0.161.0, 2026-10-07); the 0.162.1 command list describes `/mcp` as `list MCP tools; use /mcp verbose or /mcp login <name>`.

## Control what each server is allowed to do

Every server you add puts its tools in reach of the model. Codex has searched MCP tools on demand by default since 0.143.0 (2026-07-08), so an idle server costs little. A server's tool output does land in context, though, and a write tool runs with whatever permissions its token has. Scope both per server:

```toml
[mcp_servers.github]
url = "https://api.githubcopilot.com/mcp/"
bearer_token_env_var = "GITHUB_PAT_TOKEN"
enabled_tools = ["pull_request_read", "actions_list", "get_job_logs", "create_pull_request"]
default_tools_approval_mode = "prompt"   # ask before every tool call from this server
startup_timeout_sec = 30                 # default 30
tool_timeout_sec = 120                   # default 300

[mcp_servers.github.tools.get_job_logs]
approval_mode = "auto"                   # back to the default: no prompt if the server marks the tool read-only
output_token_limit = 8000                # cap one huge CI log
```

- `enabled_tools` is an allow list; `disabled_tools` removes tools after it is applied.
- `default_tools_approval_mode` sets the server-wide policy, and `[mcp_servers.<name>.tools.<tool>]` overrides it for one tool. The four values, as `codex-rs/core/src/mcp_tool_call.rs` implements them in 0.157.1, key off the annotations the server publishes for each tool:
  - `auto` (the default) skips the prompt for tools the server marks read-only and asks for tools marked destructive or open-world, or not annotated at all.
  - `prompt` asks before every call.
  - `writes` asks before every call to a tool that is not marked read-only.
  - `approve` never asks.

  These apply when Codex can ask. With `approval_policy = "never"` (for example in unattended `codex exec` runs), a call that would prompt is rejected instead, unless the sandbox is full access, in which case it runs without asking (checked in 0.157.1). For CI, give the servers a job depends on `auto` or an explicit per-tool `approve`.
- `output_token_limit` caps a single tool's output, which is the setting that stops a log dump from pushing your task out of context. [Reducing MCP token cost](/en/ecosystem/mcp/reducing-mcp-token-cost/) covers the wider trade-off.
- `enabled = false` turns a server off without deleting its table. `codex mcp remove <name>` deletes it.

Tool names differ per server and version. Read the real ones from `/mcp verbose` before you write an allow list, because a misspelled name silently removes that tool.

## Keep secrets out of config.toml

Codex offers three ways to pass a secret without writing it into the file:

```toml
# stdio: forward named variables from your shell to the server process
[mcp_servers.github_local]
command = "docker"
args = ["run", "-i", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", "ghcr.io/github/github-mcp-server"]
env_vars = ["GITHUB_PERSONAL_ACCESS_TOKEN"]

# HTTP: send the value of an env var as `Authorization: Bearer <value>`
[mcp_servers.context7]
url = "https://mcp.context7.com/mcp"
bearer_token_env_var = "CONTEXT7_API_KEY"

# HTTP: any header, filled from an env var (here SENTRY_AUTH_HEADER="Sentry-Bearer sntrys_...")
[mcp_servers.sentry_token]
url = "https://mcp.sentry.dev/mcp"
env_http_headers = { "Authorization" = "SENTRY_AUTH_HEADER" }
```

Three things to avoid. `codex mcp add --env KEY=VALUE` writes the value itself into `config.toml`, so use it only for non-secret settings. A literal `bearer_token` key is rejected for HTTP servers (0.157.1), so old snippets that set one fail to load. And OAuth credentials from `codex mcp login` go to the OS keyring when one is available, otherwise to a file under `CODEX_HOME`, which you should treat like a private key. [Securing MCP servers](/en/ecosystem/mcp/mcp-security/) covers token scoping.

## Share one MCP setup across the team

For a team, commit the servers every teammate needs to `.codex/config.toml` in the repository, with only environment-variable names in it, and document the variables in `AGENTS.md` or the README. Each developer then trusts the project once and exports their own tokens. Two shortcuts help with migration: `/import` in the Codex TUI brings MCP servers, settings, and plugins over from Claude Code and Cursor (since 0.145.0), and `codex plugin list` shows vendor plugins that bundle a server's config with skills.

Configuration is the same idea in all three tools, but the files differ: Claude Code uses `.mcp.json` and `claude mcp add`, and Cursor uses `mcp.json`. Server packages and URLs are identical. See [MCP setup for Claude Code](/en/claude-code/quick-start/mcp-setup/) and [MCP setup for Cursor](/en/cursor-ide/quick-start/mcp-setup/).

## How do you verify MCP-backed work without reading every line?

An MCP server changes what the agent knows, not how you check the result. Verify three things, in this order.

**1. The server is up.** For unattended runs, mark the servers a job depends on as required. With `required = true`, `codex exec` exits with an error when that server fails to initialize, instead of carrying on without it:

```toml
[mcp_servers.context7]
command = "npx"
args = ["-y", "@upstash/context7-mcp"]
required = true
```

**2. The tool was actually called.** `codex exec --json` prints one event per line, and each MCP call appears as an `item.completed` event whose item has `type` `mcp_tool_call`, with `server`, `tool`, and `status` (`completed` or `failed`). This smoke test fails when no Context7 call completed:

```bash
codex exec --json --ephemeral \
  "Use the context7 MCP server to resolve the library ID for React. Reply with the ID only." \
  | jq -r 'select(.type=="item.completed" and .item.type=="mcp_tool_call")
           | [.item.server, .item.tool, .item.status] | @tsv' \
  | grep -q $'^context7\t.*\tcompleted$' && echo "context7 OK" || { echo "context7 NOT CALLED"; false; }
```

The final `false` fails a CI step without closing an interactive shell. Run it after onboarding a teammate, after a Codex upgrade, and in CI before any job that relies on the server. [Non-interactive Codex](/en/codex/automate/non-interactive/) covers `codex exec` in pipelines.

**3. The change is correct.** Docs from Context7 or a stack trace from Sentry do not make the code right. The gate is still your tests, type checker, and linter, plus a failing test written before the fix. Ask for that explicitly:

**Copy-paste prompt: Sentry error to tested fix**

```text
Use the sentry MCP server to find the unresolved issue with the most events in
the last 24 hours for project web-frontend. Show me the issue ID, the top three
stack frames, and the local files they map to. Then write a failing test that
reproduces it, make the smallest fix that turns the test green, and run the full
test suite. Do not change code outside the files the stack trace points to.
```

**Copy-paste prompt: MCP health check**

```text
For every MCP server you have access to, call one read-only tool with a trivial
argument. Report a table with columns: server, tool called, result (ok / error),
and the error text if any. Do not call any tool that creates, updates, or deletes
anything.
```

The reviewer then checks the evidence — the issue ID, the failing-then-passing test, the green suite — rather than reading the diff line by line.

## When Codex MCP servers break

| Symptom | Likely cause | Recovery |
| --- | --- | --- |
| Codex reports a config error after you edit a table | A key from the other transport (for example `env_vars` on a `url` server, or a literal `bearer_token`), or a value of the wrong type | Fix the key; `codex mcp get <name> --json` should parse cleanly |
| A setting has no effect | A misspelled or unknown key, which Codex 0.157.1 ignores silently (for example `headers` instead of `http_headers`) | For transport keys (`url`, `bearer_token_env_var`, `env_http_headers`, `http_headers`), timeouts and tool lists, check `codex mcp get <name> --json`; that output does not show `required`, approval modes or `output_token_limit`, so compare those names against the Codex config reference |
| Server listed but shows no tools in `/mcp` | The command failed: `npx` not on `PATH`, Docker not running, a missing env var | Run the exact `command` and `args` in a terminal; export the variables named in `env_vars` before starting Codex |
| "startup timeout" on first run | `npx` or `docker pull` downloads the package inside the 30-second default | Pre-fetch once (`npx -y @upstash/context7-mcp --help`), or raise `startup_timeout_sec` |
| Tool calls time out on large pages or logs | Slow tool, 300-second default | Raise `tool_timeout_sec` for that server only; cap output with `output_token_limit` |
| Works in the terminal, not in the IDE extension or app | Different `CODEX_HOME`, or a variable exported only in your interactive shell | Point both at one `CODEX_HOME`; put exports in a file your GUI session also loads, then restart the app |
| Team servers missing in a fresh clone | The project is not trusted, so `.codex/config.toml` is loaded but disabled | Trust the project when Codex asks, then restart the session |
| OAuth login never completes | Corporate proxy or a firewall blocks the local callback | Set `mcp_oauth_callback_port` to an allowed port, or run `codex mcp login <name> --no-browser` and paste the callback URL |
| Two parallel sessions break Playwright | Both use one persistent browser profile | Add `--isolated` to the Playwright args |

The [MCP connection issues](/en/ecosystem/mcp/mcp-connection-issues/) page covers transport-level debugging that applies to every client, and [Codex error recovery](/en/codex/quick-start/error-recovery/) covers what to do when a task goes wrong midway.

## Where to go next with MCP in Codex

- [Codex error recovery](/en/codex/quick-start/error-recovery/) — The next quick-start step: undo wrong edits, sandbox failures, and stuck tasks.
- [Connect Codex to GitHub](/en/codex/quick-start/github-integration/) — PR reviews and @codex mentions, which pair with the GitHub MCP server.
- [MCP server catalogue](/en/ecosystem/mcp/) — Verified install commands for every major server, per tool.
- [Securing MCP servers](/en/ecosystem/mcp/mcp-security/) — Token scopes, prompt injection through tool output, and server vetting.
