Description
Is your feature request related to a problem? Please describe.
Clients like Claude Desktop and Cursor do not perform client registration, meaning they do not implement the MCP OAuth spec’s dynamic client registration step. These clients call the /.well-known/oauth-authorization-server
and then immediately call the /authorize
endpoint, skipping client registration entirely.
This behavior leads to a 400 error during authorization since the server tries to fetch the client_id
, assuming the client has already registered. While this complies with the OAuth spec, it introduces usability issues for servers that explicitly disabled client registration. According to the spec, client registration is not mandatory, so servers with registration disabled should still be able to authorize clients to follow the rest of the flow correctly.
Describe the solution you’d like
When client_registration_options.enabled
is set to False
, the /authorize
endpoint should not attempt to load or validate the client_id
. Instead, it should use the values provided directly from the auth_request
, such as redirect_uri
and scopes
.
Here’s a proposed conditional logic update for the /authorize
endpoint:
# src/mcp/server/auth/handlers/authorize.py
@dataclass
class AuthorizationHandler:
provider: OAuthAuthorizationServerProvider[Any, Any, Any]
async def handle(self, request: Request) -> Response:
...
# Get client information only if registration is enabled
if self.settings.auth.client_registration_options.enabled:
client = await self.provider.get_client(auth_request.client_id)
if not client:
return await error_response(
error="invalid_request",
error_description=f"Client ID '{auth_request.client_id}' not found",
attempt_load_client=False,
)
try:
redirect_uri = client.validate_redirect_uri(auth_request.redirect_uri)
except InvalidRedirectUriError as validation_error:
return await error_response(
error="invalid_request",
error_description=validation_error.message,
)
try:
scopes = client.validate_scope(auth_request.scope)
except InvalidScopeError as validation_error:
return await error_response(
error="invalid_scope",
error_description=validation_error.message,
)
else:
# Trust values from the auth_request
redirect_uri = auth_request.redirect_uri
scopes = auth_request.scopes
And then proceed to build the AuthorizationParams from these values:
auth_params = AuthorizationParams(
state=state,
scopes=scopes,
code_challenge=auth_request.code_challenge,
redirect_uri=redirect_uri,
redirect_uri_provided_explicitly=auth_request.redirect_uri is not None,
)
Describe alternatives you’ve considered
- Manually pre-registering clients like Cursor and Claude (impractical and not scalable).
- Forcing all clients to register, even when the server has client_registration_options.enabled=False (not spec-compliant).
Additional context
This change would enhance compatibility with real-world MCP clients that omit registration, while remaining spec-compliant. It improves developer experience when running MCP servers in environments where client registration is not required or desired.