How to Build Your Own MCP Server: A Beginner's Guide
To build an MCP server in TypeScript, install the v2 SDK package @modelcontextprotocol/server with Zod v4, register each tool with server.registerTool and a Zod input schema, serve it over stdio, then add it to Claude Code with claude mcp add or a .mcp.json entry. A one-tool server is about 50 lines.
To build an MCP server in TypeScript, install @modelcontextprotocol/server (the v2 SDK, now the stable line) and Zod v4, register each tool with server.registerTool(), and serve it over stdio so Claude Code, Cursor or VS Code can launch it. Whether you want to connect your assistant to an internal API, a proprietary database, or a custom workflow, the shape is the same.
It is simpler than you might expect. A one-tool server is about 50 lines of TypeScript, and you can have one running in under an hour. (If you would rather use an existing server than build one, start with our top MCP server picks.)
Key Takeaways
- MCP servers are just TypeScript (or Python) programs that follow a specific protocol. No special infrastructure required.
- Use the v2 TypeScript SDK. It ships as
@modelcontextprotocol/serverand@modelcontextprotocol/clientand implements the 2026-07-28 MCP spec. The v1 package,@modelcontextprotocol/sdk, only gets bug and security fixes. - The SDK handles the protocol and input validation. You write one Zod schema per tool; the SDK derives the JSON Schema the model sees and rejects bad arguments before your handler runs.
- Local testing is fast. Run
claude mcp add(or edit.mcp.json) to point Claude Code at your server and start using it immediately. - You can expose tools (actions), resources (data), and prompts (templates) from a single server. Start with one tool.
What You Will Build
In this guide, you will build a simple MCP server that provides a weather lookup tool. When finished, you will be able to ask your AI assistant "What is the weather in San Francisco?" and it will use your server to fetch and return live weather data from the free wttr.in service, which needs no API key.
This example is intentionally simple, but the patterns you learn apply to any integration: internal APIs, databases, file systems, cloud services, or any system you can call from code.
We built and ran this exact server on October 9, 2026 with @modelcontextprotocol/server 2.3.1, zod 4.6.5 and TypeScript 7.0.2.
Prerequisites
Before you start, make sure you have:
- Node.js 20 or later installed (
node --versionto check). The v2 SDK requires it. - An AI tool that supports MCP -- Claude Code is the easiest to configure, but Cursor and VS Code with Copilot also work
- Basic TypeScript familiarity -- you do not need to be an expert, but you should be comfortable with types and async/await
- A text editor -- any editor will do, though your AI coding tool can help you write the server itself
Step 1: Set Up the Project
Create a new directory and initialize a TypeScript project:
mkdir my-weather-mcp
cd my-weather-mcp
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/server zod
npm install -D typescript @types/node
The key dependency is @modelcontextprotocol/server, which provides the server framework and handles all MCP protocol communication. Tool schemas use Standard Schema, so any compatible library works; this guide uses Zod v4, the library the SDK's own examples use. The type=module line matters because the v2 SDK ships ES modules only.
Create a tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"types": ["node"]
},
"include": ["src/**/*"]
}
The "types": ["node"] entry loads the Node.js type definitions the SDK's stdio transport relies on. Without it, TypeScript 7 reports Cannot find name 'Buffer'.
Create the source directory:
mkdir src
Step 2: Define Tools
Create src/index.ts -- this is your entire server:
import { McpServer } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import * as z from "zod/v4";
const server = new McpServer({ name: "weather", version: "1.0.0" });
server.registerTool(
"get_weather",
{
description: "Get the current weather for a city",
inputSchema: z.object({
city: z.string().describe("The city name to look up weather for"),
}),
},
async ({ city }) => {
// Fetch live conditions from the free wttr.in JSON API (no key needed).
const res = await fetch(
`https://wttr.in/${encodeURIComponent(city)}?format=j1`
);
if (!res.ok) {
return {
content: [{ type: "text", text: `Weather lookup failed: HTTP ${res.status}` }],
isError: true,
};
}
const data = await res.json();
const current = data.current_condition[0];
return {
content: [
{
type: "text",
text: `Weather in ${city}: ${current.temp_F}F, ${current.weatherDesc[0].value}. Humidity: ${current.humidity}%. Wind: ${current.windspeedMiles} mph.`,
},
],
};
}
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("weather MCP server running on stdio");
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
Here is what each piece does:
McpServercreates your server instance with a name and version.server.registerTool(name, config, handler)registers a tool. The config holds thedescriptionand aninputSchemawritten as a Zod object; the SDK turns that schema into the JSON Schema the model sees and validates every call against it.- The handler receives validated, typed inputs and returns content, a list of typed blocks.
isError: truemarks a failed result the model can read and react to. StdioServerTransportconnects the server over standard input/output, which is how local AI tools talk to MCP servers. The SDK's own tutorial uses a shortcut from the same/stdioimport instead:serveStdio(createServer), which takes a function that builds the server.
The tool description is important -- this is what the AI reads to decide when to use your tool. Make it clear and specific.
If a caller passes the wrong type, say { "city": 42 }, the SDK rejects it before your handler runs and returns an isError result reading "Input validation error: Invalid arguments for tool get_weather: city: Invalid input: expected string, received number".
Adding more tools
You can register as many tools as you need. Each server.registerTool() call adds another capability. For example, add a forecast tool alongside the current weather tool, above main():
server.registerTool(
"get_forecast",
{
description: "Get a weather forecast of up to 3 days for a city",
inputSchema: z.object({
city: z.string().describe("The city name"),
days: z.number().int().min(1).max(3).default(3).describe("Number of days"),
}),
},
async ({ city, days }) => {
const res = await fetch(
`https://wttr.in/${encodeURIComponent(city)}?format=j1`
);
if (!res.ok) {
return {
content: [{ type: "text", text: `Forecast lookup failed: HTTP ${res.status}` }],
isError: true,
};
}
const data = await res.json();
const lines = data.weather
.slice(0, days)
.map((d: any) => `${d.date}: ${d.mintempF}F to ${d.maxtempF}F`);
return { content: [{ type: "text", text: lines.join("\n") }] };
}
);
Step 3: Test Locally
First, build the project:
npx tsc
Before involving an AI tool, you can call your tools directly with the MCP Inspector, a local web app that launches your server and lets you run each tool by hand:
npx @modelcontextprotocol/inspector node dist/index.js
Now connect Claude Code. The quickest route is one command, where everything after -- is the launch command:
claude mcp add weather -- node /absolute/path/to/my-weather-mcp/dist/index.js
That adds the server at local scope, just for you. To share it with your team instead, commit a .mcp.json file at the root of the project where you use Claude Code:
{
"mcpServers": {
"weather": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/my-weather-mcp/dist/index.js"]
}
}
}
A stdio entry takes command, args, and an optional env object for environment variables such as API keys. Claude Code asks you to approve a project-scoped server from .mcp.json the first time you start an interactive session. Then run /mcp inside Claude Code: weather shows as connected with get_weather listed. Try asking: "What is the weather in New York?"
The AI will recognize that this question can be answered by your get_weather tool, call it with city: "New York", and present the results in its response.
Debugging tips
If the server does not work immediately, here are the most common issues:
- Path errors: Make sure the path in
argsis the absolute path to your builtdist/index.js. - Run it by hand first:
node dist/index.jsshould printweather MCP server running on stdioto stderr and then wait. If it exits or crashes, fix that before changing any host config. - Build errors: Run
npx tscand fix any TypeScript errors before testing. The server must compile cleanly. - Console output: Avoid using
console.log()in your server -- MCP communicates over stdio, so any stray output on stdout corrupts the protocol. Useconsole.error()for debug logging, as stderr does not interfere.
Next steps
Once your basic server works, consider these enhancements:
- Add resources to expose static data or configuration that the AI can read without calling a tool.
- Add prompts to provide pre-built templates that guide the AI's use of your tools.
- Call your tools from hooks. Claude Code's
mcp_toolhook type runs a tool on a configured MCP server at a lifecycle event; our Claude Code hooks guide covers the setup. - Ship it in a plugin. A Claude Code plugin can bundle an MCP server alongside skills and a mod; see how to build a Claude Code mod.
- Publish to npm so others can install and use your server with a single command.
- Add error handling with clear error messages that help the AI understand what went wrong and suggest fixes to the user.
Every server you build makes your AI assistant a little more capable. Start with the v2 SDK, one registerTool() call and a stdio transport, then grow from there.
Corrections and Updates
- October 9, 2026: This page taught the v1 TypeScript SDK (
@modelcontextprotocol/sdkwithserver.tool()); the code now uses v2, the stable line since July 27, 2026 (@modelcontextprotocol/server,server.registerTool()andzod/v4), and needs Node.js 20 or later. - October 9, 2026: The
.mcp.jsonexample used acwdfield that Claude Code's MCP docs do not document, and a code comment said the tool returned mock data while it fetched live data from wttr.in. The example now uses an absolute path inargs, and the comment describes the live fetch.
Sources
- MCP TypeScript SDK README. v2 as the stable line, the
@modelcontextprotocol/serverand@modelcontextprotocol/clientpackages, the 2026-07-28 spec, Standard Schema with Zod v4, the minimalregisterToolexample, and v1 fixes for at least six months after the 2026-07-27 v2 release. Checked October 9, 2026. - MCP TypeScript SDK, "Build your first server". Node.js 20 requirement,
npm pkg set type=modulebecause the SDK ships ES modules only,registerToolconfig and validation,serveStdio, logging to stderr, and the MCP Inspector command. Checked October 9, 2026. - MCP TypeScript SDK, "Plug into a real host". The
claude mcp addlaunch-command form and running the server by hand to debug a host. Checked October 9, 2026. - npm, @modelcontextprotocol/server. Version 2.3.1 and the
node >=20engine requirement. Checked October 9, 2026. - Claude Code docs, "Connect Claude Code to tools via MCP".
claude mcp addsyntax, local and project scopes, the.mcp.jsonformat withcommand,argsandenv, and the approval prompt for project-scoped servers. Checked October 9, 2026. - Claude Code docs, "Hooks reference". The
mcp_toolhook type. Checked October 9, 2026. - Model Context Protocol, "SDKs". The official SDK list and tiers. Checked October 9, 2026.
- Model Context Protocol, "Versioning". 2026-07-28 as the current protocol version. Checked October 9, 2026.
Read next
More skills and workflows- Claude
MCP 2026-07-28 Moves to a Stateless Core as Claude Rolls Out Support
Anthropic says the MCP 2026-07-28 specification moves the Model Context Protocol from a bidirectional stateful design to a request/response model, adds versioned extensions for MCP Apps and Tasks, and aligns authorization with production OAuth 2.0 and OIDC deployments.
- Claude Code
Claude Code Hooks: A Practical Guide With Six Recipes
Claude Code hooks run your own shell commands, HTTP endpoints, MCP tools, prompts, or agents at fixed points in a session. You define them in settings.json, narrow them with matchers, and block actions with exit code 2 or JSON. Exit 0 is not approval, and a hook allow never loosens your deny rules.
- Claude Code
How to Build a Claude Code Mod: Files, Hooks, State, and Testing
A Claude Code mod is a plugin whose JavaScript or TypeScript hooks run inside your session. Ask Claude to write one, or create plugin.json, hooks/hooks.json, and a module that exports register. Load it with --plugin-dir, keep state in $.state, then check it with claude plugin validate and claude plugin test.
Frequently Asked Questions
What programming languages can I use to build an MCP server?
Official MCP SDKs cover TypeScript, Python, C#, Go, Rust and Ruby at Tier 1, Java at Tier 2, and Swift, PHP and Kotlin at Tier 3. The tier reflects feature completeness, protocol support and maintenance commitment. TypeScript and Python have the most tutorials and examples, which makes them the easiest starting points.
Which version of the TypeScript MCP SDK should I use?
Use v2, the stable line, published as @modelcontextprotocol/server and @modelcontextprotocol/client. It implements the 2026-07-28 MCP spec. The older v1 package, @modelcontextprotocol/sdk, gets bug fixes and security updates for at least six months after the July 27, 2026 v2 release, so plan a migration.
Do I need to deploy my MCP server to use it?
No. Most MCP servers run locally on your machine. Your AI tool starts the server process automatically when it needs to use it. Deployment is only necessary if you want to share the server across a team or run it in a CI/CD environment.