Files

5.3 KiB

APP-4099: Fix MCP OAuth redirect_uri RFC 6749 Compliance

Summary

Warp currently appends ?server_id=<uuid> to the redirect URI used during MCP OAuth flows. Some OAuth servers normalize or strip query parameters from redirect URIs during Dynamic Client Registration (DCR), then reject subsequent authorization requests because the URI in the request no longer exactly matches the registered one. This breaks OAuth authentication for any MCP server whose OAuth provider performs exact redirect URI comparison, which is the RFC-recommended default.

The fix is to use a clean redirect URI (warp://mcp/oauth2callback) throughout the flow and route incoming callbacks using the standard OAuth state parameter instead.

Problem

When a user attempts to connect to an OAuth-protected MCP server, Warp:

  1. Registers the client via DCR, sending redirect_uris: ["warp://mcp/oauth2callback?server_id=<uuid>"]
  2. Some OAuth servers normalize this URI during registration (stripping the query parameter), storing only warp://mcp/oauth2callback
  3. Warp then sends the authorization request with redirect_uri=warp://mcp/oauth2callback?server_id=<uuid>
  4. The OAuth server rejects the request with unknown_redirect_uri because RFC 6749 §3.1.2.2 requires exact string comparison and the two URIs no longer match

Additionally, even when DCR succeeds with the full URI, the practice of embedding client-routing data in the redirect_uri violates the spirit of RFC 6749, which recommends using the state parameter for client-specific context.

This issue blocks Warp users from connecting to MCP servers backed by strict OAuth providers (e.g. Hydra/ORY, some enterprise identity providers), with no workaround available within Warp.

Goals

  • MCP OAuth flows succeed with OAuth servers that perform exact string comparison on redirect URIs
  • The redirect_uri used in DCR and authorization requests is identical and free of Warp-internal routing parameters
  • Server routing for incoming OAuth callbacks uses the standard state parameter
  • Multiple simultaneous MCP OAuth flows continue to work correctly
  • Existing MCP servers that already have successful cached credentials are unaffected

Non-goals

  • Changing how Warp handles non-MCP OAuth flows (e.g. GitHub integration auth, Warp Drive auth)
  • Modifying the rmcp crate's CSRF token generation or validation logic
  • Supporting MCP servers that send back a modified state value in the callback (such servers would also break rmcp's existing CSRF validation, which is a separate issue)

Figma / Design References

Figma: none provided (no UI changes in this fix)

User Experience

Normal OAuth connection flow (after fix)

  1. User adds or enables an OAuth-protected MCP server in Settings → MCP
  2. Warp opens the server's OAuth authorization URL in the user's browser, as today
  3. The user completes authorization in the browser
  4. The browser redirects to warp://mcp/oauth2callback?code=<code>&state=<csrf_token> — no server_id appended
  5. Warp handles the callback, exchanges the code for tokens, and connects the server
  6. The MCP server appears as connected in Settings → MCP, as today

The user experience is identical to the current flow. The change is entirely internal.

Error behavior

  • If the callback state parameter is missing or does not match any active OAuth flow, Warp logs an error and the server remains in an unauthenticated state. The user may retry by toggling the server off and on
  • This error behavior is the same as the current behavior when server_id is missing or unrecognized

Invariants

  • The redirect_uri registered during DCR is always {scheme}://mcp/oauth2callback with no query parameters
  • The redirect_uri used in the authorization request is always identical to the one used in DCR
  • The state parameter in the authorization URL is an opaque CSRF token generated by rmcp; Warp does not modify it
  • The state value from the OAuth callback is used both for server routing and for CSRF validation (passed through to rmcp's handle_callback)
  • Each active OAuth flow has a unique state (CSRF token), allowing concurrent flows for different servers to be routed independently

Success Criteria

  • Warp successfully completes an MCP OAuth flow with an OAuth provider that rejects redirect URIs containing query parameters (e.g. Hydra/ORY)
  • The redirect_uri sent in the authorization request exactly matches the URI registered during DCR
  • Warp correctly routes OAuth callbacks to the right server using the state parameter
  • Concurrent OAuth flows for two different MCP servers both complete successfully
  • MCP servers with pre-existing cached credentials continue to connect without re-authenticating

Validation

  • Manual test: configure an MCP server backed by an OAuth provider with strict redirect URI validation (e.g. a local Hydra instance or the reporter's setup) and verify the full flow succeeds
  • Unit test: handle_oauth_callback correctly routes a callback using the state param, and returns an error when the state is unrecognized
  • Unit test: the redirect URI constructed in make_authenticated_client contains no query parameters
  • Regression: existing MCP servers with cached credentials (GitHub MCP, Figma MCP) continue to authenticate without re-auth

Open Questions

None at this time.