MCP bearer-token env var appears configured even when missing from active process

Open 💬 4 comments Opened Jun 25, 2026 by alamorre

What version of Codex CLI is running?

codex-cli 0.142.0

What platform is your computer?

macOS arm64 / Codex Desktop-bundled CLI

What issue are you seeing?

For a Streamable HTTP MCP server configured with bearer_token_env_var, codex mcp list and codex mcp get can make the server look bearer-authenticated even when the referenced environment variable is not visible to the active Codex process. The actual MCP request then reaches the server without an Authorization header, so protected tools are hidden or startup fails.

This is confusing because the status path says the server has bearer auth configured, while the runtime path cannot send it. codex doctor does notice an MCP warning, but the more direct MCP commands do not surface the missing env var or tell the user that the active Desktop/thread/process needs a restart after the launcher environment is changed.

No token values are included in this report.

What steps can reproduce the bug?

  1. Configure a remote Streamable HTTP MCP server with a bearer-token env var:

``toml
[mcp_servers.example]
url = "https://example.com/mcp"
bearer_token_env_var = "EXAMPLE_MCP_TOKEN"
``

  1. Launch Codex Desktop or Codex CLI from an environment where EXAMPLE_MCP_TOKEN is not present.
  2. Run codex mcp list or codex mcp get example.
  3. Attempt to use the MCP server from the active Codex session.

Observed behavior

In a real repro with a Breakdown MCP server:

$ codex mcp get breakdown
breakdown
  enabled: true
  transport: streamable_http
  url: https://www.breakdown.sh/api/mcp
  bearer_token_env_var: BREAKDOWN_API_TOKEN
  http_headers: -
  env_http_headers: -

$ printenv BREAKDOWN_API_TOKEN >/dev/null && echo set || echo missing
missing

$ codex mcp list
Name       Url                               Bearer Token Env Var  Status   Auth
breakdown  https://www.breakdown.sh/api/mcp  BREAKDOWN_API_TOKEN   enabled  Bearer token

The server-side MCP diagnostic reports that the request arrived without a bearer token:

{
  "ok": false,
  "state": "missing_token",
  "summary": "Breakdown MCP is reachable, but this request did not include a bearer token.",
  "error": { "code": "unauthorized", "message": "Missing bearer token" }
}

codex doctor --summary does catch a generic warning:

[!!] mcp  MCP configuration has optional issues - Set the missing MCP env vars or disable the affected server.

But the direct codex mcp list/get flow still reads as if bearer auth is ready, and the active MCP tool surface remains unusable until the process/thread is restarted with the env var available.

Expected behavior

Codex should distinguish these states in the MCP status path:

  • bearer-token env var is configured
  • bearer-token env var is present in the current process environment
  • bearer-token env var is missing, so no Authorization header will be sent

Concretely, one or more of these would help:

  • codex mcp list/get should show a warning/status such as missing env var EXAMPLE_MCP_TOKEN instead of only Auth: Bearer token.
  • codex doctor should make the missing server/env-var actionable from the default output, while still redacting secret values.
  • The active Desktop/thread MCP startup error should say that the configured bearer-token env var is missing from the process environment and that restarting Codex is required after changing GUI/launcher env vars.
  • If Codex supports live MCP client/session refresh, changing MCP auth config or logging in should invalidate/recreate the active MCP client; otherwise the UI should explicitly say a new process/thread is required.

Additional information

This may overlap with other MCP auth/session-state reports, but this issue is specifically about bearer_token_env_var being configured while absent from the active process environment. Related issues I found while checking for duplicates:

  • #29279: active Desktop thread remains stuck after MCP OAuth login/state changes
  • #26760: bearer-token auth failures can suggest the wrong OAuth remediation
  • #26072: Streamable HTTP MCP server appears enabled/authenticated but tools are not exposed

View original on GitHub ↗

4 Comments

Nicolas0315 · 2 months ago

I pushed a proposed fix for this here:

https://github.com/openai/codex/compare/main...Nicolas0315:codex:codex/mcp-missing-bearer-env-status?expand=1

Summary:

  • Treat bearer_token_env_var as unavailable when the named variable is missing, empty, or invalid Unicode in the active Codex process.
  • Keep the wire/json auth status compatible by mapping that internal unavailable state to not_logged_in.
  • Surface the missing env var clearly in codex mcp list text output and add bearer_token_env_var_status to codex mcp get text output.
  • Prefer a missing-env explanation in MCP startup failure text instead of implying an OAuth login is missing.

Verification:

  • just fmt
  • git diff --check
  • cargo test --manifest-path codex-rs\Cargo.toml -p codex-rmcp-client determine_auth_status_
  • cargo test --manifest-path codex-rs\Cargo.toml -p codex-mcp mcp_init_error_display_reports_missing_bearer_token_env_var
  • cargo test --manifest-path codex-rs\Cargo.toml -p codex-mcp mcp_startup_failure_reason_requires_existing_oauth_and_auth_failure
  • cargo test --manifest-path codex-rs\Cargo.toml -p codex-cli --test mcp_list list_and_get_report_missing_bearer_token_env_var -- --exact
  • cargo test --manifest-path codex-rs\Cargo.toml -p codex-cli --test mcp_list
  • cargo check --manifest-path codex-rs\Cargo.toml -p codex-cli -p codex-mcp -p codex-rmcp-client

