codex mcp login omits OAuth resource indicator from authorize request, causing wrong token audience

Open 💬 13 comments Opened Mar 7, 2026 by bluedog13
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

Summary

When running:

codex mcp login example-mcp --scopes myscope,offline_access

Codex CLI opens an OAuth authorize URL that includes the requested scopes, but omits the protected resource indicator for the target MCP server.

Because resource is missing, the authorization server can mint a token for a default audience instead of the MCP server being logged into.

Environment

  • Codex CLI: codex-cli 0.111.0
  • OS: macOS
  • Arch: arm64

Reproduction

  1. Configure an MCP server whose protected resource is something like:

``text
https://resource.example.com/mcp
``

  1. Run:

``bash
codex mcp login example-mcp --scopes myscope,offline_access
``

  1. Observe the authorize URL printed by Codex CLI.

Actual behavior

Codex CLI prints an authorize URL shaped like:

https://auth.example.com/connect/authorize?response_type=code&client_id=...&state=...&code_challenge=...&code_challenge_method=S256&redirect_uri=http%3A%2F%2F127.0.0.1%3ANNNNN%2Fcallback&scope=myscope+offline_access

The URL is missing:

&resource=https%3A%2F%2Fresource.example.com%2Fmcp

Login then reports success, but the resulting token audience is for a different resource than the MCP server being logged into.

Expected behavior

Codex CLI should include the discovered MCP protected resource in the OAuth request, for example:

...&scope=myscope+offline_access&resource=https%3A%2F%2Fresource.example.com%2Fmcp

The token request should also be checked to ensure the same resource value is carried through consistently.

Impact

This makes codex mcp login appear successful while producing a token for the wrong audience/resource, which then breaks authentication against the intended MCP server.

Notes

This appears to be an MCP OAuth flow issue in Codex CLI rather than a DCR issue.

The target MCP server advertises its protected resource via OAuth protected resource metadata, and the CLI appears to be honoring scopes but not the discovered resource indicator.

View original on GitHub ↗

13 Comments

github-actions[bot] contributor · 4 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #13890
  • #12589
  • #12762

Powered by Codex Action

bluedog13 · 4 months ago

Fix Available

I have a fix for this issue on my fork: bluedog13/codex@fix-mcp-oauth-resource-binding

Unable to open a PR directly (repo restricts to collaborators), but the branch is ready to merge.

Root Cause

rmcp 0.15.0 did not add the RFC 8707 resource parameter to the authorization URL or token exchange. The Codex wrapper only appended it when explicitly configured via oauth_resource, leaving the default case without any resource binding — causing tokens to be minted for the wrong audience.

Changes (6 files, 1 commit)

  • Bump rmcp 0.15.0 → 0.16.0 — natively adds resource=server_url to both the authorization URL and token-exchange request (RFC 8707)
  • Bump reqwest to 0.13 in rmcp-client only — required by rmcp 0.16
  • Align StreamableHttpClient::post_message with new extra_headers param added in rmcp 0.16
  • Add replace_query_param helper for explicit oauth_resource config overrides
  • Add integration tests for default and override resource flows

Verification

  • cargo check --workspace — passes
  • cargo test -p codex-rmcp-client — all 33 tests pass
  • New test oauth_login_includes_server_url_resource_by_default verifies resource=server_url in both auth URL and token request
  • New test oauth_login_uses_configured_oauth_resource_when_provided verifies override in auth URL

Compare: https://github.com/openai/codex/compare/main...bluedog13:codex:fix-mcp-oauth-resource-binding

razashariff · 4 months ago

The audience mismatch here is a symptom of a broader gap — MCP authentication is entirely dependent on OAuth, which breaks in multiple ways (wrong audience, expired tokens, missing DCR support, remote environments).

MCPS (MCP Secure) provides a complementary per-message signing layer that works independently of OAuth state. Every MCP message carries an ECDSA P-256 signature binding the agent identity (passport_id), a unique nonce, timestamp, and SHA-256 hash of the full JSON-RPC body. Even if the OAuth token is scoped to the wrong audience, the MCPS signature cryptographically proves which agent made the request and that the message was not tampered with in transit.

