Skip to main content
This page is for teams running their own remote MCP server who hit a wall at the identity step: the server connects fine, but creating an identity — the OAuth handshake — fails. Almost every self-hosted OAuth problem lives on the server, not in MCP Manager, and they cluster into a handful of recognizable patterns. MCP Manager implements the standard OAuth flow by the book; this page helps you find what your server is doing differently and fix it.
If you’re connecting a third-party server (Atlassian, Slack, HubSpot) rather than one you built, start with Find & Connect MCP Servers and the per-server guides instead. This page is specifically about debugging a server you control — see also Building Your Own MCP Server.

The symptom that brings most people here

The classic report sounds like this: connecting to the server works, but creating the identity fails — often with a message rendered by your own server that reads something like:
Client Not Registered
The client ID a1b2c3d4-… was not found in the server's client registry.
Two details make this diagnosable:
  • It’s the identity step that fails, not the connection. A plain reachability test passes; the OAuth authorize hop is what breaks.
  • It often looks intermittent. A fresh setup works for a little while, then the same error returns — and everyone on the team hits it.
That intermittency is the tell. The error page is served by your server, not by MCP Manager, and it almost always means the client MCP Manager registered a moment earlier is no longer in the place your server looks for it. The rest of this page explains why, and the much shorter list of other things it can be.

What MCP Manager actually does during the identity step

Knowing the exact sequence tells you where to look on your server. When you add a server by URL and create an identity, MCP Manager runs the standard OAuth 2.1 flow the MCP authorization spec defines:
  1. Discovery. MCP Manager fetches your server’s /.well-known/oauth-authorization-server (RFC 8414) and, where present, the protected-resource metadata (RFC 9728) to learn your registration_endpoint, authorization_endpoint, and token_endpoint.
  2. Dynamic client registration (DCR). If a registration_endpoint is advertised, MCP Manager POSTs to it (RFC 7591) and your server returns a fresh client_id. This is a server-to-server call from MCP Manager’s backend.
  3. Authorization. MCP Manager redirects your browser to the authorization_endpoint with that client_id and a PKCE challenge. You approve, and your server redirects back to MCP Manager’s fixed callback URL.
  4. Token exchange + refresh. MCP Manager exchanges the code at the token_endpoint, stores the tokens encrypted, and refreshes them automatically from then on.
The callback your server must allow is always:
Callback URL
https://mcp.mcpmanager.ai/api/v1/mcpm/inbound/oauth/callback
The crucial structural fact is that steps 2 and 3 are two separate requests from two different origins — a backend registration call, then a browser authorize redirect: If those two requests reach different instances of your server, and the registration was only stored in the first instance’s memory, the second instance has no record of the client_id — and renders the error you’re seeing.

Cause #1 — ephemeral client storage on a multi-instance host

This is the overwhelmingly common case, and it matches the intermittent symptom exactly. Many MCP server frameworks default to an in-memory store for dynamically registered clients. On a single long-lived process that’s fine. But on an autoscaling or serverless host — Google Cloud Run, AWS Lambda/Fargate, Kubernetes with more than one replica, anything behind a load balancer — that store is per-instance and ephemeral: it isn’t shared between instances and doesn’t survive a scale event or restart. The DCR call writes the client into one instance’s memory; the browser authorize request is load-balanced to another instance that never saw it.
Why it looks intermittent. A fresh attempt can succeed when both hops happen to land on the same warm instance, then fail minutes later once traffic is balanced elsewhere or the instance recycles. “It works for a bit after a fresh login, then breaks for everyone” is the signature of ephemeral per-instance storage — not a flaky network.

The fix: shared storage + a stable key

The fix is on the server, and it has two parts — getting only the first is a common near-miss:
  1. Give the OAuth layer a network-accessible, shared client store so a registration written by one instance is visible to every instance. Redis (e.g. Cloud Memorystore) is the usual choice; a database, Firestore, or object storage also work.
  2. Use a stable encryption key shared across instances. If the client store is encrypted with a per-instance ephemeral key, a second instance still can’t decrypt what the first wrote — so you get the same failure even with shared storage. Derive the key from a secret manager so every instance loads the same one.
