How do I pass environment variables to an MCP server?
claude mcp add --env API_KEY=your-key <name> -- <command> Or reference ${API_KEY} in .mcp.json to keep the secret out of git.
Answer
Pass variables at add time with --env KEY=value, or put them in the env field of the server's .mcp.json entry. To keep a shared config free of secrets, reference them: ${VAR} expands to the value, ${VAR:-default} falls back. Expansion works in command, args, env, url, and headers.
What it does
Two ways in. On the command line:
claude mcp add --env AIRTABLE_API_KEY=your-key airtable -- npx -y @mcpservers/airtable
Or in the server's .mcp.json entry:
{
"mcpServers": {
"db": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@some/db-server"],
"env": {
"DATABASE_URL": "postgresql://user:pass@localhost/db"
}
}
}
}
The second form has an obvious problem for a file you commit, which is what expansion solves. ${VAR} takes the value from your environment; ${VAR:-default} falls back when it is not set.
Expansion works in five places: command, args, env, url, and headers.
{
"mcpServers": {
"api-server": {
"type": "http",
"url": "${API_BASE_URL:-https://api.example.com}/mcp",
"headers": {
"Authorization": "Bearer ${API_KEY}"
}
}
}
}
An unset variable with no default does not stop the config loading. Claude Code reports a missing-variable warning for that server in claude mcp list and uses the literal ${VAR} text as-is — which is why the server then fails to authenticate rather than failing to start.
When to use it
--env for a server you are adding for yourself, where the value is not going anywhere else.
${VAR} in .mcp.json for anything the team shares. The file describes which variables the server needs; each machine supplies its own. That is the whole point of committing the config.
Add :-default wherever a sensible fallback exists. It turns a silent misconfiguration into a working default.
Example
A .mcp.json you can commit — the file names the variables, each machine supplies the values:
{
"mcpServers": {
"db": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@some/db-server"],
"env": {
"DATABASE_URL": "${DATABASE_URL}",
"DB_POOL_SIZE": "${DB_POOL_SIZE:-5}"
}
}
}
}
Anyone who clones this needs DATABASE_URL in their shell; the pool size works without one.
One variable you get for free
Claude Code sets CLAUDE_PROJECT_DIR in the spawned server's environment, pointing at the stable project root:
process.env.CLAUDE_PROJECT_DIR
It does not change when you add or remove working directories mid-session.
There is a catch worth knowing: that variable is set in the *server's* environment, not in Claude Code's own. Referencing it with ${VAR} expansion in command or args of a project-scoped .mcp.json, or a local- or user-scoped entry in ~/.claude.json, needs a default:
{
"mcpServers": {
"local-tools": {
"type": "stdio",
"command": "node",
"args": ["${CLAUDE_PROJECT_DIR:-.}/scripts/server.js"]
}
}
}
Plugin-provided MCP configurations substitute ${CLAUDE_PROJECT_DIR} directly and do not need it.
Common mistakes
Committing a real key in env. Reference it instead. .mcp.json goes into version control by design.
Missing a tools list caused by a missing key. A server that connects and then offers no tools usually started without a variable it needed. Open /mcp and select it to see the list — empty is the symptom. That is a different status from Connected · tools fetch failed, which carries an error detail of its own.