You add an MCP server to Claude Code and it requires an environment variable. But the server doesn't show up when you run /mcp, no error surfaces, nothing useful in the logs. Nine times out of ten: an env var it needs is set in ~/.bashrc, and nothing Claude Code launches will ever see it.

# The mechanic

Claude Code spawns each MCP server as a plain child process. No interactive shell, no .bashrc sourcing. .bashrc is for interactive shells only. Non-interactive children inherit the parent's already-resolved environment, not the file.

So this:

# ~/.bashrc
export MY_API_KEY=sk-xxxxxxxx

is invisible to MCP servers, hooks, and skill scripts. The server starts, can't read the var, exits silently. Claude Code only sees "didn't initialise."

# Where to put env vars instead

In rough order of preference:

  1. systemd environment.d (Linux, systemd-based distros). Drop ~/.config/environment.d/mcp.conf:

    MY_API_KEY=sk-xxxxxxxx
    ANTHROPIC_LOG=debug
    

    These are part of the user session, loaded by systemd --user, inherited by every desktop, IDE, terminal, and Claude Code process spawned from that session. Log out / log in to apply, or systemctl --user daemon-reload and relog.

  2. The MCP server's own env config. In ~/.claude/settings.json or project .mcp.json:

    {
      "mcpServers": {
        "myserver": {
          "command": "/path/to/server",
          "env": { "MY_API_KEY": "sk-xxxxxxxx" }
        }
      }
    }
    

    Explicit and per-server. Downside: secrets end up in a config file. Only safe if it isn't synced or committed.

  3. ~/.profile or ~/.zprofile. Login shells source these, and most display managers do start the user session via a login shell. Cleaner than .bashrc but more fragile across distros.

  4. launchctl setenv on macOS. Persists only inside the current launchd session unless you wire a LaunchAgent plist.

# Confirming the failure mode

When an MCP server is missing from /mcp and you suspect env vars are the cause:

  • Run the binary from a non-interactive shell to reproduce:

    bash -c '/path/to/mcp-server'
    

    An interactive shell sources .bashrc. bash -c doesn't. If it fails here, it'll fail when Claude Code spawns it.

  • Add a startup log line that prints os.environ.get("MY_API_KEY", "(missing)"). Fastest way to confirm the var isn't reaching the child.

# Child processes not seeing what you'd expect is a recurring failure mode

In every case, don't assume your shell's environment is what the child process actually sees. Set the var where the child picks it up.

# References