Skip to content

Commit 5912ded

Browse files
committed
fix(auth): reject null redirect_uris in DCR registration
An explicit JSON null for redirect_uris passes OAuthClientMetadata validation (the list[AnyUrl] | None union bypasses min_length), then crashes the validation loop with TypeError, returning 500 instead of an RFC 7591 error response. Reject null as invalid_client_metadata before iterating and cover it with a registration test.
1 parent d6b3e4c commit 5912ded

2 files changed

Lines changed: 34 additions & 6 deletions

File tree

src/mcp/server/auth/handlers/register.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import secrets
22
import time
33
from dataclasses import dataclass
4-
from typing import Any, cast
4+
from typing import Any
55
from uuid import uuid4
66

7-
from pydantic import AnyUrl, BaseModel, ValidationError
7+
from pydantic import BaseModel, ValidationError
88
from starlette.requests import Request
99
from starlette.responses import Response
1010

@@ -36,10 +36,20 @@ async def handle(self, request: Request) -> Response:
3636
body = await request.body()
3737
client_metadata = OAuthClientMetadata.model_validate_json(body)
3838

39-
# Validate redirect_uris per RFC 7591 section 2. The metadata
40-
# model requires a non-empty list (min_length=1), so no presence
41-
# guard is needed; cast narrows the optional field for pyright.
42-
for uri in cast(list[AnyUrl], client_metadata.redirect_uris):
39+
# Validate redirect_uris per RFC 7591 section 2. The type union
40+
# with None means an explicit JSON null passes model validation
41+
# (min_length only constrains the list branch), so reject it as
42+
# invalid metadata before iterating.
43+
redirect_uris = client_metadata.redirect_uris
44+
if redirect_uris is None:
45+
return PydanticJSONResponse(
46+
content=RegistrationErrorResponse(
47+
error="invalid_client_metadata",
48+
error_description="redirect_uris must be a non-empty list",
49+
),
50+
status_code=400,
51+
)
52+
for uri in redirect_uris:
4353
try:
4454
validate_redirect_uri(uri)
4555
except ValueError as e:

tests/server/auth/test_error_handling.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,21 @@ async def test_registration_rejects_non_http_redirect_uri_scheme(client: httpx2.
324324
data = response.json()
325325
assert data["error"] == "invalid_redirect_uri"
326326
assert data["error_description"] == "Redirect URI must use an HTTP(S) scheme"
327+
328+
329+
@pytest.mark.anyio
330+
async def test_registration_rejects_null_redirect_uris(client: httpx2.AsyncClient):
331+
client_data = {
332+
"redirect_uris": None,
333+
"token_endpoint_auth_method": "client_secret_post",
334+
"grant_types": ["authorization_code", "refresh_token"],
335+
"response_types": ["code"],
336+
"client_name": "Test Client",
337+
}
338+
339+
response = await client.post("/register", json=client_data)
340+
341+
assert response.status_code == 400, response.content
342+
data = response.json()
343+
assert data["error"] == "invalid_client_metadata"
344+
assert data["error_description"] == "redirect_uris must be a non-empty list"

0 commit comments

Comments
 (0)