Overview
An MCP server exposes tools that an MCP client such as Claude Code can call. This guide builds a one-tool stdio server in TypeScript with the v2 SDK, which is the stable line and supersedes the unified @modelcontextprotocol/sdk package that v1 used. Tool naming and description rules live in mcp-tool-design; the wire format is in mcp-protocol.
Prerequisites
- Node 22.19 or newer (MCP Inspector requires it).
- A deterministic operation to expose: an API call, a database query, or a local command.
Steps
1. Create the project
mkdir my-mcp-server && cd my-mcp-server
npm init -y && npm pkg set type=module
npm install @modelcontextprotocol/server zod
npm install -D typescript tsx @types/nodeUse the Python SDK instead when the tools live in a Python codebase.
2. Register a tool
// src/index.ts
import { McpServer } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import * as z from "zod/v4";
const server = new McpServer({ name: "my-mcp-server", version: "0.1.0" });
server.registerTool(
"get_weather",
{
description: "Return current weather for a city. Use when the user asks about weather.",
inputSchema: z.object({ city: z.string().describe("City name, e.g. London") }),
},
async ({ city }) => ({
// fetchWeather is your own API call
content: [{ type: "text", text: JSON.stringify(await fetchWeather(city)) }],
}),
);
await server.connect(new StdioServerTransport());The description is what the model reads to decide when to call the tool, so state when to use it. On stdio, stdout carries the protocol; write logs to console.error, never console.log.
3. Test in MCP Inspector
npx @modelcontextprotocol/inspector npx tsx src/index.tsSelect get_weather, fill in city, and run it. Inspector stores OAuth tokens and env values in the OS keychain when one is available, and in ~/.mcp-inspector/secrets.json unencrypted when not; do not paste production secrets on a machine without a keychain.
4. Register with Claude Code
claude mcp add --transport stdio --scope project my-mcp-server -- \
npx tsx /absolute/path/to/my-mcp-server/src/index.tsUse an absolute path to the entry file. For a remote server use the Streamable HTTP transport; see mcp-transports.
Verify it worked
claude mcp listThen ask Claude Code “What is the weather in Berlin?” and confirm an mcp__my-mcp-server__get_weather call in the transcript. In Inspector, the tool’s input schema should list city as required.
Common errors
Cannot use import statement outside a module. Set"type": "module"inpackage.json.- Inspector connects but shows no tools. The process exited during startup; read stderr, and check for a stray
console.logcorrupting the stream. - A required field arrives missing. Mark optional inputs with
.optional()or.default(...)in the zod schema. - Tool is never called. The description is vague; rewrite it to say when to use the tool.
- Server fails only inside Claude Code. The
commandis not on itsPATH; use an absolute path tonodeornpx.
FAQ
Which TypeScript package should I use to build an MCP server?
Use the v2 SDK (@modelcontextprotocol/server), the stable line that supersedes the unified @modelcontextprotocol/sdk package v1 used; use the Python SDK when the tools live in a Python codebase.
Why does my stdio MCP server show no tools in Inspector?
The process exited during startup or a stray console.log corrupted the stream. On stdio, stdout carries the protocol, so write logs to console.error.
Why does Claude Code never call my MCP tool?
The description is vague. The model reads it to decide when to call the tool, so rewrite it to say when to use the tool.
Is it safe to put secrets into MCP Inspector?
Only on a machine with an OS keychain. Without one, Inspector stores OAuth tokens and env values unencrypted in ~/.mcp-inspector/secrets.json.
Which Node version does MCP Inspector need?
Node 22.19 or newer.