modelcontextprotocol / modelcontextprotocol/go-sdk
auth: DCR registration omits scopes, causing invalid_scope on strict auth servers
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 5.1k
- Forks
- 543
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 37
Description
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with AuthorizationCodeHandler and trace how requestedScopes are computed relative to handleRegistration and RegisterClient. Check how DynamicClientRegistrationConfig.Metadata.Scope distinguishes caller-provided values, then verify that discovered scopes are present in DCR metadata before authorization while explicit Scope values remain unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- authentication
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100