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.

Every HTTP leg serves the handler behind host/origin validation — via createMcpHonoApp() (or another framework app factory, which arm the checks by default on localhost binds), or, for raw node:http stories, via the localhostHostValidation() / localhostOriginValidation() guards from @modelcontextprotocol/node composed in front of toNodeHandler on an explicit loopback bind. New stories follow the canonical skeleton — see CONTRIBUTING.md.

The one exception to the generic commands is the reference pair: cli-client/ and todos-server/ have their own entry points (pnpm --filter @mcp-examples/cli-client start, pnpm --filter @mcp-examples/todos-server start:http) — see their READMEs.

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, era-split subscriptions
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
cli-client/ Reference host: LLM chat CLI with provider seam — tool loop, @-mention resources, prompt commands, sampling, elicitation, roots, OAuth, cancellation stdio + http dual
todos-server/ Reference server (pairs with cli-client): every server feature with a real job — CRUD tools, sampling, multi-round elicitation, subscriptions, progress 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
extension-capabilities/ Declaring capabilities.extensions and reading the negotiated map 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
bearer-auth-web/ Web-standard twin: host/origin guards + requireBearerAuth + createMcpHandler as one fetch handler 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/ Per-page snippet companions synced into the docs/ guide pages Not a client/server story pair; typechecked and executed by pnpm docs:examples.
server-quickstart/, client-quickstart/ Standalone starter projects (the original quickstart 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 Fall back to SSE for servers that predate Streamable HTTP in the client docs — 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 connect_sseFallback snippet in guides/clients/connect.examples.ts is the complete pattern.