siyuan-note/siyuan · error
server returned %s without an OAuth Bearer challenge
Error message
server returned %s without an OAuth Bearer challenge
What it means
Returned by mcpOAuthHandler.Authorize when the WWW-Authenticate header parsed successfully but contains no 'Bearer' scheme challenge. Without a Bearer challenge, SiYuan cannot start an OAuth flow, so it reports the HTTP status verbatim via %s.
Source
Thrown at kernel/mcp/client/oauth.go:192
func credentialToken(credential oauthCredential) *oauth2.Token {
return &oauth2.Token{
AccessToken: credential.AccessToken,
TokenType: credential.TokenType,
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
}View on GitHub (pinned to 251596fc0d)
Solutions
- If the endpoint uses Basic/static auth, supply it via server.Headers (e.g. {"Authorization":"Basic <base64>"}); hasAuthorizationHeader will then disable the OAuth path entirely.
- If OAuth is expected, the MCP server must advertise a Bearer challenge — report the misconfiguration to the server operator.
- Remove any proxy that is rewriting Bearer into Basic in front of the MCP server.
Example fix
// before: endpoint uses Basic auth, OAuth path trips
{"type":"http","url":"https://mcp.example.com"}
// after: supply static Authorization header, OAuth skipped
{"type":"http","url":"https://mcp.example.com","headers":{"Authorization":"Basic <base64-user-pass>"}} Defensive patterns
Strategy: validation
Validate before calling
func hasBearerHeader(headers []string) bool {
for _, h := range headers {
if strings.HasPrefix(strings.ToLower(strings.TrimSpace(h)), "bearer ") { return true }
}
return false
} Prevention
- Use static server.Headers for Basic/Digest auth instead of OAuth.
- Confirm the MCP server actually advertises a Bearer challenge before expecting OAuth.
When it happens
Trigger: The 401 response carries a parseable challenge but only for a non-Bearer scheme (e.g. 'Basic', 'Digest'). hasBearerChallenge(challenges) returns false and Authorize returns this error including resp.Status.
Common situations: Endpoint protected by HTTP Basic auth instead of OAuth; reverse proxy in front of the MCP server enforcing Basic/Digest; server advertises a custom scheme; client expected OAuth but the deployment uses static credentials.
Related errors
- parse OAuth challenge: %w
- mcp oauth authorization required
- server returned %s
- 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/d0ff3ab146d8c5e2.
Report an issue: GitHub.