How to Build an MCP Server for Claude Code
A step-by-step tutorial: build a minimal MCP server in Node and TypeScript, expose one tool over stdio, and register it with Claude Code via claude mcp add and a project .mcp.json.
設定をやめて、構築を始めよう。
AIオーケストレーション付きSaaSビルダーテンプレート。
An MCP server is a small program that exposes your own tools and data to Claude Code over a shared standard, the Model Context Protocol. You can build a working one in a single Node and TypeScript file, register it with one claude mcp add command, and then ask Claude Code to call it like any other tool.
This is a build tutorial. By the end you will have a minimal stdio server that exposes one tool, wired into Claude Code two different ways, with a way to confirm Claude is actually calling it.
Most people never get here. In our State of Claude Code 2026 analysis, only about 17% of public Claude Code repos ship a project .mcp.json, and that is a floor since MCP can also be configured outside the project file. Plenty of developers consume MCP servers. Far fewer have built one. This post moves you into the second group.
Table of Contents
- What Is an MCP Server?
- The Mini Project Layout
- Set Up the Project
- Write the Server
- Register It With Claude Code
- Verify Claude Code Sees and Calls the Tool
- What You Get
- Frequently Asked Questions
設定をやめて、構築を始めよう。
AIオーケストレーション付きSaaSビルダーテンプレート。
What Is an MCP Server?
Claude Code can edit files and run shell commands on its own. The moment it needs something outside that, a row from your internal database, a record from a private API, a calculation only your service knows how to do, it has no way in. An MCP server is the way in.
Think of it as a small program that publishes a menu of tools. Claude Code starts the program, reads the menu, and from then on it can order off that menu during a conversation. The menu format is the Model Context Protocol, an open standard, so the same server works with Claude Code, Claude Desktop, Cursor, and any other MCP client. You write it once.
If you want the conceptual background before building, the MCP basics guide covers how the protocol and transports fit together, and what Claude Code is covers the tool itself. This post assumes you already use Claude Code and want to expose your own tool to it.
Every server you build has the same three parts: a tool definition (the name, description, and inputs Claude is allowed to send), a handler (the code that runs when Claude calls the tool), and a transport (how the server and Claude Code talk). For a local tool, the transport is stdio: Claude Code launches your server as a subprocess and speaks to it over standard input and output.
The Mini Project Layout
Here is everything the finished project contains. It is deliberately small, four files including the lockfile, with the server logic living in one TypeScript file.
weather-mcp/
├── package.json
├── tsconfig.json
├── src/
│ └── server.ts
└── .mcp.json # added later, registers the server with Claude CodeThe tool we will build is a get_forecast tool that returns a short weather summary for a city. It uses a tiny hardcoded lookup so the example stays self-contained and runs with no API key. Swapping that lookup for a real fetch call to a weather API or a database query is a one-line change, and the rest of the server stays identical.
Set Up the Project
Start with an empty folder and initialize a Node project. The two dependencies are the official SDK and zod, which the SDK uses to validate the inputs Claude sends. You also need a couple of dev tools to run TypeScript directly.
mkdir weather-mcp && cd weather-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript tsx @types/nodeNow create package.json. The important parts are "type": "module", since the SDK ships as ES modules, and a start script that runs the server with tsx so you do not need a separate compile step. Replace the generated file with this.
{
"name": "weather-mcp",
"version": "1.0.0",
"type": "module",
"bin": {
"weather-mcp": "./src/server.ts"
},
"scripts": {
"start": "tsx src/server.ts"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.29.0",
"zod": "^3.25.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"tsx": "^4.19.0",
"typescript": "^5.6.0"
}
}Add a minimal tsconfig.json so editors and the type checker understand the module setup. These settings match how the SDK expects to be imported.
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["src/**/*.ts"]
}Write the Server
This is the whole server. It creates an McpServer, registers a single get_forecast tool with a zod input schema, returns a text result from the handler, and then connects over stdio. Read it once, then save it as src/server.ts.
// src/server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Stand-in for a real API or database. Swap this for a fetch() call
// or a SQL query and the rest of the server stays the same.
const FORECASTS: Record<string, string> = {
london: "12C, light rain, wind 18 km/h",
"san francisco": "17C, fog clearing by noon, wind 22 km/h",
tokyo: "24C, clear skies, wind 9 km/h",
};
const server = new McpServer({
name: "weather-mcp",
version: "1.0.0",
});
server.registerTool(
"get_forecast",
{
title: "Get Forecast",
description: "Get a short weather forecast for a given city.",
inputSchema: {
city: z.string().describe("City name, for example 'London' or 'Tokyo'"),
},
},
async ({ city }) => {
const key = city.trim().toLowerCase();
const forecast = FORECASTS[key];
if (!forecast) {
return {
content: [
{
type: "text",
text: `No forecast available for "${city}". Try London, San Francisco, or Tokyo.`,
},
],
};
}
return {
content: [
{
type: "text",
text: `Forecast for ${city}: ${forecast}`,
},
],
};
},
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
// Logs go to stderr. stdout is reserved for the MCP protocol,
// so never use console.log here.
console.error("weather-mcp running on stdio");
}
main().catch((error) => {
console.error("Fatal error starting weather-mcp:", error);
process.exit(1);
});A few details that trip people up. The imports use the .js extension even though the file is TypeScript; that is the SDK's published module layout and it is correct as written. The inputSchema is a plain object that maps each field name to a zod type, not a wrapping z.object(...); the SDK builds the JSON schema Claude sees from that shape. And every log line uses console.error, because stdout carries the protocol messages and any stray console.log would corrupt them.
Confirm it starts cleanly before wiring anything up. It should print the running line to stderr and then sit waiting for input, which is correct. Press Ctrl+C to stop it.
npm startRegister It With Claude Code
There are two ways to tell Claude Code about the server, and you only need one. The first is the claude mcp add command, which writes the configuration for you. Run this from the project root.
claude mcp add --scope project weather -- npx tsx /absolute/path/to/weather-mcp/src/server.tsThe --scope project flag stores the entry in a .mcp.json file at your project root so it travels with the repo. The -- separates Claude's own flags from the command that launches your server; everything after it runs untouched. Use an absolute path for the script so Claude Code can find it regardless of the working directory. If your server needs secrets, pass them with --env, which accepts repeated KEY=value pairs:
claude mcp add --env WEATHER_API_KEY=your-key --scope project weather -- npx tsx /absolute/path/to/weather-mcp/src/server.tsThe second way is to write the .mcp.json file by hand. This is the file the command above produces, and editing it directly is useful when you want it in version control from the start. Create .mcp.json at the project root with this content.
{
"mcpServers": {
"weather": {
"command": "npx",
"args": ["tsx", "/absolute/path/to/weather-mcp/src/server.ts"],
"env": {}
}
}
}Each server lives under the mcpServers key. command is the executable Claude Code runs, args is the argument list passed to it, and env holds environment variables handed to the server process. If your tool needs a key, add it to env (for example "WEATHER_API_KEY": "your-key") rather than hardcoding it in the source. Project-scoped servers from .mcp.json prompt you for approval the first time Claude Code loads them, which is why the file is safe to share.
Verify Claude Code Sees and Calls the Tool
First confirm the server connected. From the project directory, list the registered servers. A healthy entry shows the server connected; a project server still awaiting your approval shows as pending until you open Claude Code and accept it.
claude mcp listYou can inspect a single server's status and config the same way:
claude mcp get weatherNow open Claude Code in the project and check the tool surfaced. Ask it directly:
what MCP tools do you have?Your get_forecast tool should appear in the list, namespaced under the weather server. Then ask for the thing the tool does and watch Claude call it rather than guessing:
what's the weather in Tokyo?Claude Code should invoke get_forecast with city: "Tokyo" and answer from the result your handler returned. If it does not appear, the usual causes are a wrong absolute path in command or args, a console.log corrupting the stdout stream, or a project server you have not approved yet. The console.error line you added shows up in Claude Code's logs and is the fastest way to confirm the process actually launched.
What You Get
You now have a repeatable pattern. One small file, one tool definition, one handler, registered with a single command or a few lines of JSON. The forecast lookup is a placeholder, but the structure does not change when you point it at something real: replace the hardcoded object with a fetch to your API or a query against your database, keep the same registerTool shape, and Claude Code can drive it.
That is the leap from consuming MCP servers to building them. From here, the custom integrations guide covers REST and Postgres handler patterns, the best MCP add-ons roundup shows what the community has already built, and MCP basics fills in the protocol details.
If you would rather not wire integrations from scratch every time, the Build This Now Code Kit is a Claude Code harness for Next.js and Supabase with the build team and integrations pre-wired: planning agents, a build pipeline, evaluators, quality gates, and auth, payments, and database already connected. It is $29 one-time with no subscription. You can build everything in this post by hand, of course; the kit just hands you a project that already has the plumbing in place.
Frequently Asked Questions
What is an MCP server?
An MCP server is a small program that exposes tools and data to any AI client over the Model Context Protocol, a shared standard. Claude Code launches the server, asks it which tools exist, and can then call those tools mid-conversation. You write the server once and any MCP-compatible client (Claude Code, Claude Desktop, Cursor, and others) can use it.
What do I need to build an MCP server for Claude Code?
Node 18 or newer, the official @modelcontextprotocol/sdk package, and zod for input validation. A working stdio server is one TypeScript file: create an McpServer, register one tool with registerTool, and connect a StdioServerTransport. You do not need a web server or any hosting; Claude Code runs the file as a local subprocess.
How do I register my MCP server with Claude Code?
Two ways, both shown in this post. Run claude mcp add --scope project <name> -- <command> [args] to have Claude Code write the entry for you, or add the server by hand to a .mcp.json file at your project root under the mcpServers key with command, args, and env fields. Project-scoped servers are checked into version control and prompt for approval the first time.
What is the difference between stdio and HTTP transport?
Stdio transport runs the server as a local subprocess and talks to it over stdin and stdout. It is the simplest option for a tool that runs on your own machine, which is what this tutorial uses. HTTP transport is for remote servers you reach over a URL, which suits hosted services and team-shared connectors but adds auth and deployment work.
How do I verify Claude Code is actually using my tool?
Run claude mcp list to confirm the server connected, then ask Claude Code in chat "what MCP tools do you have?" and your tool name should appear. Ask it to do the thing your tool does and watch it call the tool. You can also write to stderr in your handler; Claude Code surfaces that output in its logs while stdout stays reserved for the protocol.
設定をやめて、構築を始めよう。
AIオーケストレーション付きSaaSビルダーテンプレート。