Skip to content

Commit 6ced33b

Browse files
Make refresh_token grant type optional in DCR handler
Updated the Dynamic Client Registration (DCR) handler to comply with RFC 7591 by making the refresh_token grant type optional. Previously, the handler incorrectly required both authorization_code and refresh_token grant types, which was unnecessarily restrictive and non-compliant with the RFC. Changes: - Modified grant_types validation to only require authorization_code - Updated error message to reflect the new requirement - Renamed test to test_client_registration_with_authorization_code_only - Added test for missing authorization_code (now the true error case) - Updated test assertions to match new validation behavior This change improves RFC 7591 compliance and provides clients with greater flexibility in their registration options. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> Github-Issue: #1650
1 parent c51936f commit 6ced33b

File tree

2 files changed

+20
-4
lines changed

2 files changed

+20
-4
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,11 @@ async def handle(self, request: Request) -> Response:
7373
),
7474
status_code=400,
7575
)
76-
if not {"authorization_code", "refresh_token"}.issubset(set(client_metadata.grant_types)):
76+
if "authorization_code" not in client_metadata.grant_types:
7777
return PydanticJSONResponse(
7878
content=RegistrationErrorResponse(
7979
error="invalid_client_metadata",
80-
error_description="grant_types must be authorization_code and refresh_token",
80+
error_description="grant_types must include 'authorization_code'",
8181
),
8282
status_code=400,
8383
)

tests/server/fastmcp/auth/test_auth_integration.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -889,19 +889,35 @@ async def test_client_registration_default_scopes(
889889
assert registered_client.scope == "read write"
890890

891891
@pytest.mark.anyio
892-
async def test_client_registration_invalid_grant_type(self, test_client: httpx.AsyncClient):
892+
async def test_client_registration_with_authorization_code_only(self, test_client: httpx.AsyncClient):
893+
"""Test that registration succeeds with only authorization_code (refresh_token is optional per RFC 7591)."""
893894
client_metadata = {
894895
"redirect_uris": ["https://client.example.com/callback"],
895896
"client_name": "Test Client",
896897
"grant_types": ["authorization_code"],
897898
}
898899

900+
response = await test_client.post("/register", json=client_metadata)
901+
assert response.status_code == 201
902+
client_info = response.json()
903+
assert "client_id" in client_info
904+
assert client_info["grant_types"] == ["authorization_code"]
905+
906+
@pytest.mark.anyio
907+
async def test_client_registration_missing_authorization_code(self, test_client: httpx.AsyncClient):
908+
"""Test that registration fails when authorization_code grant type is missing."""
909+
client_metadata = {
910+
"redirect_uris": ["https://client.example.com/callback"],
911+
"client_name": "Test Client",
912+
"grant_types": ["refresh_token"],
913+
}
914+
899915
response = await test_client.post("/register", json=client_metadata)
900916
assert response.status_code == 400
901917
error_data = response.json()
902918
assert "error" in error_data
903919
assert error_data["error"] == "invalid_client_metadata"
904-
assert error_data["error_description"] == "grant_types must be authorization_code and refresh_token"
920+
assert error_data["error_description"] == "grant_types must include 'authorization_code'"
905921

906922
@pytest.mark.anyio
907923
async def test_client_registration_with_additional_grant_type(self, test_client: httpx.AsyncClient):

0 commit comments

Comments
 (0)