How to Build Your First MCP Server: A Step-by-Step Tutorial for Developers
Imagine you’re building an AI assistant that needs to talk to your database, pull data from GitHub, post messages to Slack, and check the weather — all at once. Without a standard protocol, you’d be writing custom glue code for every single combination. Four AI models × five tools = twenty bespoke integrations. Add another tool and the matrix explodes.
That’s the N×M integration problem, and it’s exactly what the Model Context Protocol (MCP) was designed to solve.
What is MCP and Why It Matters
MCP is an open standard developed by Anthropic that acts like a USB-C port for AI tool integration. Just as USB-C gives you one universal connector for power, data, and video — eliminating the drawer full of incompatible cables — MCP gives AI models one universal interface to connect with any external tool or data source.
Instead of N×M custom integrations, you write one MCP server per tool and one MCP client per AI model. The result: N+M integrations. The math alone is reason enough to care.
With MCP, your Claude-powered assistant can call a weather API, query a Postgres database, and push a GitHub commit — all through the same protocol, with no custom adapter code in between. There are already 5,000+ community-built MCP servers in the wild, plus official Anthropic servers for GitHub, Slack, Postgres, and more.
Let’s build one from scratch.
Prerequisites and Environment Setup
Before writing a single line of code, make sure your environment is ready:
- Node.js 22 LTS (or Python 3.10+ if you prefer the Python SDK)
- npm 9+ (ships with Node 22)
- Claude Desktop installed on your machine (for live testing)
For this tutorial, we’ll use the TypeScript MCP SDK. Scaffold your project:
mkdir weather-mcp-server && cd weather-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node ts-node
npx tsc --init
Update your tsconfig.json to target ES2022 and set "moduleResolution": "bundler". You’re ready to build.
Building a Simple Weather MCP Server
We’ll expose two tools: get-alerts (active weather alerts for a US state) and get-forecast (a location-based forecast). Both pull from the free National Weather Service API — no API key required.
Create src/index.ts:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// 1. Instantiate the server
const server = new McpServer({
name: "weather",
version: "1.0.0",
});
const NWS_BASE = "https://api.weather.gov";
const headers = { "User-Agent": "weather-mcp/1.0" };
// 2. Register the get-alerts tool
server.tool(
"get-alerts",
"Fetch active weather alerts for a US state",
{ state: z.string().length(2).describe("Two-letter state code, e.g. CA") },
async ({ state }) => {
const res = await fetch(`${NWS_BASE}/alerts/active?area=${state}`, { headers });
const data = await res.json();
const alerts = data.features.slice(0, 5).map((f: any) => ({
event: f.properties.event,
area: f.properties.areaDesc,
severity: f.properties.severity,
headline: f.properties.headline,
}));
return {
content: [{ type: "text", text: JSON.stringify(alerts, null, 2) }],
};
}
);
// 3. Register the get-forecast tool
server.tool(
"get-forecast",
"Get a weather forecast for a latitude/longitude",
{
latitude: z.number().describe("Decimal latitude"),
longitude: z.number().describe("Decimal longitude"),
},
async ({ latitude, longitude }) => {
const pointRes = await fetch(`${NWS_BASE}/points/${latitude},${longitude}`, { headers });
const pointData = await pointRes.json();
const forecastUrl = pointData.properties.forecast;
const forecastRes = await fetch(forecastUrl, { headers });
const forecastData = await forecastRes.json();
const periods = forecastData.properties.periods.slice(0, 3);
return {
content: [{ type: "text", text: JSON.stringify(periods, null, 2) }],
};
}
);
// 4. Connect over stdio and start listening
const transport = new StdioServerTransport();
await server.connect(transport);
That’s it — under 50 lines of meaningful code for a fully functional, production-ready MCP server.
Let’s break down what’s happening:
McpServeris the core class. You give it a name and version — these appear in Claude’s tool-use metadata.server.tool()registers each tool with a name, description, a Zod schema for input validation, and an async handler. The description is critical: Claude reads it to decide when to call your tool.StdioServerTransportwires the server to standard input/output — the communication channel Claude Desktop uses to talk to local MCP servers.server.connect()starts the event loop. From this point on, Claude can invoke your tools.
Connecting Your Server to Claude Desktop
Now let’s make Claude Desktop aware of your new server. Open (or create) the Claude Desktop config file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Add your server under the mcpServers key:
{
"mcpServers": {
"weather": {
"command": "npx",
"args": ["ts-node", "/absolute/path/to/weather-mcp-server/src/index.ts"]
}
}
}
A few things to note:
- Use the absolute path to your
index.tsfile — relative paths will break. - Claude Desktop launches your server as a child process via the command you specify. It communicates over stdio automatically.
- You can register multiple servers in the same config by adding more keys to
mcpServers.
Save the file and fully quit and relaunch Claude Desktop (not just close the window). Claude won’t pick up config changes on a simple window close.
Testing It Live and Next Steps
Once Claude Desktop restarts, look for the hammer icon (🔨) in the chat input area. Click it to see the tools your server has registered — you should see get-alerts and get-forecast listed.
Now just ask Claude naturally:
“Are there any weather alerts in Florida right now?”
“What’s the forecast for downtown Chicago this weekend?”
Claude will automatically call the appropriate tool, pass the right parameters (validated by your Zod schema), and weave the structured response into its answer. No manual tool invocation needed on your end.
Debugging tip: If your tools don’t appear, check the Claude Desktop logs at ~/Library/Logs/Claude/mcp*.log (macOS) for startup errors from your server process.
Where to Go From Here
You’ve proven the concept — now the ecosystem is yours to explore:
- Official Anthropic servers: Production-ready MCP servers for GitHub, Slack, Postgres, and more are open-source and ready to deploy.
- Community servers: Browse mcp.so or the awesome-mcp-servers repo — 5,000+ servers covering everything from Jira to Figma to local file systems.
- Resources and Prompts: MCP isn’t just tools. Servers can also expose resources (file-like data Claude can read) and prompts (reusable prompt templates) — powerful primitives for more complex workflows.
- Remote servers: Move beyond stdio by implementing an HTTP+SSE transport for servers that need to run in the cloud, behind authentication, or serve multiple clients simultaneously.
MCP transforms the way AI models interact with the world — and as you’ve just seen, the barrier to entry is remarkably low. One protocol, endless integrations.