Document MCP env_vars for secret-manager session tokens like BW_SESSION

Open 💬 2 comments Opened Apr 22, 2026 by verickwayne

Summary

Codex MCP subprocesses did not inherit the shell's secret-manager session environment by default. In this case an MCP server that resolved AGENTMAIL_API_KEY through Bitwarden appeared configured/enabled, but the model could not reliably discover/call the server's tools until I explicitly passed BW_SESSION through env_vars.

This is probably working as designed from a strict environment-isolation perspective, but the failure mode was hard to diagnose: codex mcp list / codex mcp get showed the server as enabled, and the same wrapper worked from the shell. A clearer warning or docs note would help MCP servers that resolve credentials from CLI secret managers.

Environment

  • Codex CLI: 0.122.0
  • macOS: 15.6.1
  • Node used by the MCP scripts: v25.8.2
  • MCP transport: stdio
  • Secret manager: Bitwarden CLI (bw)

Repro Shape

  1. Configure a stdio MCP server whose wrapper resolves its API key from either AGENTMAIL_API_KEY or Bitwarden CLI:
[mcp_servers.AgentMail]
command = "/path/to/agentmail-mcp.sh"
args = []
startup_timeout_sec = 20
default_tools_enabled = true
default_tools_approval_mode = "approve"
enabled_tools = ["list_inboxes"]
  1. In the interactive shell, Bitwarden is unlocked and the wrapper works:
bw status
/path/to/agentmail-mcp.sh   # raw MCP initialize/tools/list works
  1. In Codex, the server appears enabled, but native model calls to the server/tool are unavailable or fail because the MCP subprocess sees Bitwarden as locked.

A diagnostic MCP resource inside the subprocess showed the important difference:

{
  "hasAgentMailApiKeyEnv": false,
  "home": "/Users/...",
  "pathHasHomebrew": true,
  "bitwardenStatusExit": 0,
  "bitwardenStatusStdout": "{...\"status\":\"locked\"}"
}
  1. Passing the session variables through fixes it:
[mcp_servers.AgentMail]
command = "/path/to/agentmail-mcp.sh"
args = []
startup_timeout_sec = 20
default_tools_enabled = true
default_tools_approval_mode = "approve"
env_vars = ["AGENTMAIL_API_KEY", "BW_SESSION"]
enabled_tools = ["list_inboxes"]

[mcp_servers.AgentMail.env]
HOME = "/Users/..."
PATH = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"

After that, native Codex MCP calls to the tool succeed.

Expected

Either:

  • Docs explicitly call out that secret-manager session variables like BW_SESSION must be passed through with env_vars; or
  • codex mcp get/list / startup diagnostics make it easier to see that a configured MCP server's runtime environment differs from the launching shell in a way that can lock secret-manager access.

Actual

The server looked enabled from codex mcp list, but the actionable cause was only visible after building a custom diagnostic MCP resource.

Notes

No raw credentials were stored in config.toml; env_vars was sufficient and is the right fix once discovered.

View original on GitHub ↗

2 Comments

chanwooyang1 · 4 months ago

Thanks for the detailed repro and workaround. I looked through the current repo docs, and this seems like a focused MCP documentation improvement rather than a CLI/config behavior change.

The missing piece appears to be explaining how env_vars differs from [mcp_servers.<name>.env]:

  • env_vars = [...] forwards selected variables from the launching environment into the stdio MCP subprocess.
  • [mcp_servers.<name>.env] sets explicit literal values in config.toml.

That distinction is important for secret-manager session variables such as BW_SESSION, where users usually should not write the secret/session value directly into config.toml.

A narrow docs update could add an example like:

[mcp_servers.AgentMail]
command = "/path/to/agentmail-mcp.sh"
env_vars = ["AGENTMAIL_API_KEY", "BW_SESSION"]

[mcp_servers.AgentMail.env]
PATH = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"

This would make it clearer that MCP subprocesses may not see secret-manager session state unless the relevant environment variables are explicitly passed through.

If this scope looks right to maintainers, I’d be happy to prepare a narrow docs PR if invited, following the repo’s invitation-only contribution policy.

Herrtian · 3 months ago

I prepared a focused docs/schema branch for the env vs env_vars distinction:

https://github.com/Herrtian/codex/tree/docs/mcp-env-vars-secret-manager

The branch keeps the change small and only updates the config comments plus generated schema:

  • env is described as literal environment values set for a stdio MCP server.
  • env_vars is described as names forwarded from the launching shell.
  • the schema text calls out secret-manager session variables such as BW_SESSION, where forwarding is usually preferable to writing the value into config.

Validation I ran:

git diff --check -- codex-rs/config/src/mcp_types.rs codex-rs/core/config.schema.json
cargo run -p codex-core --bin codex-write-config-schema
cargo test -p codex-core config_schema_matches_fixture -- --nocapture

PR creation appears to be limited by the repo's invitation-only contribution policy, so I'm leaving the branch here rather than opening an unsolicited PR. If this matches the intended docs direction, could a maintainer invite a PR or pick up the branch internally?