# Build a minimal MCP server with the TypeScript SDK's McpServer, registerTool and stdio

> A minimal TypeScript MCP server creates an McpServer, registers one tool with registerTool and a Zod input schema, serves it over stdio, and is tested with the MCP Inspector.

## Answer

Install `@modelcontextprotocol/server` and `zod`. Create an `McpServer`, register a tool with `server.registerTool(name, { description, inputSchema }, handler)`, and hand a factory for the server to `serveStdio`. Then test it with `npx @modelcontextprotocol/inspector npx tsx src/index.ts`. This page uses **SDK v2**, which implements MCP specification **2026-07-28** and still serves 2025-era clients over stdio by default. The older v1 package `@modelcontextprotocol/sdk` is covered in the version note at the end.

## Details

### 1. Set up the project

The SDK ships ES modules only and needs Node.js 20 or later.

```sh
mkdir hello-mcp && cd hello-mcp
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/server zod tsx
mkdir src
```

### 2. Write the server (`src/index.ts`)

```ts
import { McpServer } from '@modelcontextprotocol/server';
import { serveStdio } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';

function createServer(): McpServer {
  const server = new McpServer({ name: 'hello', version: '1.0.0' });

  server.registerTool(
    'add',
    {
      description: 'Add two numbers',
      inputSchema: z.object({ a: z.number(), b: z.number() })
    },
    async ({ a, b }) => ({
      content: [{ type: 'text', text: String(a + b) }]
    })
  );

  return server;
}

void serveStdio(createServer);
console.error('hello MCP server running on stdio'); // stderr, never stdout
```

The SDK builds the JSON Schema that the model sees from the one Zod schema. It checks arguments against that schema before your handler runs and infers the handler's argument types from it. A handler can return `isError: true` to report a failure that the model can read.

The SDK README's Getting Started example wires the transport by hand instead, with `new StdioServerTransport()` from `@modelcontextprotocol/server/stdio` and `await server.connect(transport)`. The v2 stdio guide says `serveStdio` replaces that pattern.

### 3. Run it

```sh
npx tsx src/index.ts
```

Only the stderr banner appears. A stdio server waits on stdin until a client talks to it. Press `Ctrl+C` to stop it.

### 4. Test with the MCP Inspector

```sh
npx @modelcontextprotocol/inspector npx tsx src/index.ts
```

The Inspector launches your command and connects over stdio. It prints a URL with a one-time session token. Open the URL, click **Connect**, open the **Tools** tab, pick `add` and run it. For CI, the same package has a scriptable mode:

```sh
npx @modelcontextprotocol/inspector --cli npx tsx src/index.ts --method tools/list
```

The Inspector itself needs Node 22.19.0 or newer.

### Verification checklist

- `tools/list` shows `add` with an `inputSchema` that has `a` and `b`.
- Calling `add` with `{"a": 2, "b": 3}` returns a text block with `5`.
- Calling it with a string for `a` never reaches your handler. The call returns a normal result with `isError: true` and the text `Input validation error: Invalid arguments for tool add: a: Invalid input: expected number, received string`. We saw this with `@modelcontextprotocol/server` 2.1.0 and a 2025-11-25 `initialize`.

### Pitfall: `console.log`

stdout is the protocol channel. One `console.log`, whether yours or a dependency's, puts a line on it that no JSON-RPC parser accepts, and the host then fails to parse the stream. Use `console.error`.

### Version note: v1 vs v2

| | v1 (`@modelcontextprotocol/sdk`, 1.30.x) | v2 (`@modelcontextprotocol/server`, 2.x) |
|---|---|---|
| Import | `@modelcontextprotocol/sdk/server/mcp.js`, `.../server/stdio.js` | `@modelcontextprotocol/server`, `.../server/stdio` |
| stdio wiring | `new StdioServerTransport()` + `server.connect(transport)` | `serveStdio(factory)` |
| `inputSchema` | Raw shape: `{ a: z.number() }` | Schema object: `z.object({ a: z.number() })` |

v1 remains on the `v1.x` branch and gets bug fixes and security updates for at least six months after v2's release. If you copy a snippet from an older tutorial, check which of these three columns it matches.

## See also

- [[mcp-transports-stdio-vs-http]]
- [[mcp-connection-errors]]
- [[mcp-tools-resources-prompts]]
- [[what-is-mcp]]

## Sources

- [MCP TypeScript SDK — README (v2, main branch)](https://github.com/modelcontextprotocol/typescript-sdk)
- [MCP TypeScript SDK — Build your first server](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/get-started/first-server.md)
- [MCP TypeScript SDK — Serve over stdio](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/serving/stdio.md)
- [MCP TypeScript SDK v1.x — Server guide](https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.x/docs/server.md)
- [MCP Inspector](https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector)

## Sources

- [MCP TypeScript SDK — README (v2, main branch)](https://github.com/modelcontextprotocol/typescript-sdk)
- [MCP TypeScript SDK — Build your first server](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/get-started/first-server.md)
- [MCP TypeScript SDK — Serve over stdio](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/serving/stdio.md)
- [MCP TypeScript SDK v1.x — Server guide](https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.x/docs/server.md)
- [MCP Inspector](https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector)