MCP app-server 0.147 strips trailing slash from expected authorization-server issuer
What version of Codex is running?
codex-cli 0.147.0-alpha.6.5, bundled with ChatGPT Desktop 26.803.41515 (build 6321).
Platform
macOS arm64.
What issue are you seeing?
The 0.147 app-server rejects an already-authenticated Streamable HTTP MCP server while initializing/calling its tools because the expected authorization-server issuer has had its trailing slash removed, but the issuer returned by authorization-server metadata retains that slash.
Sanitized form of the error:
issuer mismatch:
expected https://auth.example.invalid/oauth2
received https://auth.example.invalid/oauth2/
The MCP URL-only configuration has no static bearer token or headers. mcpServerStatus/list reports authStatus=oAuth but exposes an empty tool catalog; a direct read-only mcpServer/tool/call then returns the issuer mismatch.
Reproduction
- Configure a Streamable HTTP MCP server using only a URL such as
https://mcp.example.invalid/mcp. - Complete OAuth once. The authorization-server metadata advertises an issuer ending in
/, such ashttps://auth.example.invalid/oauth2/. - Start the bundled 0.147 app-server over stdio.
- Send
initializewithexperimentalApi=true, theninitialized. - Start an ephemeral read-only thread.
- Call
mcpServerStatus/list, followed by a read-onlymcpServer/tool/call.
Control evidence
On the same machine, against the same unchanged MCP endpoint and stored OAuth account:
- pinned
@openai/codex@0.146.1app-server succeeds; - its allowlisted read-only catalog contains exactly three tools;
- a fresh serial traversal completed 42/42 tool calls (3 metadata and 39 tree calls) with zero OAuth or issuer errors;
- bundled
0.147.0-alpha.6.5fails before the first tool result with the mismatch above.
No PAT, copied bearer token, custom OAuth client secret, browser scraping, or direct REST fallback is involved.
Expected behavior
Codex should preserve and compare the same canonical issuer value on both sides. If authorization-server metadata declares https://auth.example.invalid/oauth2/, the client must not strip the slash only from its expected value and then reject the metadata value it originally discovered.
Relationship to existing issues
This appears distinct from:
- #31573, where the OAuth callback handler drops the RFC 9207
issparameter; - #35716, where an OAuth
resourceparameter gains a trailing slash.
This failure happens when an already-authenticated MCP server is initialized/called through app-server, and concerns authorization-server issuer canonicalization rather than callback parsing or the protected-resource parameter.
6 Comments
seme
same problem
I traced this to the RMCP dependency rather than the app-server wrapper. Current Codex
mainpinsrmcp 3.0.0. In that release, protected-resource discovery reconstructs the expected issuer from the well-known URL; for a non-root issuer it usesissuer.set_path("/{issuer_path}"), which turns/oauth2/into/oauth2. Its equality helper only tolerates a trailing slash at the origin root, so the mismatch here is deterministic.RMCP 3.1.2 already carries the narrow fix: it preserves the exact
authorization_serversidentifier through discovery and addsprotected_resource_metadata_preserves_non_root_issuer_trailing_slash.That suggests the smallest Codex path is upgrading RMCP and retaining an integration fixture where protected-resource metadata advertises
https://auth.example.invalid/oauth2/and authorization-server metadata returns that exact issuer. I have not run the Codex suite with 3.1.2, so I cannot yet rule out unrelated dependency-upgrade fallout.I can reproduce a related issuer-validation failure with a GitLab Self-Managed Streamable HTTP MCP server on Windows.
Environment:
codex-cli 0.147.0-alpha.6.6https://gitlab.example.org/api/v4/mcpConfiguration:
codex mcp loginfails before opening the browser authorization flow:The server's OAuth discovery chain appears internally consistent:
/api/v4/mcpreturns401with:``
text
``WWW-Authenticate: Bearer resource_metadata="https://gitlab.example.org/.well-known/oauth-protected-resource/api/v4/mcp"
``
json
``{
"authorization_servers": [
"https://gitlab.example.org"
]
}
https://gitlab.example.org/.well-known/oauth-authorization-serverreturns:``
json
``{
"issuer": "https://gitlab.example.org"
}
So this is not a trailing-slash mismatch, but Codex is still expecting the protected MCP resource URL as the authorization-server issuer rather than preserving the
authorization_serversidentifier from protected-resource metadata.Silentpartnercoding's RMCP discovery analysis seems relevant. Would updating RMCP to the version that preserves the exact discovered authorization-server identifier also address this root-issuer versus MCP-resource mismatch?
I can also reproduce!
TL;DR: since 0.147.0-alpha.6.5, Codex never mints or refreshes an OAuth token against my MCP server whose endpoint lives on a subpath (like
https://<host>/mcp). Users with an existing token keep working until it expires, after that the client silently retries the dead token forever instead of re-authenticating, and the UI still shows the server as authenticated.I run a production MCP server with this setup:
https://<host>/mcp(a subpath, not the root)issuer:https://<host>(the origin, no path)/.well-known/oauth-protected-resourceand/.well-known/oauth-protected-resource/mcp: both return 200 and point at the origin as the authorization server/.well-known/oauth-authorization-server: returns 200 with the origin asissuer/.well-known/oauth-authorization-server/mcp,/.well-known/openid-configuration/mcp): return 404unlike the GitLab report above, there is no wildcard metadata involved (discovery either 404s or returns consistent origin metadata).
This is what I'm observing (grouping a week of traffic by
codex-mcp-clientuser agent):| version | outcome against a subpath-resource server |
| -------------------------------------------------------------- | ----------------------------------------- |
| 0.146.0 / 0.146.0-alpha.9.2 / 0.146.1 | completes OAuth, successful tool calls |
| 0.147.0-alpha.1.2 | completes OAuth, successful tool calls |
| 0.147.0-alpha.6.5 and later (0.147.0, 0.148.0-alphas, 0.148.0) | zero successful requests |
2 things:
/tokenor any/.well-knownpath (caveat: some discovery flows send an empty user agent, which we cannot attribute per version) (versions up to0.147.0-alpha.1.2reach both)We serve everything rmcp's root-fallback discovery needs, and 0.147.0-alpha.1.2 (already on rmcp 3.0.0) completes the flow successfully, hence the change that broke subpath-resource servers appears to land in codex between 0.147.0-alpha.1.2 and 0.147.0-alpha.6.5...
lmk if you have any further details 🙏