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.