min-instances and session affinity won’t reliably fix this. Pinning to one warm instance or enabling sticky sessions only narrows the window — the registration and authorize requests come from different origins (a backend call and a browser), so they can’t be guaranteed to land together. Shared storage plus a stable key is the real fix; instance pinning just hides the bug until the next scale event.

FastMCP’s OIDCProxy, specifically

FastMCP’s built-in OIDCProxy is a frequent source of this exact failure on Cloud Run, because its client store defaults to in-memory and its default encryption key is ephemeral. The fix maps directly onto the two parts above:
  • Set the proxy’s client_storage to a shared backend so registrations are durable and visible to every instance — see FastMCP’s client_storage parameter reference.
  • Wrap that store so it’s encrypted with a stable key (for example, a Fernet key derived from a Secret Manager secret), rather than the default ephemeral key.
Illustrative example
# Shared, durable client store + a stable encryption key,
# so every Cloud Run instance reads the same registrations.
oidc_proxy = OIDCProxy(
    # ... your existing upstream OIDC config ...
    client_storage=shared_backed_store,   # Redis / Firestore / GCS, not in-memory
)
FastMCP’s OIDC Proxy documentation covers the cloud and multi-instance deployment guidance in full and is authoritative for the exact current API. See also the FastMCP cookbook.
This isn’t unique to FastMCP. Any server that keeps DCR clients in process memory — other frameworks, or a hand-rolled OAuth layer — fails the same way once it runs more than one instance. The two-part fix (shared store + stable key) is identical regardless of framework, and the TypeScript SDK and Spring AI cookbooks call out the equivalent for those stacks. Cloudflare Workers avoids it by storing clients in KV.

The other usual suspects

If the failure is consistent rather than intermittent, or the storage fix doesn’t resolve it, work down this list. Each is a distinct server-side cause.
If your server doesn’t publish /.well-known/oauth-authorization-server (RFC 8414), or it omits registration_endpoint, MCP Manager can’t discover where to register and can’t run automatic DCR — so it falls back to asking you for a Client ID and Secret. Verify what your server actually advertises by fetching that well-known path and reading the JSON. Confirm registration_endpoint, authorization_endpoint, and token_endpoint are present and point at reachable URLs. MCP Manager handles a server that legitimately has no metadata by guiding you to pre-registration — but if you intended DCR to work, missing metadata is why it didn’t.
Some servers ignore the redirect_uris supplied during DCR and enforce their own allowlist at the authorize step. If MCP Manager’s callback isn’t on it, the authorize hop is rejected. Add the exact callback URL to your server’s allowed redirect URIs:
https://mcp.mcpmanager.ai/api/v1/mcpm/inbound/oauth/callback
A server may advertise a registration_endpoint but in practice only accept a fixed allowlist of well-known clients (Claude, Cursor, ChatGPT, and the like), rejecting any dynamically registered client. That’s a limitation in the server’s DCR implementation — open DCR means accepting clients you didn’t pre-approve. Either widen the server to accept dynamically registered clients, or expose a pre-registration path so MCP Manager can connect with a Client ID and Secret you issue it.
MCP Manager negotiates a supported token_endpoint_auth_method (client_secret_post, client_secret_basic, or none) and requests the scopes your metadata advertises. If your server requires a method it doesn’t advertise, or a scope it doesn’t list in scopes_supported, registration can succeed while the token exchange fails. Make sure the methods and scopes your server enforces match the ones it advertises in its metadata.

A quick diagnostic path

What MCP Manager does on its side

When the OAuth callback fails, MCP Manager doesn’t fail silently. It records an alert (error.inbound_server.oauth_callback_failed) capturing the provider’s error code and description and the redirect URI involved, and deep-links you to it so you can read exactly what the upstream returned. The server’s identity then shows a Not connected state, and you can retry through the reconnect flow once the server side is fixed — which also lets you switch the server to pre-registration or token auth if you decide not to rely on DCR. See Alerts and Authentication & Identity.
If your server is private, remember MCP Manager’s discovery and registration calls come from a single static IP, shown at Security → IP addresses. Allowlist it so those backend hops aren’t silently dropped by a firewall. See Remote MCP Servers.