Skip to content

Commit f8a5778

Browse files
authored
Merge branch 'main' into main
2 parents 41a1973 + 1940040 commit f8a5778

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
@@ -88,6 +88,17 @@ async def handle(self, request: Request) -> Response:
8888
status_code=400,
8989
)
9090

91+
# The MCP spec requires servers to use the authorization `code` flow
92+
# with PKCE
93+
if "code" not in client_metadata.response_types:
94+
return PydanticJSONResponse(
95+
content=RegistrationErrorResponse(
96+
error="invalid_client_metadata",
97+
error_description="response_types must include 'code' for authorization_code grant",
98+
),
99+
status_code=400,
100+
)
101+
91102
client_id_issued_at = int(time.time())
92103
client_secret_expires_at = (
93104
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
@@ -59,8 +59,9 @@ class OAuthClientMetadata(BaseModel):
5959
"authorization_code",
6060
"refresh_token",
6161
]
62-
# this implementation only supports code; ie: it does not support implicit grants
63-
response_types: list[Literal["code"]] = ["code"]
62+
# The MCP spec requires the "code" response type, but OAuth
63+
# servers may also return additional types they support
64+
response_types: list[str] = ["code"]
6465
scope: str | None = None
6566

6667
# 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
@@ -1004,6 +1004,62 @@ async def test_client_registration_client_credentials(self, test_client: httpx.A
10041004
client_info = response.json()
10051005
assert client_info["grant_types"] == ["client_credentials"]
10061006

1007+
@pytest.mark.anyio
1008+
async def test_client_registration_with_additional_response_types(
1009+
self, test_client: httpx.AsyncClient, mock_oauth_provider: MockOAuthProvider
1010+
):
1011+
"""Test that registration accepts additional response_types values alongside 'code'."""
1012+
client_metadata = {
1013+
"redirect_uris": ["https://client.example.com/callback"],
1014+
"client_name": "Test Client",
1015+
"grant_types": ["authorization_code", "refresh_token"],
1016+
"response_types": ["code", "none"], # Keycloak-style response with additional value
1017+
}
1018+
1019+
response = await test_client.post("/register", json=client_metadata)
1020+
assert response.status_code == 201
1021+
data = response.json()
1022+
1023+
client = await mock_oauth_provider.get_client(data["client_id"])
1024+
assert client is not None
1025+
assert "code" in client.response_types
1026+
1027+
@pytest.mark.anyio
1028+
async def test_client_registration_response_types_without_code(self, test_client: httpx.AsyncClient):
1029+
"""Test that registration rejects response_types that don't include 'code'."""
1030+
client_metadata = {
1031+
"redirect_uris": ["https://client.example.com/callback"],
1032+
"client_name": "Test Client",
1033+
"grant_types": ["authorization_code", "refresh_token"],
1034+
"response_types": ["token", "none", "nonsense-string"],
1035+
}
1036+
1037+
response = await test_client.post("/register", json=client_metadata)
1038+
assert response.status_code == 400
1039+
error_data = response.json()
1040+
assert "error" in error_data
1041+
assert error_data["error"] == "invalid_client_metadata"
1042+
assert "response_types must include 'code'" in error_data["error_description"]
1043+
1044+
@pytest.mark.anyio
1045+
async def test_client_registration_default_response_types(
1046+
self, test_client: httpx.AsyncClient, mock_oauth_provider: MockOAuthProvider
1047+
):
1048+
"""Test that registration uses default response_types of ['code'] when not specified."""
1049+
client_metadata = {
1050+
"redirect_uris": ["https://client.example.com/callback"],
1051+
"client_name": "Test Client",
1052+
"grant_types": ["authorization_code", "refresh_token"],
1053+
# response_types not specified, should default to ["code"]
1054+
}
1055+
1056+
response = await test_client.post("/register", json=client_metadata)
1057+
assert response.status_code == 201
1058+
data = response.json()
1059+
1060+
assert "response_types" in data
1061+
assert data["response_types"] == ["code"]
1062+
10071063

10081064
class TestAuthorizeEndpointErrors:
10091065
"""Test error handling in the OAuth authorization endpoint."""

0 commit comments

Comments
 (0)