Build Your First MCP Server in TypeScript
In this tutorial you'll go from an empty folder to a working MCP server connected to Claude in about 30 minutes. We'll build a small "dev utilities" server that exposes one genuinely useful tool, test it with the MCP Inspector, and wire it into a real client.
Prerequisites: Node.js 18+, basic TypeScript, and an MCP client (Claude Desktop, Claude Code, or VS Code). New to MCP? Read What Is MCP? first — it's 5 minutes.
Step 1 — Scaffold the project
mkdir devutils-mcp && cd devutils-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
Add a minimal tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "dist",
"strict": true
},
"include": ["src"]
}
And in package.json, set the module type and a build script:
{
"type": "module",
"scripts": { "build": "tsc" }
}
Step 2 — Create the server and register a tool
Create src/index.ts. We'll expose one tool: generate_id,
which produces UUIDs, nano-IDs, or timestamps — something models are notoriously bad
at doing reliably on their own.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { randomUUID, randomBytes } from "node:crypto";
const server = new McpServer({
name: "devutils",
version: "1.0.0",
});
server.registerTool(
"generate_id",
{
title: "Generate ID",
description:
"Generate unique identifiers: UUID v4, URL-safe random IDs, or Unix timestamps.",
inputSchema: {
kind: z.enum(["uuid", "nanoid", "timestamp"]).describe("Type of ID"),
count: z.number().int().min(1).max(100).default(1)
.describe("How many to generate"),
},
},
async ({ kind, count }) => {
const make = {
uuid: () => randomUUID(),
nanoid: () => randomBytes(12).toString("base64url"),
timestamp: () => Date.now().toString(),
}[kind];
const ids = Array.from({ length: count }, make);
return {
content: [{ type: "text", text: ids.join("\n") }],
};
}
);
Three things worth noticing:
- The description matters. It's what the model reads to decide when to use your tool. Write it for the model, not for humans.
- The zod schema is your contract. The SDK validates inputs before your handler runs, and the client shows the schema to the model.
- Results are content blocks. Return
{ type: "text", ... }for most things; images and structured content are also supported.
Step 3 — Connect the transport
Append this to src/index.ts:
const transport = new StdioServerTransport();
await server.connect(transport);
Gotcha: with stdio transport, never console.log() — stdout is the protocol channel, and stray output corrupts messages. Use console.error() for debugging; stderr is safe.
Build it:
npm run build
Step 4 — Test with the MCP Inspector
The Inspector is an interactive debugger for MCP servers — use it before touching any client config:
npx @modelcontextprotocol/inspector node dist/index.js
It opens a browser UI. Click Connect, open the Tools tab,
and you should see generate_id. Run it with kind: "uuid",
count: 3 — you should get three UUIDs back. If this works, your server is correct.
Step 5 — Connect it to Claude
Claude Desktop
Add to claude_desktop_config.json (Settings → Developer → Edit Config):
{
"mcpServers": {
"devutils": {
"command": "node",
"args": ["/absolute/path/to/devutils-mcp/dist/index.js"]
}
}
}
Claude Code
claude mcp add devutils -- node /absolute/path/to/devutils-mcp/dist/index.js
VS Code
Run MCP: Add Server from the command palette, choose stdio, and give it the same command.
Restart the client, then ask: "Generate 5 UUIDs for my test fixtures." The model will call your tool and return real UUIDs — generated by your code, not hallucinated.
Where to go from here
- Wrap something real. Replace
generate_idwith a tool that queries your database or calls an internal API — the pattern is identical. - Add resources and prompts. Use
server.registerResource()to expose readable data andserver.registerPrompt()for reusable templates. - Before you ship to anyone else, run through the MCP security checklist — especially if your tools touch real data.
Get the MCP curriculum by email
New tutorials and guides as they're published, in order — from fundamentals to production. Free.
First lesson is on its way.
No spam. Unsubscribe anytime.