One MCP server, three configs: Claude Code, Cursor and Codex
In short: all three tools speak MCP, but each keeps its servers in a different file. Claude Code uses .mcp.json at the repo root, Cursor uses .cursor/mcp.json, and Codex uses [mcp_servers.<name>] tables in .codex/config.toml. The JSON files look alike, but two details break copy-paste: Claude Code needs "type": "http" on remote servers, and every tool writes environment variables differently. Below is the same two-server setup written for each tool, the differences that matter, and how to check that each one loaded.
Where each tool looks
| Claude Code | Cursor | Codex | |
|---|---|---|---|
| Project file (commit it) | .mcp.json at the project root | .cursor/mcp.json | .codex/config.toml, trusted projects only |
| Personal file | ~/.claude.json (user scope, and local scope per project) | ~/.cursor/mcp.json | ~/.codex/config.toml |
| Format | JSON, mcpServers | JSON, mcpServers | TOML, [mcp_servers.<name>] |
| Env variable in a value | ${VAR} or ${VAR:-default} | ${env:VAR} | Name the variable in a field such as bearer_token_env_var |
| See what loaded | claude mcp list, /mcp | Customize, MCP Logs in the Output panel | codex mcp list, /mcp |
Sources: the Claude Code MCP docs, the Cursor MCP docs and the Codex MCP docs, all checked on 2026-10-29. Some third-party guides still say Codex has no project-level MCP config. The current Codex docs say it does: put the tables in .codex/config.toml in the repo, and Codex loads them once you trust the project.
The same setup, written three ways
The example has two servers: Context7, a local stdio server that the Codex docs use as their example (npx -y @upstash/context7-mcp), and team-docs, a stand-in for a remote HTTP server that needs a bearer token. mcp.example.com and TEAM_DOCS_TOKEN are placeholders; swap in your server's URL and auth. The token value never appears in a file. Each developer sets TEAM_DOCS_TOKEN in their own shell.
We checked that all three files parse (JSON and TOML). We didn't run them inside the three tools, so test them in yours before you rely on them.
Claude Code: .mcp.json
{
"mcpServers": {
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"]
},
"team-docs": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"headers": {
"Authorization": "Bearer ${TEAM_DOCS_TOKEN}"
}
}
}
}
- An entry with a
urlneeds atype("http","sse"or"ws"). Claude Code reads an entry without one as a stdio server, so it skips aurl-only entry and reportshas a "url" but no "type". The docs accept"streamable-http"as another name for"http". ${VAR}and${VAR:-default}expand incommand,args,env,urlandheaders. If a variable is unset and has no default, the config still loads,claude mcp listshows a missing-variable warning, and the literal${VAR}text is used.- In a remote server's
urlandheaders, some credential variables always read as empty, includingANTHROPIC_API_KEY,ANTHROPIC_AUTH_TOKENandNPM_TOKEN. That stops a repo's.mcp.jsonfrom sending your own credentials to a server it names. Use a variable name of your own, likeTEAM_DOCS_TOKEN. - Claude Code sets
CLAUDE_PROJECT_DIRinside a stdio server's environment. To use it incommandorargsof.mcp.json, write${CLAUDE_PROJECT_DIR:-.}, because the variable isn't set in Claude Code's own environment.
Cursor: .cursor/mcp.json
{
"mcpServers": {
"context7": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"]
},
"team-docs": {
"url": "https://mcp.example.com/mcp",
"headers": {
"Authorization": "Bearer ${env:TEAM_DOCS_TOKEN}"
}
}
}
}
- Variables use
${env:NAME}. Cursor also offers${userHome},${workspaceFolder}(the folder that contains.cursor/mcp.json),${workspaceFolderBasename}and${/}. They resolve incommand,args,env,urlandheaders. - Remote servers are written as
urlplus optionalheaders, as in Cursor's own examples. Cursor's stdio field table lists"type": "stdio"as required, so the example includes it. - Stdio servers can load more variables from a file with
envFile(for example"${workspaceFolder}/.env"). Remote servers can't useenvFile.
Codex: .codex/config.toml
# .codex/config.toml (loaded only in a trusted project)
[mcp_servers.context7]
command = "npx"
args = ["-y", "@upstash/context7-mcp"]
[mcp_servers.team-docs]
url = "https://mcp.example.com/mcp"
bearer_token_env_var = "TEAM_DOCS_TOKEN"
- Codex doesn't use
${...}placeholders in these fields. It takes the variable's name instead:bearer_token_env_varfor anAuthorizationbearer token,env_http_headersfor other headers read from the environment, andenv_varsto forward named variables to a stdio server.envsets fixed values. - Codex loads project
.codex/layers only when you trust the project. In an untrusted project it skips them, including project config, hooks and rules. With several.codex/config.tomlfiles, the one closest to your working directory wins (config basics). - The ChatGPT desktop app, Codex CLI and IDE extension share this configuration, so setting it up once covers all three.
- Per-server controls that the other two don't put in this file:
enabled_toolsanddisabled_tools,default_tools_approval_mode(auto,prompt,writesorapprove),startup_timeout_sec(default 10) andtool_timeout_sec(default 60).
Adding servers from the command line
# Claude Code: write the server into .mcp.json for the team
claude mcp add --transport http --scope project team-docs https://mcp.example.com/mcp
claude mcp add --transport stdio --scope project context7 -- npx -y @upstash/context7-mcp
# Codex: add a stdio server to your Codex config
codex mcp add context7 -- npx -y @upstash/context7-mcp
# Check what loaded
claude mcp list
codex mcp list
In claude mcp add, everything after -- is the server's own command, so its flags aren't read as Claude's. Without --scope, Claude Code uses local scope: the server goes into your entry for this project inside ~/.claude.json, not into .mcp.json, and your teammates won't see it. For a remote server in Codex, the docs show codex mcp add <name> --url <url>, and codex mcp login <name> starts an OAuth sign-in. Cursor adds servers through Customize or by editing mcp.json.
Which definition wins
- Claude Code: local scope beats project scope, which beats user scope. Duplicates are matched by name, and the whole entry from the winning scope is used, with no field-by-field merge. A personal local copy of
team-docstherefore hides the team's version completely. - Codex: CLI flags first, then project
.codex/config.tomlfiles (closest first), then a selected profile, then~/.codex/config.toml. - Cursor: the docs list the project and global files but don't say which wins when both define the same server name. Give team servers names you won't reuse in your global file.
Approvals and trust
A committed MCP config runs commands on every machine that opens the repo, so each tool puts a gate in front of it:
- Claude Code asks you to approve servers from
.mcp.jsonin an interactive session. Until then,claude mcp listshows them as pending approval. A repo can't approve its own servers: approval settings committed to.claude/settings.jsonare ignored until you trust the folder.claude mcp reset-project-choicesresets your choices. Inclaude -pruns and Agent SDK sessions there's no prompt, and Claude Code loads project servers without asking, so add a server todisabledMcpjsonServersor use--strict-mcp-configif you don't want it there. - Codex ignores
.codex/config.tomluntil you trust the project. - Cursor asks for approval before it runs an MCP tool by default, and MCP follows the same run modes as terminal commands.
Read a server's source before you approve it, and give it a narrowly scoped token. If you're also keeping agents away from secrets files, our .env guide covers the file-access side for all three tools.
Keeping three files in sync
There's no shared MCP file that all three tools read, so you maintain three. A few habits keep that manageable:
- Use the same server names in every file, so a rule like "use
team-docsfor internal API questions" inAGENTS.mdorCLAUDE.mdmeans the same thing in each tool. - Commit references, never values. Keep a
.env.exampleor a short README table that lists the variables each developer needs. - When you change a server, change all three files in the same commit. A short note in your instruction file helps, the same way we suggest pointing
CLAUDE.mdatAGENTS.mdin one source of truth. - Third-party sync tools exist. If you use one, check its output against the formats above, especially
"type"on remote servers and the variable syntax.
Checklist
- Write
.mcp.json,.cursor/mcp.jsonand.codex/config.tomlwith the same server names. - Add
"type": "http"to every remote server in.mcp.json. - Replace every secret with a variable:
${VAR},${env:VAR}, or a Codex*_env_varfield. - Run
claudein the repo and approve the project servers, then checkclaude mcp list. - Trust the project in Codex, then check
codex mcp listor/mcp. - In Cursor, check the servers are on in Customize and look at MCP Logs in the Output panel if one fails.
FAQ
Where does each tool keep project MCP servers?
Claude Code reads .mcp.json at the project root. Cursor reads .cursor/mcp.json in the project. Codex reads [mcp_servers.<name>] tables in .codex/config.toml, but only in a project you've trusted. The personal versions are ~/.claude.json, ~/.cursor/mcp.json and ~/.codex/config.toml.
Can I copy Cursor's mcp.json into Claude Code's .mcp.json?
Not as is. Both use an mcpServers object, but Claude Code treats an entry with no "type" as a stdio server, so a remote server with only a "url" is skipped with an error until you add "type": "http". Environment variables also differ: Cursor writes ${env:NAME}, Claude Code writes ${NAME} or ${NAME:-default}.
Does Codex support project-level MCP servers?
Yes. Current Codex docs let you add [mcp_servers.<name>] tables to .codex/config.toml in the repo. Codex loads project .codex/ layers only for trusted projects, and the config closest to your working directory wins. The ChatGPT desktop app, Codex CLI and IDE extension share this configuration.
How do I keep API keys out of a committed MCP config?
Commit only variable references. In Claude Code use ${TOKEN} in .mcp.json, in Cursor use ${env:TOKEN}, and in Codex name the variable with bearer_token_env_var, env_http_headers or env_vars instead of writing the value. Each developer sets the variable in their own shell. Codex stores an OAuth client secret in config.toml, so keep that kind of setup in your personal ~/.codex/config.toml.
Why does my project MCP server not show up in Claude Code?
Claude Code asks you to approve project servers from .mcp.json in an interactive session. Until you do, claude mcp list shows the server as pending approval. Run claude in the folder and accept the trust dialog and the server prompt. To ask again later, run claude mcp reset-project-choices.
Sources
- Claude Code docs: Connect Claude Code to tools via MCP (scopes, .mcp.json, variable expansion)
- Cursor docs: Model Context Protocol (mcp.json, config interpolation)
- Codex docs (ChatGPT Learn): Model Context Protocol (config.toml, mcp_servers)
- Codex docs (ChatGPT Learn): Config basics (project config, trusted projects)