Published as an IETF Internet-Draft and available on npm as mcp-secure (zero dependencies).

SproutSeeds · 4 months ago

The default authorize path still only gets resource through append_query_param(..., "resource", oauth_resource) in codex-rs/rmcp-client/src/perform_oauth_login.rs.

  • Codex sends resource only when oauth_resource is configured explicitly
  • when the protected resource should come from MCP / OAuth metadata, the default authorize request goes out without it
  • the token exchange in rmcp 0.15.0 also runs without adding resource

The authorize request and token exchange are both missing default resource binding.
This is not a dynamic client registration issue.
Carry the discovered protected resource by default while still letting explicit oauth_resource override it on both requests.

bluedog13 · 4 months ago

Thanks for the analysis — you're describing the exact root cause my fix addresses. The rmcp 0.15.0 → 0.16.0 bump natively adds resource=server_url to both the authorization URL and the token exchange request by default (RFC 8707), so explicit oauth_resource configuration is no longer required for the default case. The explicit oauth_resource config still works as an override when needed.

You can see the full diff here: https://github.com/openai/codex/compare/main...bluedog13:codex:fix-mcp-oauth-resource-binding

bluedog13 · 3 months ago

Hi team — just checking if this is on the radar. This bug is actively impacting third-party MCP providers — Paddle's MCP server docs are advising users to manually append the resource parameter as a workaround (https://github.com/PaddleHQ/paddle-mcp-server/issues/59), and Kapa.ai has confirmed the issue is on the Codex side.

I also noticed #12589 was marked resolved but users are still reporting the same problem in the latest release.

My fix (rmcp 0.15 → 0.16 bump + resource binding) is rebased on latest main: https://github.com/openai/codex/compare/main...bluedog13:codex:fix-mcp-oauth-resource-binding

Happy to open a PR if that would be helpful.

gitNetw0rk · 2 months ago

Same bug/symptoms confirmed; when mcp auth flow from Codex VSCode extension is triggered!

langchain-infra · 2 months ago

Hi team any plans to fix this? Claude Code seems to be able to support this natively.

bluedog13 · 2 months ago

For additional context, Claude Code appears to have a similar unresolved DCR issue as well: it ignores scopes in its DCR flow. See: https://github.com/anthropics/claude-code/issues/4540

That case is somewhat easier to work around because default scopes can be assigned. In this case, however, the issue is more visible because there does not appear to be an equivalent way to assign a default resource for a MCP server.

Other agent harnesses, including Gemini CLI, Copilot CLI, and OpenCode CLI, appear to handle this correctly.

akelv · 2 months ago

Hi team — wanted to add a concrete reproduction from our side.

We run the Builder OS gateway at Cho Tot / Carousell Group — a Cloudflare Workers remote MCP server with OAuth 2.1 + PKCE + Dynamic Client Registration + RFC 8707 audience binding (Google Workspace SSO upstream). Today's Codex CLI connect attempt against our staging endpoint:

  • Codex completes DCR successfully (the gateway issues a client_id).
  • Codex opens /authorize?response_type=code&client_id=…&state=…&code_challenge=…&code_challenge_method=S256&redirect_uri=http%3A%2F%2F127.0.0.1%3A51539%2Fcallback&scope=rw — no resource= parameter.
  • Gateway rejects with invalid_request (400) per its RFC 8707 §2.1 check, and the Codex page renders "Invalid request".

Same symptom @bluedog13 described above. We took a look at the fix on their fork — the integration tests in codex-rs/rmcp-client/tests/oauth_resource.rs mock a full OAuth server (WWW-Authenticate, .well-known/oauth-protected-resource, .well-known/oauth-authorization-server, /register, /token) and assert that resource=<server_url> lands in both the authorize URL and the token-exchange body for the default path. That's the contract our gateway is enforcing.

Sharing in case it helps prioritise — we have Codex users in the org who'd love to connect our internal MCP gateway, and this is the one thing blocking them today.

Happy to test against a pre-release build if there's a way to pull one, or provide any additional reproduction details that would help. Thanks for the work on this!

mahesh-helpcare · 2 months ago

I prepared and tested a patch for this in a fork:
https://github.com/mahesh-helpcare/codex/tree/fix/mcp-oauth-resource-token-exchange

