Why your MCP server cannot see env vars from .bashrc

Claude Code spawns MCP servers as plain child processes. They never source .bashrc. Set MCP env vars in environment.d or the MCP env config, not your shell rc files.

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