Skip to main content
This section is for developers building their own MCP server to run behind MCP Manager — whether you’re at the start, choosing a framework and deciding how to wire up authentication, or you already have a server and it won’t connect cleanly. MCP Manager connects to any standards-compliant remote MCP server; this guide is about making your server one of them, and fixing it when it isn’t.
Connecting a third-party server (Atlassian, Slack, HubSpot)? That’s a different job — see Find & Connect MCP Servers and the per-server guides. This section is about a server you build and operate.

Two requirements, and everything else is detail

Strip away the framework choices and a server that works behind MCP Manager does exactly two things:
  1. It’s reachable over HTTPS as a Streamable HTTP MCP server. Streamable HTTP is the current MCP remote transport — a single endpoint (commonly /mcp) that accepts a JSON-RPC POST and answers with either a JSON body or a text/event-stream body. MCP Manager connects over Streamable HTTP only. It does not fall back to the older, separate HTTP+SSE transport (the 2024-11-05 design with distinct /sse and /messages endpoints); a server that exposes only that legacy transport is rejected as unsupported. Streaming your responses as text/event-stream from the single Streamable HTTP endpoint is fine — what’s unsupported is the two-endpoint legacy transport. Build on Streamable HTTP.
  2. It authenticates in one of the three ways MCP Manager supports — standard OAuth with dynamic client registration, OAuth with client pre-registration, or a token in a custom header.
When you add a server by URL, MCP Manager runs a discovery call and connects automatically if it can. Every framework decision below maps onto these two axes.

Choosing an authentication mode

The biggest decision is how your server authenticates, because it determines how much you build and how seamless the connect experience is. MCP Manager supports three modes (covered in depth under Authentication & Identity); here’s how to choose.

Standard OAuth + DCR

The most seamless. Your server is an OAuth 2.1 authorization server that supports dynamic client registration (RFC 7591) and publishes well-known metadata. MCP Manager registers itself and users just approve a consent screen. Most build effort, best UX.

OAuth pre-registration

Your server speaks OAuth but doesn’t register clients on the fly (or proxies an upstream IdP that doesn’t). You issue MCP Manager a Client ID and Client Secret once and allow its callback URL. Less to build, still per-user.

Token in a header

No OAuth at all — your server checks an API key or bearer token. You hand MCP Manager the header name and value. Least to build; one shared identity unless you scope tokens per user.
If you only need a single shared service identity and want the least to build, a token in a header is the fastest path to a governed server. Reach for OAuth + DCR when you want every user to authenticate as themselves with no setup on their part — that’s where MCP Manager’s per-user identity model shines. See per-user versus shared identity.

Pick your framework

The framework you choose mostly decides how much of the OAuth work is done for you. The key fact: only some frameworks can be an OAuth authorization server with dynamic client registration out of the box. The rest are resource servers — they verify a token but expect a separate authorization server — which maps to the pre-registration or token modes above. All of them can serve Streamable HTTP; just don’t ship a server that speaks only the legacy HTTP+SSE transport, which MCP Manager won’t connect to.
FrameworkLanguageOAuth + DCR built in?Registered-client storageRead the cookbook
FastMCPPythonYes — OAuthProvider, OAuthProxy, OIDCProxyIn-memory on Linux by default — must configure a shared storeFastMCP →
MCP TypeScript SDKTypeScriptYes — mcpAuthRouter, ProxyOAuthServerProviderOnly an in-memory demo store ships — bring your ownTypeScript →
Cloudflare WorkersTypeScriptYes — workers-oauth-providerWorkers KV — durable across instances (but eventually consistent)Cloudflare →
Spring AIJavaYes — mcp-authorization-serverIn-memory by default — switch to JDBC for productionSpring AI →
Go (official SDK, mcp-go)GoNo — resource-server / token onlyN/A — DCR lives in an external authorization serverGo →

FastMCP (Python)

The most popular Python framework, with full OAuth and DCR. The shared-storage gotcha and the proxy patterns, explained.

