TypeScript MCP Server Starter
A production-ready TypeScript MCP server template using HTTP transport with API key authentication, structured logging, health monitoring, and Docker support.
Overview
This template gives you a solid foundation for building Model Context Protocol (MCP) servers in TypeScript. It uses HTTP transport so your server can be deployed as a standard web service and called by any MCP-compatible client including Claude Desktop, Cursor, and Windsurf. Authentication is handled via API keys passed in request headers, making it straightforward to secure your server without complex OAuth flows.
The template is designed for developers who are new to MCP but comfortable with TypeScript and Node.js. Every piece of the stack โ logging, configuration, authentication, health checks, and testing โ is wired up and working out of the box. You clone the repo, set your environment variables, run docker compose up, and you have a running MCP server ready to extend with your own tools.
Inside you will find three realistic tool implementations: a text analysis tool that counts words, sentences, and estimates reading time; a URL metadata fetcher that retrieves page titles and descriptions; and a Markdown-to-plain-text converter. These serve as practical examples of how to validate inputs, handle errors gracefully, and return well-structured responses โ patterns you will apply directly to your own domain-specific tools.
What You'll Learn
- How to structure a TypeScript MCP server project for maintainability and growth
- Registering tools with the MCP SDK including input schemas and typed handlers
- Implementing API key authentication as Express middleware
- Loading and validating configuration from environment variables with Zod
- Setting up Pino for structured JSON logging with request correlation IDs
- Building a
/healthendpoint that returns dependency status as structured JSON - Writing unit and integration tests for MCP tools using Jest and Supertest
- Containerising a Node.js service with a multi-stage Dockerfile and Docker Compose
Architecture
HTTP Client / MCP Host
โ
โผ
Express Server
โโโ POST /mcp โ MCP tool calls (requires X-API-Key header)
โโโ GET /mcp โ SSE stream for server-sent events
โโโ DELETE /mcp โ Session teardown
โโโ GET /health โ Dependency & liveness check (no auth)
โ
โผ
McpServer (SDK)
โโโ analyzeText โ Word / sentence / reading-time stats
โโโ fetchUrlMetadata โ Page title + description via HTTP
โโโ markdownToText โ Strip Markdown โ plain text
Requests arrive at Express. The requireApiKey middleware rejects any request to /mcp that is missing or has an incorrect X-API-Key header. Valid requests are forwarded to a StreamableHTTPServerTransport instance which handles the MCP wire protocol and dispatches to the registered tool handlers.
Project Structure
.
โโโ src/
โ โโโ __tests__/
โ โ โโโ setup.ts # Jest global env setup
โ โ โโโ health.test.ts # Health endpoint tests
โ โ โโโ server.test.ts # Auth + MCP protocol tests
โ โโโ tools/
โ โ โโโ analyzeText.ts # Text statistics tool
โ โ โโโ fetchUrlMetadata.ts # URL metadata tool
โ โ โโโ markdownToText.ts # Markdown converter tool
โ โโโ auth.ts # API key middleware
โ โโโ config.ts # Zod-validated env config
โ โโโ health.ts # Health check handler
โ โโโ logger.ts # Pino logger setup
โ โโโ server.ts # Express app + MCP wiring
โโโ .env.example # Environment variable documentation
โโโ Dockerfile # Multi-stage production image
โโโ docker-compose.yml # Local / CI compose file
โโโ jest.config.ts # Jest configuration
โโโ package.json
โโโ tsconfig.json
Prerequisites
- Node.js 20+ โ required by the
enginesfield inpackage.json - npm 10+ โ ships with Node.js 20
- Docker + Docker Compose โ for the containerised workflow (optional)
- Basic familiarity with TypeScript and Express
Quick Start
Local development
# 1. Clone and install
git clone <your-repo-url>
cd typescript-mcp-starter
npm install
# 2. Configure environment
cp .env.example .env
# Edit .env and set MCP_API_KEY to a strong random value:
# openssl rand -hex 32
# 3. Start the dev server (hot-reload)
npm run dev
# 4. Verify health
curl http://localhost:3000/health
# 5. Call a tool
curl -X POST http://localhost:3000/mcp \
-H 'Content-Type: application/json' \
-H 'X-API-Key: <your-key>' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
Docker
cp .env.example .env # fill in MCP_API_KEY
docker compose up --build
Features
| Feature | Details |
|---|---|
| HTTP transport | StreamableHTTPServerTransport โ works with any MCP-compatible host |
| API key auth | X-API-Key header validated by Express middleware |
| Zod config | All env vars validated at startup; process exits on misconfiguration |
| Pino logging | Pretty in dev, JSON in production; configurable log level |
| Health endpoint | GET /health returns dependency status; no auth required |
| 3 example tools | analyzeText, fetchUrlMetadata, markdownToText |
| Full test suite | Jest + Supertest; covers auth, health, and tool behaviour |
| Docker | Multi-stage image; drops to node user; includes HEALTHCHECK |
Works With
- Claude Desktop โ add the server URL and API key to
claude_desktop_config.json - Cursor โ configure under Settings โ MCP Servers
- Windsurf โ add to your MCP configuration file
- Any MCP-compatible client that supports HTTP transport
Claude Desktop configuration example
{
"mcpServers": {
"my-server": {
"url": "http://localhost:3000/mcp",
"headers": {
"X-API-Key": "your-api-key-here"
}
}
}
}
Configuration
All configuration is read from environment variables. Copy .env.example to .env and adjust as needed.
| Variable | Required | Default | Description |
|---|---|---|---|
MCP_API_KEY | โ | โ | API key clients must send in X-API-Key header. Min 16 chars. |
PORT | 3000 | TCP port the server listens on | |
NODE_ENV | development | development | production | test | |
LOG_LEVEL | info | trace | debug | info | warn | error | fatal | |
REQUEST_TIMEOUT_MS | 30000 | Timeout for outbound HTTP requests (ms) | |
MAX_URL_REDIRECTS | 5 | Max redirects followed by fetchUrlMetadata |
Generate a strong API key:
openssl rand -hex 32
Deployment
Railway / Render / Fly.io
- Push your repo to GitHub.
- Create a new service pointing at the repo.
- Set the environment variables listed above in the platform's dashboard.
- The platform will detect the
Dockerfileand build automatically.
Manual VPS
# Build the production image
docker build -t mcp-server .
# Run with env vars from a .env file
docker run -d \
--name mcp-server \
-p 3000:3000 \
--env-file .env \
--restart unless-stopped \
mcp-server
Production Checklist
-
MCP_API_KEYis set to a cryptographically random value of โฅ 32 characters -
NODE_ENV=productionis set (enables JSON logging) -
LOG_LEVEL=infoor higher (avoiddebug/tracein prod) - Server is behind a TLS-terminating reverse proxy (nginx, Caddy, ALB, etc.)
- Health endpoint is wired to your load balancer / container orchestrator
- Docker image is built from the
runnerstage (notbuilder) - Container runs as the
nodeuser (non-root) -
.envis in.gitignoreand never committed - Dependency audit:
npm audit --omit=dev
Testing
# Run all tests
npm test
# Watch mode
npm run test:watch
# Coverage report
npm run test:coverage
The test suite covers:
health.test.tsโGET /healthshape, timestamp validity, no-auth access, uptime formatserver.test.tsโ authentication (missing key, wrong key, valid key),tools/listresponse shape, tool call happy-paths and error cases
Tests use a shared setup.ts that injects safe test values for all required environment variables before any module is imported.