Skip to content

Commit 7f01cd0

Browse files
committed
Treat an unstamped CIMD record like one stamped elsewhere: drop its tokens, then stamp it
Records this SDK creates for a client ID metadata document always carry the issuer stamp, so an unstamped one comes from an older store and its tokens have no confirmed origin. Dropping them once and stamping the record converges after a single re-authorization instead of presenting a refresh token of unknown provenance to whichever server PRM names.
1 parent 76542f8 commit 7f01cd0

2 files changed

Lines changed: 14 additions & 8 deletions

File tree

src/mcp/client/auth/oauth2.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -656,10 +656,11 @@ async def async_auth_flow(self, request: httpx2.Request) -> AsyncGenerator[httpx
656656
self.context.client_info is not None
657657
and self.context.client_info.client_id == self.context.client_metadata_url
658658
and self.context.auth_server_url is not None
659-
and self.context.client_info.issuer not in (None, self.context.auth_server_url)
659+
and self.context.client_info.issuer != self.context.auth_server_url
660660
):
661661
# A CIMD client_id is portable across authorization servers; the tokens issued
662-
# under it and the cached metadata are not. Keep the record, re-stamped.
662+
# under it and the cached metadata are not. Keep the record, re-stamped. An
663+
# unstamped record's tokens have unknown provenance and are dropped the same way.
663664
self.context.clear_tokens()
664665
self.context.oauth_metadata = None
665666
self.context.client_info.issuer = self.context.auth_server_url

tests/client/test_auth.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3282,15 +3282,20 @@ async def test_expired_token_is_not_refreshed_ahead_of_the_request_before_metada
32823282

32833283

32843284
@pytest.mark.anyio
3285+
@pytest.mark.parametrize("stamped_issuer", ["https://old-as.example.com", None], ids=["stamped-elsewhere", "unstamped"])
32853286
async def test_cimd_record_is_restamped_and_its_tokens_and_cached_metadata_dropped_when_prm_names_a_new_issuer(
3286-
client_metadata: OAuthClientMetadata, mock_storage: MockTokenStorage, valid_tokens: OAuthToken
3287+
client_metadata: OAuthClientMetadata,
3288+
mock_storage: MockTokenStorage,
3289+
valid_tokens: OAuthToken,
3290+
stamped_issuer: str | None,
32873291
) -> None:
32883292
"""SEP-2352 for CIMD: the URL client_id survives an authorization-server change, nothing else does.
32893293
3290-
A long-lived provider holds a CIMD record stamped with the old issuer, tokens minted there, and
3291-
the old server's cached metadata. As soon as PRM names a different issuer, the tokens and the
3292-
cached metadata are dropped and the record is re-stamped and persisted, so a failed
3293-
rediscovery cannot leave the old endpoints in play and no refresh reaches the new server.
3294+
A long-lived provider holds a CIMD record stamped with another issuer (or, from an older store,
3295+
not stamped at all), tokens of matching provenance, and cached metadata. As soon as PRM names
3296+
the issuer in use, the tokens and the cached metadata are dropped and the record is re-stamped
3297+
and persisted, so a failed rediscovery cannot leave old endpoints in play and no refresh token
3298+
of unconfirmed origin reaches the named server.
32943299
"""
32953300
cimd_url = "https://client.example.com/.well-known/mcp-client"
32963301
provider = OAuthClientProvider(
@@ -3300,7 +3305,7 @@ async def test_cimd_record_is_restamped_and_its_tokens_and_cached_metadata_dropp
33003305
client_metadata_url=cimd_url,
33013306
)
33023307
provider.context.client_info = OAuthClientInformationFull(
3303-
client_id=cimd_url, token_endpoint_auth_method="none", issuer="https://old-as.example.com"
3308+
client_id=cimd_url, token_endpoint_auth_method="none", issuer=stamped_issuer
33043309
)
33053310
provider.context.current_tokens = valid_tokens
33063311
provider.context.token_expiry_time = time.time() + 1800

0 commit comments

Comments
 (0)