TypeScript SDK

The official SDK’s full authorization server, plus the Vercel mcp-handler resource-server pattern for Next.js.

Cloudflare Workers

The closest thing to a turnkey DCR server — KV-backed storage means none of the ephemeral-client pain.

Spring AI (Java)

The one JVM path to a DCR-capable server, and why you must move off the in-memory client repository.

Go (SDK + mcp-go)

Resource-server frameworks: how to connect with pre-registration or a token when the server can’t do DCR itself.

Debug Self-Hosted OAuth

The deep dive on the single most common failure — the OAuth identity step that works, then doesn’t.

Five rules that prevent most problems

These cut across every framework. Get them right and the troubleshooting section below stays unread.
1

Serve Streamable HTTP — not the legacy SSE transport

Expose the current Streamable HTTP transport at a fixed https:// path and point MCP Manager at exactly that path. Do not ship a server that speaks only the older HTTP+SSE transport (separate /sse and /messages endpoints) — MCP Manager won’t connect to it and marks it unsupported. Watch the trailing slash, too: /mcp and /mcp/ are different URLs, and a framework that 301-redirects between them can drop the Authorization header on the way.
2

If you do OAuth, publish complete well-known metadata

Serve /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource, and make every URL inside them — issuer, authorization_endpoint, token_endpoint, registration_endpoint, resource — your public address, not an internal host or localhost. The issuer must exactly match the URL the metadata is served from.
3

Never keep DCR clients (or sessions) in process memory

On any host that runs more than one instance — Cloud Run, Lambda, Kubernetes replicas, anything load-balanced — a per-instance in-memory store loses the client MCP Manager just registered. Use a shared, network-accessible store and a stable encryption/signing key shared across instances. This is the number-one cause of “it worked, then it didn’t.” See Debug Self-Hosted OAuth.
4

Allow MCP Manager's callback URL

If your OAuth layer enforces a redirect-URI allowlist, add MCP Manager’s fixed callback:
Callback URL
https://mcp.mcpmanager.ai/api/v1/mcpm/inbound/oauth/callback
5

Bind tokens to your canonical public URL

Make the resource/audience your server validates equal to its canonical public URL (no trailing slash). A token minted for https://mcp.example.com/ won’t validate against https://mcp.example.com — a single slash is a real, common failure.
If your server is private, allow MCP Manager’s single static IP (shown at Security → IP addresses) through your firewall, so the backend discovery and registration calls aren’t silently dropped.

Troubleshooting: it works in a quick test, but fails in MCP Manager

