{"record":{"id":"66cd3960354abc93","repo":"PrefectHQ/fastmcp","slug":"client-id-is-required-for-client-registration-66cd39","errorCode":null,"errorMessage":"client_id is required for client registration","messagePattern":"client_id is required for client registration","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/providers/in_memory.py","lineNumber":89,"sourceCode":"        return self.clients.get(client_id)\n\n    async def register_client(self, client_info: OAuthClientInformationFull) -> None:\n        # Validate scopes against valid_scopes if configured (matches MCP SDK behavior)\n        if (\n            client_info.scope is not None\n            and self.client_registration_options is not None\n            and self.client_registration_options.valid_scopes is not None\n        ):\n            requested_scopes = set(client_info.scope.split())\n            valid_scopes = set(self.client_registration_options.valid_scopes)\n            invalid_scopes = requested_scopes - valid_scopes\n            if invalid_scopes:\n                raise ValueError(\n                    f\"Requested scopes are not valid: {', '.join(invalid_scopes)}\"\n                )\n\n        if client_info.client_id is None:\n            raise ValueError(\"client_id is required for client registration\")\n        if client_info.client_id in self.clients:\n            # As per RFC 7591, if client_id is already known, it's an update.\n            # For this simple provider, we'll treat it as re-registration.\n            # A real provider might handle updates or raise errors for conflicts.\n            pass\n        self.clients[client_info.client_id] = client_info\n\n    async def authorize(\n        self, client: OAuthClientInformationFull, params: AuthorizationParams\n    ) -> str:\n        \"\"\"\n        Simulates user authorization and generates an authorization code.\n        Returns a redirect URI with the code and state.\n        \"\"\"\n        if client.client_id not in self.clients:\n            raise AuthorizeError(\n                error=\"unauthorized_client\",\n                error_description=f\"Client '{client.client_id}' not registered.\",","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/providers/in_memory.py#L71-L107","documentation":"register_client stores clients keyed by client_id, so it requires that the submitted OAuthClientMetadata actually carries a non-None client_id. RFC 7591 normally has the server mint the id, but this simple provider expects the caller to supply one; a None id cannot be a dictionary key.","triggerScenarios":"Calling register_client with OAuthClientMetadata whose client_id field is None — e.g. constructing metadata manually without assigning an id, or a registration handler forwarding pre-registration metadata.","commonSituations":"Hand-rolling dynamic client registration tests; generating client metadata client-side without assigning an explicit client_id; a custom registration endpoint that skips id generation before delegating to the provider.","solutions":["Assign a client_id to the OAuthClientMetadata before calling register_client (e.g. generate one with secrets.token_hex(16))","If simulating RFC 7591 dynamic registration, let the server generate the id before storing rather than passing empty metadata","Check that whatever builds the metadata object actually sets client_id and not a similarly named field"],"exampleFix":"// before\nmeta = OAuthClientMetadata(redirect_uris=[\"http://localhost/callback\"])\nprovider.register_client(meta)\n// after\nmeta = OAuthClientMetadata(client_id=\"my-client-123\", redirect_uris=[\"http://localhost/callback\"])\nprovider.register_client(meta)","handlingStrategy":"type-guard","validationCode":"if client_info.client_id is None:\n    client_info = client_info.model_copy(update={\"client_id\": secrets.token_hex(16)})\nawait provider.register_client(client_info)","typeGuard":"def is_registerable(meta: OAuthClientMetadata) -> bool:\n    return meta.client_id is not None and len(meta.client_id) > 0","tryCatchPattern":"try:\n    await provider.register_client(client_info)\nexcept ValueError as e:\n    if \"client_id is required\" in str(e):\n        client_info.client_id = secrets.token_hex(16)\n        await provider.register_client(client_info)","preventionTips":["Always mint a client_id when constructing OAuthClientMetadata by hand","Centralize metadata construction in one factory so the id is never forgotten","Assert client_id is set in tests that exercise registration"],"tags":["oauth","client-registration","validation"],"backgroundTag":"missing-client-id","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}