Repository navigation
Trailing slash in OAuthMetadata's issuer causes issues with clients #1919
Description
Activity
- addedbugSomething isn't workingSomething isn't workingauthIssues and PRs related to Authentication / OAuthIssues and PRs related to Authentication / OAuthready for workEnough information for someone to start working onEnough information for someone to start working onP1Significant bug affecting many users, highly requested featureSignificant bug affecting many users, highly requested feature
on Jan 22, 2026 claude commented
on Jan 22, 2026 claudeboton Jan 22, 2026 – with Claude · Hidden as outdatedshow commentMore actions- added a commit that references this issue
on Jan 22, 2026 - added a commit that references this issue
on Jan 23, 2026 - added a commit that references this issue
on Feb 8, 2026 - added a commit that references this issue
on Feb 22, 2026 - added a commit that references this issue
on Mar 9, 2026 Planning to open a PR for this. The fix strips trailing slashes when constructing OAuth and protected-resource metadata in
routes.py, passing canonical URL strings sourl_preserve_empty_pathon the metadata models keeps RFC 8414/9728 wire forms intact (e.g.https://example.comnothttps://example.com/).This should also address #1265. Regression tests cover
AnyHttpUrl-typed issuer inputs and root-level PRM responses.- added 2 commits that reference this issue
on Jul 11, 2026 Adding a client-side data point, since this issue and the linked PRs focus on the server side (
build_metadata()/routes.pyemitting the slash).The same ambiguity bites in the client validator, against a third-party AS that nobody here controls.
validate_metadata_issuer()inmcp/client/auth/utils.py(mcp 2.0.0) does an exact string compare:if str(oauth_metadata.issuer) != expected_issuer: raise OAuthFlowError( f"Authorization server metadata issuer mismatch: {oauth_metadata.issuer} != {expected_issuer}" )
Real-world repro (Indeed's MCP server):
GET https://mcp.indeed.com/.well-known/oauth-protected-resource/claude/mcp→
{"authorization_servers":["https://secure.indeed.com/"]}← trailing slashGET https://secure.indeed.com/.well-known/oauth-authorization-server→
{"issuer":"https://secure.indeed.com"}← no trailing slash- Client aborts:
Authorization server metadata issuer mismatch: https://secure.indeed.com != https://secure.indeed.com/
The
expected_issueris derived from the PRM'sauthorization_serversentry, so a provider that is internally inconsistent between its two documents makes the flow unreachable. Per RFC 3986 §6.2.1 these are the same URI; the two documents just disagree on the character.Note the asymmetry: PRM
authorization_serversis a list of AnyHttpUrl, so pydantic normalizes a bare origin to a trailing slash on the client side too — meaning a server that correctly emits"issuer": "https://example.com"(exactly what the PRs here aim to produce) can still fail client validation once its origin is round-tripped throughAnyHttpUrl. Fixing only the server side may not close this.Workaround in use locally — compare with
.rstrip("/")on both sides, which keeps host/scheme/path mismatches raising:if str(oauth_metadata.issuer).rstrip("/") != expected_issuer.rstrip("/"):
Happy to open a separate issue/PR for the client-side validator if you'd prefer to keep this one scoped to metadata construction.
- added a commit that references this issue
on Sep 7, 2026 Hitting this issue with Google official MCP servers (Calendar, Gmail, Slides, etc)
Example for Calendar
https://calendarmcp.googleapis.com/.well-known/oauth-protected-resource/mcp/v1returns{ "resource": "https://calendarmcp.googleapis.com/mcp/v1", "authorization_servers": ["https://accounts.google.com/"] }while
https://accounts.google.com/.well-known/oauth-authorization-serverreturns"issuer": "https://accounts.google.com".If you need a reproducible example, I can provide one.
Initial Checks
Description
In the
.well-known/oauth-authorization-serverendpoint and , theissueris forced to always contain a trailing slash e.g.,https://your-mcp.com/instead ofhttps://your-mcp.comas a byproduct of using pydantic's
AnyHttpUrltype.This causes issues in both Google's ADK and IBM's MCP Context Forge because:
OAuthMetadata.issuercontains the trailing slash, the discovery process is aborted.OAuth 2.0 Authorization Server Metadata spec says that the client MUST remove trailing paths from when the issuer contains a path component:
if the trailing
/inhttps://example.com/is a "path component", and should thus be stripped by the client, so I think the spec is ambiguous about the responsibilities of the client in the case where there the issuer identifier value contains a lone trailing slash.I did note that the examples of issuer identifiers in the spec do not contain a lone trailing slash, i.e. they are
https://example.comrather thanhttps://example.com/.For these reasons, and
I think it's worth it to consider interpreting the spec as "the
issuerfield should not contain a trailing slash".I also believe this issue could be similar in mechanism, but different in scope, to what is described in #1265
Example Code
Python & MCP Python SDK