RevenueCat / RevenueCat/ai-toolkit

OAuth: authorize rejects every redirect_uri except http://localhost[:port]/callback — Claude Code desktop app cannot connect

Open
#58 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
69
Forks
9
Avg merge
16h 49m
Merged PRs (30d)
5

Description

Describe the bug

https://api.revenuecat.com/oauth2/authorize rejects every redirect_uri except the exact shape http://localhost[:port]/callback, returning:

{"error":"invalid_redirect_uri","error_description":"Invalid redirect URI"}

This makes the MCP server unreachable from any client that does not use that one callback shape. Concretely, the Claude Code desktop app cannot connect at all — it uses claude://claude.ai/mcp-auth-callback/sdk and gets a 400 before the user ever sees a consent screen. The Claude Code terminal CLI works, because it happens to use http://localhost:<port>/callback.

Two parts of the allowlist look like real defects independent of any one client:

  1. http://127.0.0.1:<port>/callback is rejected while http://localhost:<port>/callback is accepted. RFC 8252 §7.3 recommends that native apps use the IP literal in preference to localhost, precisely because localhost depends on host-file resolution. RevenueCat rejects the recommended form and accepts only the discouraged one.

  2. The path is matched exactly. http://localhost:54545/oauth/cb is rejected. Any loopback client whose callback path is not literally /callback fails, regardless of scheme or host.

  3. Environment

    1. Platform: macOS 15 (Darwin 25.5.0), Claude Code desktop app
    2. Plugin version: revenuecat 2.2.1 (this repo, .mcp.jsonhttps://mcp.revenuecat.ai/mcp)
    3. Authorization server: https://api.revenuecat.com/oauth2/authorize, discovered via https://mcp.revenuecat.ai/.well-known/oauth-authorization-server
    4. Environment:
      • Production
  4. Steps to reproduce

The failing URL the desktop app generates:

https://api.revenuecat.com/oauth2/authorize?response_type=code&client_id=Q2xhdWRlIENvZGU%3D&code_challenge=…&code_challenge_method=S256&redirect_uri=claude%3A%2F%2Fclaude.ai%2Fmcp-auth-callback%2Fsdk&state=…&scope=*%3A*%3Aread+*%3A*%3Aread_write&resource=https%3A%2F%2Fmcp.revenuecat.ai%2Fmcp

Holding client_id, code_challenge, state and scope constant and varying only redirect_uri:

CID='Q2xhdWRlIENvZGU%3D'
CH='aGYC5jEP116uOdaeukbOvcQUZg9Cj-OgqofVi5VAtwg'
for RU in \
  'claude%3A%2F%2Fclaude.ai%2Fmcp-auth-callback%2Fsdk' \
  'https%3A%2F%2Fclaude.ai%2Fapi%2Fmcp%2Fauth_callback' \
  'http%3A%2F%2F127.0.0.1%3A54545%2Fcallback' \
  'http%3A%2F%2Flocalhost%3A54545%2Fcallback' \
  'http%3A%2F%2Flocalhost%3A54545%2Foauth%2Fcb' \
  'https%3A%2F%2Flocalhost%3A9999%2Fcallback' ; do
  curl -s -o /dev/null -w "%{http_code}  $RU\n" \
    "https://api.revenuecat.com/oauth2/authorize?response_type=code&client_id=$CID&code_challenge=$CH&code_challenge_method=S256&redirect_uri=$RU&state=probe&scope=*%3A*%3Aread"
done
  1. Expected vs. actual behavior
redirect_uri Expected Actual
claude://claude.ai/mcp-auth-callback/sdk 302 to consent 400 invalid_redirect_uri
https://claude.ai/api/mcp/auth_callback 302 to consent 400 invalid_redirect_uri
http://127.0.0.1:54545/callback 302 to consent 400 invalid_redirect_uri
http://localhost:54545/oauth/cb 302 to consent 400 invalid_redirect_uri
https://localhost:9999/callback 302 to consent 400 invalid_redirect_uri
http://localhost:54545/callback 302 to consent 302 → app.revenuecat.com
http://localhost:9999/callback 302 to consent 302 ✅
http://localhost/callback 302 to consent 302 ✅

So the accepted set is: scheme http only, host literally localhost, any port or none, path exactly /callback.

The rejection is attributable to redirect_uri alone — the accepted probes above omit resource entirely and still succeed. client_id is genuinely validated and the one in use is valid: substituting a nonexistent client_id returns {"error":"invalid_client","error_description":"Invalid client ID"}, not invalid_redirect_uri.

  1. Other information

https://mcp.revenuecat.ai/.well-known/oauth-authorization-server advertises a registration_endpoint (https://api.revenuecat.com/oauth2/register), i.e. RFC 7591 Dynamic Client Registration. An authorization server that advertises DCR but enforces a single hard-coded redirect_uri shape cannot serve clients that register their own callback — which is the entire point of DCR.

To be precise about what this report does and does not claim: I did not exercise the registration endpoint, so I am not asserting that RevenueCat discards the redirect_uris submitted at registration time. The observable fact is only that the authorize endpoint accepts exactly one shape. Checking whether the stored registration for this client contains the Claude callback would be a useful first triage step on your side.

Suggested fix: honor the redirect_uris recorded at registration; failing that, at minimum accept http://127.0.0.1:<port>/<any-path> alongside localhost per RFC 8252, and allowlist the Claude callbacks claude://claude.ai/mcp-auth-callback/sdk and https://claude.ai/api/mcp/auth_callback.

  1. Additional context

Workaround for anyone hitting this: use the rc CLI (npx @revenuecat/cli, then rc auth login), which authenticates independently of the MCP OAuth flow. Authenticating from the Claude Code terminal CLI also works, since it uses the loopback /callback shape.

I have a documentation-only PR against this repo that records the workaround under the Claude Code install section — it does not and cannot fix the underlying allowlist, which is server-side.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with .mcp.json and the advertised OAuth metadata at https://mcp.revenuecat.ai/.well-known/oauth-authorization-server; the report indicates the failing validation is in the external authorization service, not this repository. Check whether the registration endpoint preserves redirect_uris, then determine whether the server-side allowlist can be changed; the documentation-only workaround PR is separate.

Written by the indexing model from the issue text.

Assessment

Domain
api, authentication
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.