create-stateless-mcp
v0.1.0
Published
Express-based TypeScript starter for stateless MCP servers with explicit application state handles.
Maintainers
Readme
Stateless MCP Starter
An Express + TypeScript starter for building stateless MCP servers with explicit, typed application state.
This repo demonstrates a simple production-oriented pattern:
- Keep MCP protocol handling stateless.
- Store application state behind a
StateStore<TState>. - Return opaque handles like
cart_...,booking_..., ordraft_...from tools. - Require later tool calls to pass those handles back as normal arguments.
The included shopping-cart tools are just an example domain. The important idea is not ecommerce; it is the explicit state-handle pattern. The same shape works for bookings, support tickets, onboarding flows, insurance quotes, approvals, drafts, and other multi-turn workflows.
Why This Exists
Remote MCP servers should be easy to scale horizontally. A later request should not need to find the same process, protocol session, or in-memory object that handled the first request.
Instead, the model receives an application handle from one tool call:
{
"cart_id": "cart_abc123"
}Then it passes that handle into the next tool call:
{
"cart_id": "cart_abc123",
"sku": "ceramic-mug",
"quantity": 2
}Any server instance can handle either request because the state lives in the configured StateStore, not in MCP protocol/session state.
Quickstart
Generate a new project:
pnpm dlx create-stateless-mcp my-server
cd my-server
pnpm install
pnpm devThe server starts on http://127.0.0.1:3000 by default.
curl http://127.0.0.1:3000/healthExpected response:
{
"status": "ok",
"service": "stateless-mcp-starter"
}You can also run this checkout directly:
pnpm install
pnpm devGenerator
This package provides a create-stateless-mcp CLI:
pnpm dlx create-stateless-mcp my-serverFor local development from this repo:
node bin/create-stateless-mcp.js my-server --yesOptions:
node bin/create-stateless-mcp.js my-server \
--state-adapter memory \
--auth-mode dev \
--example-domain shopping-cart \
--yesSupported choices:
--state-adapter memory|dynamodb|postgres--auth-mode dev|bearer--example-domain shopping-cart|minimal
Use shopping-cart when you want a concrete working example. Use minimal when you want the MCP server scaffold without example domain code.
Included MCP Tools
The shopping-cart example exposes these tools over /mcp:
list_products: returns available product SKUs.create_cart: creates a cart and returnscart_id.add_cart_item: adds an item usingcart_id,sku, andquantity.get_cart: fetches the cart usingcart_id.
The tool names and cart types are intentionally replaceable. They exist to make the handle flow obvious.
State Model
StateStore<TState> is the application state boundary. Records include:
- handle
- type
- tenant/user scope
- status
- version
- timestamps
- TTL
- typed value
Handles are opaque identifiers, not authorization. Every state read or write also receives tenant/user scope.
Included adapters:
InMemoryStateStorefor local development and tests.DynamoStateStorefor shared production-style state.PostgresStateStorefor shared production-style state in Postgres.
Memory mode is zero setup:
STATE_ADAPTER=memoryDynamoDB mode:
STATE_ADAPTER=dynamodb
DYNAMODB_TABLE_NAME=stateless-mcp-state
AWS_REGION=us-east-1Postgres mode:
STATE_ADAPTER=postgres
POSTGRES_CONNECTION_STRING=postgres://postgres:[email protected]:5432/stateless_mcp
POSTGRES_TABLE_NAME=mcp_stateSee docs/adapters.md for adapter details and the Postgres schema.
Auth And Context
Local development defaults to permissive dev auth:
AUTH_MODE=devProduction should use the bearer-token stub or replace it with your own auth layer:
NODE_ENV=production
AUTH_MODE=bearer
AUTH_BEARER_TOKEN=replace-with-a-real-secretEvery MCP request gets a typed context with requestId, tenantId, userId, and scopes. In dev mode, x-tenant-id, x-user-id, and x-user-scopes headers are optional. In bearer mode, tenant/user headers are required and state operations remain scoped by both values.
MCP SDK Compatibility
This starter uses the production-ready v1 @modelcontextprotocol/sdk package and Streamable HTTP transport. The HTTP transport is configured without server-side session IDs, so application continuity comes from explicit handles and the configured StateStore.
Protocol-specific wiring is isolated in src/http/app.ts and src/mcp/createServer.ts, so future SDK changes should not require rewriting your state model, tools, or auth boundary.
Project Structure
src/
auth/ request auth and typed request context
cart/ example shopping-cart domain
http/ Express app, /health, and /mcp route
mcp/ MCP server and tool registration
state/ StateStore interface and adapters
test/ focused unit and flow tests
docs/ deployment, adapters, and explicit-handle notes
bin/ create-stateless-mcp generatorScripts
pnpm dev
pnpm test
pnpm buildWhat This Is
- A starter for stateless remote MCP servers.
- A concrete example of explicit multi-turn application state.
- A TypeScript/Express scaffold with tests, Docker support, and a generator.
- A place to swap in your own domain tools and state types.
What This Is Not
- A complete auth solution.
- A hosted deployment platform.
- A framework that hides MCP concepts from you.
- An ecommerce product. The cart domain is only an example.
Deployment
See docs/deployment.md for Docker, required environment variables, and no-sticky-session guidance.
Future Milestones
- Redis
StateStoreadapter. - JWT auth hardening.
- More deployment examples.
- More generated templates.
Release
This project is prepared for npm publishing as create-stateless-mcp, but no publish or release action should happen without explicit approval. See RELEASE_CHECKLIST.md.
