# How to Build an MCP Server in TypeScript

> How to build an MCP server in TypeScript — a runnable tutorial with the official SDK: set up, register a tool, connect, and test with the MCP Inspector.

Source: https://designrevision.com/blog/building-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](/blog/what-is-mcp) and [how MCP works](/blog/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](https://github.com/modelcontextprotocol/typescript-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](/blog/add-mcp-server-to-claude-code), [Cursor](/blog/add-mcp-server-to-cursor)) — optional, since we'll test with the Inspector first.

## Step 1: Set Up the Project

Create a project, add the SDK and [zod](https://zod.dev) (for input schemas), and the TypeScript toolchain:

```bash
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`:

```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`.

```typescript
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`:

```typescript
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:

```bash
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:

```bash
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**:

```bash
claude mcp add my-mcp-server -- node /absolute/path/to/build/server.js
```

For **Cursor**, add it to `.cursor/mcp.json`:

```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](/blog/add-mcp-server-to-claude-code) and [Cursor](/blog/add-mcp-server-to-cursor), and we compare all of them in [MCP clients compared](/blog/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](/blog/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.

```typescript
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](/mcp), which wraps a [shadcn/ui component registry](/components) 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

- [How MCP Works: MCP Architecture Explained (2026)](/blog/how-mcp-works)
- [What Is MCP? A Frontend Developer's Guide](/blog/what-is-mcp)
- [MCP Clients Compared: Every AI Coding Agent](/blog/mcp-clients-compared)
- [MCP Tools Reference — Parameters & Examples](/mcp/tools)
- [How to Add an MCP Server to Claude Code (2026 Guide)](/blog/add-mcp-server-to-claude-code)
- [DesignRevision MCP — the shadcn/ui MCP server](/mcp)
