
How to Build an MCP Server in Node.js and TypeScript (Step-by-Step)
A beginner-friendly, step-by-step guide to building your first Model Context Protocol (MCP) server using Node.js and TypeScript — with working code you can run in minutes.
If you've used Claude, Cursor, or any AI coding assistant lately, you've probably seen the term MCP thrown around a lot. MCP — short for Model Context Protocol — is quickly becoming the standard way for AI models to talk to external tools, files, and APIs.
The good news? Building your own MCP server is a lot simpler than it sounds. In this guide, you'll build a working MCP server from scratch using Node.js and TypeScript, in well under an hour.
What Is an MCP Server, in Plain English?
Think of an MCP server as a small, well-defined API — except instead of a frontend calling it, an AI model calls it.
Without MCP, every AI tool that wants to talk to GitHub, a database, or a weather API needs custom, one-off integration code. MCP standardizes that connection. You write one MCP server, and any MCP-compatible AI client (Claude Desktop, Claude Code, Cursor, and others) can use it immediately, with no extra glue code.
An MCP server typically exposes one or more of:
- Tools — functions the model can call (e.g., "search inventory", "create a ticket")
- Resources — data the model can read (e.g., files, database rows)
- Prompts — reusable prompt templates
For this tutorial, we'll focus on tools, since that's the most common and most useful starting point.
What You'll Need
- Node.js 18 or later
- Basic TypeScript familiarity
- An editor (VS Code works fine)
- 30–45 minutes
Step 1: Set Up the Project
mkdir mcp-dice-server
cd mcp-dice-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsxCreate a minimal tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src"]
}Step 2: Write the Server
We'll build something deliberately simple: a dice-rolling tool. Simple enough to fully understand in one sitting, but the pattern scales directly to real tools — database lookups, API calls, file operations, anything.
Create src/index.ts:
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: "dice-server",
version: "1.0.0",
});
server.registerTool(
"roll_dice",
{
title: "Roll Dice",
description: "Rolls one or more dice and returns the results",
inputSchema: {
sides: z.number().int().min(2).default(6),
count: z.number().int().min(1).max(20).default(1),
},
},
async ({ sides, count }) => {
const rolls = Array.from(
{ length: count },
() => Math.floor(Math.random() * sides) + 1
);
return {
content: [
{
type: "text",
text: `Rolled ${count}d${sides}: [${rolls.join(", ")}] (total: ${rolls.reduce((a, b) => a + b, 0)})`,
},
],
};
}
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch((err) => {
console.error("Fatal error:", err);
process.exit(1);
});That's it — that's a complete, working MCP server. Walking through what's happening:
McpServeris the core server instance — give it a name and version.registerTooldefines a callable function, with a Zod schema describing its inputs. The AI model reads this schema to know what arguments it can pass.StdioServerTransportwires the server up to communicate over stdin/stdout, which is how most local MCP clients (like Claude Desktop) talk to servers.
Step 3: Run It
Add a script to package.json:
{
"scripts": {
"start": "tsx src/index.ts"
}
}Run it:
npm start
You won't see much output — that's expected. The server is now listening on stdio, waiting for an MCP client to connect.
Step 4: Connect It to an MCP Client
To test with Claude Desktop, add this to your claude_desktop_config.json:
{
"mcpServers": {
"dice-server": {
"command": "npx",
"args": ["tsx", "/absolute/path/to/mcp-dice-server/src/index.ts"]
}
}
}Restart Claude Desktop, and you should see "dice-server" listed under available tools. Try asking: "Roll three six-sided dice." The model will call your roll_dice tool directly and return real results — not a guess.
Common Pitfalls (and How to Avoid Them)
- Relative paths in config — MCP client configs need absolute paths to your script, not relative ones.
- Console logging breaks stdio transport — never use
console.loginside an MCP server using stdio transport; it corrupts the protocol stream. Useconsole.errorfor debug output instead, since stderr is left untouched. - Schema mismatches — if the model's tool call fails silently, double-check that your Zod schema matches what you're destructuring in the handler.
- Forgetting to
await server.connect()— without this, the server process exits immediately after starting.
Where to Go From Here
Once this pattern clicks, the same structure extends to genuinely useful tools:
- Wrapping an internal REST API as an MCP tool
- Exposing a database query as a "resource"
- Building a GitHub issue-creation tool for your team's workflow
- Connecting MCP to a CI/CD pipeline for automated status checks
The dice example is intentionally trivial — the real value of MCP is that this exact same registerTool pattern is how production-grade integrations get built too.
FAQ
Is MCP only for Claude? No. MCP is an open protocol. Any client that implements the MCP spec — including several AI coding tools — can connect to your server.
Do I need to use TypeScript? No, MCP also has an official Python SDK. TypeScript is used here because of strong tooling and type-safety for tool schemas.
Can an MCP server call external APIs? Yes — inside a tool handler, you can make any network call you'd make in normal Node.js code (fetch a REST API, query a database, etc.).
Is this production-ready? The pattern is. For production, add proper error handling, input validation, logging (via stderr or a file, not stdout), and authentication if the server is exposed beyond local use.
Wrapping Up
MCP servers are simpler to build than the buzz around them suggests. With one tool, one schema, and a transport connection, you have a fully functioning server an AI model can call directly. From here, swapping the dice logic for a real API call is a small step — the architecture stays exactly the same.