GuidesHosting choices

Connect Claude Code or Codex to a remote MCP server with an API key

The command on each tool, which file the key is written to and whether in clear, keeping it out of a shared repository, what a wrong key shows, scopes, OAuth as the other route, and timeouts.

October 8, 2026The Everpod team
The short answer

Both agents connect to a remote MCP server over HTTP and send the key as a bearer token, but they take it differently. Claude Code takes the whole header on the claude mcp add command and writes it into its own config file. Codex takes the name of an environment variable on codex mcp add and reads the key from it each time it connects. The two commands are below. Neither opens a sign-in window when a header or a variable is set. On a server, the difference matters: Claude Code’s file holds the key in clear, so a server shared with a team or a repository that others clone wants the ${VAR} form in .mcp.json instead, while Codex, used this way, writes only the variable’s name, and where the key itself lives, an export or a profile, is then your shell’s business. Check it with claude mcp list, which connects and reports the server’s own error when the key is wrong; Codex’s codex mcp list only shows the configuration, and the connection is tried when a session starts.

# Claude Code: the header travels with the command
claude mcp add --transport http --scope user name https://api.example.com/mcp \
  --header "Authorization: Bearer YOUR_KEY"

# Codex: the key is read from an environment variable at run time
export MY_KEY=YOUR_KEY
codex mcp add name --url https://api.example.com/mcp --bearer-token-env-var MY_KEY

Claude Code: the command, and where the key goes

“HTTP servers are the recommended option for connecting to remote MCP servers,” and the MCP page gives the shape above, with -H as the short form of --header (in a JSON entry the type streamable-http is accepted as an alias of http). The scope decides the file. Local, the default, “loads only in the project where you added it and stays private to you” and is stored in ~/.claude.json under that project’s path; user scope goes in the same file and applies to every project; project scope writes .mcp.json in the repository root, which you check into version control “so everyone on your team gets the same MCP tools and services.” Anthropic describes ~/.claude.json as a file Claude Code “writes for itself; you don’t need to edit it,” holding your sign-in session, MCP server configurations and per-project state.

We added a server with a bearer header at user scope in an empty config home and read the file back: the header sits in it as "Authorization": "Bearer …", in clear, in a file in your home folder. The add command prints the header as [REDACTED]; on 2.1.294 claude mcp get name printed it in full, whatever the changelog for 2.1.161 says about redacting credential headers, so treat that command’s output as a secret. And Claude Code keeps the five newest earlier versions of ~/.claude.json under ~/.claude/backups/, so a key you first added as a literal and later moved to a variable is still on disk until those copies roll off. On your own laptop all of that is fine. On a machine where the config file might be read by others, or in a .mcp.json that gets committed, use a variable: “${VAR} expands to the value of environment variable VAR,” in the url and headers of an HTTP server, so the entry reads "Authorization": "Bearer ${MY_KEY}" and the key lives in the shell’s environment. Passed on the command line in single quotes, the reference is written unexpanded, which we checked. If the variable is unset “the config still loads: Claude Code reports a missing-variable warning for that server in claude mcp list output and uses the unexpanded ${VAR} text as-is.” One trap: “In a remote server’s url and headers, Claude Code reads credential variables from your environment as empty rather than expanding them,” a set the docs give by example rather than in full (its own, such as ANTHROPIC_API_KEY; a cloud provider’s; others such as HTTPS_PROXY and NPM_TOKEN), with no warning when it happens; the debug log says “never expanded toward a remote server” when it does. Give your variable a name of your own, and check the first connection.

Codex: the command, and where the key goes

Codex “stores MCP configuration in config.toml,” ~/.codex/config.toml by default, or a project’s .codex/config.toml “(trusted projects only).” The field for a bearer token names a variable: the plain-text bearer_token was replaced by bearer_token_env_var in October 2025, “environment variable name for a bearer token to send in Authorization.” After the command above, the whole entry we found in the file was:

[mcp_servers.name]
url = "https://api.example.com/mcp"
bearer_token_env_var = "MY_KEY"

So the variable has to be set in the environment Codex starts from, on a server typically your shell profile or the tmux session you run it in. A header with another name goes through env_http_headers, a map of “header names to environment variable names,” and http_headers holds static values, which is the one place a literal key would land in the file. Used with the variable, a committed .codex/config.toml gives nothing away, though Codex loads it only in a project it has been told to trust.

Checking it, and what a wrong key shows

On Claude Code the check is claude mcp list, which connects to each server it lists (a project server awaiting approval, or a disabled one, is shown without connecting) and “shows a health status next to each,” ✔ Connected, ! Needs authentication or ✘ Failed to connect, appending the failure detail: “the HTTP status or error code, plus any error text the server returned.” A wrong key under a configured header is reported as a failure, not as a sign-in to do: “If you configured headers.Authorization for the server and the server rejects that header, Claude Code reports the connection as failed instead of falling back to OAuth.” With a made-up key against two servers, GitHub’s MCP endpoint and Everpod’s own, both answered 401 and the tool printed the same line each time, followed by the server’s own message (Anthropic documents the wording only for its helper-command variant, so expect the words to move):

