From 929df70154f5346731dc54bf303f9f0a7b63170d Mon Sep 17 00:00:00 2001 From: automaton82 Date: Fri, 19 Sep 2025 13:01:07 -0400 Subject: [PATCH 1/7] Update grant types validation logic in register.py --- src/mcp/server/auth/handlers/register.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mcp/server/auth/handlers/register.py b/src/mcp/server/auth/handlers/register.py index 963e86b5a..7a65ae917 100644 --- a/src/mcp/server/auth/handlers/register.py +++ b/src/mcp/server/auth/handlers/register.py @@ -68,11 +68,11 @@ 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", - error_description="grant_types must be authorization_code and refresh_token", + error_description="grant_types must contain authorization_code and refresh_token", ), status_code=400, ) From 8eabbdb99ddb13da7a0cd5552e56edcf0b9db063 Mon Sep 17 00:00:00 2001 From: automaton82 Date: Fri, 19 Sep 2025 13:02:05 -0400 Subject: [PATCH 2/7] Update grant_types to allow string values --- src/mcp/shared/auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mcp/shared/auth.py b/src/mcp/shared/auth.py index 8ebc76864..e8896d5ca 100644 --- a/src/mcp/shared/auth.py +++ b/src/mcp/shared/auth.py @@ -47,9 +47,9 @@ 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[Union[Literal["authorization_code", "refresh_token"], str]] = [ "authorization_code", - "refresh_token", + "refresh_token" ] # The MCP spec requires the "code" response type, but OAuth # servers may also return additional types they support From 744134ee760add9fbe91d2badc52d1af2851fd6a Mon Sep 17 00:00:00 2001 From: automaton82 Date: Fri, 19 Sep 2025 13:05:46 -0400 Subject: [PATCH 3/7] Fix formatting in auth.py for grant_types list --- src/mcp/shared/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mcp/shared/auth.py b/src/mcp/shared/auth.py index e8896d5ca..78e1dab7e 100644 --- a/src/mcp/shared/auth.py +++ b/src/mcp/shared/auth.py @@ -49,7 +49,7 @@ class OAuthClientMetadata(BaseModel): # grant_types: this implementation only supports authorization_code & refresh_token grant_types: list[Union[Literal["authorization_code", "refresh_token"], str]] = [ "authorization_code", - "refresh_token" + "refresh_token", ] # The MCP spec requires the "code" response type, but OAuth # servers may also return additional types they support From 405395528e77c854734653a2033fee0dcd408c21 Mon Sep 17 00:00:00 2001 From: automaton82 Date: Fri, 19 Sep 2025 13:06:45 -0400 Subject: [PATCH 4/7] Add Union type to imports in auth.py --- src/mcp/shared/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mcp/shared/auth.py b/src/mcp/shared/auth.py index 78e1dab7e..de9cc9288 100644 --- a/src/mcp/shared/auth.py +++ b/src/mcp/shared/auth.py @@ -1,4 +1,4 @@ -from typing import Any, Literal +from typing import Any, Literal, Union from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field, field_validator From 591e153a7be09cca0d1d69af2b5c185870f1f80e Mon Sep 17 00:00:00 2001 From: automaton82 Date: Fri, 19 Sep 2025 13:08:25 -0400 Subject: [PATCH 5/7] Update error description for client metadata validation --- src/mcp/server/auth/handlers/register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mcp/server/auth/handlers/register.py b/src/mcp/server/auth/handlers/register.py index 7a65ae917..93720340a 100644 --- a/src/mcp/server/auth/handlers/register.py +++ b/src/mcp/server/auth/handlers/register.py @@ -72,7 +72,7 @@ async def handle(self, request: Request) -> Response: return PydanticJSONResponse( content=RegistrationErrorResponse( error="invalid_client_metadata", - error_description="grant_types must contain authorization_code and refresh_token", + error_description="grant_types must be authorization_code and refresh_token", ), status_code=400, ) From 0c4632071ee98ca1f2a14a1c3d32a7e3b239aa14 Mon Sep 17 00:00:00 2001 From: automaton82 Date: Fri, 19 Sep 2025 13:30:35 -0400 Subject: [PATCH 6/7] Refactor typing for grant_types in auth.py --- src/mcp/shared/auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mcp/shared/auth.py b/src/mcp/shared/auth.py index de9cc9288..b7f048bba 100644 --- a/src/mcp/shared/auth.py +++ b/src/mcp/shared/auth.py @@ -1,4 +1,4 @@ -from typing import Any, Literal, Union +from typing import Any, Literal from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field, field_validator @@ -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[Union[Literal["authorization_code", "refresh_token"], str]] = [ + grant_types: list[Literal["authorization_code", "refresh_token"] | str] = [ "authorization_code", "refresh_token", ] From f983a04b972eeb11e6cc24b0eec816a0a9026fbc Mon Sep 17 00:00:00 2001 From: automaton82 Date: Fri, 19 Sep 2025 14:49:50 -0400 Subject: [PATCH 7/7] Add test for client registration with additional grant type --- .../fastmcp/auth/test_auth_integration.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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