Remote MCP: scopes_supported should be extracted from the protected resource metadata document

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

What version of Codex CLI is running?

0.116.0

What subscription do you have?

Enterprise

Which model were you using?

_No response_

What platform is your computer?

_No response_

What terminal emulator and version are you using (if applicable)?

_No response_

What issue are you seeing?

Hello team,

I believe there is an issue with https://github.com/openai/codex/pull/14419 as Codex extracts the scopes_supported value from the auth server metadata document (/.well-known/oauth-authorization-server) instead of the protected resource one (/.well-known/oauth-protected-resource).

The MCP specification states the following:

https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#scope-selection-strategy

If scope is not available, use all scopes defined in scopes_supported from the Protected Resource Metadata document, omitting the scope parameter if scopes_supported is undefined.

I am currently developing an MCP server and was testing the DCR flow with the Codex CLI. With this bug, the Codex MCP client is ignoring the scopes I am defining in /.well-known/oauth-protected-resource and thus creates an OAuth app with the wrong scopes.

What steps can reproduce the bug?

Define scopes_supported in /.well-known/oauth-protected-resource

What is the expected behavior?

scopes_supported is extracted from /.well-known/oauth-protected-resource

Additional information

_No response_

View original on GitHub ↗

7 Comments

github-actions[bot] contributor · 5 months ago

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

  • #13891
  • #13890

Powered by Codex Action

mrbob-git · 5 months ago

I traced this to the scope discovery added in #14419.

Right now codex-rs/rmcp-client/src/auth_status.rs only fetches /.well-known/oauth-authorization-server... and reads scopes_supported from that response. That value is then threaded through codex-rs/core/src/mcp/auth.rs as discovered_scopes and used for login when there are no explicit/configured scopes.

I do not see any corresponding read of /.well-known/oauth-protected-resource for scopes_supported, so this matches the behavior reported here.

The smallest fix looks like:

  • keep using the authorization-server metadata for authorization_endpoint / token_endpoint
  • source scopes_supported from the protected-resource metadata document instead
  • add a regression test where the two well-known docs advertise different scopes and verify Codex uses the protected-resource value
matoous · 4 months ago

Hi! I had a similar issue for our MCP where authorization server and the MCP server itself are two different (independent) hosts. I have fixed and tested it locally, by updating rmcp to 1.3 and adjusting the auth logic. It's not a log of changes that are required and I would be happy to open a pull request(s) if given the option.

s1korrrr · 4 months ago

I reproduced this against current main and prepared a small contributor-grade fix here:

Repro:

  1. Point Codex at a remote MCP server whose protected resource metadata advertises scopes_supported, but whose authorization server metadata omits it.
  2. Start OAuth/device-code auth flow.
  3. Current main fails to surface the protected-resource scopes because discovery only reads scopes_supported from the authorization server metadata path.

Root cause:
codex-rs/rmcp-client/src/auth_status.rs only pulls scopes_supported from OAuth Authorization Server Metadata. The MCP auth guidance expects clients to use the Protected Resource Metadata document for that field when scope support is advertised there.

Approaches considered:

  • Thread scopes_supported through the existing auth-server-only discovery path by adding fallback logic in place.
  • Split discovery into explicit authorization-server metadata + protected-resource metadata fetches, then assemble the final discovery result from both.

I chose the second option because it keeps the spec boundary explicit, stays small, and avoids further overloading the authorization-server metadata helper.

Fix summary:

  • keep authorization-server discovery for endpoints/issuer
  • add protected-resource metadata discovery (/.well-known/oauth-protected-resource/... with root fallback)
  • prefer scopes_supported from protected-resource metadata when present
  • add focused unit coverage for the pure metadata merge and path selection logic
  • add HTTP-backed regression tests for protected-resource scope preference

Validation:

  • passed: cargo test -p codex-rmcp-client streamable_http_oauth_discovery_prefers_protected_resource_scopes_without_fallback -- --nocapture
  • passed: cargo test -p codex-rmcp-client streamable_http_oauth_discovery_omits_scopes_when_protected_resource_metadata_has_none -- --nocapture
  • passed: cargo test -p codex-rmcp-client protected_resource_metadata_paths_try_endpoint_path_before_root -- --nocapture
  • passed: cargo fmt --check -p codex-rmcp-client
  • passed: git diff --check

