Describe the bug
When using AuthorizationCodeHandler with DynamicClientRegistrationConfig, the SDK registers the DCR client before computing the scopes it will request during authorization. If the caller doesn't set ClientRegistrationMetadata.Scope the client is registered without any scope restriction. Section 2 of RFC 7591 says:
scope:
String containing a space-separated list of scope values (as
described in Section 3.3 of OAuth 2.0 [RFC6749]) that the client
can use when requesting access tokens. The semantics of values in
this list are service specific. If omitted, an authorization
server MAY register a client with a default set of scopes.
I think the important part being that the server MAY register a client with default scopes but for registration endpoints that aren't exclusively used for MCP, it's hard to know what that default set of scopes would be.
To Reproduce
- Configure an AuthorizationCodeHandler with DynamicClientRegistrationConfig, leaving Metadata.Scope empty
- Connect to an MCP server whose auth server enforces scope restrictions on DCR clients (i.e. the authorization endpoint rejects scopes the client wasn't registered for)
- The server's Protected Resource Metadata advertises scopes_supported
- The 401 response does not include a WWW-Authenticate header with a scope parameter
The SDK discovers the PRM scopes, registers the client without them, then requests authorization with them and the auth server rejects the request.
Expected behavior
The SDK should propagate the discovered scopes into DynamicClientRegistrationConfig.Metadata.Scope before calling RegisterClient, so the client is registered for the same scopes it will request. Callers who explicitly set Scope should not be affected.
Logs
Error: calling "initialize": sending "initialize": rejected by transport: authorization error: invalid_scope: OAuth 2.0 Parameter: scope
Additional context
The fix is to move scope computation above handleRegistration, and set the DCR metadata scope when the caller didn't provide one:
if dcrCfg := h.config.DynamicClientRegistrationConfig; dcrCfg != nil && dcrCfg.Metadata.Scope == "" && len(requestedScopes) > 0 {
dcrCfg.Metadata.Scope = strings.Join(requestedScopes, " ")
}
I believe this would also match what is described in the Scope Selection Strategy section of the specification as well
Reproduced on v1.6.1 and v1.7.0-pre.3. I've made a small change locally that seems to fix the issue I'm seeing. I'm happy to put that up as a PR if that'd be helpful but wanted to confirm the behavior with you first. Thanks!
Edit:
It seems like what I'm describing is what happens with the typescript-sdk. Looks like they are resolving the scopes here. Then registering the DCR client here with those scopes. I don't know what the correct behavior is, but I think there's a difference.
Describe the bug
When using AuthorizationCodeHandler with DynamicClientRegistrationConfig, the SDK registers the DCR client before computing the scopes it will request during authorization. If the caller doesn't set ClientRegistrationMetadata.Scope the client is registered without any scope restriction. Section 2 of RFC 7591 says:
I think the important part being that the server MAY register a client with default scopes but for registration endpoints that aren't exclusively used for MCP, it's hard to know what that default set of scopes would be.
To Reproduce
The SDK discovers the PRM scopes, registers the client without them, then requests authorization with them and the auth server rejects the request.
Expected behavior
The SDK should propagate the discovered scopes into DynamicClientRegistrationConfig.Metadata.Scope before calling RegisterClient, so the client is registered for the same scopes it will request. Callers who explicitly set Scope should not be affected.
Logs
Additional context
The fix is to move scope computation above handleRegistration, and set the DCR metadata scope when the caller didn't provide one:
if dcrCfg := h.config.DynamicClientRegistrationConfig; dcrCfg != nil && dcrCfg.Metadata.Scope == "" && len(requestedScopes) > 0 {
dcrCfg.Metadata.Scope = strings.Join(requestedScopes, " ")
}
I believe this would also match what is described in the Scope Selection Strategy section of the specification as well
Reproduced on v1.6.1 and v1.7.0-pre.3. I've made a small change locally that seems to fix the issue I'm seeing. I'm happy to put that up as a PR if that'd be helpful but wanted to confirm the behavior with you first. Thanks!
Edit:
It seems like what I'm describing is what happens with the typescript-sdk. Looks like they are resolving the scopes here. Then registering the DCR client here with those scopes. I don't know what the correct behavior is, but I think there's a difference.