PrefectHQ/fastmcp · error · ValueError
client_id is required for client registration
Error message
client_id is required for client registration
What it means
Dynamic client registration must produce a ProxyDCRClient with a client_id; the SDK's RegistrationHandler can build a client_info object with client_id=None in degenerate cases. OAuthProxy.register_client refuses such input with ValueError because a proxy client without an ID cannot participate in the OAuth flow.
Source
Thrown at fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py:988
allowed_redirect_uri_patterns=self._allowed_client_redirect_uris,
allow_unregistered_redirect_uris=True,
)
return None
@override
async def register_client(self, client_info: OAuthClientInformationFull) -> None:
"""Register a client locally
When a client registers, we create a ProxyDCRClient that is more
forgiving about validating redirect URIs, since the DCR client's
redirect URI will likely be localhost or unknown to the proxied IDP. The
proxied IDP only knows about this server's fixed redirect URI.
"""
# Create a ProxyDCRClient with configured redirect URI validation
if client_info.client_id is None:
raise ValueError("client_id is required for client registration")
# SEP-837: the SDK's RegistrationHandler drops application_type when it
# builds this object, so prefer the value the HTTP route recovered from
# the raw request body. Fall back to the object's own field for direct
# (non-HTTP) callers. Write it back so the DCR response echoes the type.
#
# The SDK splits the registration *request* model from the registered
# *client record*: `OAuthClientMetadata.application_type` defaults to
# "native", while `OAuthClientInformationFull.application_type` is
# `str | None` and defaults to None. Normalize the unset case back to
# "native" so a client that omits the field gets the RFC 7591 default
# recorded explicitly, on both the HTTP and direct-call paths.
pending_application_type = _pending_application_type.get()
if pending_application_type is not None:
client_info.application_type = pending_application_type
elif client_info.application_type is None:
client_info.application_type = "native"
application_type = client_info.application_typeView on GitHub (pinned to 1f02114297)
Solutions
- Ensure the registration flow assigns a client_id before register_client is invoked
- If using a custom registration handler, generate and set a client_id (e.g. secrets.token_urlsafe) on the client_info
- Check the MCP SDK version for regressions in RegistrationHandler that drop client_id
Example fix
// before client_info = OAuthClientMetaData(redirect_uris=[uri]) # client_id None proxy.register_client(client_info) // after client_info = OAuthClientMetaData(client_id=secrets.token_urlsafe(16), redirect_uris=[uri]) proxy.register_client(client_info)
Defensive patterns
Strategy: validation
Validate before calling
if client_info.client_id is None:
client_info = client_info.model_copy(update={"client_id": secrets.token_urlsafe(16)})
proxy.register_client(client_info) Type guard
def has_client_id(client_info) -> bool:
return getattr(client_info, "client_id", None) is not None Try / catch
try:
proxy.register_client(client_info)
except ValueError as e:
if "client_id is required" in str(e):
client_info.client_id = secrets.token_urlsafe(16)
proxy.register_client(client_info)
else:
raise Prevention
- Always assign client_id in custom registration handlers before calling register_client
- Test DCR flows end-to-end so SDK regressions in RegistrationHandler surface early
- Pin and review MCP SDK versions for client-building changes
When it happens
Trigger: Calling register_client with a client_info whose client_id is None — e.g. via the DCR route (_register_client) or directly from _start_flow with a partially-built ClientRegistration.
Common situations: Custom DCR handlers or middleware constructing OAuthClientMetaData/registration objects manually without assigning an ID; SDK behavior changes in newer MCP versions dropping client_id; test harnesses fabricating client objects.
Understand the failure class
Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.
Related errors
- client_id is required for client registration
- 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
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/b4dfb34d53152305.
Report an issue: GitHub.