All field notes

How to Build Your First MCP Server in 10 Minutes

The shortest path from zero to a working MCP server that Claude can call. TypeScript SDK, working code, and the gotchas that will save you an hour.

On this page8 sections
  1. 01Prereqs
  2. 02Step 1: Scaffold
  3. 03Step 2: Write the server
  4. 04Step 3: Register it with Claude Desktop
  5. 05Step 4: Test it
  6. 06Step 5: Add a real tool
  7. 07Common gotchas
  8. 08Test without restarting Claude every time

This is the shortest path from zero to a working MCP server that Claude can call. We'll use the official TypeScript SDK because it's the most mature.

Prereqs

  • Node 18+
  • An Anthropic Claude Desktop install, or any MCP-aware client

Step 1: Scaffold

mkdir hello-mcp && cd hello-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod

Step 2: Write the server

Create server.ts:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new Server(
  { name: "hello-mcp", version: "0.1.0" },
  { capabilities: { tools: {} } }
);

server.tool(
  "greet",
  "Returns a friendly greeting for a given name",
  z.object({ name: z.string() }),
  async ({ name }) => ({
    content: [{ type: "text", text: `Hello, ${name}!` }],
  })
);

const transport = new StdioServerTransport();
await server.connect(transport);

Step 3: Register it with Claude Desktop

Open ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or the Windows equivalent. Add:

{
  "mcpServers": {
    "hello": {
      "command": "npm",
      "args": ["exec", "-y", "tsx", "--", "/absolute/path/to/server.ts"]
    }
  }
}

Restart Claude Desktop. Open a chat. You should see a small plug icon. Click it; greet should appear.

Step 4: Test it

Ask Claude: "Use the greet tool with name=Alice." It will call your server and reply with Hello, Alice!.

Step 5: Add a real tool

Replace greet with something useful. A common first real tool is one that hits an internal API:

server.tool(
  "search_customers",
  "Search the customer database by email or name",
  z.object({ query: z.string() }),
  async ({ query }) => {
    const res = await fetch(
      `https://api.example.com/customers?q=${encodeURIComponent(query)}`,
      { headers: { Authorization: `Bearer ${process.env.API_KEY}` } }
    );
    const data = await res.json();
    return { content: [{ type: "text", text: JSON.stringify(data) }] };
  }
);

Common gotchas

  • Logs go to stderr, not stdout. Stdout is reserved for the protocol.
  • Tool descriptions are read by the model. Write them like you're explaining to a junior dev, not a compiler.
  • Don't catch and swallow errors. Let them bubble up so the model sees something useful.

Test without restarting Claude every time

Restarting Claude to test each change gets old fast. PreMan can act as an MCP client over HTTP or stdio, so you point it at your server, hit "Refresh tools," and see the schema, descriptions, and live responses without leaving the browser.

→ Connect your local MCP server to PreMan

Bring the loop to your API

Catch the regression. Open a verified fix.

Join the waitlist to see which users a release may affect, monitor endpoints in production, and prepare a reviewable fix PR when something breaks.