Skip to content

Auth and keys

Claude Code sends "Bearer " with no key: fix the empty header

Find out why a ${VAR} in a remote MCP server's headers reaches the server empty in Claude Code, confirm it in the debug log, and rename the variable to fix it.

By Lucian Dyer, FetchMCP team · Published

This article was drafted with AI assistance from the sources listed below and edited and published by the FetchMCP team. Disclosure: the FetchMCP API, which this article mentions, is this site's own product.

An empty Bearer header, step by step
  1. 1Read the warningclaude mcp list names a missing variable. No warning points to a protected credential name.
  2. 2Check the logRun claude --debug-file and search for "never expanded toward a remote server".
  3. 3RenameCopy the value into a variable with your own name, such as FETCHMCP_API_KEY.
  4. 4Point the header at itWrite Bearer ${FETCHMCP_API_KEY} in .mcp.json and restart Claude Code.
  5. 5Verifyclaude mcp list shows the server connected and /mcp lists its tools.

Your .mcp.json says "Authorization": "Bearer ${SOMETHING}", the variable is exported, and the server still answers 401. Claude Code reports a failed connection. The key is in your shell, but it never reaches the server.

There are three ways a ${VAR} header goes wrong in Claude Code. Two of them warn you. The third is silent by design. Checked against the Claude Code MCP docs on 2026-11-08.

How expansion is supposed to work

${VAR} expands to the value of VAR. ${VAR:-default} expands to VAR if it is set, or to default if it is not. Claude Code accepts either form in a server's command, args, env, url and headers.

A working remote entry looks like this:

{
  "mcpServers": {
    "fetchmcp": {
      "type": "http",
      "url": "https://fetchmcp.net/mcp",
      "headers": {
        "Authorization": "Bearer ${FETCHMCP_API_KEY}"
      }
    }
  }
}

The file holds the reference, each developer holds the key. Where that file lives and who sees it is covered in the scopes post.

Case 1: the variable is not set

claude mcp list warns about a missing variable and names it. The same warning shows in /mcp. The config still loads, and Claude Code sends the unexpanded text, so the server sees the literal ${FETCHMCP_API_KEY} instead of a key.

Fix it by setting the variable where Claude Code starts:

export FETCHMCP_API_KEY="YOUR_KEY"
claude

Case 2: the variable is set to an empty string

FETCHMCP_API_KEY="" counts as set. A ${VAR:-default} reference then expands to the empty string, not to the default. The docs give a URL example: if the variable for the base URL is empty, the url collapses to /mcp and claude mcp list reports 'url' is not a valid URL.

Unset the variable instead of emptying it when you want the default:

unset FETCHMCP_API_KEY

Case 3: the name is a protected credential

This is the silent one. In a remote server's url and headers, Claude Code reads certain credential variables as empty instead of expanding them. The point is to stop a project's .mcp.json or a plugin from sending your Claude Code or cloud credentials to whatever server it names.

The docs list examples, not a closed set:

  • Claude Code's own credentials, such as ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN
  • cloud provider credentials, such as AWS_BEARER_TOKEN_BEDROCK
  • other credentials in your environment, such as HTTPS_PROXY and NPM_TOKEN

Bearer ${ANTHROPIC_AUTH_TOKEN} reaches the server as Bearer with nothing after it. The server rejects it, usually with 401, and Claude Code shows a failed connection. There is no missing-variable warning, because the variable is not missing.

Two details catch people:

  1. A :-default does not help. For a covered name, the fallback is ignored and the value reads as empty whether or not you set it.
  2. Base URLs still expand. "url": "${ANTHROPIC_BASE_URL}/mcp" works, unless the value itself embeds a credential such as a username and password.

Any name outside the covered set, such as API_KEY, expands as written.

Confirm it in the debug log

claude --debug-file writes a log you can search. When a remote server's url or headers references a covered variable that you have set, Claude Code names it there:

claude --debug-file /tmp/claude-debug.log

Let the server connect, exit, then search the file:

grep "never expanded toward a remote server" /tmp/claude-debug.log

A hit names the variable. No hit, plus a 401, points back to cases 1 and 2, or to a wrong key.

The fix: give the key a name of your own

Copy the value into a variable that only this server uses, then reference that name. The docs give this as the way to pass a covered credential on purpose:

export MY_SERVER_TOKEN="$ANTHROPIC_AUTH_TOKEN"

For a fetch server, you rarely want to reuse another tool's credential at all. Give the server its own key under its own name, as in the snippet above with FETCHMCP_API_KEY.

  1. Rename the variable in your shell profile.
  2. Change the header to Bearer ${YOUR_NEW_NAME}.
  3. Restart Claude Code so it reads the new environment.
  4. Run claude mcp list. A failed server shows the failure detail on its status line, and claude mcp get <name> shows it on an Issue: line.

/mcp and claude mcp list show a ${VAR} reference by name, not by value, for servers in local, project or user scope. The /mcp detail view does this from v2.1.268. A screenshot of either shows the variable name, not the key.

More on bearer headers, OAuth and keeping keys out of git is in the authentication guide.

Try it with a real server

FetchMCP API runs a remote MCP server over Streamable HTTP and takes Authorization: Bearer <key>, the same key as its REST API. The MCP server registers 2 tools: get_page_contents reads one public URL per call, and get_account_status shows credits left, plan and rate limit at no cost. Once claude mcp list shows the server connected, call get_account_status as a first check. Failed fetches cost 0 credits.

The current Claude Code config is in the MCP docs. Keys come with an account: sign up here. Companies and individual professionals can sign up.

Frequently asked questions

Why does Claude Code send an empty Bearer header to my MCP server?

In a remote server's url and headers, Claude Code reads certain credential variables as empty on purpose, for example ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, AWS_BEARER_TOKEN_BEDROCK, HTTPS_PROXY and NPM_TOKEN. The server receives "Bearer " with nothing after it and usually answers 401. Copy the value into a variable with a name of your own and reference that.

Does a :-default fallback help with a protected variable name?

No. For the covered credential names, Claude Code ignores the :-default fallback in a remote server's url and headers, and the value reads as empty whether or not the variable is set.

What happens if the variable in .mcp.json is not set at all?

The config still loads. Claude Code warns in claude mcp list and in /mcp, names the missing variable, and uses the literal ${VAR} text unexpanded, so the server receives that text instead of a key.

How do I confirm Claude Code dropped a credential variable?

Start Claude Code with claude --debug-file /tmp/claude-debug.log, connect to the server, then search that file for the phrase never expanded toward a remote server. The line names the variable.

Sources

  1. Connect Claude Code to tools via MCP (Claude Code docs) (Documentation)