Thanks to visit codestin.com
Credit goes to github.com

Skip to content

bug: OAuth2 authorize rejects redirect_uri that is not the first registered redirect_uris entry (Cursor MCP) #28910

Description

@matifali

Current Behavior

Cursor desktop can't complete OAuth2 against the remote MCP server (/api/experimental/mcp/http). After Dynamic Client Registration succeeds, GET /oauth2/authorize renders:

400 - Invalid Query Parameters
Query param "redirect_uri" must exactly match cursor://anysphere.cursor-mcp/oauth/callback

Cursor registers two redirect URIs in one DCR request, per their MCP docs:

  • cursor://anysphere.cursor-mcp/oauth/callback
  • https://www.cursor.com/agents/mcp/oauth/callback

Coder stores both, then only ever validates against the first one.

Root cause

coderd/oauth2provider/registration.go persists the full list but derives a single primary URL:

CallbackURL:  req.RedirectURIs[0], // Primary redirect URI
RedirectUris: req.RedirectURIs,

app.RedirectUris is never read again. ShowAuthorizePage, ProcessAuthorize (authorize.go) and Tokens (tokens.go) all do:

callbackURL, err := url.Parse(app.CallbackURL)
...
params, _, err := extractAuthorizeParams(r, callbackURL)

and httpapi.QueryParamParser.RedirectURL enforces v.String() != base.String() against that single URL.

So any RFC 7591 client that registers more than one redirect_uris entry and uses anything but index 0 is rejected. This affects Cursor today and will affect any client that registers a desktop deep link plus a web callback.

Expected Behavior

redirect_uri on /oauth2/authorize and /oauth2/token is accepted if it exactly matches any entry in the app's registered redirect_uris (OAuth 2.1 exact-match, but against the whole allowlist). Apps created through the UI/API have an empty redirect_uris, so fall back to callback_url for them.

Steps to Reproduce

  1. Run Coder with CODER_EXPERIMENTS=oauth2,mcp-server-http.
  2. In Cursor, add an MCP server with URL https://<deployment>/api/experimental/mcp/http (or install the Coder Cursor plugin).
  3. Click Connect. Browser opens the Coder authorize page and renders the 400 above.

Proposed fix

In authorize.go and tokens.go, replace the single callbackURL with a lookup that matches the presented redirect_uri against app.RedirectUris (falling back to app.CallbackURL when the list is empty), and pass the matched URL into extractAuthorizeParams / extractTokenRequest so the existing exact-match check still holds. When redirect_uri is omitted, keep the current behavior of defaulting to callback_url. Add a test in authorize_test.go and tokens_test.go that registers two URIs and authorizes with the second.

Environment

  • Coder version: dev.coder.com (main, 8b8c806)
  • Cursor desktop, remote MCP with OAuth2 discovery

Related to #19581 (same Cursor callbacks, earlier failure at registration).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions