Meta MCP OAuth blocked by issuer mismatch: support explicit per-server trusted issuer override

Open 💬 6 comments Opened Aug 17, 2026 by ameenmo

What feature would you like to see?

Add an explicit, per-MCP trusted OAuth issuer override for remote MCP servers whose protected-resource metadata points to an authorization-server URL that publishes a different issuer.

Suggested configuration shape:

[mcp_servers.meta-ads]
url = "https://mcp.facebook.com/ads"
oauth_expected_issuer = "https://www.facebook.com"

The override should be opt-in, scoped to one exact MCP server URL, require an absolute HTTPS issuer URL, and preserve strict issuer validation for every server without the setting. Codex should still validate the authorization response and token exchange against the configured issuer. A warning during login would make the exception visible.

What problem would this solve?

Meta's official hosted MCP endpoints currently publish internally inconsistent OAuth discovery metadata. Codex CLI 0.147.0 rejects both before opening the browser.

For Meta Ads:

GET https://mcp.facebook.com/.well-known/oauth-protected-resource/ads
resource: https://mcp.facebook.com/ads
authorization_servers[0]: https://mcp.facebook.com/ads

GET https://mcp.facebook.com/.well-known/oauth-authorization-server/ads
issuer: https://www.facebook.com
authorization_endpoint: https://www.facebook.com/v26.0/dialog/oauth
token_endpoint: https://graph.facebook.com/v26.0/oauth/access_token

For Meta Developer Tools:

GET https://mcp.facebook.com/.well-known/oauth-protected-resource/devtools
resource: https://mcp.facebook.com/devtools
authorization_servers[0]: https://mcp.facebook.com/devtools

GET https://mcp.facebook.com/.well-known/oauth-authorization-server/devtools
issuer: https://www.facebook.com
authorization_endpoint: https://www.facebook.com/v26.0/dialog/oauth
token_endpoint: https://graph.facebook.com/v26.0/oauth/access_token

Reproduction:

codex mcp add meta-ads --url https://mcp.facebook.com/ads
codex mcp login meta-ads

Actual result:

Error: Authorization server issuer mismatch: expected https://mcp.facebook.com/ads, received https://www.facebook.com

The same failure occurs for https://mcp.facebook.com/devtools.

Meta should ideally correct its discovery metadata. Until then, a per-server explicit override would provide a bounded compatibility path without weakening issuer checks globally. A same-registrable-domain heuristic would be too broad; the requested setting should compare exact configured URLs only.

Environment:

Codex CLI: 0.147.0
macOS: Apple Silicon
Transport: streamable_http
Authentication: OAuth

This is distinct from #35459, which reports a Meta Ads streamable-HTTP handshake failure after OAuth has already completed. This request concerns the earlier discovery/issuer-validation stage.

View original on GitHub ↗

6 Comments

ameenmo · 11 days ago

Update from a working local patch: an exact per-server issuer override for https://www.facebook.com successfully moves both Meta endpoints past the reported issuer mismatch. Meta Ads then reaches a separate server-side limitation: invalid_client_metadata because dynamic registration is unavailable, matching #24103. Meta Developer Tools proceeds to the browser authorization flow. This confirms the issuer override is narrowly useful but does not bypass OAuth client registration policy.

jdcodes1 · 11 days ago

Adding the spec grounding and the exact code seam for anyone implementing this, since both support the request as filed.

Codex's rejection is spec-correct; Meta's metadata is non-conformant. RFC 9728 defines authorization_servers as a list of authorization-server issuer identifiers. Meta lists the resource URL itself (https://mcp.facebook.com/ads) as the issuer identifier, and then the AS metadata fetched from that identifier declares issuer: https://www.facebook.com. RFC 8414 §3.3 requires the issuer value in the retrieved metadata to be identical to the issuer identifier used to construct the well-known URL — so any conforming client will reject this pair, not just Codex. The durable fix is on Meta's side: publish authorization_servers: ["https://www.facebook.com"] in the protected-resource metadata (and serve the AS metadata under that issuer). Worth reporting to them in parallel; until then an explicit override is the right bounded escape hatch.

Where the check actually lives. The discovery + issuer validation is not in this repo's code but in the pinned upstream rmcp crate (rmcp = "=3.0.0" in codex-rs/Cargo.toml), inside rmcp::transport::AuthorizationManager, which codex mcp login drives here:

https://github.com/openai/codex/blob/1f41cc5d92722748e45cae9cecc6d883a4e7cbb1/codex-rs/rmcp-client/src/perform_oauth_login.rs#L699-L707

Notably, that call site already relaxes one discovery strictness knob per policy — auth_manager.set_allow_missing_issuer(true) — so there's precedent for exactly this kind of scoped relaxation. Implementing oauth_expected_issuer means threading the config value from McpServerConfig to this call site plus either (a) a new rmcp API (set_expected_issuer(...) alongside set_allow_missing_issuer) or (b) codex-side discovery that resolves metadata itself and hands rmcp pre-validated endpoints. (a) is smaller and benefits other rmcp consumers hitting the same Meta endpoints.

Design constraints worth keeping from the proposal, plus two additions:

  • Exact string match on the configured issuer, absolute HTTPS only, per-server scope — as proposed; no registrable-domain heuristics.
  • Keep RFC 9207 enforcement against the override value: the callback's iss parameter is already plumbed through handle_callback_with_issuer (perform_oauth_login.rs#L632-L639), so the override should become the expected iss, not disable the check.
  • Pin the authorization/token endpoints to origins consistent with the overridden issuer's metadata at login time and warn if they later change — the override is a statement about one specific issuer, and silently following moved endpoints would reopen the mix-up risk the issuer check exists to prevent.
dbraaten42 · 8 days ago

This is also an issue with gitlab, the problem is not unique to Meta

Error: Authorization server issuer mismatch: expected https://gitlab.com/api/v4/mcp, received https://gitlab.com

anupamchugh · 8 days ago

Another affected vendor: Namecheap's hosted MCP (https://mcp.namecheap.com/mcp). It fails the same way, but it is a distinct sub-case from Meta and GitLab, and I think it matters for how the override is scoped.

Namecheap's discovery metadata is internally consistent. Unlike Meta, it satisfies RFC 9728 and RFC 8414 §3.3:

GET https://mcp.namecheap.com/.well-known/oauth-protected-resource
{"resource":"https://mcp.namecheap.com/",
 "authorization_servers":["https://mcp.namecheap.com"],
 "scopes_supported":["openid","offline_access","mcp.namecheap.com"]}

GET https://mcp.namecheap.com/.well-known/oauth-authorization-server
issuer:                 https://mcp.namecheap.com     <- matches the issuer identifier
authorization_endpoint: https://www.namecheap.com/connect/authorize
token_endpoint:         https://auth.namecheap.com/connect/token
registration_endpoint:  https://mcp.namecheap.com/register
authorization_response_iss_parameter_supported: (absent)

Discovery validates cleanly. The violation is purely in the authorization response: the callback returns the host of the authorization endpoint rather than the declared issuer identifier, contrary to RFC 9207 §2.

http://localhost:3118/callback?code=...&state=...&iss=https%3A%2F%2Fwww.namecheap.com

Reproduction (codex-cli 0.147.0, macOS Apple Silicon, streamable_http):

codex mcp add namecheap --url https://mcp.namecheap.com/mcp
Error: failed to handle OAuth callback
Caused by:
    Authorization server issuer mismatch: expected https://mcp.namecheap.com, received https://www.namecheap.com

Claude Code rejects it identically, so this is not client-specific:

Issuer mismatch in authorization response (RFC 9207): expected "https://mcp.namecheap.com", received "https://www.namecheap.com"

Why the sub-case matters. For Meta the mismatch surfaces at discovery, so an override applied there is sufficient. For Namecheap discovery is fine and the mismatch only appears at the RFC 9207 callback check — so an implementation that only overrides the expected discovery issuer would not fix this endpoint. @jdcodes1's third constraint already covers it ("the override should become the expected iss, not disable the check"), and I would just make that explicit as a requirement rather than a nicety: the setting has to reach handle_callback_with_issuer, not only the discovery comparison.

Also worth noting: Namecheap omits authorization_response_iss_parameter_supported from its AS metadata while sending iss anyway. RFC 9207 §2.4 requires clients to validate iss whenever it is present, so silently ignoring an unadvertised iss would not be a safe general workaround either.

Namecheap has no public repository for the hosted MCP (their GitHub org carries ILC, terraform-provider-namecheap, and go-namecheap-sdk, none of it MCP), so there is no upstream tracker to file against — which makes the bounded client-side override the only available path for this one until support routes it internally.

mdrbx · 3 days ago

+1 As it also blocks Claude Design MCP, which we be a nice addition as there is no equivalent with OpenAI so far.

amirahmady · 1 day ago

For the GitLab report above: that one is a different root cause and is now filed separately as #40885.

GitLab's metadata agrees with itself — authorization_servers[0] and the authorization server's issuer are both https://gitlab.com — so unlike Meta's endpoints there is nothing non-conformant to override. Codex appears to take the expected issuer from the resource URL instead of authorization_servers, which rejects a conformant server. Worth keeping distinct, since a trusted-issuer override would mask that rather than fix it.