From 36305581d78043284dfc50db3ddc5be8d861da41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=AF=E5=9F=BA=E9=AD=81?= <1412414664@qq.com> Date: Mon, 22 Jun 2026 21:53:45 +0800 Subject: [PATCH 1/2] fix: normalize OAuth redirect URI URL subtypes --- src/mcp/shared/auth.py | 7 +++++++ tests/shared/test_auth.py | 19 +++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/mcp/shared/auth.py b/src/mcp/shared/auth.py index 4fabb1a894..22b12e75b6 100644 --- a/src/mcp/shared/auth.py +++ b/src/mcp/shared/auth.py @@ -84,6 +84,13 @@ class OAuthClientMetadata(BaseModel): software_id: str | None = None software_version: str | None = None + @field_validator("redirect_uris", mode="before") + @classmethod + def _coerce_redirect_uris_to_any_url(cls, v: object) -> object: + if isinstance(v, list): + return [str(item) if isinstance(item, AnyUrl) else item for item in v] + return v + @field_validator( "client_uri", "logo_uri", diff --git a/tests/shared/test_auth.py b/tests/shared/test_auth.py index 7463bc5a8a..b5194fd948 100644 --- a/tests/shared/test_auth.py +++ b/tests/shared/test_auth.py @@ -1,9 +1,9 @@ """Tests for OAuth 2.0 shared code.""" import pytest -from pydantic import ValidationError +from pydantic import AnyHttpUrl, AnyUrl, ValidationError -from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthMetadata +from mcp.shared.auth import InvalidRedirectUriError, OAuthClientInformationFull, OAuthClientMetadata, OAuthMetadata def test_oauth(): @@ -109,6 +109,21 @@ def test_valid_url_passes_through_unchanged(): assert str(metadata.client_uri) == "https://udemy.com/" +def test_redirect_uri_url_subtypes_are_normalized_for_validation(): + client = OAuthClientInformationFull( + client_id="abc123", + redirect_uris=[AnyHttpUrl("https://example.com/callback")], + ) + + redirect_uri = AnyUrl("https://example.com/callback") + assert redirect_uri in (client.redirect_uris or []) + assert client.validate_redirect_uri(redirect_uri) == redirect_uri + assert client.model_dump(mode="json")["redirect_uris"] == ["https://example.com/callback"] + + with pytest.raises(InvalidRedirectUriError): + client.validate_redirect_uri(AnyUrl("https://example.com/other")) + + def test_information_full_inherits_coercion(): """OAuthClientInformationFull subclasses OAuthClientMetadata, so the same coercion applies to DCR responses parsed via the full model.""" From 6dcbf7cb3b66da87dc671b6d9f108066c8617833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=AF=E5=9F=BA=E9=AD=81?= <1412414664@qq.com> Date: Mon, 22 Jun 2026 21:58:16 +0800 Subject: [PATCH 2/2] fix: satisfy pyright for redirect URI normalization --- src/mcp/shared/auth.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mcp/shared/auth.py b/src/mcp/shared/auth.py index 22b12e75b6..2dfebcd88d 100644 --- a/src/mcp/shared/auth.py +++ b/src/mcp/shared/auth.py @@ -1,4 +1,4 @@ -from typing import Any, Literal +from typing import Any, Literal, cast from pydantic import AnyHttpUrl, AnyUrl, BaseModel, ConfigDict, Field, field_validator @@ -88,7 +88,8 @@ class OAuthClientMetadata(BaseModel): @classmethod def _coerce_redirect_uris_to_any_url(cls, v: object) -> object: if isinstance(v, list): - return [str(item) if isinstance(item, AnyUrl) else item for item in v] + redirect_uris = cast(list[object], v) + return [str(item) if isinstance(item, AnyUrl) else item for item in redirect_uris] return v @field_validator(