Skip to content

Latest commit

 

History

History
172 lines (142 loc) · 14.5 KB

File metadata and controls

172 lines (142 loc) · 14.5 KB

MCP TypeScript SDK examples

One story per directory. Every story is a runnable, self-verifying client/server pair: server.ts is what you would deploy, client.ts is what a host would write — it connects, exercises the feature with the public client API, asserts results, and exits 0. CI runs every pair over every transport it supports (scripts/examples/run-examples.ts); a non-zero exit fails the build.

Each story is its own private workspace package (@mcp-examples/<story>). Run any pair from the repo root:

# stdio (the client spawns the server itself):
pnpm --filter @mcp-examples/<story> client

# Streamable HTTP (two terminals):
pnpm --filter @mcp-examples/<story> server -- --http --port 3000
pnpm --filter @mcp-examples/<story> client -- --http http://127.0.0.1:3000/mcp

Add -- --legacy to the client command for the 2025-era handshake.

Start here

Story What it teaches
tools/ Register tools, infer input/output schemas, call them, structured output
prompts/ Prompts + argument completion
resources/ Static + templated resources, list/read
dual-era/ One factory, both protocol eras, both transports

Feature stories

Story What it teaches Transports Era
mrtr/ Multi-round-trip write-once tool, secure requestState stdio + http modern
subscriptions/ subscriptions/listen: client.listen() + auto-open, handler.notify / ServerEventBus stdio + http modern
streaming/ In-flight progress, logging, cancellation stdio + http dual
elicitation/ Elicitation (form + URL mode), both eras: push-style on 2025, inputRequired on 2026 stdio + http dual
sampling/ Tool that requests LLM sampling from the client, both eras: push-style on 2025, inputRequired on 2026 stdio + http dual
stickynotes/ "Real app" capstone: tools mutate state, a resource per note, listChanged, elicitation-confirmed clear stdio + http dual
caching/ cacheHints stamping on cacheable results (2026-07-28) stdio + http modern
gateway/ connect({ prior }) — probe once, zero-round-trip connect for every worker (gateway pattern) http modern
custom-methods/ Vendor-prefixed methods + custom notifications stdio + http dual
schema-validators/ ArkType, Valibot, Zod, and outputSchema stdio + http dual
custom-version/ supportedProtocolVersions / version negotiation stdio + http legacy
parallel-calls/ Multiple clients / parallel tool calls, per-client notifications stdio + http dual
legacy-routing/ isLegacyRequest in front of an existing sessionful 1.x deployment + a strict modern entry on one port http dual (in-body)
bearer-auth/ Resource server with bearer token; 401 + WWW-Authenticate http dual
oauth/ OAuth authorization_code: in-repo AS (auto-consent) + headless redirect-following client http dual
oauth-client-credentials/ OAuth client_credentials (machine-to-machine): in-repo AS + ClientCredentialsProvider http dual
scoped-tools/ Per-tool scope on createMcpHandler — bearer-verify gate + handler-level ctx.http?.authInfo checks http modern

HTTP hosting variants

Story What it teaches Transports Era
stateless-legacy/ createMcpHandler default posture (the minimal deployment) http dual (in-body)
json-response/ createMcpHandler({ responseMode: 'json' }) http modern
hono/ createMcpHandler(...).fetch on Hono / web-standard runtimes http dual
sse-polling/ SEP-1699 SSE polling/resumption (sessionful 2025) http legacy
standalone-get/ Standalone GET stream + listChanged push (sessionful 2025) http legacy

dual (in-body) = the client connects to both eras inside one runner invocation; the story demonstrates one server serving both side by side.

Excluded

Directory What it is Why not in CI
repl/ Fully-featured HTTP playground server + readline client Interactive — client.ts reads from stdin. Run manually in two terminals.
guides/ Snippet collections synced into docs/server.md and docs/client.md Typecheck-only; not a runnable pair.
server-quickstart/, client-quickstart/ Website-tutorial sources External network / API key; typecheck-only.
shared/ Argv/assert scaffold (parseExampleArgs/check/siblingPath); demo OAuth provider + InMemoryEventStore at the ./auth subpath Not a story — imported by every story as scaffolding.

Multi-node deployment patterns

When deploying MCP servers in a horizontally scaled environment (multiple server instances), there are a few different options that can be useful for different use cases:

  • Stateless mode - no need to maintain state between calls.
  • Persistent storage mode - state stored in a database; any node can handle a session.
  • Local state with message routing - stateful nodes + pub/sub routing for a session.

Stateless mode

To enable stateless mode, configure the NodeStreamableHTTPServerTransport with:

sessionIdGenerator: undefined;
┌─────────────────────────────────────────────┐
│                  Client                     │
└─────────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────┐
│                Load Balancer                │
└─────────────────────────────────────────────┘
          │                       │
          ▼                       ▼
┌─────────────────┐     ┌─────────────────────┐
│  MCP Server #1  │     │    MCP Server #2    │
│ (Node.js)       │     │  (Node.js)          │
└─────────────────┘     └─────────────────────┘

Persistent storage mode

Configure the transport with session management, but use an external event store:

sessionIdGenerator: () => randomUUID(),
eventStore: databaseEventStore
┌─────────────────────────────────────────────┐
│                  Client                     │
└─────────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────┐
│                Load Balancer                │
└─────────────────────────────────────────────┘
          │                       │
          ▼                       ▼
┌─────────────────┐     ┌─────────────────────┐
│  MCP Server #1  │     │    MCP Server #2    │
│ (Node.js)       │     │  (Node.js)          │
└─────────────────┘     └─────────────────────┘
          │                       │
          │                       │
          ▼                       ▼
┌─────────────────────────────────────────────┐
│           Database (PostgreSQL)             │
│                                             │
│  • Session state                            │
│  • Event storage for resumability           │
└─────────────────────────────────────────────┘

Streamable HTTP with distributed message routing

For scenarios where local in-memory state must be maintained on specific nodes, combine Streamable HTTP with pub/sub routing so one node can terminate the client connection while another node owns the session state.

┌─────────────────────────────────────────────┐
│                  Client                     │
└─────────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────┐
│                Load Balancer                │
└─────────────────────────────────────────────┘
          │                       │
          ▼                       ▼
┌─────────────────┐     ┌─────────────────────┐
│  MCP Server #1  │◄───►│    MCP Server #2    │
│ (Has Session A) │     │  (Has Session B)    │
└─────────────────┘     └─────────────────────┘
          ▲│                     ▲│
          │▼                     │▼
┌─────────────────────────────────────────────┐
│         Message Queue / Pub-Sub             │
│                                             │
│  • Session ownership registry               │
│  • Bidirectional message routing            │
│  • Request/response forwarding              │
└─────────────────────────────────────────────┘

Backwards compatibility (Streamable HTTP ↔ legacy SSE)

A client that needs to fall back from Streamable HTTP to the legacy HTTP+SSE transport (for servers that only implement the older transport) follows the connect_sseFallback recipe in the client guide — try StreamableHTTPClientTransport first, fall back to SSEClientTransport on a 4xx. There is no runnable pair for this in examples/ (the legacy SSE server transport is deprecated); the snippet in guides/clientGuide.examples.ts is the complete pattern.