github/github-mcp-server · error

Forbidden: insufficient scopes

Error message

Forbidden: insufficient scopes

What it means

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.

Source

Thrown at pkg/http/middleware/scope_challenge.go:141

			// which is used to construct the well-known OAuth protected resource URL
			resourcePath := oauth.ResolveResourcePath(r, oauthCfg)
			resourceMetadataURL := oauth.BuildResourceMetadataURL(r, oauthCfg, resourcePath)

			// Build recommended scopes: existing scopes + required scopes
			recommendedScopes := make([]string, 0, len(activeScopes)+len(requiredScopes))
			recommendedScopes = append(recommendedScopes, activeScopes...)
			recommendedScopes = append(recommendedScopes, requiredScopes...)

			// Build the WWW-Authenticate header value
			wwwAuthenticateHeader := fmt.Sprintf(`Bearer error="insufficient_scope", scope=%q, resource_metadata=%q, error_description=%q`,
				strings.Join(recommendedScopes, " "),
				resourceMetadataURL,
				"Additional scopes required: "+strings.Join(requiredScopes, ", "),
			)

			// Send scope challenge response with the superset of existing and required scopes
			w.Header().Set("WWW-Authenticate", wwwAuthenticateHeader)
			http.Error(w, "Forbidden: insufficient scopes", http.StatusForbidden)
		}
		return http.HandlerFunc(fn)
	}
}

View on GitHub (pinned to 0ea1f775a7)

Solutions

  1. Read the WWW-Authenticate header: the scope="..." value lists exactly which scopes to request
  2. Re-run the OAuth flow requesting those scopes (or grant them via app settings) and retry with the new token
  3. As a client, implement the insufficient_scope challenge: parse resource_metadata and re-authorize instead of surfacing a generic 403
  4. Avoid narrow-scope consent for general-purpose MCP usage

Example fix

# before: client treats 403 as fatal
resp = call_tool('create_issue', args)

# after: handle the scope challenge
if resp.status == 403 and 'insufficient_scope' in resp.headers.get('WWW-Authenticate', ''):
    needed = parse_scopes(resp.headers['WWW-Authenticate'])
    token = reauthorize(requested_scopes=needed)  # new OAuth flow
    resp = call_tool('create_issue', args, token=token)
Defensive patterns

Strategy: validation

Validate before calling

// pre-check before invoking a tool with an OAuth token
granted, err := scopeFetcher.FetchTokenScopes(ctx, token)
if err == nil {
	if info, _ := scopes.GetToolScopeInfo(toolName); info != nil && !info.HasAcceptedScope(granted...) {
		return fmt.Errorf("token lacks scopes for %s: need one of %v - re-authorize first",
			toolName, info.GetRequiredScopesSlice())
	}
}

Try / catch

if resp.StatusCode == http.StatusForbidden {
	if strings.Contains(resp.Header.Get("WWW-Authenticate"), "insufficient_scope") {
		// parse scope= and resource_metadata= from the header, re-run the OAuth flow with those scopes, retry
	}
}

Prevention

When it happens

Trigger: 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.

Common situations: Remote MCP clients authorized without 'repo'; enterprise OAuth apps with admin-restricted scopes; users declining optional scopes during consent and then invoking write tools.

Understand the failure class

Related errors


AI-assisted analysis of github/github-mcp-server@0ea1f775a7 (2026-08-15). Data as JSON: /api/errors/6a588bf2ffa5ace0. Report an issue: GitHub.