The fastest way to understand MCP is to build a server. You don’t need a database or a network service to start — a single tool that does one small thing is enough to see the whole loop: define a capability, connect a transport, and watch Claude Code call it.
This walkthrough builds a minimal MCP server in Node and TypeScript with the official SDK, exposes one tool, wires it into Claude Code, and tests it. If the protocol concepts are still fuzzy, read the Model Context Protocol explained first — this assumes you know what tools, resources, and transports are.
What you’ll build
A server with one tool that takes two numbers and returns their sum. Trivial on purpose: the point is the wiring, not the logic. Once the loop works, swapping in a real tool — a database query, an internal API call — is the same shape.
Before you start
- Node.js installed (a current LTS release).
- Claude Code installed and working.
- A terminal — PowerShell on Windows or bash in WSL.
- Basic comfort with TypeScript and npm.
Set up the project
Create a folder and initialize it:
mkdir add-server
cd add-server
npm init -y
Install the official TypeScript SDK and the tooling to build and validate input:
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
zod defines and validates the tool’s input schema. Add a minimal tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "dist",
"strict": true
},
"include": ["src/**/*.ts"]
}
Write the server
Create src/index.ts. The structure is always the same: make a server, register capabilities, connect a transport.
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: "add-server",
version: "1.0.0",
});
server.registerTool(
"add",
{
title: "Add two numbers",
description: "Returns the sum of a and b.",
inputSchema: { a: z.number(), b: z.number() },
},
async ({ a, b }) => {
return {
content: [{ type: "text", text: String(a + b) }],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
Three things are happening. The McpServer declares the server’s name and version. registerTool adds the add tool with a description and an input schema — the description and schema are what the model reads to decide when and how to call it. Then connect attaches a stdio transport so Claude Code can launch the server as a subprocess and exchange messages over stdin/stdout.
Note the .js extensions on the SDK imports — that’s required under Node16 module resolution even though the source is TypeScript. Exact export names can shift between SDK versions, so if an import fails, check the current SDK reference rather than guessing.
Build it
Add a build script to package.json:
{
"scripts": {
"build": "tsc"
}
}
Compile:
npm run build
That produces dist/index.js, the file Claude Code will run.
The three primitives, briefly
You just registered a tool. The SDK lets you register the other two MCP primitives the same way, and it’s worth seeing them side by side so you know what’s available when your server grows past a single action.
MCP primitives in the SDK
| Primitive | What it does |
|---|---|
| Tool | An action the model can invoke |
| Resource | Read-only data the server exposes |
| Prompt | A reusable template the user invokes |
Most servers start with one tool and add more over time. Resources and prompts are optional — reach for them when the model needs to read context or when you want to offer a canned workflow as a slash command.
Connect it to Claude Code
Register the built server with claude mcp add, pointing the command at dist/index.js. In WSL:
claude mcp add add-server -- node /home/you/add-server/dist/index.js
On native Windows, run node through the command interpreter to avoid the shim issue that bites npm-installed launchers:
claude mcp add add-server -- cmd /c node C:\Users\YourName\add-server\dist\index.js
That cmd /c wrapper and the Windows path differences are covered in depth in the Windows and WSL MCP setup guide — worth a read if the connection fails.
Test it
Start Claude Code and check the server is live:
/mcp
You should see add-server connected with one tool, add. Now ask the agent to use it:
Use the add tool to add 21 and 21.
Claude Code calls your tool, your handler returns 42, and the result comes back into the conversation. That round trip — request out over stdio, your code runs, result back — is the entire protocol in action.
Where to take it
Replace the add handler with something real and you have a useful server. A few directions:
- Wrap an internal API so the agent can query a service you own.
- Expose read-only data as resources — config files, logs, a status endpoint.
- Add input validation with richer zod schemas so bad arguments fail clearly.
Keep tool descriptions specific. The model decides whether to call a tool based on its description, so “run a read-only SQL query against the analytics DB” beats “database tool” every time.
Wrapping up
A working MCP server is genuinely small: create the server, register one tool with a schema, connect a stdio transport, build, and add it to Claude Code. Everything beyond that — more tools, resources, prompts, a remote HTTP transport — is layered on the same foundation you just built.
From here, see which servers other people have already built in the best MCP servers for Claude Code roundup, so you don’t reinvent something that exists. And keep the MCP explainer handy as a reference for the primitives.