diff --git a/src/mcp/server/auth/handlers/register.py b/src/mcp/server/auth/handlers/register.py index 963e86b5a..93720340a 100644 --- a/src/mcp/server/auth/handlers/register.py +++ b/src/mcp/server/auth/handlers/register.py @@ -68,7 +68,7 @@ async def handle(self, request: Request) -> Response: ), status_code=400, ) - if set(client_metadata.grant_types) != {"authorization_code", "refresh_token"}: + if not {"authorization_code", "refresh_token"}.issubset(set(client_metadata.grant_types)): return PydanticJSONResponse( content=RegistrationErrorResponse( error="invalid_client_metadata", diff --git a/src/mcp/shared/auth.py b/src/mcp/shared/auth.py index 8ebc76864..b7f048bba 100644 --- a/src/mcp/shared/auth.py +++ b/src/mcp/shared/auth.py @@ -47,7 +47,7 @@ class OAuthClientMetadata(BaseModel): # ie: we do not support client_secret_basic token_endpoint_auth_method: Literal["none", "client_secret_post"] = "client_secret_post" # grant_types: this implementation only supports authorization_code & refresh_token - grant_types: list[Literal["authorization_code", "refresh_token"]] = [ + grant_types: list[Literal["authorization_code", "refresh_token"] | str] = [ "authorization_code", "refresh_token", ] diff --git a/tests/server/fastmcp/auth/test_auth_integration.py b/tests/server/fastmcp/auth/test_auth_integration.py index d5c195947..fa33fbf43 100644 --- a/tests/server/fastmcp/auth/test_auth_integration.py +++ b/tests/server/fastmcp/auth/test_auth_integration.py @@ -937,6 +937,23 @@ async def test_client_registration_invalid_grant_type(self, test_client: httpx.A assert error_data["error"] == "invalid_client_metadata" assert error_data["error_description"] == "grant_types must be authorization_code and refresh_token" + @pytest.mark.anyio + async def test_client_registration_with_additional_grant_type(self, test_client: httpx.AsyncClient): + client_metadata = { + "redirect_uris": ["https://client.example.com/callback"], + "client_name": "Test Client", + "grant_types": ["authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:device_code"], + } + + response = await test_client.post("/register", json=client_metadata) + assert response.status_code == 201 + client_info = response.json() + + # Verify client was registered successfully + assert "client_id" in client_info + assert "client_secret" in client_info + assert client_info["client_name"] == "Test Client" + @pytest.mark.anyio async def test_client_registration_with_additional_response_types( self, test_client: httpx.AsyncClient, mock_oauth_provider: MockOAuthProvider