Most MCP servers I see in the wild start as a quick script and stay that way — no validation, no structured logging, no tests, and a deploy story that means shipping node_modules around.
I got tired of rebuilding the same scaffolding every time a client project needed a Model Context Protocol server, so I open-sourced the template I now start every one from: 🚀 mcp-server-template
It's a production-ready TypeScript/Node.js foundation for building MCP servers that connect AI agents like Claude Desktop and Cursor to your tools, data, and workflows.
𝗚𝗲𝘁𝘁𝗶𝗻𝗴 𝘀𝘁𝗮𝗿𝘁𝗲𝗱 𝘁𝗮𝗸𝗲𝘀 𝗳𝗼𝘂𝗿 𝗰𝗼𝗺𝗺𝗮𝗻𝗱𝘀:
git clone https://github.com/qmmughal/mcp-server-template.git
cd mcp-server-template && npm install
cp .env.example .env
npm run dev
That spins up a working server in watch mode. npm test runs the Vitest suite, npm run build bundles everything into a single dist/index.js with esbuild — no node_modules to deploy.
𝗪𝗵𝗮𝘁 𝗮 𝘁𝗼𝗼𝗹 𝗮𝗰𝘁𝘂𝗮𝗹𝗹𝘆 𝗹𝗼𝗼𝗸𝘀 𝗹𝗶𝗸𝗲:
Every tool gets a Zod schema, a definition, and a handler — so a malformed AI payload gets rejected with a clean error instead of crashing your process:
const schema = z.object({
text: z.string().describe("The text to process"),
repeat: z.number().int().min(1).max(10).optional()
});
export async function handleExampleTool(args: unknown, service: ExampleService) {
return withErrorHandling("process_text", async () => {
const { text, repeat } = validateArgs(schema, args);
const result = await service.processText(text, repeat);
return { content: [{ type: "text", text: result }] };
});
}
𝗘𝘅𝘁𝗲𝗻𝗱𝗶𝗻𝗴 𝗶𝘁 𝗳𝗼𝗿 𝘆𝗼𝘂𝗿 𝗼𝘄𝗻 𝘁𝗼𝗼𝗹𝘀:
- Drop a new file in src/tools/ following the same schema → definition → handler shape
- Register it in src/tools/index.ts — add your definition to the tools list and a case to the switch statement that routes CallToolRequest to your handler
- Put your real logic in src/services/ so the protocol layer stays thin and your business logic stays unit-testable in isolation
- Resources (data the AI can read) and Prompts (reusable templates) follow the exact same pattern in their own folders — copy the example, rename, adjust the schema
Structured pino logging routes safely to stderr throughout, so it never corrupts the MCP stdio transport regardless of what you add.
When you're ready to ship, tag a release and a GitHub Actions workflow publishes to both npm and the MCP Registry automatically:
git tag v1.0.0 && git push origin v1.0.0
The goal is simple — spend your time on the logic that makes your MCP server useful, not on re-solving logging, validation, and bundling for the fifth time.
It's MIT licensed and live on GitHub now. If you're building agentic AI integrations, I'd love feedback or a star:
🔗 github.com/qmmughal/mcp-server-template
Top comments (1)
The schema → definition → handler split is a solid default. One test I’d add to the template is a contract test against the built artifact, not only the TypeScript source.
Start the bundled
dist/index.jsover stdio, calltools/list, compare the exposed names and input schemas to a reviewed snapshot, then execute one valid and one invalid request per tool. That catches missing registration, bundling surprises, schema drift, and accidental stdout output in the exact artifact users install.It also makes a release diff much easier to review: a changed tool contract becomes explicit before the npm or registry publish step.