You finally found an MCP server that does exactly what you need — a database client, a docs fetcher, a browser driver — and the README shows a config snippet for Claude Desktop. But you're in Codex, or Gemini CLI, or one of the half-dozen other agentic CLIs people actually use day to day. None of them store config the same way. Some use JSON, one uses TOML, the HTTP field is called url here and httpUrl there, and "scope" means three different things depending on the tool.
Model Context Protocol is supposed to be the universal plug between agents and external tools. The protocol is universal. The configuration is not. This is a per-tool reference for adding an MCP server to the five CLIs I see most: Claude Code, Codex CLI, Gemini CLI, Qwen Code, and Oh My Pi.
The two things every config comes down to
Before the per-tool sections, two concepts carry across all of them.
Transport. An MCP server is reached either as a local subprocess (stdio — the CLI launches a command and talks over stdin/stdout) or over the network (streamable HTTP, or the deprecated SSE). Rule of thumb: local tool → stdio; hosted/remote tool → HTTP. SSE is legacy; don't pick it for anything new.
Scope. Every CLI distinguishes between config that lives in your home directory (personal, available everywhere) and config that lives in the repo (committable, shared with your team). The exact names differ, but the mental model is the same: secrets and personal tooling go in the user/global file; team-shared servers go in the project file.
The canonical JSON key is mcpServers in four of the five tools. Codex is the odd one out — it uses TOML [mcp_servers.<name>] tables.
Claude Code
The CLI does the editing for you. For a local stdio server, the -- separator is load-bearing: everything before it is Claude's flags, everything after is the server command.
# stdio
claude mcp add my-server -- npx -y @scope/some-mcp-server
# remote HTTP
claude mcp add --transport http my-server https://example.com/mcp
# remote SSE (deprecated)
claude mcp add --transport sse my-server https://example.com/sse
Claude Code has three scopes, set with --scope:
local(default) — private to you, stored in~/.claude.jsonunder the project pathproject— written to.mcp.jsonat the repo root, committed and shareduser— stored in~/.claude.json, available across all your projects
(Older versions called these "project" and "global" — that's the same idea under new names.) When a project .mcp.json server first appears, Claude prompts for approval and shows it as ⏸ Pending approval in claude mcp list; reset those choices with claude mcp reset-project-choices.
A committed .mcp.json looks like this, and it supports env-var expansion so you never hardcode secrets:
{
"mcpServers": {
"my-server": {
"command": "npx",
"args": ["-y", "@scope/some-mcp-server"],
"env": { "API_KEY": "${MY_API_KEY}" }
},
"remote": {
"type": "streamable-http",
"url": "https://example.com/mcp",
"headers": { "Authorization": "Bearer ${TOKEN:-}" }
}
}
}
${VAR} and ${VAR:-default} work in command, args, env, url, and headers. A required variable with no default and no value will fail config parsing — that's intentional. Supported type values: stdio, http (alias streamable-http), sse, and ws.
Tools surface as mcp__<servername>__<toolname>; plugin-bundled servers as mcp__plugin_<plugin-name>_<server-name>__<tool-name>. Prompts become slash commands like /mcp__servername__promptname. Names get normalized — anything outside [A-Za-z0-9_-] becomes _.
Management: claude mcp list, claude mcp get <name>, claude mcp remove <name>, /mcp inside a session (status + OAuth), claude mcp add-json <name> '<json>' to paste raw JSON, and claude mcp serve to run Claude Code itself as a stdio MCP server.
Codex CLI
Codex uses TOML, and the CLI command mirrors Claude's -- pattern:
codex mcp add context7 -- npx -y @upstash/context7-mcp
codex mcp add my-server --env API_KEY=xyz -- npx -y @scope/server
Config lands in ~/.codex/config.toml (global) or .codex/config.toml (project — trusted projects only), and the same file is shared by the CLI and the IDE extension. The TOML tables:
[mcp_servers.context7]
command = "npx"
args = ["-y", "@upstash/context7-mcp"]
[mcp_servers.context7.env]
API_KEY = "xyz"
[mcp_servers.remote]
url = "https://example.com/mcp"
bearer_token_env_var = "MY_TOKEN"
stdio servers need command plus optional args, env, env_vars, cwd. Streamable HTTP servers need url plus optional bearer_token_env_var, http_headers, env_http_headers. Extra knobs worth knowing: startup_timeout_sec (default 10), tool_timeout_sec (default 60), enabled, enabled_tools/disabled_tools for filtering, and default_tools_approval_mode (auto/prompt/approve). Verify with /mcp in the TUI or codex mcp --help.
Gemini CLI
Gemini stores MCP servers under mcpServers in settings.json: ~/.gemini/settings.json (user) or .gemini/settings.json (project — and project is the default scope for the add command).
gemini mcp add my-server npx -y @scope/some-mcp-server
gemini mcp add -t http remote https://example.com/mcp
The full signature is gemini mcp add [options] <name> <commandOrUrl> [args...] with -s/--scope (user|project, default project), -t/--transport (stdio default, sse, http), -e/--env, -H/--header, --trust, --timeout, --include-tools, --exclude-tools.
Watch the HTTP field name. Gemini uses
httpUrlfor streamable HTTP — noturl. stdio usescommand/args/env/cwd; SSE usesurl. This is the single biggest reason a config copied from Claude Code or a README won't work in Gemini.
{
"mcpServers": {
"remote": {
"httpUrl": "https://example.com/mcp",
"timeout": 30000
}
}
}
Per-server options include timeout, trust, includeTools, and excludeTools (excludeTools takes precedence). Discovered tools are namespaced mcp_{serverName}_{toolName}, and the docs explicitly warn against underscores in server names because they confuse the policy parser.
Qwen Code
Qwen Code is a fork of Gemini CLI, so if you already know Gemini, you know Qwen — the schema is shared. Servers live under mcpServers in .qwen/settings.json (project, default) or ~/.qwen/settings.json (user).
qwen mcp add my-server npx -y @scope/some-mcp-server
qwen mcp add -t http remote https://example.com/mcp
qwen mcp add [options] <name> <commandOrUrl> [args...] takes -t/--transport (stdio default, sse, http), -s/--scope, -e/--env, -H/--header, --timeout (default 600000), and --trust. Field names match Gemini exactly: stdio uses command/args/env/cwd; streamable HTTP uses httpUrl; SSE uses url. Tool filtering is includeTools/excludeTools, and again excludeTools wins.
Oh My Pi
Oh My Pi (OMP) is the most config-flexible of the bunch. It reads from .omp/mcp.json (project), ~/.omp/agent/mcp.json (user), or a fallback mcp.json/.mcp.json in the project root. The format is a top-level mcpServers object plus an optional disabledServers array. Server names must match ^[a-zA-Z0-9_.-]{1,100}$.
{
"mcpServers": {
"local": {
"command": "npx",
"args": ["-y", "@scope/server"]
},
"remote": {
"type": "http",
"url": "https://example.com/mcp",
"headers": { "Authorization": "Bearer ..." }
}
}
}
Transports: stdio (default) with command/args/env/cwd; HTTP with type: "http" plus url/headers; legacy SSE with type: "sse" plus url. Validation enforces that stdio entries have a command, http/sse have a url, and you can't set both. Shared fields include enabled, timeout, auth, and oauth. There are also lifecycle controls like lifecycle: "lazy" and idleTimeout so a server only spins up when needed and shuts down after idle — handy when you've got a long list of servers and don't want them all running. (Verify the exact lifecycle field set against the schema, as those details move.)
Management happens in-session via slash commands: /mcp add (guided setup), /mcp reload, /mcp list, /mcp test <name>, /mcp reconnect <name>, plus /mcp resources, /mcp prompts, and /mcp notifications. Usefully, OMP can auto-discover servers from other tools' configs — .claude/, .cursor/, .vscode/, opencode.json, and more — so an existing setup may largely import itself.
A note on Google Antigravity
If you're on Google Antigravity (2.0, IDE, and CLI), it shares a central MCP config at ~/.gemini/config/mcp_config.json with a single mcpServers object. stdio entries use command/args/env. For Google-authenticated remote servers, set authProviderType: "google_credentials", which uses Application Default Credentials via gcloud auth application-default login. You edit it through Manage MCP Servers → View raw config in the IDE. Note this is a separate file from Gemini CLI's settings.json — they're related but not the same config surface, so verify the path for your version.
The cross-tool cheat sheet
| CLI | Format | Config location(s) | HTTP field | Tool name pattern |
|---|---|---|---|---|
| Claude Code | JSON | ~/.claude.json, .mcp.json | url (type: http) | mcp__server__tool |
| Codex CLI | TOML | ~/.codex/config.toml, .codex/config.toml | url (in [mcp_servers.<name>]) | — |
| Gemini CLI | JSON | ~/.gemini/settings.json, .gemini/settings.json | httpUrl | mcp_server_tool |
| Qwen Code | JSON | ~/.qwen/settings.json, .qwen/settings.json | httpUrl | mcp_server_tool |
| Oh My Pi | JSON | .omp/mcp.json, ~/.omp/agent/mcp.json | url (type: http) | — |
When it won't connect
Two failure modes account for most "failed to connect" reports across every CLI:
- Stripped environment. The CLI doesn't source your shell rc files or full
$PATH, sonpx/node/pythonmay not resolve even though they work in your terminal. Use absolute paths to the executables. - stdout pollution (stdio only). stdio uses stdout as the JSON-RPC channel. Any
console.log/printto stdout corrupts the stream and reads as a transport error. The server must log to stderr.
If only some stdio servers connect when you've configured several, that has been reported as a version-specific Claude Code bug (around v2.1.20) rather than expected behavior — check it against your current version before assuming it's broken by design.
Bottom line
The protocol is portable; the config is not. Whenever you copy an MCP snippet between tools, check three things: the file (JSON vs Codex's TOML), the HTTP field (url in Claude/Codex/OMP, httpUrl in Gemini/Qwen), and the scope (keep secrets in your private user/global file or behind env-var expansion, never in a committed literal). Prefer stdio for local servers and streamable HTTP for remote, skip SSE for anything new, and confirm with /mcp before you trust it. Get those right and a server that works in one CLI translates to the rest in about two minutes.