siyuan-note/siyuan · error

server returned

Error message

server returned %s

What it means

Authorize was invoked after the MCP server rejected a request, but the WWW-Authenticate response carried no OAuth Bearer challenge (or none could be parsed as one). The library only knows how to start an OAuth flow when the server advertises a Bearer challenge describing the resource/auth servers; anything else (Basic, Digest, none, or a plain error page) is opaque. This guards against guessing auth schemes the client cannot perform.

Solutions

  1. Verify the MCP server URL points at the actual OAuth-protected MCP endpoint, not a proxy or UI route
  2. Check with curl -i that the server returns WWW-Authenticate: Bearer ... on 401; fix server/proxy config to emit it
  3. If a proxy strips the header, configure it to pass through WWW-Authenticate
  4. If the server does not use OAuth at all, supply credentials via a supported non-OAuth auth method instead of calling Authorize

Example fix

// before: pointing at a proxy that swallows challenges
server := mcp.NewClientHandler("https://gw.example.com/mcp")
// after: point directly at the OAuth-protected resource
server := mcp.NewClientHandler("https://mcp.example.com/mcp")
Defensive patterns

Strategy: validation

Validate before calling

resp, _ := http.Get(serverURL)
ch, err := oauthex.ParseWWWAuthenticate(resp.Header.Values("WWW-Authenticate"))
if err != nil || !hasBearerChallenge(ch) {
    return errors.New("endpoint does not advertise OAuth Bearer challenge")
}

Try / catch

err := h.Authorize(ctx, req, resp)
if err != nil && strings.Contains(err.Error(), "without an OAuth Bearer challenge") {
    surfaceToUser("Server did not advertise OAuth; check URL/proxy WWW-Authenticate passthrough")
}

Prevention

When it happens

Trigger: Calling MCPClient.Authorize when resp.StatusCode is 401/403 but the response lacks a WWW-Authenticate header with scheme Bearer, e.g. a reverse proxy returns 401 with Basic challenge, or the endpoint returns an HTML error page with no challenge at all.

Common situations: MCP server URL points at a gateway/proxy that strips or rewrites WWW-Authenticate; wrong URL hits a non-OAuth endpoint; server is not actually an OAuth-protected resource; API gateway uses API keys instead of OAuth.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


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

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