RevenueCat / RevenueCat/ai-toolkit
OAuth: authorize rejects every redirect_uri except http://localhost[:port]/callback — Claude Code desktop app cannot connect
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 69
- Forks
- 9
- Avg merge
- 16h 49m
- Merged PRs (30d)
- 5
Description
- I have read the Contribution Guidelines
- I have searched for existing Github issues
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:
-
http://127.0.0.1:<port>/callbackis rejected whilehttp://localhost:<port>/callbackis accepted. RFC 8252 §7.3 recommends that native apps use the IP literal in preference tolocalhost, precisely becauselocalhostdepends on host-file resolution. RevenueCat rejects the recommended form and accepts only the discouraged one. -
The path is matched exactly.
http://localhost:54545/oauth/cbis rejected. Any loopback client whose callback path is not literally/callbackfails, regardless of scheme or host. -
Environment
- Platform: macOS 15 (Darwin 25.5.0), Claude Code desktop app
- Plugin version:
revenuecat2.2.1 (this repo,.mcp.json→https://mcp.revenuecat.ai/mcp) - Authorization server:
https://api.revenuecat.com/oauth2/authorize, discovered viahttps://mcp.revenuecat.ai/.well-known/oauth-authorization-server - Environment:
- Production
-
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
- 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.
- 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.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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