Skip to content

Commit 1940040

Browse files
authored
Accept additional response_types values from OAuth servers (#1323)
1 parent 146d7ef commit 1940040

File tree

3 files changed

+70
-2
lines changed

3 files changed

+70
-2
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,17 @@ async def handle(self, request: Request) -> Response:
7777
status_code=400,
7878
)
7979

80+
# The MCP spec requires servers to use the authorization `code` flow
81+
# with PKCE
82+
if "code" not in client_metadata.response_types:
83+
return PydanticJSONResponse(
84+
content=RegistrationErrorResponse(
85+
error="invalid_client_metadata",
86+
error_description="response_types must include 'code' for authorization_code grant",
87+
),
88+
status_code=400,
89+
)
90+
8091
client_id_issued_at = int(time.time())
8192
client_secret_expires_at = (
8293
client_id_issued_at + self.options.client_secret_expiry_seconds

src/mcp/shared/auth.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ class OAuthClientMetadata(BaseModel):
5151
"authorization_code",
5252
"refresh_token",
5353
]
54-
# this implementation only supports code; ie: it does not support implicit grants
55-
response_types: list[Literal["code"]] = ["code"]
54+
# The MCP spec requires the "code" response type, but OAuth
55+
# servers may also return additional types they support
56+
response_types: list[str] = ["code"]
5657
scope: str | None = None
5758

5859
# these fields are currently unused, but we support & store them for potential

tests/server/fastmcp/auth/test_auth_integration.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,62 @@ async def test_client_registration_invalid_grant_type(self, test_client: httpx.A
937937
assert error_data["error"] == "invalid_client_metadata"
938938
assert error_data["error_description"] == "grant_types must be authorization_code and refresh_token"
939939

940+
@pytest.mark.anyio
941+
async def test_client_registration_with_additional_response_types(
942+
self, test_client: httpx.AsyncClient, mock_oauth_provider: MockOAuthProvider
943+
):
944+
"""Test that registration accepts additional response_types values alongside 'code'."""
945+
client_metadata = {
946+
"redirect_uris": ["https://client.example.com/callback"],
947+
"client_name": "Test Client",
948+
"grant_types": ["authorization_code", "refresh_token"],
949+
"response_types": ["code", "none"], # Keycloak-style response with additional value
950+
}
951+
952+
response = await test_client.post("/register", json=client_metadata)
953+
assert response.status_code == 201
954+
data = response.json()
955+
956+
client = await mock_oauth_provider.get_client(data["client_id"])
957+
assert client is not None
958+
assert "code" in client.response_types
959+
960+
@pytest.mark.anyio
961+
async def test_client_registration_response_types_without_code(self, test_client: httpx.AsyncClient):
962+
"""Test that registration rejects response_types that don't include 'code'."""
963+
client_metadata = {
964+
"redirect_uris": ["https://client.example.com/callback"],
965+
"client_name": "Test Client",
966+
"grant_types": ["authorization_code", "refresh_token"],
967+
"response_types": ["token", "none", "nonsense-string"],
968+
}
969+
970+
response = await test_client.post("/register", json=client_metadata)
971+
assert response.status_code == 400
972+
error_data = response.json()
973+
assert "error" in error_data
974+
assert error_data["error"] == "invalid_client_metadata"
975+
assert "response_types must include 'code'" in error_data["error_description"]
976+
977+
@pytest.mark.anyio
978+
async def test_client_registration_default_response_types(
979+
self, test_client: httpx.AsyncClient, mock_oauth_provider: MockOAuthProvider
980+
):
981+
"""Test that registration uses default response_types of ['code'] when not specified."""
982+
client_metadata = {
983+
"redirect_uris": ["https://client.example.com/callback"],
984+
"client_name": "Test Client",
985+
"grant_types": ["authorization_code", "refresh_token"],
986+
# response_types not specified, should default to ["code"]
987+
}
988+
989+
response = await test_client.post("/register", json=client_metadata)
990+
assert response.status_code == 201
991+
data = response.json()
992+
993+
assert "response_types" in data
994+
assert data["response_types"] == ["code"]
995+
940996

941997
class TestAuthorizeEndpointErrors:
942998
"""Test error handling in the OAuth authorization endpoint."""

0 commit comments

Comments
 (0)