Four runnable MCP servers built with the toolkit.
| File | What it shows |
|---|---|
| basic-auth-server.ts | API key auth, JWT auth, reading the auth context in a handler |
| full-middleware-stack.ts | All five packages composed on one server |
| production-server.ts | Auth + rate limit + cache + logging with realistic tools and stats |
| shared-state-redis.ts | Cache and rate limit in Redis / Valkey, shared by every replica |
Build the workspace first. The examples import the packages by their published names
(@mcp-toolkit/auth and friends), which npm workspaces resolve to each package's
dist/, so an unbuilt checkout fails at import time:
npm install
npm run buildThe examples are run with tsx, which is not a
dependency of this repo. npx tsx fetches it on demand:
npx tsx examples/basic-auth-server.tsexamples/ is typechecked by npm run typecheck:examples, which CI runs, so these
files stay compilable against the current SDK.
| Variable | Used by | Default |
|---|---|---|
MCP_API_KEY |
all four | dev-api-key-12345 (basic, full, shared-state), prod-key-abc123 (production) |
JWT_SECRET |
basic-auth-server (jwt mode) |
super-secret-jwt-key |
MCP_ALLOWED_ORIGINS |
full-middleware-stack | unset, so the origin allowlist is skipped |
LOG_LEVEL |
production-server | info |
LOG_FILE |
production-server | unset, so logs go to stderr only |
REDIS_URL |
shared-state-redis | redis://localhost:6379 |
The defaults are development placeholders. Set real values before exposing any of these.
basic-auth-server.ts takes a mode argument: npx tsx examples/basic-auth-server.ts jwt
starts the JWT variant instead of the API key one.
npx @modelcontextprotocol/inspector npx tsx examples/production-server.tsThe Inspector lists the tools and calls them interactively. Note that a call it sends carries no credential, so every tool answers with an auth error -- which is itself the useful signal that the middleware is active.
To see auth accept and reject, the credential has to travel somewhere the server can
read it. Under stdio that is the request's params._meta:
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"1.0.0"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"greet","arguments":{"name":"Sagar"},"_meta":{"x-api-key":"dev-api-key-12345"}}}' \
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"greet","arguments":{"name":"Sagar"}}}' \
| npx tsx examples/basic-auth-server.tsRequest 2 returns the greeting; request 3 returns
Missing API key in "x-api-key" header. On a Streamable HTTP or SSE transport the same
credential arrives as a real HTTP header instead.
shared-state-redis.ts needs a Redis 5+ or Valkey server. Run two copies against the
same REDIS_URL and call slow-square with the same n on each: the second answers
from the shared cache, and its computedBy field still names the first process's pid.
The 5-token bucket is shared too, so 5 calls in total go through before both copies
answer Rate limited.
Every example uses StdioServerTransport, so:
- Logs go to
stderr. Writing them tostdoutwould corrupt the JSON-RPC stream, which is why the logger defaults tostderr. withCorshas noOriginto validate.full-middleware-stack.tsapplies it only whenMCP_ALLOWED_ORIGINSis set, because an allowlist with no origin present rejects every call by design.
See the root README for the full list of limitations.