How to Build an MCP Server in TypeScript
To build an MCP server in TypeScript, install the official @modelcontextprotocol/sdk, create an McpServer, register a tool with registerTool, and connect it over a transport. That's the whole shape — an MCP server is a small program that exposes tools an AI client can call. This guide is a complete, runnable walkthrough: project setup, a working tool, testing with the MCP Inspector, and connecting it to Claude Code or Cursor. It assumes you know what MCP is and how MCP works; if not, skim those first.
Last updated: July 2026. The MCP TypeScript SDK evolves — the McpServer/registerTool API below is the current one; older tutorials using Server + setRequestHandler are the deprecated pattern. Confirm against the official SDK if a signature has changed.
Prerequisites
- Node.js 18+ and a package manager (npm here).
- Basic TypeScript.
- An MCP client to test against later (Claude Code, Cursor) — optional, since we'll test with the Inspector first.
Step 1: Set Up the Project
Create a project, add the SDK and zod (for input schemas), and the TypeScript toolchain:
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod@3
npm install -D typescript @types/node
MCP servers are ESM, so set "type": "module" in package.json. A minimal tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./build",
"strict": true
},
"include": ["src"]
}
Step 2: Write the MCP Server
Create src/server.ts. The core is three moves: instantiate an McpServer, register a tool, and (next step) connect a transport. Note the ESM import paths end in .js — that's required, even though the files are .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: "my-mcp-server",
version: "1.0.0",
});
// Register a tool: name, schema + description, and a handler.
server.registerTool(
"add",
{
description: "Add two numbers and return the sum",
inputSchema: {
a: z.number().describe("The first number"),
b: z.number().describe("The second number"),
},
},
async ({ a, b }) => ({
content: [{ type: "text", text: `${a + b}` }],
})
);
Three things worth calling out. The inputSchema is a plain object of zod fields ({ a: z.number() }), not z.object({...}) — the SDK wraps it for you. The .describe() calls aren't decoration: they're what the model reads to know when and how to call the tool, so write them like documentation. And the handler returns a content array, each item typed (text here) — that's what goes back to the model.
Step 3: Connect a Transport
A server does nothing until it's connected to a transport. For a local server, that's stdio — the client launches your server as a subprocess and talks over standard input/output. Add this to the bottom of src/server.ts:
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});
One rule that trips everyone: never console.log to stdout in a stdio server — stdout is the protocol channel, and stray output corrupts the JSON-RPC stream. Log to stderr (console.error) instead.
Step 4: Build and Run
Compile and you have a runnable server:
npx tsc
node build/server.js
It'll sit there waiting for a client to speak to it over stdio — that's correct. Add a "build": "tsc" script to package.json so you can npm run build.
Step 5: Test with the MCP Inspector
Before wiring it into an AI, test it in isolation. The MCP Inspector is the official tool for this — it launches your server and gives you a UI to list and call tools:
npx @modelcontextprotocol/inspector node build/server.js
Open the URL it prints, find your add tool, call it with a and b, and confirm it returns the sum. If the tool shows up and responds, your server works — no AI client needed yet.
Step 6: Connect It to Claude Code or Cursor
Now make a real client use it. For Claude Code:
claude mcp add my-mcp-server -- node /absolute/path/to/build/server.js
For Cursor, add it to .cursor/mcp.json:
{
"mcpServers": {
"my-mcp-server": {
"command": "node",
"args": ["/absolute/path/to/build/server.js"]
}
}
}
Restart the client, and the agent can call your add tool. The exact mechanics per client are in our guides for Claude Code and Cursor, and we compare all of them in MCP clients compared.
Beyond Tools: Resources and Prompts
Tools are one of three MCP primitives, and the same server can expose all three. Resources are read-only data the app can pull into context (server.registerResource(...)); prompts are reusable templates the user triggers (server.registerPrompt(...)). Most servers are mostly tools, but resources are worth reaching for when you're exposing data rather than actions. See how MCP works for how the three fit the protocol.
Building an MCP Server From an Existing API
The most common real case isn't a toy add tool — it's wrapping an API you already have. The pattern is the same: register a tool per operation, and in the handler call your API and return the result.
server.registerTool(
"search_users",
{
description: "Search users by name",
inputSchema: { query: z.string().describe("Name to search for") },
},
async ({ query }) => {
const res = await fetch(`https://api.example.com/users?q=${query}`);
const data = await res.json();
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
That's the whole idea behind most production servers — including DesignRevision MCP, which wraps a shadcn/ui component registry as tools an agent can call. You expose a few well-described operations, not your entire API surface.
Local vs. Remote (and Other Languages)
This guide builds a local (stdio) server. To make it remote, swap the transport for streamable HTTP and host it behind a URL — the tool code is identical; only the transport and deployment change. And if TypeScript isn't your stack, MCP has official SDKs for Python (the other most popular choice), Java, Kotlin, and C#; the registerTool → return-content → connect shape carries over, only the syntax differs.
Conclusion
Building an MCP server in TypeScript is genuinely small: install @modelcontextprotocol/sdk, create an McpServer, registerTool with a zod schema and a handler that returns content, and connect a StdioServerTransport. Test with the MCP Inspector, add it to Claude Code or Cursor, and you've extended an AI with a capability it didn't have. Start with one tool that does something real — wrapping an API you already run is the fastest path from "hello world" to useful.
Related Resources
Frequently Asked Questions
-
Install the official MCP SDK, create a server object, register one or more tools with an input schema and a handler, and connect the server to a transport (stdio for local, HTTP for remote). In TypeScript that's the @modelcontextprotocol/sdk package, an McpServer instance, server.registerTool(...), and a StdioServerTransport. Then test it with the MCP Inspector and add it to a client like Claude Code or Cursor.
-
MCP has official SDKs for TypeScript, Python, Java, Kotlin, C#, and more, so you can build a server in whichever you prefer. TypeScript and Python are the most common. This guide uses TypeScript; the concepts — register a tool, return content, connect a transport — are identical across languages, only the syntax changes.
-
Use the MCP Inspector — run npx @modelcontextprotocol/inspector node build/server.js and it opens a UI where you can see your server's tools, call them with arguments, and inspect the responses, all without wiring it into a real AI client first. It's the fastest way to confirm a tool works before connecting the server to Claude or Cursor.
-
Wrap it. Create a tool per operation you want to expose, and in each handler call your existing API (with fetch or your SDK) and return the result in the content array. The MCP server becomes a thin, AI-friendly layer over the API you already have — you're not rewriting the API, just describing a few of its operations as tools a model can discover and call.
-
No. You only build an MCP server if you want to expose your own tool or data to AI clients. To simply use existing servers (GitHub, Playwright, DesignRevision MCP), you just add them to your client's config — no coding. Build a server when the capability you need doesn't exist yet.
-
For Claude Code, run claude mcp add my-server -- node /path/to/build/server.js. For Cursor, add an entry under mcpServers in .cursor/mcp.json with the command and args. Both launch your compiled server as a local stdio subprocess. See our per-client setup guides for the exact steps.
Join 50k+ subscribers
Web dev, SaaS, growth & marketing. Weekly.
Keep Learning
More articles you might find interesting.