Dev.to Security 🔐 Cybersecurity 👁 0 📖 6 min read

The MCP Redirect URI Edge Cases Dynamic Client Registration Doesn't Save You From

Scope of this post This is not another "what is redirect_uri_mismatch" explainer. I already wrote the desktop-client version of that bug elsewhere on this blog — localhost vs 127.0.0.1, ephemeral local callback servers

Scope of this post

This is not another "what is redirect_uri_mismatch" explainer. I already wrote the desktop-client version of that bug elsewhere on this blog — localhost vs 127.0.0.1, ephemeral local callback servers, the basics.

This post covers a narrower, later-stage problem: the redirect URI and session bugs that show up after you've already implemented Dynamic Client Registration (DCR) correctly. Teams ship DCR, watch the happy path work in testing, and then get paged three weeks later when a client that authenticated fine on day one suddenly can't.

What's explicitly not here:

  • Basic redirect URI string-matching mistakes (covered already)
  • PKCE and authorization server discovery mechanics (separate post, already covered)
  • Full session-store architecture, revocation policy, and multi-tenant client registries — that's the production-hardening layer, and it's dense enough that it lives in the paid pack, not a blog post

If you're still debugging "my redirect_uri doesn't match," stop here and go fix that first. This post assumes DCR is working.

Why DCR doesn't actually solve the redirect URI problem — it relocates it

Dynamic Client Registration feels like it should end redirect URI pain: the client registers itself, the server echoes back an accepted redirect_uris list, done. In practice, DCR just moves the trust boundary from "a human configured this URI once" to "two independent systems have to agree on an exact string, indefinitely, across restarts, redeploys, and port changes." That's a harder problem, not an easier one, and real issue trackers are full of it right now.

Here are five specific failure modes that show up after DCR is "working," ranked by how often they actually page someone.

Gotcha #1: The authorization endpoint disagrees with the registration endpoint

The most confusing version of this bug is when registration succeeds but authorization rejects the exact same URI moments later. One recent report against Tableau's MCP server showed exactly this: client registration succeeds via Dynamic Client Registration, but the authorization endpoint rejects the exact same redirect URI that was previously accepted during registration, even though the redirect URI used during authorization appears to be identical to the redirect URI accepted during dynamic client registration.

That's not a client bug. It's two code paths on the server — the register handler and the authorize handler — validating against different sources of truth (a cache vs. a database, a normalized string vs. a raw one). If you're building the server side, the fix is to make both endpoints read from the same canonical store, not two independently-maintained allowlists.

Gotcha #2: Loopback redirect URIs work for CLIs, except when the provider doesn't trust loopback at all

For native/CLI clients, convention is to register a loopback URI like http://127.0.0.1:<random-port>/callback. Most authorization servers accept this per spec. Some don't. A recent Atlassian MCP integration report shows the authorization server flatly refusing it: loopback (127.0.0.1) redirect URIs registered via dynamic client registration should be accepted for local/CLI OAuth flows, but Atlassian's authorization server rejects the redirect URI as not registered for the dynamically-registered client, blocking authentication entirely.

If you're building an MCP client that targets third-party servers, don't assume loopback-via-DCR is universally supported — some providers require a pre-registered static client with a fixed redirect URI instead, and your fallback path needs to exist before a user hits this in production, not after.

Gotcha #3: Stale client registrations die silently, and nothing tells the client to re-register

This is the one that pages people weeks after launch, not during testing. When an identity provider redeploys, migrates its database, or rotates its OAuth backend, dynamically-registered clients can simply stop existing server-side — while the local cache of that client's credentials looks perfectly healthy. One report against an agent framework described this precisely: when an MCP server's dynamically-registered OAuth client becomes invalid server-side (e.g. IdP redeploy, DB migration, rebrand), the client continues to present the dead client_id indefinitely, and the IdP rejects it with 'Redirect URI Mismatch', and the agent does not detect this condition or re-run dynamic client registration. The giveaway that it's not a token problem: the cached refresh tokens are still valid for years into the future — the breakage is purely the registered client, not the credentials it holds.

The practical fix: your client needs to treat invalid_client / "redirect URI not registered" as a distinct error class from invalid_grant / expired token, and respond by re-running DCR from scratch, not by refreshing a token that was never the problem.

Gotcha #4: client_uri origin-matching breaks tools that proxy through a different domain than they authenticate from

Some authorization servers enforce that the optional client_uri metadata field shares an origin with your registered redirect_uri — a reasonable anti-phishing check that quietly breaks tools built for flexibility. One widely-used MCP debugging tool hit this directly: strict OAuth providers reject the configuration with error "client_uri must have the same origin as a redirect_uri," because the redirect_uri uses the current local origin while client_uri points to an unrelated domain.

If you're writing a server that enforces this check, document it loudly in your registration error response — "client_uri origin must match redirect_uri origin" is a one-line fix for integrators once they know it's required. If you're writing a client, default client_uri to the same origin as your redirect URI unless you have a specific reason not to.

Gotcha #5: Session binding that happens after the OAuth handshake, not during it

This is the subtlest one and it's specifically a session issue, not a redirect issue — but it gets mistaken for a redirect bug constantly because the symptom is identical: the user completes auth successfully, gets a valid token, and tool calls still fail. A conformance tooling doc for MCP lists this as its own distinct failure class: token audience mismatch (OAuth flow succeeds but tools/list returns 401 because aud doesn't match the MCP resource URL), scope mismatch (token issued but the tool handler requires different scopes), and session binding, where some servers require a separate session init after auth.

If your MCP server requires a distinct session-initialization call after the OAuth code exchange, a client that treats "got a valid token" as "ready to call tools" will silently fail on its first real request. The redirect URI did its job correctly — the bug is one layer downstream, in session lifecycle, and it's easy to misdiagnose as yet another OAuth config error.

What this post deliberately doesn't solve

Each of these five gotchas has a narrow, specific fix at the code level, which is what I covered above. What none of them answer on their own is the bigger operational question: how do you design a client registration store that survives IdP redeploys, how do you version and rotate client secrets without an outage window, how do you build a test suite that catches "authorize endpoint disagrees with register endpoint" before a real user hits it, and how do you scope session permissions so a stale or hijacked client can't silently reuse another caller's authorization. That's exactly the gap between "I fixed the bug I saw in the logs" and "I'd trust this in front of real traffic" — and it's the part that doesn't compress into a blog post.

If you're building toward that production bar, the AI Agent Incident Postmortem & Permission-Scoping Template Pack gives you a structured way to document exactly these kinds of auth and session failures when they happen, plus the permission-scoping templates to limit blast radius the next time a client registration goes stale or a session gets bound to the wrong caller.

The one-line takeaway

DCR doesn't end your redirect URI problems — it just moves them from "did I type the URI correctly" to "do my registration and authorization code paths agree, does my client know how to tell a dead registration from an expired token, and does my session lifecycle actually match what my OAuth flow assumes it does." Fix those four questions explicitly, and most of what looks like a mysterious OAuth bug turns out to be a one-line diagnosis.

Written with AI assistance and reviewed for accuracy.

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.