---
title: "How to build an MCP server"
slug: "build-an-mcp-server"
category: "howto"
tags: ["howto", "mcp", "ai-agents", "tutorial", "typescript", "tools", "json-schema"]
status: "stable"
last_updated: 2026-08-14
summary: "Choose a stack, define tools with JSON Schema, register the server, and verify it in MCP Inspector before wiring to Claude Code."
related: ["[[ai-agents/mcp-servers]]", "[[ai-agents/mcp-tool-design]]", "[[ai-agents/mcp-protocol]]", "[[ai-agents/mcp-transports]]", "[[ai-agents/mcp-security]]", "[[howto/run-claude-code-with-mcp]]", "[[howto/connect-claude-to-postgres-via-mcp]]", "[[tooling/claude-code-workflow]]"]
---

## Overview

An MCP server exposes named tools that Claude Code (or any MCP client) can call during a session. Tools wrap shell commands, database queries, API calls, or any deterministic operation. The [[ai-agents/mcp-protocol]] defines the wire format; this guide focuses on the implementation path in TypeScript using the official SDK. The tool design rules live in [[ai-agents/mcp-tool-design]].

## Prerequisites

- Node 22 on the path.
- An Anthropic API key or Claude Code installed, to test after wiring.
- MCP Inspector installed: `npm install -g @modelcontextprotocol/inspector`.
- A target integration: a REST API, a database, or a local command you want to expose.

## Steps

### 1. Choose a stack

Use the TypeScript SDK (`@modelcontextprotocol/sdk`) for servers that will be distributed as npm packages or run in Node. Use the Python SDK (`mcp`) for data tools that live inside Python environments. This guide uses TypeScript; the Python API mirrors it closely.

```bash
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript tsx @types/node
```

Add `"type": "module"` to `package.json` and a `tsconfig.json` targeting ES2022.

### 2. Define tools with JSON Schema

Each tool has a name, a description the model reads to decide when to call it, and an `inputSchema` in JSON Schema format. Keep descriptions crisp; the model reads them at inference time.

```typescript
// src/index.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: "0.1.0",
});

server.tool(
  "get_weather",
  "Return current weather for a city. Use when the user asks about weather.",
  {
    city: z.string().describe("City name, e.g. 'London'"),
    units: z.enum(["metric", "imperial"]).default("metric"),
  },
  async ({ city, units }) => {
    // Replace with a real API call.
    const data = await fetchWeather(city, units);
    return { content: [{ type: "text", text: JSON.stringify(data) }] };
  }
);
```

Zod schemas convert to JSON Schema automatically. Pass `z.object({...})` or individual field schemas as the third argument. See [[ai-agents/mcp-tool-design]] for naming and description rules.

### 3. Wire the transport

MCP servers communicate over stdio (for local Claude Code use) or HTTP/SSE (for networked clients). Start with stdio; switching later requires only swapping the transport object. See [[ai-agents/mcp-transports]] for when to use each.

```typescript
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP server running on stdio");
}

main().catch(console.error);
```

Add a start script to `package.json`:

```json
"scripts": { "start": "tsx src/index.ts" }
```

### 4. Test with MCP Inspector

MCP Inspector is a GUI that connects to the server over stdio and lets you call tools manually before wiring to Claude Code.

```bash
npx @modelcontextprotocol/inspector node --loader tsx src/index.ts
```

Inspector opens in the browser. Select the `get_weather` tool, fill in `city`, and click Run. Confirm the response matches expectations.

### 5. Register the server in Claude Code

Add an entry to `.mcp.json` at the root of the project that will consume the server. `.mcp.json` is the project-scoped, shared MCP registry; `.claude/settings.json` holds permissions and hooks, not server definitions. See [[howto/run-claude-code-with-mcp]] for the full configuration reference.

```json
{
  "mcpServers": {
    "my-mcp-server": {
      "command": "node",
      "args": ["--loader", "tsx", "/path/to/my-mcp-server/src/index.ts"]
    }
  }
}
```

For a published npm package, replace the `node` command with `npx -y my-mcp-server`. You can also register the server without hand-editing JSON: `claude mcp add --transport stdio my-mcp-server -- node --loader tsx /path/to/my-mcp-server/src/index.ts`.

## Verify it worked

```bash
# Inside a claude session:
/mcp
# Expected: my-mcp-server: connected (N tools available)

# Confirm the tool is callable:
# Ask: "What is the weather in Berlin?"
# Claude calls get_weather; the result appears in the response.
```

Validate the JSON Schema the SDK generates by running `server.listTools()` from a test script and checking each tool's `inputSchema`.

## Common errors

- **`SyntaxError: Cannot use import statement`**. Add `"type": "module"` to `package.json` or compile with `tsc` before running.
- **Inspector shows no tools**. The server connected but exited before registration completed. Wrap `server.connect` in a `try/catch` and log errors to `stderr`.
- **Zod schema mismatch**. Required fields with no default throw if the caller omits them. Add `.optional()` or `.default(value)` as needed.
- **Claude Code shows "disconnected"**. The server path in `settings.json` is wrong or the `command` binary is not on the PATH used by Claude Code. Use an absolute path for reliability.
- **Tool description is too long**. MCP clients include descriptions in the context window. Keep each description under 100 characters; move rationale to the tool result instead.

## Related

- [[ai-agents/mcp-servers]]
- [[ai-agents/mcp-tool-design]]
- [[ai-agents/mcp-protocol]]
- [[ai-agents/mcp-transports]]
- [[ai-agents/mcp-security]]
- [[howto/run-claude-code-with-mcp]]
- [[howto/connect-claude-to-postgres-via-mcp]]
- [[tooling/claude-code-workflow]]
