PrefectHQ/fastmcp · error · ValueError
Requested scopes are not valid: {', '.join(invalid_scopes)}
Error message
Requested scopes are not valid: {', '.join(invalid_scopes)} What it means
The in-memory OAuth provider validates that scopes requested during dynamic client registration are a subset of the provider's configured valid_scopes. If any requested scope is outside that set, register_client raises this ValueError listing the offending scopes.
Source
Thrown at fastmcp_slim/fastmcp/server/auth/providers/in_memory.py:84
self._refresh_to_access_map: dict[
str, str
] = {} # refresh_token_str -> access_token_str
async def get_client(self, client_id: str) -> OAuthClientInformationFull | None:
return self.clients.get(client_id)
async def register_client(self, client_info: OAuthClientInformationFull) -> None:
# Validate scopes against valid_scopes if configured (matches MCP SDK behavior)
if (
client_info.scope is not None
and self.client_registration_options is not None
and self.client_registration_options.valid_scopes is not None
):
requested_scopes = set(client_info.scope.split())
valid_scopes = set(self.client_registration_options.valid_scopes)
invalid_scopes = requested_scopes - valid_scopes
if invalid_scopes:
raise ValueError(
f"Requested scopes are not valid: {', '.join(invalid_scopes)}"
)
if client_info.client_id is None:
raise ValueError("client_id is required for client registration")
if client_info.client_id in self.clients:
# As per RFC 7591, if client_id is already known, it's an update.
# For this simple provider, we'll treat it as re-registration.
# A real provider might handle updates or raise errors for conflicts.
pass
self.clients[client_info.client_id] = client_info
async def authorize(
self, client: OAuthClientInformationFull, params: AuthorizationParams
) -> str:
"""
Simulates user authorization and generates an authorization code.
Returns a redirect URI with the code and state.View on GitHub (pinned to 1f02114297)
Solutions
- Remove or fix the invalid scopes in the OAuthClientMetadata scope string sent to register_client
- Add the missing scopes to client_registration_options.valid_scopes when instantiating the in-memory provider
- Print the diff: set(requested.split()) - set(valid_scopes) to see exactly which scopes are rejected
Example fix
// before provider = InMemoryOAuthProvider(client_registration_options=ClientRegistrationOptions(valid_scopes=["read", "write"])) await provider.register_client(OAuthClientMetadata(scope="read write admin")) // after await provider.register_client(OAuthClientMetadata(scope="read write")) # drop 'admin' # or widen: valid_scopes=["read", "write", "admin"]
Defensive patterns
Strategy: validation
Validate before calling
valid = set(provider.client_registration_options.valid_scopes or [])
requested = set(client_scope_string.split())
if invalid := requested - valid:
raise ValueError(f"Adjust client scopes; not allowed: {', '.join(invalid)}") Type guard
def scopes_allowed(scope_string: str | None, valid_scopes: list[str] | None) -> bool:
if scope_string is None or valid_scopes is None:
return True
return set(scope_string.split()) <= set(valid_scopes) Try / catch
try:
await provider.register_client(client_info)
except ValueError as e:
# message lists offending scopes; trim requested scopes and retry
logger.warning("Registration rejected: %s", e) Prevention
- Keep a single shared constant of allowed scopes used by both provider config and clients
- Validate client scope strings in CI against the provider's valid_scopes
- Watch for typo'd scope separators (spaces required, not commas)
When it happens
Trigger: Calling register_client(client_info) where client_info.scope contains one or more space-separated scopes not present in client_registration_options.valid_scopes (which must also be configured for the check to run).
Common situations: A client app requests scopes like 'read write admin' while the provider was configured with valid_scopes=['read', 'write']; typos in scope names ('read:profile' vs 'read_profile'); valid_scopes tightened after clients were built.
Related errors
- CIMD redirect_uri must have a host: {uri!r}
- CIMD document is not valid JSON: {e}
- CIMD client_id mismatch: document says '{doc.client_id}' but
- CIMD document must have jwks_uri or jwks for private_key_jwt
- Assertion must include exp claim
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/309a30666cc1c3ca.
Report an issue: GitHub.