Plinth / guides / build-mcp-server-typescript

How to build an MCP server in TypeScript

~6 min read · Node 20+ · TypeScript · Model Context Protocol

If your stack is already TypeScript, you don't need to drop into Python to ship an MCP server. The official @modelcontextprotocol/sdk is first-class TS, and with strict types plus Zod for argument schemas you get a server that's hard to misuse. Here's the fast path, then the parts the quickstart leaves out.

1. Set up the project

npm init -y
npm i @modelcontextprotocol/sdk zod
npm i -D typescript @types/node
npx tsc --init

Set "type": "module" in package.json and target ES2022 with "module": "NodeNext" in tsconfig.json. MCP's SDK is ESM.

2. Define a server and a tool

Use Zod to describe tool inputs — the SDK turns the schema into what the client sees and validates arguments for you.

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({ name: "weather", version: "1.0.0" });

server.tool(
  "get_forecast",
  { city: z.string(), days: z.number().int().default(1) },
  async ({ city, days }) => ({
    content: [{ type: "text", text: `${city}: clear, ${days}d ahead` }],
  })
);

const transport = new StdioServerTransport();
await server.connect(transport);

Compile with tsc and run the output with node. The server speaks JSON-RPC over stdio, the same channel desktop clients use to launch it.

3. Connect a client

Register the launch command (e.g. node dist/server.js) in your MCP client's config. The client spawns the process and pipes JSON-RPC through stdin/stdout.

The gotcha that breaks everyone In Node it's tempting to console.log() while debugging. Don't — console.log writes to stdout, which is the protocol stream. It corrupts the JSON-RPC framing and the client disconnects. Use console.error (stderr) or a logger pinned to stderr.

4. Add resources and prompts

Tools are actions; resources expose readable data and prompts expose reusable templated messages. The SDK has server.resource(...) and server.prompt(...) with the same ergonomics. Keep handlers thin and let Zod reject bad input at the edge.

5. From "it runs" to "it ships"

The server above works. Putting it in front of a real agent exposes the same gaps as every other stack:

Back it with tests that drive the server over the actual protocol, not mocked internals, so a refactor can't silently break a tool contract.

Or start from a base that already has all of it

The Plinth MCP Server Starter Kit ships a strict-TypeScript template (and a matching Python one) with auth gating, rate limiting, backoff, structured stderr logging, graceful shutdown, input validation, health checks and a hardened Dockerfile already wired up — and 19 TypeScript tests driving it over the real MCP protocol.

Get the kit — $39