MCP OAuth: redirect_uri uses localhost:<port> while metadata publishes portless localhost and 127.0.0.1 (still failing in 2.1.246)

Status Open
Reported on v2.1.246
Maintainer reply None cached
Activity 0 comments · opened Aug 28, 2026

Summary

MCP OAuth still fails against spec-correct authorization servers in 2.1.246. Claude Code publishes portless loopback redirect URIs in its client metadata document, but sends a ported one at authorize time — and it sends the localhost name rather than the 127.0.0.1 literal it also publishes, which forfeits the one rule that would have made the port difference acceptable.

Filing new per the lock notice on #37747 (closed 2026-05-24 as completed, then locked; the last comment there asked for it to stay open until resolved via application_type: "native"). This report adds the part that thread did not cover: the port is only half the problem.

What it publishes vs what it sends

https://claude.ai/oauth/claude-code-client-metadata (fetched today):

"redirect_uris": ["http://localhost/callback", "http://127.0.0.1/callback"]

From the 2.1.246 binary, the runtime value:

f.searchParams.append("redirect_uri", `http://localhost:${n}/callback`)
// and on the MCP path:
let s = e.callbackPort ?? await PH(), i = `http://localhost:${s}/callback`

So the authorize request carries http://localhost:<port>/callback, which is in neither published entry.

Why the localhost spelling matters independently of the port

RFC 8252 §7.3 lets an AS ignore the port for loopback redirects, and §8.3 tells clients to use the IP literal because a name can resolve elsewhere or be hijacked. Servers implement the exemption accordingly — for IP literals only. better-auth is explicit about it in its own source:

Do NOT use this for OAuth redirect URI matching — use isLoopbackIP there.

So http://localhost/callback registered + http://localhost:3118/callback requested matches nothing, while http://127.0.0.1/callback registered + http://127.0.0.1:3118/callback requested matches fine. Claude Code already publishes the 127.0.0.1 form — it just doesn't use it.

Reproduction against a live server

A better-auth-based MCP server (CIMD advertised, RFC 9728 metadata, PKCE S256). Same authorize request, only the redirect_uri host differs:

http://127.0.0.1:3118/callback  -> 302 to the login page, authorize context intact  ✅
http://localhost:3118/callback  -> 302 /api/auth/error?error=invalid_redirect       ❌

In the product this surfaces as a browser landing on an error URL with nothing actionable, and the MCP server simply never connects.

Suggested fix

Send http://127.0.0.1:${port}/callback. It needs no metadata change (that entry is already published), it is what RFC 8252 §8.3 recommends, and it makes the port difference legal under §7.3 rather than depending on each server being lenient.

Registering application_type: "native" (the request in #37747) is worth doing as well, but it does not address this on its own: the failure here is redirect-URI matching, not the OIDC application-type constraint.

Environment

  • Claude Code 2.1.246, macOS (arm64)
  • Server: better-auth OAuth provider with @better-auth/cimd, MCP revision 2026-07-28
  • Related: #37747 (closed + locked)

View original on GitHub ↗