Skip to main content
Home/Blog/How to Add an MCP Server to Any AI Coding CLI (Claude Code, Codex, Gemini, Qwen, Oh My Pi)
Developer Tools

How to Add an MCP Server to Any AI Coding CLI (Claude Code, Codex, Gemini, Qwen, Oh My Pi)

A practical, per-tool guide to wiring up Model Context Protocol servers across Claude Code, Codex CLI, Gemini CLI, Qwen Code, and Oh My Pi — including stdio vs HTTP transports, config file locations, scopes, secrets, and the field-name gotchas that trip everyone up.

By Sean

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.json under the project path
  • project — written to .mcp.json at the repo root, committed and shared
  • user — 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 httpUrl for streamable HTTP — not url. stdio uses command/args/env/cwd; SSE uses url. 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

CLIFormatConfig location(s)HTTP fieldTool name pattern
Claude CodeJSON~/.claude.json, .mcp.jsonurl (type: http)mcp__server__tool
Codex CLITOML~/.codex/config.toml, .codex/config.tomlurl (in [mcp_servers.<name>])
Gemini CLIJSON~/.gemini/settings.json, .gemini/settings.jsonhttpUrlmcp_server_tool
Qwen CodeJSON~/.qwen/settings.json, .qwen/settings.jsonhttpUrlmcp_server_tool
Oh My PiJSON.omp/mcp.json, ~/.omp/agent/mcp.jsonurl (type: http)

When it won't connect

Two failure modes account for most "failed to connect" reports across every CLI:

  1. Stripped environment. The CLI doesn't source your shell rc files or full $PATH, so npx/node/python may not resolve even though they work in your terminal. Use absolute paths to the executables.
  2. stdout pollution (stdio only). stdio uses stdout as the JSON-RPC channel. Any console.log/print to 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.

Frequently Asked Questions

Find answers to common questions

stdio launches the server as a local subprocess and talks to it over stdin/stdout — best for tools you run on your own machine. Streamable HTTP connects to a remote server over an HTTP URL and is the recommended transport for hosted/remote servers. SSE is the older HTTP-based transport and is now deprecated in favor of streamable HTTP, though most CLIs still support it for backward compatibility. Use stdio for local, HTTP for remote, and avoid SSE for new setups.

Claude Code: ~/.claude.json (local and user scopes) and .mcp.json at the project root (project scope). Codex CLI: ~/.codex/config.toml (global) or .codex/config.toml (project). Gemini CLI: ~/.gemini/settings.json (user) or .gemini/settings.json (project). Qwen Code: ~/.qwen/settings.json (user) or .qwen/settings.json (project). Oh My Pi: .omp/mcp.json (project), ~/.omp/agent/mcp.json (user), or a fallback mcp.json/.mcp.json in the project root.

Project scope stores the server in a file inside the repo (.mcp.json, .codex/config.toml, .gemini/settings.json, etc.) so it can be committed and shared with your team. User/global scope stores it in your home directory so it's available across every project but stays private to you. Claude Code also has a third 'local' scope — private to you but scoped to a single project. Pick project scope for shared team servers and user/global for personal tooling and anything holding secrets.

Two common causes. First, the CLI spawns servers in a stripped environment that does not source your ~/.bashrc, ~/.zshrc, or full $PATH, so a command that resolves in your terminal can silently fail to launch — fix it by using absolute paths to executables (the full path to npx, node, or python). Second, for stdio servers, any console.log/print to stdout corrupts the JSON-RPC channel and surfaces as a transport error; the server must log to stderr instead.

Keep secrets out of any committed file. Pass them via the env block (or --env flag) referencing environment variables rather than literals. Claude Code supports ${VAR} and ${VAR:-default} expansion in .mcp.json across command, args, env, url, and headers. Codex supports an env table and env_http_headers/bearer_token_env_var for HTTP. The safest pattern: put the server in your private user/global config, or use env-var expansion so the committed file references a variable that each developer sets locally.

Claude Code names tools mcp__ (plugin servers use mcp__plugin__), and MCP prompts become slash commands like /mcp__server__prompt. Gemini CLI and Qwen Code use mcp__. To restrict tools, Gemini and Qwen support includeTools/excludeTools (excludeTools wins), and Codex supports enabled_tools/disabled_tools. Gemini's docs warn against underscores in server names because they can confuse the policy parser.

Run /mcp inside the session — it works in Claude Code, Codex (TUI), and Oh My Pi to show connection status and available tools. From the shell, Claude Code offers claude mcp list and claude mcp get ; Oh My Pi offers /mcp list and /mcp test . If a Claude Code project server is waiting on you, it shows as '⏸ Pending approval' in claude mcp list.

This is the most common copy-paste trap. Claude Code and Oh My Pi use url with type set to http (Claude also accepts streamable-http as an alias). Gemini CLI and Qwen Code use httpUrl for streamable HTTP. Codex uses url inside the [mcp_servers.] TOML table. SSE everywhere uses url. So a Gemini config is not drop-in compatible with Claude Code for HTTP servers — you have to rename httpUrl to url.

Building Something Great?

Our development team builds secure, scalable applications. From APIs to full platforms, we turn your ideas into production-ready software.