codex mcp login omits OAuth resource indicator from authorize request, causing wrong token audience
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
- Configure an MCP server whose protected resource is something like:
``text``
https://resource.example.com/mcp
- Run:
``bash``
codex mcp login example-mcp --scopes myscope,offline_access
- 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.
13 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Fix Available
I have a fix for this issue on my fork:
bluedog13/codex@fix-mcp-oauth-resource-bindingUnable 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
resourceparameter to the authorization URL or token exchange. The Codex wrapper only appended it when explicitly configured viaoauth_resource, leaving the default case without any resource binding — causing tokens to be minted for the wrong audience.Changes (6 files, 1 commit)
rmcp0.15.0 → 0.16.0 — natively addsresource=server_urlto both the authorization URL and token-exchange request (RFC 8707)reqwestto 0.13 inrmcp-clientonly — required by rmcp 0.16StreamableHttpClient::post_messagewith newextra_headersparam added in rmcp 0.16replace_query_paramhelper for explicitoauth_resourceconfig overridesVerification
cargo check --workspace— passescargo test -p codex-rmcp-client— all 33 tests passoauth_login_includes_server_url_resource_by_defaultverifiesresource=server_urlin both auth URL and token requestoauth_login_uses_configured_oauth_resource_when_providedverifies override in auth URLCompare: https://github.com/openai/codex/compare/main...bluedog13:codex:fix-mcp-oauth-resource-binding
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).The default authorize path still only gets
resourcethroughappend_query_param(..., "resource", oauth_resource)incodex-rs/rmcp-client/src/perform_oauth_login.rs.resourceonly whenoauth_resourceis configured explicitlyrmcp0.15.0also runs without addingresourceThe 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_resourceoverride it on both requests.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
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
resourceparameter 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-bindingHappy to open a PR if that would be helpful.
Same bug/symptoms confirmed; when mcp auth flow from Codex VSCode extension is triggered!
Hi team any plans to fix this? Claude Code seems to be able to support this natively.
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.
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:
client_id)./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— noresource=parameter.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.rsmock a full OAuth server (WWW-Authenticate,.well-known/oauth-protected-resource,.well-known/oauth-authorization-server,/register,/token) and assert thatresource=<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!
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_resourcecan make the authorization request includeresource, but the token exchange also needs to send the sameresource. Providers such as Logfire reject the callback exchange withinvalid_grant: resource parameter is requiredwhen the token request omits it.What the branch does:
rmcpto0.16.0, which carries the resource parameter through OAuth token exchangereqwest 0.13client path required by the newer rmcpresourceis presentMahesh tested a patched local Codex build against the Logfire MCP server and confirmed that login works.
I also rechecked current
origin/mainbefore posting this; it still hasrmcp = 0.15.0, andperform_oauth_login.rsstill only appendsresourcefrom configuredoauth_resource.Validation run locally:
cargo test --manifest-path codex-rs/Cargo.toml -p codex-clientcargo test --manifest-path codex-rs/Cargo.toml -p codex-rmcp-clientI tried to open this as a PR, but GitHub returned a
CreatePullRequestpermission error for this fork/account.— Codex
I can reproduce this with the official Scalev remote MCP connector on Codex CLI
0.133.0.Environment:
codex-cli 0.133.0Scalev's current connector docs say the MCP URL is
https://mcp.scalev.com/mcp, and that the assistant should discover the OAuth issuer athttps://api.scalev.com/v3/oauth.Observed protected resource metadata:
returns a resource value of:
Repro:
Actual behavior:
Additional check:
If I manually add:
to the browser authorize URL, browser authorization completes and redirects to the Codex local callback, but Codex then fails the callback/token exchange with:
Expected behavior:
resourcevalue in the authorization request.codex mcp login scalevshould 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.
I prepared a current-main branch for this path now:
SproutSeeds:cody/issue-13891-mcp-oauth-resourceCompare: https://github.com/openai/codex/compare/main...SproutSeeds:codex:cody/issue-13891-mcp-oauth-resource
It upgrades
rmcpto0.16.0, adapts the Codex rmcp client for the reqwest0.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 sameresourcevalue is sent in both the authorize request and token request.Local verification passed:
just fmtcargo build -p codex-cli --bin codexjust test -p codex-rmcp-clientnpx -y @bazel/bazelisk mod deps --lockfile_mode=updatenpx -y @bazel/bazelisk mod deps --lockfile_mode=errorjust fix -p codex-rmcp-clientgit diff --checkI tried to open this as a draft PR, but GitHub returned a
CreatePullRequestpermission error for this fork/account, so I am linking the branch here instead.