name: https://api.example.com/mcp (HTTP) - ✘ Failed to connect — Server rejected the
configured Authorization header (HTTP 401). Check that the token is valid for this MCP
endpoint — OAuth fallback is disabled when headers.Authorization is set. Error detail: …

GitHub’s answered 400 instead, with its own words about the header, to a token that wasn’t even the right shape, and a rejection can also surface as ✘ Connection error, which carries no detail. Inside a session /mcp shows the same, with a tool count beside each connected server.

Codex’s codex mcp list is a table of what is configured, name, URL, the variable’s name, “enabled” and the auth kind; with a bearer variable set it reports that without contacting the server (for an OAuth server it runs discovery), and codex mcp get name prints the entry. The connection is made when a session starts, and the place to read it is /mcp in the session, with /mcp verbose for “detailed server diagnostics.” If the named variable is unset or empty when the session starts, Codex refuses to start that server and names the variable in its error (its source says so; the docs don’t), so a missing key shows up as a server that failed to start, not as a 401 from the server. The MCP page’s line that “if no credential source resolves, Codex can connect to the server without authentication” is about a server with no credential configured at all.

A shared repository and project scope

A .mcp.json in a repository is a request to run a server on every clone, so Claude Code “prompts for approval in interactive sessions before using project-scoped servers,” records the answer per developer in .claude/settings.local.json, and ignores an approval committed to the project’s shared settings in a folder that isn’t trusted: “a cloned repository can’t approve its own servers.” In a claude -p run and in cloud sessions it “can’t show that prompt: it loads project-scoped servers without asking.” That cuts both ways for a key held in a variable: a cloned repository’s .mcp.json can put Bearer ${MY_KEY} in front of its own URL, a headless run sends it there without asking, and only the credential names above are protected. So on a server that runs other people’s repositories, read a cloned .mcp.json before a headless run, or don’t export the key in the shell that runs it. Where the same server name is set at more than one scope, local wins over project over user, and “the entire server entry from that source is used; fields are not merged across scopes.”

OAuth, where the server offers it

A key in a header is for servers that issue keys. A server that supports OAuth is added without a header, and then signed into: claude mcp login name “runs a configured server’s OAuth flow directly from your shell.” Over SSH, where there is no browser, it prints the authorization URL (or add --no-browser to force that); you open the URL on your own machine, then paste the redirect URL from the address bar back at the prompt, which needs an interactive terminal, so connect with ssh -t. The tokens “are stored securely and refreshed automatically.” Codex is the same: codex mcp login name for a server that supports it, credentials in a keyring or a file by the mcp_oauth_credentials_store setting, and “explicit bearer tokens and OAuth credentials take precedence over a helper-provided Authorization header.” The older SSE transport is deprecated on Claude Code, which “tries the HTTP transport first and switches to SSE when the server doesn’t accept it” since 2.1.265; Codex’s docs list only stdio and streamable HTTP.

Timeouts

Claude Code waits 30 seconds for a server to start (MCP_TIMEOUT, in milliseconds) and gives an HTTP server 60 seconds per request unless MCP_TOOL_TIMEOUT or the server’s own timeout field in .mcp.json raises it. Separately, a tool call that sends no response and no progress for five minutes is aborted, and raising MCP_TOOL_TIMEOUT does not lift that idle window: the per-server timeout (in milliseconds, at 1000 or more) or CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT does. Codex gives a server 10 seconds to start and 60 to run a tool, startup_timeout_sec and tool_timeout_sec on its entry, and waits only one second for an optional server when it builds a session’s first tool list (mcp_optional_startup_grace_ms; 0 waits the full startup timeout). A tool that provisions something, or waits on a slow API, needs the tool timeout raised on both, and on Claude Code the idle window too.

Before you connect a server you didn’t write

Anthropic’s line: “Verify you trust each server before connecting it. Servers that fetch external content can expose you to prompt injection risk,” and it “does not security-audit or manage any MCP server.” Both agents let you narrow what a server’s tools may do. Claude Code’s permission rules match a whole server (mcp__name) or one tool (mcp__name__tool), and a server can mark a tool so that it always asks. Codex sets a per-server default_tools_approval_mode (auto, prompt, writes, which “prompts for tools that aren’t marked read-only,” or approve), an enabled_tools allow list, and asks before a tool the server marks destructive unless the server also marks it read-only. What a key can do is the server’s own decision, so read what the key is scoped to before pasting it, and match it to what you want the agent able to do there.

Run Claude Code and Codex on an always-on developer pod.

A developer pod is a cloud computer of your own with Claude Code, Codex or both installed, reached only over your own Tailscale network. From $24 a month, built in about ten minutes.