From 6ced33bed3dbbf9ec120e64eaab8b29f48eb1376 Mon Sep 17 00:00:00 2001 From: gazzadownunder <32623517+gazzadownunder@users.noreply.github.com> Date: Sat, 22 Nov 2025 10:57:47 +1100 Subject: [PATCH] Make refresh_token grant type optional in DCR handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Github-Issue: #1650 --- src/mcp/server/auth/handlers/register.py | 4 ++-- .../fastmcp/auth/test_auth_integration.py | 20 +++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/mcp/server/auth/handlers/register.py b/src/mcp/server/auth/handlers/register.py index c65473d1fc..14d3a6aecc 100644 --- a/src/mcp/server/auth/handlers/register.py +++ b/src/mcp/server/auth/handlers/register.py @@ -73,11 +73,11 @@ async def handle(self, request: Request) -> Response: ), status_code=400, ) - if not {"authorization_code", "refresh_token"}.issubset(set(client_metadata.grant_types)): + if "authorization_code" not in client_metadata.grant_types: return PydanticJSONResponse( content=RegistrationErrorResponse( error="invalid_client_metadata", - error_description="grant_types must be authorization_code and refresh_token", + error_description="grant_types must include 'authorization_code'", ), status_code=400, ) diff --git a/tests/server/fastmcp/auth/test_auth_integration.py b/tests/server/fastmcp/auth/test_auth_integration.py index 08fcabf276..e7d07ac5b3 100644 --- a/tests/server/fastmcp/auth/test_auth_integration.py +++ b/tests/server/fastmcp/auth/test_auth_integration.py @@ -889,19 +889,35 @@ async def test_client_registration_default_scopes( assert registered_client.scope == "read write" @pytest.mark.anyio - async def test_client_registration_invalid_grant_type(self, test_client: httpx.AsyncClient): + async def test_client_registration_with_authorization_code_only(self, test_client: httpx.AsyncClient): + """Test that registration succeeds with only authorization_code (refresh_token is optional per RFC 7591).""" client_metadata = { "redirect_uris": ["https://client.example.com/callback"], "client_name": "Test Client", "grant_types": ["authorization_code"], } + response = await test_client.post("/register", json=client_metadata) + assert response.status_code == 201 + client_info = response.json() + assert "client_id" in client_info + assert client_info["grant_types"] == ["authorization_code"] + + @pytest.mark.anyio + async def test_client_registration_missing_authorization_code(self, test_client: httpx.AsyncClient): + """Test that registration fails when authorization_code grant type is missing.""" + client_metadata = { + "redirect_uris": ["https://client.example.com/callback"], + "client_name": "Test Client", + "grant_types": ["refresh_token"], + } + response = await test_client.post("/register", json=client_metadata) assert response.status_code == 400 error_data = response.json() assert "error" in error_data assert error_data["error"] == "invalid_client_metadata" - assert error_data["error_description"] == "grant_types must be authorization_code and refresh_token" + assert error_data["error_description"] == "grant_types must include 'authorization_code'" @pytest.mark.anyio async def test_client_registration_with_additional_grant_type(self, test_client: httpx.AsyncClient):