SSH verification across my available machines:

  • cyberagent-mac-m5max: fresh clone, patch apply, git diff --check, and the targeted rmcp-client/codex-mcp/codex-cli tests passed.
  • nicolas-macbook-pro: fresh clone, patch apply, and git diff --check passed; Rust toolchain is not installed there.
  • rtx5060ti: fresh clone, patch apply, and git diff --check passed; Rust builds are blocked by the host resolving link to GNU coreutils instead of the MSVC linker.
  • nicolas2025: fresh clone, patch apply, and git diff --check passed; cargo/rustc paths on that host are zero-byte stubs.
Nicolas0315 · 2 months ago

Update: I rebased the branch onto current origin/main (5267e805f) and force-with-lease updated the fork branch to 7950b8a70.

Additional verification after the rebase:

  • Local Windows: just fmt, git diff --check main...HEAD, the targeted rmcp-client/codex-mcp tests, full codex-cli --test mcp_list, and cargo check -p codex-cli -p codex-mcp -p codex-rmcp-client all passed.
  • SSH fresh-checkout diff checks passed on cyberagent-mac-m5max, nicolas-macbook-pro, rtx5060ti, and nicolas2025 against the rebased fork branch.
  • SSH on cyberagent-mac-m5max: targeted rmcp-client, codex-mcp, and codex-cli tests passed from a fresh checkout of 7950b8a70.
isaac-ranger · 16 days ago

Linux Desktop reproduction, because apparently we are testing whether a desktop application can locate a desktop secret store without first completing a shell-environment scavenger hunt.

Environment

  • ChatGPT/Codex App package: 26.803.81509-1 (x86_64)
  • Bundled Codex CLI: 0.147.0-alpha.6.6
  • Platform: Linux 7.1.5-201.fc44.x86_64 x86_64 unknown
  • Desktop secret service: GNOME Secret Service, available and unlocked
  • Transport: authenticated Streamable HTTP MCP on localhost

Confirmed behavior

  1. codex mcp list --json reports the server as configured and enabled.
  2. The server configuration correctly names a bearer_token_env_var.
  3. The official /usr/lib/chatgpt/ChatGPT process and its bundled Codex child do not contain that variable when launched normally from the desktop entry.
  4. MCP initialize without the credential returns HTTP 401.
  5. The same initialization using the correct credential returns HTTP 200 and a valid MCP session.
  6. Because authentication failed, the server's tools are simply absent from the active task.

The current user journey is admirably compact:

configure server → enable server → omit credential → suppress useful authentication failure → erase tools → invite user to question whether the server ever existed

Security through existential uncertainty is innovative, but it is not quite secret integration.

Working local workaround

I verified a workaround that:

  1. Stores the credential in GNOME Keyring.
  2. Uses a user-level desktop launcher to retrieve it at app startup.
  3. Exports it only to the ChatGPT/Codex parent process so MCP authentication can initialize.
  4. Adds a shell_environment_policy.filters exclusion so the credential is not propagated into task shells or captured by future shell snapshots.
  5. Leaves no credential bytes in the launcher, desktop file, command line, model context, or issue report.

After that, keyring-authenticated MCP initialization returns HTTP 200. So the server, token, protocol, and Linux keyring are all functioning. The missing component is the desktop app doing the desktop-app part.

A desktop application requiring users to invent a keyring-backed launch wrapper for an authentication mode the application already advertises is certainly one interpretation of “integrated.”

What #30125 should fix

The proposed status/error improvement here is necessary: configured bearer auth and usable bearer auth are not the same state, despite their shared enthusiasm for the word “bearer.”

But it does not address the underlying Desktop UX gap tracked by:

  • #22029 — local secret store for sensitive credentials
  • #24401 — supported secret/env configuration for plugin MCP servers

For Desktop, the complete behavior should be:

  • Let users bind a named MCP secret in Settings.
  • Store it in Keychain / Credential Manager / Secret Service.
  • Resolve it locally for the exact MCP server.
  • Never expose it to task shells, snapshots, prompts, logs, or unrelated subprocesses.
  • Display the actual 401 and missing-secret alias when resolution fails.
  • Refresh the MCP client after the binding changes, or clearly require an app restart.

GNOME Secret Service was already running throughout this reproduction and, in a stirring demonstration of professional boundaries, the app never bothered it.

TarjinderSingh · 12 days ago

Additional macOS Desktop reproduction:

  • ChatGPT Desktop 26.810.50856, bundled Codex CLI 0.148.0-alpha.9
  • The configured bearer-token variable is available through launchctl getenv
  • A normally launched ChatGPT process does not contain that variable
  • MCP configuration nevertheless appears enabled and bearer-authenticated

There is also a separate runtime problem: when the bundled CLI is launched directly from a shell where the token is explicitly present, its MCP tool call still fails, while standalone Codex 0.147.0 completes the identical call.

That A/B result is documented separately here: https://github.com/openai/codex/issues/38794

This supports treating Desktop credential resolution and the bundled-runtime MCP failure as two independent defects.