diff --git a/src/fastmcp/server/auth/oauth_proxy/proxy.py b/src/fastmcp/server/auth/oauth_proxy/proxy.py index 52c891258..206314c61 100644 --- a/src/fastmcp/server/auth/oauth_proxy/proxy.py +++ b/src/fastmcp/server/auth/oauth_proxy/proxy.py @@ -360,7 +360,9 @@ class OAuthProxy(OAuthProvider, ConsentMixin): else None ) self._upstream_revocation_endpoint: str | None = upstream_revocation_endpoint - self._default_scope_str: str = " ".join(self.required_scopes or []) + self._default_scope_str: str = " ".join( + valid_scopes or self.required_scopes or [] + ) # Store redirect configuration if not redirect_path: diff --git a/tests/server/auth/oauth_proxy/test_oauth_proxy.py b/tests/server/auth/oauth_proxy/test_oauth_proxy.py index 087a99a38..549d78dad 100644 --- a/tests/server/auth/oauth_proxy/test_oauth_proxy.py +++ b/tests/server/auth/oauth_proxy/test_oauth_proxy.py @@ -63,6 +63,39 @@ class TestOAuthProxyInitialization: assert proxy.client_registration_options is not None assert proxy.client_registration_options.valid_scopes == ["custom", "scopes"] + def test_default_scope_str_prefers_valid_scopes(self, jwt_verifier): + """When valid_scopes is provided, _default_scope_str should use it + instead of required_scopes. This ensures CIMD clients (which bypass + RegistrationHandler) get registered with the full set of valid scopes.""" + jwt_verifier.required_scopes = ["openid"] + proxy = OAuthProxy( + upstream_authorization_endpoint="https://auth.example.com/authorize", + upstream_token_endpoint="https://auth.example.com/token", + upstream_client_id="client-123", + upstream_client_secret="secret-456", + token_verifier=jwt_verifier, + base_url="https://api.example.com", + valid_scopes=["openid", "email", "calendar"], + jwt_signing_key="test-secret", + client_storage=MemoryStore(), + ) + assert proxy._default_scope_str == "openid email calendar" + + def test_default_scope_str_falls_back_to_required_scopes(self, jwt_verifier): + """Without valid_scopes, _default_scope_str falls back to required_scopes.""" + jwt_verifier.required_scopes = ["openid"] + proxy = OAuthProxy( + upstream_authorization_endpoint="https://auth.example.com/authorize", + upstream_token_endpoint="https://auth.example.com/token", + upstream_client_id="client-123", + upstream_client_secret="secret-456", + token_verifier=jwt_verifier, + base_url="https://api.example.com", + jwt_signing_key="test-secret", + client_storage=MemoryStore(), + ) + assert proxy._default_scope_str == "openid" + def test_redirect_path_normalization(self, jwt_verifier): """Test that redirect_path is normalized with leading slash.""" proxy = OAuthProxy(