Two requirements, and everything else is detail
Strip away the framework choices and a server that works behind MCP Manager does exactly two things:- 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-RPCPOSTand answers with either a JSON body or atext/event-streambody. 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/sseand/messagesendpoints); a server that exposes only that legacy transport is rejected as unsupported. Streaming your responses astext/event-streamfrom the single Streamable HTTP endpoint is fine — what’s unsupported is the two-endpoint legacy transport. Build on Streamable HTTP. - 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.
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
OAuth pre-registration
Token in a header
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.| Framework | Language | OAuth + DCR built in? | Registered-client storage | Read the cookbook |
|---|---|---|---|---|
| FastMCP | Python | Yes — OAuthProvider, OAuthProxy, OIDCProxy | In-memory on Linux by default — must configure a shared store | FastMCP → |
| MCP TypeScript SDK | TypeScript | Yes — mcpAuthRouter, ProxyOAuthServerProvider | Only an in-memory demo store ships — bring your own | TypeScript → |
| Cloudflare Workers | TypeScript | Yes — workers-oauth-provider | Workers KV — durable across instances (but eventually consistent) | Cloudflare → |
| Spring AI | Java | Yes — mcp-authorization-server | In-memory by default — switch to JDBC for production | Spring AI → |
| Go (official SDK, mcp-go) | Go | No — resource-server / token only | N/A — DCR lives in an external authorization server | Go → |
FastMCP (Python)
TypeScript SDK
mcp-handler
resource-server pattern for Next.js.Cloudflare Workers
Spring AI (Java)
Go (SDK + mcp-go)
Debug Self-Hosted OAuth
Five rules that prevent most problems
These cut across every framework. Get them right and the troubleshooting section below stays unread.Serve Streamable HTTP — not the legacy SSE transport
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.If you do OAuth, publish complete well-known metadata
/.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.Never keep DCR clients (or sessions) in process memory
Allow MCP Manager's callback URL
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.'Client Not Registered' / client ID not found — works briefly, then fails
'Client Not Registered' / client ID not found — works briefly, then fails
MCP Manager can't detect authentication, or drops you to manual Client ID + Secret entry
MCP Manager can't detect authentication, or drops you to manual Client ID + Secret entry
/.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.MCP Manager can't connect or initialize at all
MCP Manager can't connect or initialize at all
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.The authorize step fails with a redirect error
The authorize step fails with a redirect error
Every call returns 401 right after connecting
Every call returns 401 right after connecting
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.It connects, then breaks intermittently — or works for one user but not another
It connects, then breaks intermittently — or works for one user but not another
Connected, but no tools appear
Connected, but no tools appear
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.Users have to re-authenticate constantly
Users have to re-authenticate constantly
refresh_token in the client’s grant_types; advertise offline_access if your stack uses it)..png?fit=max&auto=format&n=gKqTvJPtsRi2bLNx&q=85&s=8abbce3efb590630de2102c43d32aadf)
.png?fit=max&auto=format&n=Dy9YsIECUbR9JZiT&q=85&s=a1f404cd7f7aeb1727c89d81137ae1ac)