OAuth authorization code is received but never exchanged for remote streamable-HTTP MCP server

Open 💬 1 comment Opened Aug 4, 2026 by MarPfi-hub

What issue are you seeing?

Summary

For a remote streamable-HTTP MCP server requiring OAuth, Codex completes discovery, dynamic client registration, and authorization, receives a valid authorization code at its loopback callback, and logs start exchange code for token. It then sends no HTTP request to the token endpoint. The code expires unredeemed, the connection remains not-ready, and no tools are registered.

Reproduced on 11 consecutive login attempts over approximately 3.5 hours. A control client (MCP Inspector) completes the identical OAuth flow against the identical endpoint successfully.

Environment

  • Codex CLI: 0.146.0-alpha.9.2
  • Codex Desktop: 26.727.51351
  • OS: Windows 11 Pro 10.0.26200
  • Server: ASP.NET Core / .NET 9; ModelContextProtocol.AspNetCore 2.0.0; OpenIddict 7.6.0
  • MCP transport: stateless streamable HTTP
  • OAuth client: public; token_endpoint_auth_method=none; PKCE S256 required

Configuration:

[mcp_servers.<server-name>]
enabled = true
url = "https://<HOST>/<RELAY_ID>/mcp"

Client-side evidence

Representative attempt:

[13:58:42Z] ERROR rmcp::transport::worker
worker quit with fatal: Transport channel closed, when AuthRequired(... invalid_token ...)

[13:58:47Z] DEBUG rmcp::transport::auth
start exchange code for token: "<truncated>"
# No subsequent auth entry, HTTP request, success, or error.

[13:59:20Z] ERROR rmcp::transport::worker
worker quit with fatal: Transport channel closed, when AuthRequired(...)

Downstream state:

MCP server tools unavailable while building tool list
has_cached_tools=false startup_complete=true

omitting MCP server without an exact ready client

Tool calls then return locally synthesized -32603 Internal error responses without a network round trip.

Server-side evidence

For every failing Codex attempt the server records:

POST /WebApi/OpenIdAuth/Register  -> 201
GET  /WebApi/OpenIdAuth/Authorize -> 302 (authorization code returned to loopback callback)
POST /mcp                          -> 401 (no Authorization header)

No POST /WebApi/OpenIdAuth/Token arrives. The authorization server recorded 11 issued authorization codes with RedemptionDate = NULL.

The authorization response is valid: state round-trips byte-for-byte, iss matches the discovered issuer, and the redirect target matches both the authorization request and dynamic client registration.

Control client

MCP Inspector uses the same server, relay, and build and successfully performs registration, authorization, POST /Token (200), authenticated MCP initialization, tools/list, and tool calls. The authorization code is redeemed and a refresh token is issued.

What steps can reproduce the bug?

  1. Configure a remote streamable-HTTP MCP server that requires OAuth, uses dynamic client registration, a public client, and PKCE S256.
  2. Run codex mcp login <server-name> and complete the browser authorization.
  3. Observe the authorization server: a code is issued but is never redeemed; no request reaches token_endpoint.
  4. Inspect ~/.codex/logs_2.sqlite: start exchange code for token appears, preceded by the fatal AuthRequired worker shutdown, but there is no later token-request, success, or failure entry.
  5. Call any tool from that MCP server. Codex returns -32603 Internal error in approximately 1.3 seconds with no server-side request.

For comparison, repeat with MCP Inspector against the same endpoint; it redeems the code and calls tools successfully.

What is the expected behavior?

After receiving the authorization code at the loopback redirect URI, Codex should send POST <token_endpoint> with grant_type=authorization_code, code, code_verifier, redirect_uri, client_id, and resource; store the access/refresh tokens; reconnect to the MCP endpoint with the bearer token; and register the server's tools.

If token exchange fails, Codex should surface a specific OAuth error rather than silently omitting the server and returning -32603 for later tool calls.

Additional information

Impact

This OAuth-protected MCP server is unusable from Codex. The user-visible symptom is a generic -32603 Internal error, while the server sees no token request or tool call, making diagnosis expensive.

Secondary observation: OAuth store contention

Multiple Codex processes contend on both OAuth stores around the failed attempts:

codex_rmcp_client::oauth::store_lock::contention
waiting for another process to finish updating MCP OAuth store state
store=fallback file / encrypted secrets

Four Codex-related processes were running concurrently. It is not proven that contention causes the abort, but it was present during the failures.

Hypothesis (not proven)

The initial AuthRequired challenge is treated as a fatal transport condition, and code exchange may subsequently be attempted through already-torn-down state. An alternative is failure inside the OAuth credential-store lock path with the error swallowed. In either case, the key observed fact is that the token endpoint receives no request after start exchange code for token.

Ruled out

  • Server availability/configuration: control client succeeds against the same endpoint and build.
  • Discovery: the well-known metadata endpoints return 200 JSON.
  • Challenge metadata: the 401 advertises resource and resource_metadata.
  • TLS and redirect mismatch: discovery succeeds and redirect values match.
  • Code expiry: exchange starts within one second of issuance; codes remain valid for five minutes.
  • Server-side tool defects: the same tools execute successfully through MCP Inspector.

Possibly related, but different signatures

#26760, #12589, #33403, #19154

View original on GitHub ↗

1 Comment

AmazingTurtle · 2 days ago

I can reproduce this on stable codex-cli 0.148.0, not only the alpha version originally reported.

Environment

  • Windows 11 Pro 10.0.26100
  • Windows ARM64
  • Node.js v24.19.0 ARM64
  • Native codex-win32-arm64 binary
  • Codex app-server using stdio
  • MCP server: Context7 (https://mcp.context7.com/mcp/oauth)
  • mcp_oauth_credentials_store = "file"
  • Unique per-session CODEX_HOME

Observed behavior

  1. mcpServer/oauth/login returns a valid authorization URL.
  2. Context7 redirects with code, state, and iss=https://clerk.context7.com.
  3. The complete callback, including its query string, reaches Codex’s callback listener on 127.0.0.1:1455.
  4. The callback listener returns a successful 2xx response.
  5. Codex never emits mcpServer/oauthLogin/completed, either for success or failure.
  6. The OAuth operation remains pending indefinitely.
  7. No .credentials.json is written to the configured CODEX_HOME.

I verified that only one Codex app-server process is running.

The strongest boundary I can currently establish is that Codex accepts the OAuth callback over HTTP but does not subsequently produce a terminal OAuth notification or credential artifact.

I cannot confirm from the provider side whether Codex sends the token-endpoint POST. Is there an app-server tracing flag or a safe logs_2.sqlite query that would expose the token-exchange transition or failure?