siyuan-note/siyuan · error
server returned %s
Error message
server returned %s
What it means
Returned by mcpOAuthHandler.Authorize when the response is HTTP 403, the Bearer challenge carried an 'error' parameter that is NOT 'insufficient_scope', and thus cannot be resolved by re-authenticating the user. The status text is embedded via %s. The rationale is that 403 with insufficient_scope is retryable via token refresh, but other 403s (e.g. invalid_token, insufficient_user_privileges) are terminal for this credential.
Source
Thrown at kernel/mcp/client/oauth.go:196
RefreshToken: credential.RefreshToken,
Expiry: credential.Expiry,
}
}
func (h *mcpOAuthHandler) Authorize(ctx context.Context, req *http.Request, resp *http.Response) (retErr error) {
defer resp.Body.Close()
defer io.Copy(io.Discard, io.LimitReader(resp.Body, 1<<20))
challenges, err := oauthex.ParseWWWAuthenticate(resp.Header.Values("WWW-Authenticate"))
if err != nil {
return fmt.Errorf("parse OAuth challenge: %w", err)
}
if !hasBearerChallenge(challenges) {
return fmt.Errorf("server returned %s without an OAuth Bearer challenge", resp.Status)
}
challengeError := bearerChallengeParam(challenges, "error")
if resp.StatusCode == http.StatusForbidden && challengeError != "insufficient_scope" {
return fmt.Errorf("server returned %s", resp.Status)
}
interactive := h.interactive.Load()
if interactive {
defer func() {
if retErr != nil && !errors.Is(retErr, context.Canceled) {
setMCPRuntimeStateForContext(ctx, h.server.ID, "authorization_required", 0, retErr.Error(), "")
}
}()
}
prm, err := discoverProtectedResource(ctx, challenges, req.URL.String(), h.client)
if err != nil {
return err
}
asm, err := auth.GetAuthServerMetadata(ctx, prm.AuthorizationServers[0], h.client)
if err != nil {
return fmt.Errorf("discover OAuth authorization server: %w", err)View on GitHub (pinned to 251596fc0d)
Solutions
- Distinguish the challenge 'error' value: for invalid_token / revoked tokens, clear the stored credential and re-run the interactive OAuth flow to obtain a fresh token.
- For insufficient_user_privileges or policy denials, the account itself is not authorized — request access from the MCP server operator; no client-side refresh will help.
- If the server is misclassifying an insufficient_scope condition as a different error, report it upstream so the retry path can engage.
Defensive patterns
Strategy: try-catch
Try / catch
// Terminal 403: do not retry OAuth; surface to user / audit account permissions.
if strings.Contains(err.Error(), "server returned 403") {
// stop retry, request operator review of account permissions
} Prevention
- Distinguish insufficient_scope (retryable via refresh) from other 403 errors (terminal).
- Review the user account's role/permissions on the MCP server when this fires.
When it happens
Trigger: MCP server returns 401 with a Bearer challenge whose 'error' param is set and not insufficient_scope, AND the response status (after re-evaluation) is 403 — the guard checks resp.StatusCode == http.StatusForbidden && challengeError != "insufficient_scope".
Common situations: User's account lacks the required role/permission (insufficient_user_privileges); token revoked server-side (invalid_token) but reported as 403; access control policy denies the principal entirely.
Related errors
- mcp oauth authorization required
- parse OAuth challenge: %w
- server returned %s without an OAuth Bearer challenge
- discover OAuth authorization server: %w
- OAuth authorization server metadata not found
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/5c12500fd4090395.
Report an issue: GitHub.