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:
- Registers the client via DCR, sending
redirect_uris: ["warp://mcp/oauth2callback?server_id=<uuid>"] - Some OAuth servers normalize this URI during registration (stripping the query parameter), storing only
warp://mcp/oauth2callback - Warp then sends the authorization request with
redirect_uri=warp://mcp/oauth2callback?server_id=<uuid> - The OAuth server rejects the request with
unknown_redirect_uribecause 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_uriused in DCR and authorization requests is identical and free of Warp-internal routing parameters - Server routing for incoming OAuth callbacks uses the standard
stateparameter - 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
rmcpcrate's CSRF token generation or validation logic - Supporting MCP servers that send back a modified
statevalue 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)
- User adds or enables an OAuth-protected MCP server in Settings → MCP
- Warp opens the server's OAuth authorization URL in the user's browser, as today
- The user completes authorization in the browser
- The browser redirects to
warp://mcp/oauth2callback?code=<code>&state=<csrf_token>— noserver_idappended - Warp handles the callback, exchanges the code for tokens, and connects the server
- 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
stateparameter 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_idis missing or unrecognized
Invariants
- The
redirect_uriregistered during DCR is always{scheme}://mcp/oauth2callbackwith no query parameters - The
redirect_uriused in the authorization request is always identical to the one used in DCR - The
stateparameter in the authorization URL is an opaque CSRF token generated by rmcp; Warp does not modify it - The
statevalue from the OAuth callback is used both for server routing and for CSRF validation (passed through to rmcp'shandle_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_urisent in the authorization request exactly matches the URI registered during DCR - Warp correctly routes OAuth callbacks to the right server using the
stateparameter - 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_callbackcorrectly routes a callback using thestateparam, and returns an error when thestateis unrecognized - Unit test: the redirect URI constructed in
make_authenticated_clientcontains 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.