siyuan-note/siyuan · error

parse OAuth challenge

Error message

parse OAuth challenge: %w

What it means

In mcpOAuthHandler.Authorize, the WWW-Authenticate headers of the server's 401/403 response are parsed with oauthex.ParseWWWAuthenticate; if that parsing fails, the error is wrapped as 'parse OAuth challenge: %w'. It means the server sent a malformed RFC 6750/9449 challenge that the client cannot interpret, so the OAuth flow cannot proceed.

Solutions

  1. Inspect the server's WWW-Authenticate headers (curl -v) and fix the server's challenge formatting if you control it
  2. Check for a proxy/gateway rewriting the header and bypass or fix it
  3. Update the server to a version emitting RFC-compliant Bearer challenges
  4. Report the malformed challenge to the MCP server vendor if it is third-party
Defensive patterns

Strategy: try-catch

Try / catch

err := handler.Authorize(ctx, req, resp)
if err != nil && strings.HasPrefix(err.Error(), "parse OAuth challenge: ") {
    // server sent a malformed WWW-Authenticate header
    logging.LogWarnf("non-RFC OAuth challenge from %s: %s", server.URL, err)
    // fail over: report to user / try alternative auth (e.g. static headers)
}

Prevention

When it happens

Trigger: An HTTP MCP server returns an auth-challenge response whose WWW-Authenticate header values cannot be parsed by oauthex (malformed parameters, invalid quoting, non-standard syntax).

Common situations: Non-conformant or beta MCP server implementations emitting broken WWW-Authenticate headers; a proxy/gateway mangling or duplicating the header; custom auth middleware producing non-RFC challenge syntax.

Related errors


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

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