[FEATURE] http MCP server: allow OAuth fallback when a configured Authorization header is empty

Open 💬 1 comment Opened Jun 20, 2026 by nick-youngblut

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

For a type: http MCP server, Claude Code disables its OAuth flow whenever headers.Authorization is present in the config — regardless of the header's value. This makes it impossible to ship a single, fixed MCP config that uses a bearer token when one is available and falls back to interactive OAuth when it isn't.

This blocks a real use case: plugins install a fixed mcpServers config that end users cannot edit, and one committed entry needs to serve both audiences:

  • Remote / SSH / headless (where the OAuth loopback callback can't complete): authenticate with a bearer API token from an env var.
  • Local / desktop (no token set): fall back to the interactive OAuth loopback.

The idiomatic attempt uses an env var with an empty default:

{
  "mcpServers": {
    "example": {
      "type": "http",
      "url": "https://mcp.example.com/mcp",
      "headers": { "Authorization": "Bearer ${MCP_API_TOKEN:-}" }
    }
  }
}

Intent: var set → Bearer <token> (token auth); var unset → Bearer (empty) → OAuth fallback.

Current behavior: with MCP_API_TOKEN set, token auth works. With it unset, the header expands to Bearer (empty token), the server returns 401, and Claude Code reports:

Failed to reconnect to example: Server rejected the configured Authorization
header (HTTP 401). Check that the token is valid for this MCP endpoint —
OAuth fallback is disabled when headers.Authorization is set.

So an empty bearer is treated as "auth configured" (OAuth disabled) rather than "no credential" (OAuth enabled). This matches the docs ("…or remove the header to use the OAuth flow") — but removing the header isn't possible for a fixed, plugin-installed config.

SO THE CRUX:

removing the header isn't possible for a fixed, plugin-installed config

Reproduction:

  1. Configure a type: http server whose endpoint requires auth and advertises OAuth (RFC 9728 WWW-Authenticate).
  2. Set "headers": { "Authorization": "Bearer ${SOME_VAR:-}" } with SOME_VAR unset → connect → 401, no OAuth fallback.
  3. Set SOME_VAR to a valid token → token auth works.

Proposed Solution

When the resolved Authorization header value is empty or whitespace-only — including after ${VAR:-} expansion to an empty string — Claude Code should treat the header as absent and run the standard OAuth discovery/flow on 401, exactly as if no header were configured.

This makes the empty-default env-var pattern "just work": a present token → header auth; an absent token → OAuth. It requires no new config surface and is backwards-compatible (a non-empty header behaves exactly as today).

Alternative Solutions

  1. Explicit per-server opt-in for header-then-OAuth fallback. A flag that tries the configured header first and falls back to OAuth on 401:

``json
{ "type": "http", "url": "…", "headers": { "Authorization": "Bearer ${VAR:-}" }, "authFallback": "oauth" }
``
More explicit, but adds config surface.

  1. Honor headersHelper for plugin http servers (#41690). A dynamic header script could omit the header entirely when no token exists, which would re-enable OAuth — but it's currently silently ignored for plugin http servers.
  1. Allow plugins to mark a header as optional (e.g. a "${VAR}"-only value is dropped when unset, rather than failing to parse or sending an empty value). Narrower than #1.
  1. Status quo + server-side or mcp-remote workarounds — rejected as unworkable:
  • Server-side handling is impossible: the client sends an empty bearer and never attempts OAuth, so the server cannot rescue the no-token case.
  • Conditional omission of the header key: no config syntax exists for it.
  • Shipping a second npx mcp-remote plugin copy for the OAuth audience: two install paths, confusing for users.

Related

  • #51581 — ${VAR} substitution not applied to .mcp.json HTTP header values
  • #41690 — headersHelper silently ignored for plugin http servers
  • #59467 — HTTP MCP client ignores configured Authorization header when server also advertises OAuth

Priority

Critical - Blocking my work

Feature Category

MCP server integration

Use Case Example

Claude Cowork plugins REQUIRE the use of type: "http" for the MCP config:

{
    "mcpServers": {
        "arc-papers": {
            "type": "http",
            "url": "https://XXX.us-west1.run.app/mcp"
        }
    }
}

HOWEVER, that setup DOES NOT WORK with Google OAuth when running Claude Code remotely, since the OAuth callback tries to use the user's local machine.

This then REQUIRES 2 COPIES OF EVERY PLUGIN:

  • One for Claude Cowork (and Claude Code local)
  • "type": "http", with Google OAuth
  • One for Claude Code (remote)
  • Either mcp-remote with Google OAuth, or "type": "http" with bearer authentication (or other alternative to OAuth)

Additional Context

_No response_

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