MCP Hub
Back to servers

template-mcp

A TypeScript MCP server template with Zod validation, dual transport (stdio/HTTP), and modular architecture for building MCP-compatible tools, resources, and prompts.

glama
Stars
1
Updated
Apr 24, 2026
Validated
Apr 26, 2026

template-mcp

MCP (Model Context Protocol) server template with TypeScript, Zod validation, and dual transport support (stdio/HTTP). Compatible with any MCP client: Claude Code, Claude Desktop, Cursor, VS Code Copilot, Windsurf, Cline, and more.

Features

  • Dual transport: stdio (local) and Streamable HTTP (remote)
  • TypeScript strict with ESM modules
  • Zod validation for tool input schemas
  • Joi env validation (fail-fast on startup)
  • Pino logging to stderr (stdio-safe)
  • Modular architecture: tools, resources, and prompts as separate modules
  • Factory pattern: createServer() for testability
  • Full test suite with MCP SDK in-memory transport
  • Quality tooling: ESLint + Prettier + Husky + lint-staged
  • Docker ready: multi-stage build
  • CI/CD: GitHub Actions pipeline

Quick Start

pnpm install
pnpm dev

Scripts

ScriptDescription
pnpm devStart with hot reload (tsx watch)
pnpm buildCompile TypeScript + resolve aliases
pnpm startRun compiled server
pnpm testRun tests
pnpm lintLint source code
pnpm type-checkType check without emit

Configuration

Copy .env.example to .env and adjust:

VariableDefaultDescription
MCP_TRANSPORTstdioTransport: stdio or http
PORT3000HTTP port (only for http transport)
LOG_LEVELinfoPino log level
NODE_ENVdevelopmentEnvironment

Project Structure

src/
├── main.ts              # Entrypoint: transport selection
├── server.ts            # createServer() factory
├── config/              # Env validation + constants
├── common/              # Logger, error helpers, types
├── tools/               # MCP tools (callable by LLMs)
├── resources/           # MCP resources (read-only data)
└── prompts/             # MCP prompts (reusable templates)

Adding a New Tool

  1. Create src/tools/my-tool.tool.ts:
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';

export function registerMyTool(server: McpServer): void {
  server.registerTool(
    'my_tool',
    {
      title: 'My Tool',
      description: 'What this tool does',
      inputSchema: {
        param: z.string().describe('Parameter description'),
      },
      annotations: {
        readOnlyHint: true,
        destructiveHint: false,
        idempotentHint: true,
        openWorldHint: false,
      },
    },
    async ({ param }) => ({
      content: [{ type: 'text', text: `Result: ${param}` }],
    }),
  );
}
  1. Register in src/tools/index.ts:
import { registerMyTool } from './my-tool.tool.js';

export function registerTools(server: McpServer): void {
  registerGreetTool(server);
  registerMyTool(server); // add here
}
  1. Add tests in src/tools/__tests__/my-tool.tool.spec.ts

Client Configuration

Claude Code

Add to .claude/settings.json:

{
  "mcpServers": {
    "template-mcp": {
      "command": "node",
      "args": ["/absolute/path/to/template-mcp/dist/main.js"]
    }
  }
}

Claude Desktop

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "template-mcp": {
      "command": "node",
      "args": ["/absolute/path/to/template-mcp/dist/main.js"]
    }
  }
}

Cursor

Add to Cursor Settings > MCP Servers:

{
  "mcpServers": {
    "template-mcp": {
      "command": "node",
      "args": ["/absolute/path/to/template-mcp/dist/main.js"]
    }
  }
}

VS Code (Copilot)

Add to .vscode/settings.json:

{
  "mcp": {
    "servers": {
      "template-mcp": {
        "command": "node",
        "args": ["/absolute/path/to/template-mcp/dist/main.js"]
      }
    }
  }
}

Docker

# Build
docker build -t template-mcp .

# Run (HTTP mode, used for remote access)
docker run -p 3000:3000 template-mcp

Tech Stack

  • Node.js 22 + TypeScript (strict, ESM)
  • MCP SDK v1 (@modelcontextprotocol/sdk)
  • Zod (tool input validation)
  • Joi (env validation)
  • Pino (stderr logging)
  • Vitest (testing)
  • ESLint + Prettier + Husky

Verificación

Todo lo siguiente está comprobado y funcionando al 100%.

Calidad de código

CheckComando
Lint + formatopnpm lint
Tipado estrictopnpm type-check
Build (tsc + alias)pnpm build

Tests unitarios (11/11)

pnpm test
SuiteCubre
greet.tool.spec.ts (5)Listado, estilos casual/formal/enthusiastic, rechazo de nombre vacío
server-info.resource.spec.ts (2)Listado, campos JSON (name, version, uptime, timestamp)
summarize.prompt.spec.ts (4)Listado, estilos brief/bullet-points, coerción numérica, defaults

Sin red ni puertos — usa InMemoryTransport del SDK.

Runtime — transporte stdio (modo por defecto)

echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}' \
  | MCP_TRANSPORT=stdio node dist/main.js

Respuesta JSON-RPC en stdout, logs en stderr.

Runtime — transporte HTTP

MCP_TRANSPORT=http PORT=3100 node dist/main.js &
# Initialize → capturar Mcp-Session-Id del header
# tools/list, resources/list, prompts/list, tools/call greet, resources/read info://server
Endpoint verificadoResultado esperado
tools/call greet {"name":"Freddy","style":"casual"}"Hey Freddy! How's it going?"
resources/read info://serverJSON con name, version, uptime, nodeVersion, timestamp

Docker

docker build -t template-mcp .  # multi-stage: base → deps → build → production
docker run -p 3000:3000 template-mcp  # arranca en HTTP mode

CI (GitHub Actions)

pnpm installpnpm lintpnpm buildpnpm test
Corre en cada push y PR a main/master.

Pipeline de commits (local)

git commit → husky → lint-staged → eslint --fix + prettier --write (solo archivos staged)

Gaps conocidos

  • HTTP transport sin tests automáticos (medio): los unit tests usan InMemoryTransport; el transporte HTTP (StreamableHTTPServerTransport) solo se verificó manualmente con curl. Para producción remota añadir tests de integración con sesión real
  • Integración con cliente MCP (medio): verificar manualmente agregando a .claude/settings.json o Cursor y confirmando que tools/resources/prompts aparecen en el cliente
  • Tool inputs extremos (bajo): strings muy largos, unicode malformado — Zod los rechaza pero el error response no está testeado vía HTTP
  • Sesiones concurrentes (bajo): fuera del scope de un template

Reviews

No reviews yet

Sign in to write a review