siyuan-note/siyuan · error

parse OAuth challenge: %w

Error message

parse OAuth challenge: %w

What it means

Returned by mcpOAuthHandler.Authorize when oauthex.ParseWWWAuthenticate cannot parse the WWW-Authenticate header(s) on a 401 response. The wrapped parse error explains the syntactic problem. Without a parseable challenge list, the OAuth flow cannot discover the authorization server.

Source

Thrown at kernel/mcp/client/oauth.go:189

	return credentialToken(refreshed), nil
}

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)

View on GitHub (pinned to 251596fc0d)

Solutions

  1. curl -i the endpoint and inspect the raw WWW-Authenticate header; confirm it is a syntactically valid Bearer challenge.
  2. If the server uses a non-Bearer scheme, OAuth cannot be used — set server.Headers with a static Authorization (or other) header instead, which disables the OAuth path via hasAuthorizationHeader.
  3. Report the malformed header to the MCP server operator if the server is supposed to be OAuth-compliant.
  4. If a proxy rewrites the header, bypass or reconfigure the proxy for this endpoint.

Example fix

# expected (well-formed)
WWW-Authenticate: Bearer realm="api", error="invalid_token"
# malformed example that triggers the error
WWW-Authenticate: Please authenticate
Defensive patterns

Strategy: validation

Validate before calling

// Verify the WWW-Authenticate header shape before relying on OAuth.
import "github.com/modelcontextprotocol/go-sdk/oauthex"
func probeChallenge(url string) error {
    // fetch the endpoint, get a 401, then:
    // _, err := oauthex.ParseWWWAuthenticate(resp.Header.Values("WWW-Authenticate"))
    return nil
}

Prevention

When it happens

Trigger: The MCP HTTP server returns 401 with one or more WWW-Authenticate header values that are not valid RFC 6750/7235 challenge strings (malformed auth-scheme, unquoted parameters, stray tokens). ParseWWWAuthenticate returns an error and Authorize wraps it.

Common situations: Server sends a custom auth scheme instead of Bearer; header is a free-form string like 'Please log in'; proxy injected a malformed challenge; server-side bug producing unquoted parameters; multiple WWW-Authenticate headers with conflicting schemes.

Related errors


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