If you’ve landed here from a search, you’re probably seeing an error while connecting your own server and you suspect MCP Manager. Usually the symptom surfaces in MCP Manager but the cause lives in your server’s OAuth or transport configuration — MCP Manager implements the standard flow by the book. Each entry below maps what you see to the most likely server-side cause and fix.
Not every one of these is your server’s fault. A few are bugs in MCP clients (the AI apps) that present as server problems — we flag those so you don’t “fix” a server that’s already correct.
You see: an error page (often rendered by your own server) saying the client ID wasn’t found in its registry, during the identity/authorize step. It’s intermittent — a fresh setup works, then everyone hits it.Most likely cause: your OAuth layer stores dynamically-registered clients in process memory, and on a multi-instance host the authorize request lands on an instance that never saw the registration. This is structural, not flaky networking.Fix: a shared, network-accessible client store plus a stable encryption key across instances. The full diagnosis and per-framework fix is in Debug Self-Hosted OAuth.
You see: discovery doesn’t find OAuth, so MCP Manager falls back to asking for a Client ID and Secret (pre-registration) when you expected automatic OAuth.Most likely cause: your server doesn’t publish complete OAuth metadata — /.well-known/oauth-authorization-server is missing or omits registration_endpoint, /.well-known/oauth-protected-resource (RFC 9728) is absent, or your 401 doesn’t carry the challenge header that points at the metadata. A wrong issuer (not byte-matching the URL it’s served from), or metadata advertising internal or localhost URLs, causes the same outcome.Fix: serve both well-known documents at your public URL, include registration_endpoint if you support DCR, and make issuer match exactly. Verify by fetching the well-known path and reading the JSON.
You see: the connection fails before any identity step — a transport error, a 404/405, a timeout, or the server is marked unsupported.Most likely cause: your server speaks only the legacy HTTP+SSE transport (separate /sse and /messages endpoints). MCP Manager connects over Streamable HTTP only and does not fall back, so an SSE-only server is rejected on a content-type mismatch. Other causes: a trailing-slash redirect (/mcp/mcp/) that strips the request, or Origin/CORS validation rejecting the call. (Streaming responses as text/event-stream from your single Streamable HTTP endpoint is fully supported — only the two-endpoint legacy transport is not.)Fix: serve Streamable HTTP at the exact path with no redirect; if you validate Origin, allow MCP Manager rather than disabling the check.
You see: the browser hop to authorize is rejected — a redirect_uri mismatch or “not registered for client.”Most likely cause: your auth server enforces its own redirect-URI allowlist and MCP Manager’s callback isn’t on it. Some servers ignore the redirect_uris sent during DCR and enforce a separate list.Fix: add https://mcp.mcpmanager.ai/api/v1/mcpm/inbound/oauth/callback to your server’s allowed redirect URIs.
You see: the identity is created, but every tool call comes back 401.Most likely cause: an audience/resource mismatch. Your server validates the token against an audience or issuer that doesn’t match what was minted — frequently a trailing-slash difference between the resource your metadata advertises and your server’s canonical URL (RFC 8707).Fix: make the advertised resource/audience the canonical no-trailing-slash public URL, and confirm your authorization server issues tokens with that exact aud.
You see: connections or identities succeed sometimes and fail other times with no clear pattern; “session not found,” or repeated re-auth prompts.Most likely cause: per-instance in-memory state on a multi-instance deployment — either DCR clients (see the first entry) or Streamable HTTP sessions held in one instance’s memory without sticky routing. Eventually-consistent stores (for example Cloudflare KV) cause a related, self-clearing version: a token or client written on one node isn’t visible on another for a short window.Fix: run the server stateless, enable sticky sessions, or externalize session and client state to a shared store. For eventually-consistent stores, tolerate a brief propagation delay with retries.
You see: the server shows connected, but the tool list is empty.Most likely cause: the token’s scopes are too narrow to expose the scope-gated tools, the server returns an empty list instead of a 401 when auth quietly failed, or the tools capability wasn’t advertised at initialize. (Client-side: some clients cache the tool list at startup — a restart can be needed.)Fix: grant the scopes the tools require, return 401 on auth failure so re-auth is triggered, and confirm your server advertises the tools capability.
You see: identities stop working after a while and users must reconnect.Most likely cause: your authorization server never issues a refresh token, so MCP Manager can’t refresh silently when the access token expires. MCP Manager refreshes automatically when a refresh token exists.Fix: enable refresh tokens on your authorization server (include refresh_token in the client’s grant_types; advertise offline_access if your stack uses it).
When MCP Manager hits an OAuth failure it records an alert with the provider’s exact error and a deep link, and marks the server Not connected so you can fix the server and use the reconnect flow. See Alerts and the failure-behavior notes in Authentication & Identity.

Further reading

Debug Self-Hosted OAuth

The deep dive on the dynamic-client-registration failure that trips up multi-instance deployments.

Authentication & Identity

The three authentication methods, per-user versus shared identity, and how credentials are stored.

Remote MCP Servers

How MCP Manager reaches a remote server and how to choose an authentication method.

Find & Connect MCP Servers

How MCP Manager detects a server’s authentication type when you add it by URL.

External sources

MCP transports

The Streamable HTTP transport every remote server should implement.

MCP authorization spec

The OAuth 2.1-based authorization model for MCP, including discovery and DCR.