The issue is that oauth_resource can make the authorization request include resource, but the token exchange also needs to send the same resource. Providers such as Logfire reject the callback exchange with invalid_grant: resource parameter is required when the token request omits it.

What the branch does:

  • updates rmcp to 0.16.0, which carries the resource parameter through OAuth token exchange
  • updates the rmcp client adapter for the changed streamable HTTP client signature
  • preserves Codex custom CA handling for the reqwest 0.13 client path required by the newer rmcp
  • adds a regression test with a mock OAuth server that rejects token exchange unless resource is present

Mahesh tested a patched local Codex build against the Logfire MCP server and confirmed that login works.

I also rechecked current origin/main before posting this; it still has rmcp = 0.15.0, and perform_oauth_login.rs still only appends resource from configured oauth_resource.

Validation run locally:

  • cargo test --manifest-path codex-rs/Cargo.toml -p codex-client
  • cargo test --manifest-path codex-rs/Cargo.toml -p codex-rmcp-client

I tried to open this as a PR, but GitHub returned a CreatePullRequest permission error for this fork/account.

— Codex

gilangramadhan · 1 month ago

I can reproduce this with the official Scalev remote MCP connector on Codex CLI 0.133.0.

Environment:

  • Codex CLI: codex-cli 0.133.0
  • Platform: macOS / Darwin arm64
  • MCP server config:
[mcp_servers.scalev]
url = "https://mcp.scalev.com/mcp"

Scalev's current connector docs say the MCP URL is https://mcp.scalev.com/mcp, and that the assistant should discover the OAuth issuer at https://api.scalev.com/v3/oauth.

Observed protected resource metadata:

GET https://mcp.scalev.com/.well-known/oauth-protected-resource/mcp

returns a resource value of:

https://mcp.scalev.com/mcp

Repro:

  1. Configure the server above.
  2. Run:
codex mcp login scalev
  1. Codex prints an authorize URL like:
https://app.scalev.com/oauth/authorize?response_type=code&client_id=...&state=...&code_challenge=...&code_challenge_method=S256&redirect_uri=http%3A%2F%2F127.0.0.1%3A55171%2Fcallback%2F...

Actual behavior:

  • The authorize URL omits:
resource=https%3A%2F%2Fmcp.scalev.com%2Fmcp
  • Scalev rejects the browser authorization page with:
Authorization Error
Invalid resource.

Additional check:

If I manually add:

&resource=https%3A%2F%2Fmcp.scalev.com%2Fmcp

to the browser authorize URL, browser authorization completes and redirects to the Codex local callback, but Codex then fails the callback/token exchange with:

Error: failed to handle OAuth callback

Caused by:
    OAuth token exchange failed: Server returned error response: The resource is invalid or does not match the authorization request

Expected behavior:

  • Codex should include the discovered protected-resource resource value in the authorization request.
  • Codex should also include/carry the same resource consistently during token exchange.
  • codex mcp login scalev should complete without manual URL editing.

This appears to be the same resource-indicator class as this issue, and also overlaps with #20729 for the token-exchange side.

SproutSeeds · 1 month ago

I prepared a current-main branch for this path now:
SproutSeeds:cody/issue-13891-mcp-oauth-resource

Compare: https://github.com/openai/codex/compare/main...SproutSeeds:codex:cody/issue-13891-mcp-oauth-resource

It upgrades rmcp to 0.16.0, adapts the Codex rmcp client for the reqwest 0.13 / streamable HTTP signature changes, preserves Codex custom-CA setup for the reqwest clients Codex builds directly, and adds a regression test where a mock OAuth server rejects token exchange unless the same resource value is sent in both the authorize request and token request.

Local verification passed:

  • just fmt
  • cargo build -p codex-cli --bin codex
  • just test -p codex-rmcp-client
  • npx -y @bazel/bazelisk mod deps --lockfile_mode=update
  • npx -y @bazel/bazelisk mod deps --lockfile_mode=error
  • just fix -p codex-rmcp-client
  • git diff --check

I tried to open this as a draft PR, but GitHub returned a CreatePullRequest permission error for this fork/account, so I am linking the branch here instead.