In /oauth/callback (src/auth/oauth-handler.ts), completeAuthorization({ scope: oauthReqInfo.scope }) uses the scopes chosen on the consent page. Cloudflare's token response carries the scopes it actually granted (AuthorizationToken.scope), and that is ignored. On refresh, handleTokenExchangeCallback ignores the refreshed token's scope too.
If Cloudflare grants fewer scopes than requested (the account can't grant one, or the user narrows them on Cloudflare's consent), the MCP token and ctx.auth.scope claim scopes the upstream token doesn't have. Tools then fail at Cloudflare's API instead of getting a clean MCP insufficient_scope challenge.
Fix: grant the MCP token the intersection of the requested scopes and Cloudflare's granted scope, at the callback and on each refresh (accessTokenScope). When Cloudflare returns no scope, keep the requested scopes.
In
/oauth/callback(src/auth/oauth-handler.ts),completeAuthorization({ scope: oauthReqInfo.scope })uses the scopes chosen on the consent page. Cloudflare's token response carries the scopes it actually granted (AuthorizationToken.scope), and that is ignored. On refresh,handleTokenExchangeCallbackignores the refreshed token'sscopetoo.If Cloudflare grants fewer scopes than requested (the account can't grant one, or the user narrows them on Cloudflare's consent), the MCP token and
ctx.auth.scopeclaim scopes the upstream token doesn't have. Tools then fail at Cloudflare's API instead of getting a clean MCPinsufficient_scopechallenge.Fix: grant the MCP token the intersection of the requested scopes and Cloudflare's granted
scope, at the callback and on each refresh (accessTokenScope). When Cloudflare returns noscope, keep the requested scopes.