Skip to content

Commit 062b5b4

Browse files
committed
fix: normalize OAuth redirect URI URL subtypes
1 parent 616476f commit 062b5b4

2 files changed

Lines changed: 20 additions & 1 deletion

File tree

src/mcp/shared/auth.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ def _empty_string_optional_url_to_none(cls, v: object) -> object:
8585
return None
8686
return v
8787

88+
@field_validator("redirect_uris", mode="before")
89+
@classmethod
90+
def _normalize_redirect_uris(cls, v: object) -> object:
91+
if isinstance(v, list):
92+
return [str(uri) for uri in v]
93+
return v
94+
8895
def validate_scope(self, requested_scope: str | None) -> list[str] | None:
8996
if requested_scope is None:
9097
return None

tests/shared/test_auth.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Tests for OAuth 2.0 shared code."""
22

33
import pytest
4-
from pydantic import ValidationError
4+
from pydantic import AnyHttpUrl, AnyUrl, ValidationError
55

66
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthMetadata
77

@@ -109,6 +109,18 @@ def test_valid_url_passes_through_unchanged():
109109
assert str(metadata.client_uri) == "https://udemy.com/"
110110

111111

112+
def test_redirect_uri_url_subtypes_are_normalized():
113+
info = OAuthClientInformationFull(
114+
client_id="abc123",
115+
redirect_uris=[AnyHttpUrl("https://example.com/callback")],
116+
)
117+
118+
incoming = AnyUrl("https://example.com/callback")
119+
120+
assert info.validate_redirect_uri(incoming) == incoming
121+
assert info.model_dump(mode="json")["redirect_uris"] == ["https://example.com/callback"]
122+
123+
112124
def test_information_full_inherits_coercion():
113125
"""OAuthClientInformationFull subclasses OAuthClientMetadata, so the
114126
same coercion applies to DCR responses parsed via the full model."""

0 commit comments

Comments
 (0)