Streamable MCP Server
A Model Context Protocol server written in TypeScript, exposed over the Streamable HTTP transport (the modern replacement for the older HTTP+SSE transport).
Features
- Streamable HTTP transport at
POST/GET/DELETE /mcp, with per-session state (via themcp-session-idheader) - Example tools:
echo,add,get-time - Example resource:
info://server GET /healthzfor container/orchestrator health checks- Multi-stage, non-root Dockerfile
Project layout
src/
server.ts # McpServer factory: tools & resources are registered here
index.ts # Express app wiring the Streamable HTTP transport + session management
Local development
npm install
npm run dev # tsx watch, restarts on change
Or build and run compiled JS:
npm run build
npm start
The server listens on http://localhost:3000/mcp by default (override with PORT).
Trying it with curl
Initialize a session (note the mcp-session-id response header, needed for follow-up requests):
curl -i -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl-test","version":"1.0"}}}'
Then, using the returned session id:
SESSION=<mcp-session-id from above>
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $SESSION" \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $SESSION" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"add","arguments":{"a":21,"b":21}}}'
Docker
Build the image:
docker build -t streamable-mcp-server .
Run it:
docker run -d -p 3000:3000 --name mcp-server streamable-mcp-server
Check health:
curl http://localhost:3000/healthz
Adding your own tools
Add new server.registerTool(...) calls in src/server.ts. Each new session gets a freshly created McpServer, so any per-session state should live inside the factory function's closure or be looked up externally (e.g. a database) rather than stored in module-level globals.