Repository navigation
feat(discovery): negotiate grant types and scopes from server metadata (CL-8796) - #12
Merged
Merged
Conversation
TheGreatAxios
commented
Sep 22, 2026
TheGreatAxios
left a comment
Contributor
Author
There was a problem hiding this comment.
Review · Approve
Discovery negotiates grant types, scopes, and the PKCE challenge method from RFC 8414/9728 server metadata instead of hard-coding them, refuses authorization servers that don't advertise S256 or a public-client auth method, and mcpClientConfig/registerMcpClient request refresh_token/offline_access by default when the server supports them.
No findings. bun run check (typecheck, lint, format, 54 tests) passes clean; the new asserts (assertSupportsS256, assertSupportsPublicClients) run on both the protected-resource and direct-AS-metadata discovery paths, and the grant/scope negotiation is exercised against realistic Linear/Granola metadata shapes.
Notes
discovery.ts:364—registerMcpClient's return type grew from{ clientId }toMcpClientRegistrationandmcpClientConfig'swantRefreshdefault flipped fromfalsetotrue; both are intentional per the commit messages and covered by tests, flagging only because they're behavior changes for existing consumers.- Two subject lines (
feat(discovery): negotiate grant types and scopes from server metadata (CL-8796),test(discovery): negotiate grant types and scopes from server metadata (CL-8796)) run to 80 chars, over the usual 72-char guideline.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
MCP OAuth discovery and dynamic registration now negotiate grant types, scopes, and PKCE support from server metadata instead of hard-coding them.
discoverMcpLoginEntry/McpLoginEntry/McpAuthorizationServer): the returned entry now carries the authorization server'sgrantTypesSupported,scopesSupported,tokenEndpointAuthMethodsSupported, andcodeChallengeMethodsSupported, plus the protected resource's ownresourceScopesSupported(RFC 9728 §2scopes_supported).OAuthDiscoveryError) whencode_challenge_methods_supportedis absent or doesn't includeS256— MCP spec, "Security Considerations" → "Authorization Code Protection": "Ifcode_challenge_methods_supportedis absent, the authorization server does not support PKCE and MCP clients MUST refuse to proceed", and "MCP clients MUST use theS256code challenge method when technically capable".token_endpoint_auth_methods_supportedis advertised but excludes"none"— this package only ever registers a public loopback client.registerMcpClient): requestsgrant_types: ["authorization_code", "refresh_token"]when the AS'sgrant_types_supportedincludesrefresh_tokenor wasn't advertised at all, otherwise just["authorization_code"]; alwaysresponse_types: ["code"]andtoken_endpoint_auth_method: "none"; forwardsscopewhen given (RFC 7591 §2/§3.2.1). It now returnsMcpClientRegistration(clientId,grantTypes,scope?) reflecting what the server echoed back as granted, falling back to what was requested when the response is silent — RFC 7591 §3.2.1 lets a server narrow the request, and a host persisting "what's actually usable" needs the echoed value.selectMcpScopes(opts)implements the spec's "Scope Selection Strategy" priority — (1) a 401WWW-Authenticatechallenge'sscopeparameter if supplied, (2) the protected resource'sscopes_supported, (3) omit the scope parameter — as small named steps. It also layers onoffline_access(spec "Refresh Tokens" section) when the caller wants a refresh token and the AS's ownscopes_supportedlistsoffline_access.wantRefreshdefaults totruein bothselectMcpScopesandmcpClientConfig, matchingregisterMcpClient's default request ofrefresh_token— a host that passes no options still getsoffline_accesswhen the AS supports it (this was the Granola case the ticket was written for); passwantRefresh: falseto opt out.mcpClientConfigusesselectMcpScopesby default; an explicitscopesoption overrides it entirely.Spec sections followed
grant_types_supported,scopes_supported,token_endpoint_auth_methods_supported,code_challenge_methods_supported).Fixtures
Test fixtures use real, publicly-checkable authorization-server metadata shapes from two live MCP servers (checked 2026-09-22) to keep the negotiation logic honest against real-world variance — no provider name or behavior lives in
src.Test plan
bun run check(typecheck, lint, format, test) passesdiscovery.ts, then green against this implementationwantRefreshdefault flipped totrue(wasfalse, disagreeing withregisterMcpClient's own default); confirmed red before the fix, green after — 54 pass / 0 fail across the packageFixes CL-8796