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

  1. 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.
  2. If OAuth is expected, the MCP server must advertise a Bearer challenge — report the misconfiguration to the server operator.
  3. 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

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


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/d0ff3ab146d8c5e2. Report an issue: GitHub.