PrefectHQ/fastmcp · error · AuthorizeError
invalid_client
invalid_client
Error message
invalid_client: Client ID is required
What it means
Just before building the AuthorizationCode, authorize() asserts that the client has a non-None client_id, since the code must record which client it was issued to. A Client whose client_id is None triggers AuthorizeError('invalid_client', 'Client ID is required').
Source
Thrown at fastmcp_slim/fastmcp/server/auth/providers/in_memory.py:135
# or if params.redirect_uri is None and client has a default.
# However, the AuthorizationHandler handles the primary validation.
pass # Let's assume AuthorizationHandler did its job.
except Exception as e: # Replace with specific validation error if client.validate_redirect_uri existed
raise AuthorizeError(
error="invalid_request", error_description="Invalid redirect_uri."
) from e
auth_code_value = f"test_auth_code_{secrets.token_hex(16)}"
expires_at = time.time() + DEFAULT_AUTH_CODE_EXPIRY_SECONDS
# Ensure scopes are a list
scopes_list = params.scopes if params.scopes is not None else []
if client.scope: # Filter params.scopes against client's registered scopes
client_allowed_scopes = set(client.scope.split())
scopes_list = [s for s in scopes_list if s in client_allowed_scopes]
if client.client_id is None:
raise AuthorizeError(
error="invalid_client", error_description="Client ID is required"
)
auth_code = AuthorizationCode(
code=auth_code_value,
client_id=client.client_id,
redirect_uri=params.redirect_uri,
redirect_uri_provided_explicitly=params.redirect_uri_provided_explicitly,
scopes=scopes_list,
expires_at=expires_at,
code_challenge=params.code_challenge,
# code_challenge_method is assumed S256 by the framework
)
self.auth_codes[auth_code_value] = auth_code
return construct_redirect_uri(
str(params.redirect_uri), code=auth_code_value, state=params.state
)
View on GitHub (pinned to 1f02114297)
Solutions
- Set client_id on the Client before calling authorize
- Verify registration flow so the Client passed downstream carries the registered id
- Add an assertion or guard in your own code: `assert client.client_id is not None` before authorization calls
Example fix
// before client = Client(redirect_uris=[...]) # client_id defaults to None provider.authorize(client, params) // after client = Client(client_id="app-1", redirect_uris=[...]) provider.authorize(client, params)
Defensive patterns
Strategy: type-guard
Validate before calling
if client.client_id is None:
raise ValueError("Client must have client_id before authorize()")
provider.authorize(client, params) Type guard
def has_client_id(client) -> bool:
return isinstance(client.client_id, str) and bool(client.client_id) Try / catch
try:
redirect = provider.authorize(client, params)
except AuthorizeError as e:
if e.error == "invalid_client":
logger.error("Client object missing client_id: %s", client) Prevention
- Construct Client objects via a factory that requires client_id
- Add client_id presence assertions in test fixtures
- Never deserialize authorization state into Client objects without validating the id
When it happens
Trigger: Calling authorize() with a Client object constructed without a client_id (client_id=None) — the earlier `client.client_id not in self.clients` check passes only if None is somehow a key, so this typically follows partially-populated client objects in tests or handlers.
Common situations: Test fixtures building Client dataclasses with defaults; a token/authorization handler losing the id when mapping between metadata and Client; deserialization dropping an absent client_id field.
Related errors
- invalid_client
- unauthorized_client
- invalid_request
- OAuth authorization failed: {error} - {error_desc}
- The device authorization request expired
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/5a2729a1934f7817.
Report an issue: GitHub.