@modelcontextprotocol/sdk) is the most capable TypeScript option: it can be a full OAuth authorization server with dynamic client registration, or a plain resource server that verifies bearer tokens. Either way it serves Streamable HTTP, so it fits any of MCP Manager’s three authentication modes. This page covers the decisions and gotchas for running it behind MCP Manager — the SDK’s own docs and source are authoritative. It also covers Vercel’s mcp-handler for Next.js at the end.
Serve Streamable HTTP — never SSE-only
UseStreamableHTTPServerTransport (from @modelcontextprotocol/sdk/server/streamableHttp.js). For a stateless deployment behind a load balancer, construct it with sessionIdGenerator: undefined; for stateful sessions, supply a generator (the transport then issues an Mcp-Session-Id). The SDK also ships a legacy SSEServerTransport for the old two-endpoint transport — don’t ship that as your only transport. See the server guide.
Match the SDK’s auth to an MCP Manager mode
The SDK splits cleanly into authorization-server mode and resource-server mode. Pick the one that matches your chosen MCP Manager mode.clientsStore.registerClient exists — so dynamic registration is opt-in by implementing it. See the SDK’s src/server/auth/ (router, providers, middleware).
client_id and client_secret it first registered, so after
you deploy a persistent client store, delete and re-add the server to force a fresh registration.Get the metadata path right
The SDK serves protected-resource metadata at a path-suffixed well-known URL when your MCP endpoint isn’t at the root — for example/.well-known/oauth-protected-resource/mcp for an endpoint at /mcp. Use the SDK’s getOAuthProtectedResourceMetadataUrl(serverUrl) helper to build it rather than hand-writing the path, and make sure the advertised issuer and resource are your public HTTPS URL, not an internal host. A mismatch here is what makes MCP Manager fall back to manual entry or reject tokens.
Vercel mcp-handler (Next.js)
If you’re shipping an MCP server as Next.js route handlers on Vercel, mcp-handler wraps the SDK with createMcpHandler(...). It is a resource server only — it verifies tokens with withMcpAuth(handler, verifyToken, options) and serves RFC 9728 protected-resource metadata, but it does not implement an authorization server or dynamic client registration.
That maps to MCP Manager’s pre-registration or token-in-header modes: an external authorization server (or a static token) handles credentials, and mcp-handler validates them. See mcp-handler authorization docs.
MCP Manager compatibility checklist
Streamable HTTP at a fixed path
StreamableHTTPServerTransport at a stable path (/mcp); don’t ship only SSEServerTransport.Stateless or shared session state
sessionIdGenerator: undefined for stateless, or externalize session state — don’t keep it in one instance’s memory.For DCR, implement registerClient + a persistent store
mcpAuthRouter, implement clientsStore.registerClient, and back it (plus codes and tokens) with a database or Redis — not the demo
in-memory Map.Public metadata, correct path suffix
oauth-protected-resource (and, for an authorization server, oauth-authorization-server) at your public URL with the right path suffix;
advertise the canonical resource.Allow MCP Manager's callback
https://app.mcpmanager.ai/api/v1/mcpm/inbound/oauth/callback.TypeScript gotchas
The demo client store is not production-grade
The demo client store is not production-grade
DemoInMemoryClientsStore is a Map for examples. On more than one instance it loses MCP Manager’s registration between the register and authorize hops. Implement a persistent OAuthRegisteredClientsStore. See Debug Self-Hosted OAuth./register only exists if you implement registerClient
/register only exists if you implement registerClient
mcpAuthRouter mounts the registration endpoint only when clientsStore.registerClient is defined. If MCP Manager can’t find a
registration_endpoint, that’s usually why — implement it or use pre-registration.Protected-resource metadata path suffix
Protected-resource metadata path suffix
/mcp, the metadata lives at /.well-known/oauth-protected-resource/mcp. Build it with getOAuthProtectedResourceMetadataUrl() rather than hand-rolling, or discovery fails.
