Skip to content

mcp: classify auth failures (HTTP + SSE) and guard OAuth credential deletion - #15536

Draft
Legoben wants to merge 1 commit into
bens/mcp_retain_spawn_configs_so_reconnect_works_for_ephemeral_managed_builtin_serversfrom
bens/mcp_classify_auth_failures_http_sse_and_guard_oauth_credential_deletion
Draft

mcp: classify auth failures (HTTP + SSE) and guard OAuth credential deletion#15536
Legoben wants to merge 1 commit into
bens/mcp_retain_spawn_configs_so_reconnect_works_for_ephemeral_managed_builtin_serversfrom
bens/mcp_classify_auth_failures_http_sse_and_guard_oauth_credential_deletion

Conversation

@Legoben

@Legoben Legoben commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Problem

Auth failures were invisible to the code that needs to react to them:

  • The warp-server managed-MCP proxy tells clients exactly why a request was denied (WWW-Authenticate: Bearer error="invalid_token", error_description="proxy_token_expired"|"proxy_token_stale"), but nothing client-side parsed it.
  • The forked legacy SSE transport used error_for_status(), discarding HTTP error bodies and challenges entirely, and its auto-reconnect retried forever at 1s — an expired-token SSE server got hammered indefinitely.
  • Any spawn failure — including a network blip or command not found — unconditionally deleted the server's cached OAuth credentials, forcing interactive re-auth (source of the throttled report_error noise, see Throttle 'No template UUID found' report_error! to once per run #15498).

Changes

  • New mcp::error_classification module:
    • classify_service_errorAuthExpiredRecoverable(reason) / AuthRequiresUser / Transient / Fatal, digging through rmcp's StreamableHttpError and both nestings of the fork's SseTransportError (the OAuth path double-wraps).
    • parse_www_authenticate_reason recognizes the proxy's re-mintable codes; downstream_auth_failed bodies classify as user-fixable.
  • SSE fork: new SseTransportError::HttpStatus { status, body (≤4KiB), www_authenticate } variant replaces error_for_status(); GET-stream errors no longer silently map to a closed stream. Reconnects are bounded (exponential, max 6) and a 401/403 on reconnect terminates immediately instead of retrying.
  • spawn_server now returns a typed McpSpawnError distinguishing AuthRequired { reason } from other failures (auth-context-missing and failed-OAuth flows populate it).
  • Credential deletion is now guarded: only a definitive auth rejection that re-minting cannot fix (AuthRequired { reason: None }) deletes cached credentials. Network/DNS/command failures and re-mintable proxy expiry never log the user out.

Tests

cargo test -p mcp — axum-based tests assert the HttpStatus capture (status/body/challenge, bounded body), challenge parsing, classification of every class, nested-error unwrapping, bounded/fatal SSE retry behavior, and the credential-deletion predicate.

@cla-bot cla-bot Bot added the cla-signed label Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant