Skip to content

Support authorization without client registration when disabled via config #890

Open
@Rodriguespn

Description

@Rodriguespn

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions