|
27 | 27 | construct_redirect_uri, |
28 | 28 | ) |
29 | 29 | from mcp.server.auth.routes import ( |
30 | | - AuthSettings, |
31 | 30 | ClientRegistrationOptions, |
32 | 31 | RevocationOptions, |
33 | 32 | create_auth_routes, |
34 | 33 | ) |
| 34 | +from mcp.server.auth.settings import AuthSettings |
35 | 35 | from mcp.server.fastmcp import FastMCP |
36 | 36 | from mcp.shared.auth import ( |
37 | 37 | OAuthClientInformationFull, |
@@ -226,7 +226,11 @@ def auth_app(mock_oauth_provider): |
226 | 226 | mock_oauth_provider, |
227 | 227 | AnyHttpUrl("https://auth.example.com"), |
228 | 228 | AnyHttpUrl("https://docs.example.com"), |
229 | | - client_registration_options=ClientRegistrationOptions(enabled=True), |
| 229 | + client_registration_options=ClientRegistrationOptions( |
| 230 | + enabled=True, |
| 231 | + valid_scopes=["read", "write", "profile"], |
| 232 | + default_scopes=["read", "write"], |
| 233 | + ), |
230 | 234 | revocation_options=RevocationOptions(enabled=True), |
231 | 235 | ) |
232 | 236 |
|
@@ -946,6 +950,57 @@ async def test_revoke_with_malformed_token(self, test_client, registered_client) |
946 | 950 | assert error_response["error"] == "invalid_request" |
947 | 951 | assert "token_type_hint" in error_response["error_description"] |
948 | 952 |
|
| 953 | + @pytest.mark.anyio |
| 954 | + async def test_client_registration_disallowed_scopes( |
| 955 | + self, test_client: httpx.AsyncClient |
| 956 | + ): |
| 957 | + """Test client registration with scopes that are not allowed.""" |
| 958 | + client_metadata = { |
| 959 | + "redirect_uris": ["https://client.example.com/callback"], |
| 960 | + "client_name": "Test Client", |
| 961 | + "scope": "read write profile admin", # 'admin' is not in valid_scopes |
| 962 | + } |
| 963 | + |
| 964 | + response = await test_client.post( |
| 965 | + "/register", |
| 966 | + json=client_metadata, |
| 967 | + ) |
| 968 | + assert response.status_code == 400 |
| 969 | + error_data = response.json() |
| 970 | + assert "error" in error_data |
| 971 | + assert error_data["error"] == "invalid_client_metadata" |
| 972 | + assert "scope" in error_data["error_description"] |
| 973 | + assert "admin" in error_data["error_description"] |
| 974 | + |
| 975 | + @pytest.mark.anyio |
| 976 | + async def test_client_registration_default_scopes( |
| 977 | + self, test_client: httpx.AsyncClient, mock_oauth_provider: MockOAuthProvider |
| 978 | + ): |
| 979 | + client_metadata = { |
| 980 | + "redirect_uris": ["https://client.example.com/callback"], |
| 981 | + "client_name": "Test Client", |
| 982 | + # No scope specified |
| 983 | + } |
| 984 | + |
| 985 | + response = await test_client.post( |
| 986 | + "/register", |
| 987 | + json=client_metadata, |
| 988 | + ) |
| 989 | + assert response.status_code == 201 |
| 990 | + client_info = response.json() |
| 991 | + |
| 992 | + # Verify client was registered successfully |
| 993 | + assert client_info["scope"] == "read write" |
| 994 | + |
| 995 | + # Retrieve the client from the store to verify default scopes |
| 996 | + registered_client = await mock_oauth_provider.clients_store.get_client( |
| 997 | + client_info["client_id"] |
| 998 | + ) |
| 999 | + assert registered_client is not None |
| 1000 | + |
| 1001 | + # Check that default scopes were applied |
| 1002 | + assert registered_client.scope == "read write" |
| 1003 | + |
949 | 1004 |
|
950 | 1005 | class TestFastMCPWithAuth: |
951 | 1006 | """Test FastMCP server with authentication.""" |
|
0 commit comments