> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mcpmanager.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Build with the TypeScript SDK

> How to build an MCP server on the official TypeScript SDK to run behind MCP Manager: serving Streamable HTTP with StreamableHTTPServerTransport, choosing between the SDK's full authorization server (mcpAuthRouter, with dynamic client registration) and resource-server mode (mcpAuthMetadataRouter + requireBearerAuth), why you must replace the in-memory demo client store with a persistent one, the protected-resource-metadata path suffix, and the Vercel mcp-handler resource-server pattern for Next.js.

The official [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) (`@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`](https://github.com/vercel/mcp-handler) for Next.js at the end.

<Note>
  Start with [Building Your Own MCP Server](/build-your-own-mcp-server/overview) for the cross-framework requirements and the auth-mode decision tree.
  This page is the TypeScript layer on top.
</Note>

## Serve Streamable HTTP — never SSE-only

Use `StreamableHTTPServerTransport` (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](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/server.md).

```ts Illustrative — see the SDK server guide theme={null}
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';

// Stateless: no session bound to one instance's memory.
const transport = new StreamableHTTPServerTransport({
  sessionIdGenerator: undefined,
});
```

<Warning>
  A server that only mounts `SSEServerTransport` (the 2024-11-05 HTTP+SSE transport) **will not connect** to MCP Manager, which speaks Streamable
  HTTP only. Streamable HTTP responses themselves may be `text/event-stream` — that's supported.
</Warning>

## 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](/build-your-own-mcp-server/overview#choosing-an-authentication-mode).

| You want                        | Use in the SDK                                                                                   | Notes                                                                           |
| ------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
| **Standard OAuth + DCR**        | `mcpAuthRouter` (mounts the authorization-server endpoints, including `/register`)               | `/register` only appears if your `clientsStore.registerClient` is implemented   |
| **OAuth + DCR proxying an IdP** | `ProxyOAuthServerProvider` with `endpoints.registrationUrl` set                                  | Forwards dynamic registration to your upstream IdP                              |
| **Pre-registration / token**    | `mcpAuthMetadataRouter` + `requireBearerAuth({ verifier, requiredScopes, resourceMetadataUrl })` | Resource-server only: you verify tokens an external authorization server issued |

For the DCR path, the registration endpoint is mounted **only** when `clientsStore.registerClient` exists — so dynamic registration is opt-in by implementing it. See the SDK's `src/server/auth/` (router, providers, middleware).

<Warning>
  The only client store that ships is the **in-memory `DemoInMemoryClientsStore`** — a `Map`, for examples only. On any multi-instance or serverless
  deployment you **must** implement a persistent `OAuthRegisteredClientsStore` (backed by a database or Redis), and persist authorization codes and
  tokens too. An in-memory store loses MCP Manager's registration between the register and authorize hops — the [DCR
  failure](/build-your-own-mcp-server/debugging-self-hosted-oauth) in a nutshell.
</Warning>

<Note>
  Fixing the store doesn't reconnect a server you already added. MCP Manager reuses the `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.
</Note>

## 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`](https://github.com/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](https://github.com/vercel/mcp-handler/blob/main/docs/AUTHORIZATION.md).

<Warning>
  On Vercel/serverless, the SSE response path needs a **Redis** URL for its pub/sub backing — pure request/response Streamable HTTP works statelessly,
  but don't rely on in-process state across invocations.
</Warning>

## MCP Manager compatibility checklist

<Steps>
  <Step title="Streamable HTTP at a fixed path">
    Mount `StreamableHTTPServerTransport` at a stable path (`/mcp`); don't ship only `SSEServerTransport`.
  </Step>

  <Step title="Stateless or shared session state">
    Use `sessionIdGenerator: undefined` for stateless, or externalize session state — don't keep it in one instance's memory.
  </Step>

  <Step title="For DCR, implement registerClient + a persistent store">
    Mount `mcpAuthRouter`, implement `clientsStore.registerClient`, and back it (plus codes and tokens) with a database or Redis — not the demo
    in-memory `Map`.
  </Step>

  <Step title="Public metadata, correct path suffix">
    Serve `oauth-protected-resource` (and, for an authorization server, `oauth-authorization-server`) at your public URL with the right path suffix;
    advertise the canonical `resource`.
  </Step>

  <Step title="Allow MCP Manager's callback">
    If you enforce a redirect allowlist, include `https://app.mcpmanager.ai/api/v1/mcpm/inbound/oauth/callback`.
  </Step>
</Steps>

## TypeScript gotchas

<AccordionGroup>
  <Accordion title="The demo client store is not production-grade" icon="database">
    `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](/build-your-own-mcp-server/debugging-self-hosted-oauth).
  </Accordion>

  <Accordion title="/register only exists if you implement registerClient" icon="user-plus">
    `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.
  </Accordion>

  <Accordion title="Protected-resource metadata path suffix" icon="route">
    For an endpoint at `/mcp`, the metadata lives at `/.well-known/oauth-protected-resource/mcp`. Build it with `getOAuthProtectedResourceMetadataUrl()` rather than hand-rolling, or discovery fails.
  </Accordion>
</AccordionGroup>

## Further reading

<CardGroup cols={2}>
  <Card title="Debug Self-Hosted OAuth" icon="bug" href="/build-your-own-mcp-server/debugging-self-hosted-oauth">
    The dynamic-client-registration failure and the persistent-store fix.
  </Card>

  <Card title="MCP TypeScript SDK" icon="js" href="https://github.com/modelcontextprotocol/typescript-sdk">
    The authoritative repo, including the server guide and auth source.
  </Card>

  <Card title="Vercel mcp-handler" icon="triangle" href="https://github.com/vercel/mcp-handler">
    The Next.js resource-server adapter and its authorization docs.
  </Card>

  <Card title="Building Your Own MCP Server" icon="hammer" href="/build-your-own-mcp-server/overview">
    The cross-framework requirements, decision tree, and troubleshooting catalog.
  </Card>
</CardGroup>
