siyuan-note/siyuan · error

server returned without an OAuth Bearer challenge

Error message

server returned %s without an OAuth Bearer challenge

What it means

After parsing the WWW-Authenticate challenges, Authorize requires at least one OAuth Bearer challenge to drive the authorization flow. If the response's challenges contain no Bearer scheme, it fails with 'server returned %s without an OAuth Bearer challenge', where %s is the HTTP status line. This guards against servers that return 401/403 without usable OAuth metadata.

Solutions

  1. Verify the endpoint actually speaks MCP with OAuth; if it uses API keys instead, configure the key in the server's Headers so the OAuth handler is skipped
  2. Check that a reverse proxy is not stripping WWW-Authenticate headers and fix its configuration
  3. Confirm the server URL points at the MCP endpoint, not a generic login page
  4. If you control the server, make it emit a Bearer WWW-Authenticate challenge on 401
Defensive patterns

Strategy: try-catch

Try / catch

err := handler.Authorize(ctx, req, resp)
if err != nil && strings.Contains(err.Error(), "without an OAuth Bearer challenge") {
    // server does not offer OAuth; fall back to static header auth
    configureStaticHeaders(server)
}

Prevention

When it happens

Trigger: An HTTP MCP server responds with 401/403 during Connect but its WWW-Authenticate headers lack a Bearer challenge (e.g. only Basic, or no challenge at all), so the OAuth handler cannot determine scope/authorization endpoints.

Common situations: Server uses non-OAuth auth (Basic auth, API key) while the client expected OAuth; misconfigured reverse proxy stripping the WWW-Authenticate header; server returning a plain 401 HTML error page from a gateway instead of the MCP OAuth flow.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/d0ff3ab146d8c5e2. Report an issue: GitHub.

Appendix: 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 9f775e8a12)