Local limitation in this sandbox: the existing live HTTP-backed tests try to bind 127.0.0.1, which is blocked here (Operation not permitted), so I added the integration-style cases but could not execute those network-bound tests locally in this run.

If this direction looks right and you want an invited PR, I can open/retarget it quickly.

FoROzX · 21 days ago

I can reproduce the same issue on Codex 0.147.0.
Before on 0.141.0 I was able to work around this by using codex mcp login <name> --scopes openid,profile,email,offline_access

However, in newer versions the OAuth flow starts automatically as part of codex mcp add --url ..., and mcp add does not provide an option to specify the scopes used for that OAuth flow.

Because Codex currently ignores scopes_supported from the protected-resource metadata and instead requests all scopes advertised by the authorization server, the automatic login fails with invalid_scope. As far as I can tell, there is also no option to add the remote MCP server without immediately starting the OAuth flow, so I currently have to fall back to Codex 0.141.0 in order to use the mcp login --scopes ... workaround.

MetalCar · 21 days ago
I can reproduce the same issue on Codex 0.147.0. Before on 0.141.0 I was able to work around this by using codex mcp login <name> --scopes openid,profile,email,offline_access However, in newer versions the OAuth flow starts automatically as part of codex mcp add --url ..., and mcp add does not provide an option to specify the scopes used for that OAuth flow. Because Codex currently ignores scopes_supported from the protected-resource metadata and instead requests all scopes advertised by the authorization server, the automatic login fails with invalid_scope. As far as I can tell, there is also no option to add the remote MCP server without immediately starting the OAuth flow, so I currently have to fall back to Codex 0.141.0 in order to use the mcp login --scopes ... workaround.

I also need the functionallity that FoROzX mentioned. And have the same use case. When do we get that fix? :)

jmtt89 · 14 days ago

Additional confirmation on codex-cli 0.147.0. All hostnames, client names, and non-standard scope names below are intentionally sanitized.

This also reproduces during codex mcp login with an existing OAuth client, so a DCR failure is not required to trigger the bug.

Sanitized setup

Protected Resource Metadata advertises only resource-specific scopes:

{
  "resource": "https://mcp.example.test/tools",
  "authorization_servers": ["https://auth.example.test/realm"],
  "scopes_supported": [
    "openid",
    "profile",
    "email",
    "offline_access",
    "example:read"
  ]
}

The shared authorization server's global OIDC metadata advertises those scopes plus unrelated scopes used by other applications:

{
  "scopes_supported": [
    "openid",
    "profile",
    "email",
    "offline_access",
    "example:read",
    "unrelated_client_scope"
  ]
}

The OAuth client is intentionally not allowed to request unrelated_client_scope.

Actual behavior

Running:

codex mcp login example

causes the first authorization request to contain the authorization server's global scope catalog, including unrelated_client_scope, instead of the Protected Resource Metadata list. The provider rejects that request with invalid_scope, after which Codex prints:

OAuth provider rejected discovered scopes. Retrying without scopes…

The second authorization request omits scope entirely and login reports success.

The resulting stored credential contains only the provider's default scopes (for example, email and profile), not offline_access. Consequently, it receives a normal short-lived refresh token rather than an offline credential. After that refresh token expires, a later Codex process fails during MCP startup with:

invalid_grant: Token is not active

This makes the fallback look successful while causing repeated interactive reauthentication later.

Control checks

  • Removing another MCP server that used the same authorization server and OAuth client did not change the behavior; the login still requested the global scopes and entered the same fallback.
  • Codex stored separate credential entries and distinct refresh tokens for each MCP server, so this does not appear to be credential storage keyed only by client_id.
  • Supplying the resource scopes explicitly with codex mcp login --scopes ... avoids requesting the unrelated global scope and is a workable manual mitigation where that command path is available.

Expected behavior

Codex should use scopes_supported from Protected Resource Metadata and should not replace it with the authorization server's global scope catalog. This also matches the current Codex documentation:

https://learn.chatgpt.com/docs/extend/mcp?surface=cli#other-configuration-options

The retry-without-scopes path compounds the original discovery bug by silently discarding offline_access; avoiding the incorrect global scope set prevents that fallback and the later refresh-token failure.