{"record":{"id":"6a588bf2ffa5ace0","repo":"github/github-mcp-server","slug":"forbidden-insufficient-scopes","errorCode":null,"errorMessage":"Forbidden: insufficient scopes","messagePattern":"Forbidden: insufficient scopes","errorType":"http","errorClass":null,"httpStatus":403,"severity":"error","filePath":"pkg/http/middleware/scope_challenge.go","lineNumber":141,"sourceCode":"\t\t\t// which is used to construct the well-known OAuth protected resource URL\n\t\t\tresourcePath := oauth.ResolveResourcePath(r, oauthCfg)\n\t\t\tresourceMetadataURL := oauth.BuildResourceMetadataURL(r, oauthCfg, resourcePath)\n\n\t\t\t// Build recommended scopes: existing scopes + required scopes\n\t\t\trecommendedScopes := make([]string, 0, len(activeScopes)+len(requiredScopes))\n\t\t\trecommendedScopes = append(recommendedScopes, activeScopes...)\n\t\t\trecommendedScopes = append(recommendedScopes, requiredScopes...)\n\n\t\t\t// Build the WWW-Authenticate header value\n\t\t\twwwAuthenticateHeader := fmt.Sprintf(`Bearer error=\"insufficient_scope\", scope=%q, resource_metadata=%q, error_description=%q`,\n\t\t\t\tstrings.Join(recommendedScopes, \" \"),\n\t\t\t\tresourceMetadataURL,\n\t\t\t\t\"Additional scopes required: \"+strings.Join(requiredScopes, \", \"),\n\t\t\t)\n\n\t\t\t// Send scope challenge response with the superset of existing and required scopes\n\t\t\tw.Header().Set(\"WWW-Authenticate\", wwwAuthenticateHeader)\n\t\t\thttp.Error(w, \"Forbidden: insufficient scopes\", http.StatusForbidden)\n\t\t}\n\t\treturn http.HandlerFunc(fn)\n\t}\n}\n","sourceCodeStart":123,"sourceCodeEnd":146,"githubUrl":"https://github.com/github/github-mcp-server/blob/0ea1f775a7c73eff1bd2e25904d01136756bbfe2/pkg/http/middleware/scope_challenge.go#L123-L146","documentation":"WithScopeChallenge runs only for OAuth tokens (gho_) on tools/call requests: it looks up the OAuth scopes the target tool accepts (from the global tool scope map built at startup), fetches the token's actually-granted scopes from GitHub, and if none of the accepted scope sets is satisfied returns 403 'Forbidden: insufficient scopes'. The response carries WWW-Authenticate: Bearer error=\"insufficient_scope\" with a superset of existing+required scopes and a resource_metadata URL (RFC 9727 style) so clients can re-authorize with the right scopes.","triggerScenarios":"A tools/call with a gho_ token whose granted scopes do not cover the tool - e.g. an issue-writing tool invoked with a token granted only read:user, because consent requested a minimal scope set or an org restricted the grant.","commonSituations":"Remote MCP clients authorized without 'repo'; enterprise OAuth apps with admin-restricted scopes; users declining optional scopes during consent and then invoking write tools.","solutions":["Read the WWW-Authenticate header: the scope=\"...\" value lists exactly which scopes to request","Re-run the OAuth flow requesting those scopes (or grant them via app settings) and retry with the new token","As a client, implement the insufficient_scope challenge: parse resource_metadata and re-authorize instead of surfacing a generic 403","Avoid narrow-scope consent for general-purpose MCP usage"],"exampleFix":"# before: client treats 403 as fatal\nresp = call_tool('create_issue', args)\n\n# after: handle the scope challenge\nif resp.status == 403 and 'insufficient_scope' in resp.headers.get('WWW-Authenticate', ''):\n    needed = parse_scopes(resp.headers['WWW-Authenticate'])\n    token = reauthorize(requested_scopes=needed)  # new OAuth flow\n    resp = call_tool('create_issue', args, token=token)","handlingStrategy":"validation","validationCode":"// pre-check before invoking a tool with an OAuth token\ngranted, err := scopeFetcher.FetchTokenScopes(ctx, token)\nif err == nil {\n\tif info, _ := scopes.GetToolScopeInfo(toolName); info != nil && !info.HasAcceptedScope(granted...) {\n\t\treturn fmt.Errorf(\"token lacks scopes for %s: need one of %v - re-authorize first\",\n\t\t\ttoolName, info.GetRequiredScopesSlice())\n\t}\n}","typeGuard":null,"tryCatchPattern":"if resp.StatusCode == http.StatusForbidden {\n\tif strings.Contains(resp.Header.Get(\"WWW-Authenticate\"), \"insufficient_scope\") {\n\t\t// parse scope= and resource_metadata= from the header, re-run the OAuth flow with those scopes, retry\n\t}\n}","preventionTips":["Request the full documented scope set at initial authorization rather than the minimum","Parse WWW-Authenticate on 403 instead of surfacing a generic error","Maintain a client-side map of tool to required scopes and pre-check before tools/call"],"tags":["http","oauth","scopes","authorization","mcp"],"backgroundTag":null,"analyzedSha":"0ea1f775a7c73eff1bd2e25904d01136756bbfe2","analyzedAt":"2026-08-15T18:10:19.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}