MCP config API key: use an environment variable, not the file

Cursor, Claude Code, Windsurf and Codex can read an MCP API key from an environment variable. Here is each client's syntax and what an unset variable does.

5 min readSume
All posts

Yes: keep the API key in an environment variable and let the MCP config point at it, so the key never lands in a file you commit. Cursor, Claude Code and Windsurf each expand a variable inside the config's headers; Codex names the variable instead. The syntax differs, and so does what happens when the variable is unset.

Client behavior below comes from each vendor's own page, read 2026-09-29. For Sume, an API-key session sends Authorization: Bearer <key> or x-api-key to https://mcp.sume.com/mcp, per MCP OAuth and API keys.

What is each client's syntax?

Cursor, Claude Code and Windsurf expand variables in headers, and Codex has fields that read a header value from a variable, so the Sume key goes in Authorization as Bearer plus the variable. Gemini CLI's page documents expansion for the env block only, so it is listed for comparison.

From each client's docs (Claude Code, Cursor, Windsurf, Codex, Gemini CLI), read 2026-09-29.
ClientSyntaxDocumented whereIf the variable is unset
Cursor${env:NAME}command, args, env, url, headersNot stated on the page
Claude Code${VAR} or ${VAR:-default}command, args, env, url, headers in .mcp.jsonLoads with the ${VAR} text unexpanded and warns in claude mcp list
Windsurf${env:VAR_NAME}, or ${file:/path} for a file's contentscommand, args, env, serverUrl, url, headersResolves to an empty string
Codexbearer_token_env_var names the variable; env_http_headers maps header names to variable namesA [mcp_servers.<name>] table in config.tomlNot stated on the page
Gemini CLI$VAR or ${VAR}Documented for the env block of a server entryResolves to an empty string

What does a Sume entry look like?

In Claude Code's .mcp.json, the transport type is required for a URL entry, and the header reads the variable:

  • Cursor uses the same shape with no type and "Bearer ${env:SUME_API_KEY}".
  • Windsurf uses serverUrl for the address and ${env:SUME_API_KEY}.
  • Codex takes bearer_token_env_var = "SUME_API_KEY" under the server's table; see Codex bearer_token_env_var with Sume.
{
  "mcpServers": {
    "sume": {
      "type": "http",
      "url": "https://mcp.sume.com/mcp",
      "headers": {
        "Authorization": "Bearer ${SUME_API_KEY}"
      }
    }
  }
}

What happens if the variable is not set?

This is where the clients differ. Windsurf's page says an unset ${env:...} resolves to an empty string, so a header written as Bearer ${env:SUME_API_KEY} would carry no key at all, and the server would have nothing to authenticate. Claude Code keeps the literal ${VAR} text and warns, so the failure is visible in claude mcp list. Its page also says that in a remote server's url and headers, credential names such as ANTHROPIC_API_KEY always read as empty; a name of your own such as SUME_API_KEY expands as written.

Either way the fix is to export the variable in the shell or profile that launches the client. Cursor's page adds that remote servers do not support envFile, so for a remote server set the variable in your shell profile or system environment.

Is an API key the right credential here?

For interactive clients, Sume's quickstart prefers the OAuth connector flow and says not to paste API keys into chat. The API key remains the path for automation that does not speak OAuth. An API-key session sees the whole hosted tool set, including paid tools, so treat the variable like any secret and rotate the key if it appears in logs or chat history. See MCP server API key vs OAuth for the trade-off, and the quickstart for the OAuth setup.

Sources

Related posts

More in Integrations

All Integrations posts

Written by Sume