“invalid_redirect_uri”
The authorization server refused the redirect URI the client sent, because it does not exactly match one it has registered for that client. The three common causes: (1) The client's metadata document lists loopback redirect URIs without a port and the server compares ports, which RFC 8252 §7.3 says it should not do for loopback. To tell: The client uses a client ID metadata document (the doctor's client lines say so) and the rejected URI has a port. (2) The identity provider allowlists redirect URIs and the client's is not on the list. To tell: Compare the doctor's "Redirect URIs to allow" lines with your allowlist. (3) localhost versus 127.0.0.1, or a custom scheme such as cursor:// with a host part the server does not accept. To tell: The rejected URI differs from the registered one only in the host or the scheme. One command shows which step breaks: npx --allow-git=root github:agentwares/mcp-oauth-doctor https://your-server.example/mcp.
Check your server now
npx --allow-git=root github:agentwares/mcp-oauth-doctor https://your-server.example/mcp
Discovery only: no credential, no client registration. It prints the first broken step, whose it is, and the fix with the spec section.
Who prints it
the authorization server, shown by Claude Code, Codex, Cursor and others after the consent redirect.
The three causes, and how to tell them apart
- Cause 1. The client's metadata document lists loopback redirect URIs without a port and the server compares ports, which RFC 8252 §7.3 says it should not do for loopback.
The check cannot see this one from outside: it happens after consent, or inside the client. The client uses a client ID metadata document (the doctor's client lines say so) and the rejected URI has a port. - Cause 2. The identity provider allowlists redirect URIs and the client's is not on the list.
The check cannot see this one from outside: it happens after consent, or inside the client. Compare the doctor's "Redirect URIs to allow" lines with your allowlist. - Cause 3. localhost versus 127.0.0.1, or a custom scheme such as cursor:// with a host part the server does not accept.
The check cannot see this one from outside: it happens after consent, or inside the client. The rejected URI differs from the registered one only in the host or the scheme.
Public reports
- anthropics/claude-code#37747, opened 23 March 2026: Claude Code's metadata document listed loopback URIs without the port; 51 reactions, closed
- openai/codex#23929, opened 21 May 2026: Figma's server rejects Codex's redirect URI; 17 reactions, open
- better-auth/better-auth#10946, opened 23 August 2026: an identity provider rejects Cursor's cursor:// URI with a host part; open
Keep checking
Other messages
- “Authorization with the MCP server failed”
- “Couldn't reach the MCP server”
- “Failed to discover OAuth metadata”
- “Dynamic Client Registration not supported”
- “Incompatible auth server: does not support dynamic client registration”
- “Issuer mismatch”
- “Connection expired”
- “token exchange failed”
Written 8 October 2026 from the public issues above. The sign-in check · MCP Liveness · Terms · Privacy