
This fix is part of MCP troubleshooting, within The MCP Guide. Still stuck? Request a free MCP readiness review.
If your MCP server says an API key or variable is missing, but it works when you run it in a terminal, the client did not pass that variable. Desktop clients do not load your shell profile, and stdio servers inherit only a small set of variables. The fastest fix is to put the variable in the server's env block in the client's config file, using that client's own reference syntax, then fully restart the client.
Checked against: the MCP debugging guide, Claude Code's MCP docs, Cursor's MCP docs and the VS Code MCP configuration reference on 2 November 2026. eSEOspace did not run these steps.
Key Takeaways
- The MCP debugging guide says stdio servers "inherit only a limited subset of environment variables automatically."
- Variables you export in
.zshrcor.bashrcreach apps started from that shell, not apps opened from the Dock, Start menu or a launcher. - Each client uses its own reference syntax:
${VAR}in Claude Code,${env:VAR}in Cursor and in VS Code'smcp.json. - Claude Code loads a server even when a
${VAR}is unset, and warns inclaude mcp listinstead of failing. - Claude Code deliberately sends some credential variables as empty to remote servers, which often shows up as a 401.
What this error means
When a client starts a local server, it creates a new process with a fresh environment. The MCP debugging guide says the inherited set is "platform-dependent," and tells you to use the env key to provide your own values.
So a key that lives only in your terminal session never reaches the server. The server then fails at startup or on the first tool call. Typical messages include "API key not set," "missing required environment variable" or an HTTP 401 from the upstream service.
The same thing happens with remote servers when a header refers to a variable the client cannot see. The request goes out with an empty or literal ${VAR} value, and the server rejects it.
Quick fix
- Find the exact variable names in the server's README.
- Add them to the server's
envblock in the client config, not to your shell profile. - Use the reference syntax your client supports, or paste the literal value if the file stays private.
- Check spelling and case.
API_KEYandapi_keyare different variables. - Fully quit and reopen the client. A window reload is not always enough.
- Read the client's MCP log for the server's first error line.
A minimal Claude Desktop block, from the debugging guide:
{
"mcpServers": {
"myserver": {
"command": "mcp-server-myapp",
"env": {
"MYAPP_API_KEY": "some_key"
}
}
}
}
Fix by client / OS
Claude Desktop
Put values in env inside claude_desktop_config.json. On macOS it is in ~/Library/Application Support/Claude/, on Windows in %APPDATA%\Claude\ and on Linux in ~/.config/Claude/. On Windows, the local server guide shows one special case. If the log mentions ${APPDATA} in a path with an ENOENT error, add the expanded APPDATA value (for example C:\\Users\\user\\AppData\\Roaming\\) to env.
Claude Code
Add variables when you register the server:
claude mcp add myserver --env API_KEY=your-key -- npx -y @example/mcp-server
The Claude Code docs note two traps. --env takes several pairs, so if the server name comes right after it the CLI reads the name as another pair and rejects it. Put the name first or another option in between. And everything after -- goes to the server, so --env must come before it.
In a shared .mcp.json, you can reference variables as ${VAR} or ${VAR:-default} in command, args, env, url and headers. If a variable is unset and has no default, Claude Code still loads the server with the literal ${VAR} text and warns in claude mcp list and /mcp. An empty string counts as set, so ${VAR:-default} expands to empty, not to the default.
For remote servers, Claude Code reads some credential variables, such as ANTHROPIC_AUTH_TOKEN, as empty in url and headers. The docs say this stops a project file from sending your Claude credentials to a server it names. The server then usually returns 401. The docs list ANTHROPIC_API_KEY, AWS_BEARER_TOKEN_BEDROCK, HTTPS_PROXY and NPM_TOKEN among the covered names. Copy the value into a variable with your own name and reference that instead.
Cursor
Cursor's MCP docs support ${env:NAME} in command, args, env, url and headers, plus envFile for stdio servers:
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["${workspaceFolder}/tools/mcp_server.py"],
"env": { "API_KEY": "${env:API_KEY}" }
}
}
}
The docs say envFile does not work for remote (HTTP or SSE) servers. Cursor's help page adds that variables from your shell profile must be available to Cursor, and to restart Cursor after you change the profile.
VS Code
In .vscode/mcp.json (the VS Code format) use ${env:NAME}, envFile, or an ${input:...} prompt that VS Code stores securely. The configuration reference says the portable .mcp.json format uses ${NAME} or ${NAME:-default} instead, and does not support envFile or input prompts. It also warns that VS Code's client "treats bare $NAME as literal text," so always use braces. For remote and container sessions, the variable must exist on the remote host.
macOS, Windows and Linux
On macOS, apps opened from the Dock or Finder do not read .zshrc. Launching the client from a terminal can work as a test, but the env block is the reliable fix. On Windows and Linux, the same rule applies: a value that exists only in one terminal session is not visible to an app started elsewhere. Put it in the config's env block or in a variable the client can see, then restart the client.
Where to find the logs
- Claude Desktop:
~/Library/Logs/Claude,%APPDATA%\Claude\logsor~/.config/Claude/logs. Themcp-server-SERVERNAME.logfile holds the server's stderr. - Claude Code:
claude mcp listand/mcpshow missing-variable warnings. If a remote server's header references a covered credential variable, runclaude --debug-file /tmp/claude-debug.logand search fornever expanded toward a remote server, per the docs. - Cursor: Output panel, then MCP Logs.
- VS Code: MCP: List Servers, select the server, Show Output.
If it still fails
- The server reads a config file instead. Some servers want a
.envnext to them. The debugging guide says the working directory "may be undefined (like/on macOS)," so use absolute paths. - Docker in the middle. Values in
envreachdocker, not the container, unless you also pass-e NAME. See Docker MCP server not connecting. - WSL in the middle. Windows variables do not cross into WSL by default. See WSL setup errors.
- Auth errors remain. If the value arrives but the service rejects it, follow fixing MCP 401 Unauthorized.
You can generate a clean block per client with the MCP config generator, and decode error codes with the MCP error code lookup.
Next step
Once keys reach the server, keep them out of shared files and chat logs. Our secret leakage guide covers safer storage. If your team needs a review of how servers and keys are set up, eSEOspace offers an MCP readiness audit.
More in the MCP Guide
Free tools: MCP Error Code Lookup · MCP Config Generator
Latest research: State of MCP: October 2026
Frequently asked questions
Why does the server work in my terminal but not in the client?
Your terminal loaded your shell profile, so the variable exists there. The client started the server in its own environment without that profile. Add the variable to the server's env block.
Can I commit an MCP config with API keys in it?
Avoid it. Use references such as ${VAR} (Claude Code), ${env:VAR} (Cursor and VS Code) or VS Code input prompts, and keep real values in your environment or a private file.
Why does Claude Code send an empty token to my remote server?
Its docs say some credential variables, such as ANTHROPIC_AUTH_TOKEN, always read as empty in a remote server's url and headers. Rename your own variable to something not on that list.
Do I need to restart after editing env values?
Yes. The client reads the config when it starts the server. Cursor's help page says to restart Cursor after updating your shell profile, and Claude Desktop needs a full quit.











