| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -77,6 +77,17 @@ async def handle(self, request: Request) -> Response: | |||
| 77 | 77 | status_code=400, | |
| 78 | 78 | ) | |
| 79 | 79 | ||
| 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 | + | ||
| 80 | 91 | client_id_issued_at = int(time.time()) | |
| 81 | 92 | client_secret_expires_at = ( | |
| 82 | 93 | client_id_issued_at + self.options.client_secret_expiry_seconds | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -51,8 +51,9 @@ class OAuthClientMetadata(BaseModel): | |||
| 51 | 51 | "authorization_code", | |
| 52 | 52 | "refresh_token", | |
| 53 | 53 | ] | |
| 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"] | ||
| 56 | 57 | scope: str | None = None | |
| 57 | 58 | ||
| 58 | 59 | # these fields are currently unused, but we support & store them for potential | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -937,6 +937,62 @@ async def test_client_registration_invalid_grant_type(self, test_client: httpx.A | |||
| 937 | 937 | assert error_data["error"] == "invalid_client_metadata" | |
| 938 | 938 | assert error_data["error_description"] == "grant_types must be authorization_code and refresh_token" | |
| 939 | 939 | ||
| 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 | + | ||
| 940 | 996 | ||
| 941 | 997 | class TestAuthorizeEndpointErrors: | |
| 942 | 998 | """Test error handling in the OAuth authorization endpoint.""" | |
| Back | FazBrowse Home | New Git URL |
0 commit comments