Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

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.