The existing MCP servers cover a lot — but they don't cover everything. Maybe you need Claude to query your company's internal API. Or read from a proprietary database. Or call a webhook that nobody's built a server for yet. Building your own MCP server is more approachable than you'd expect. Here's a complete beginner guide.
What you'll build
We'll build a simple but real MCP server called my-weather-server that gives Claude one tool: get_weather. It takes a city name, calls a free weather API, and returns the current conditions. By the end, you'll understand the full pattern — and adapting it to your own use case is just changing what the tool does.
Step 1: Set up your project
Create a new directory and initialize it:
mkdir my-weather-server
cd my-weather-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install --save-dev typescript @types/node tsx
Create a tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"strict": true
}
}
Step 2: Write the server
Create src/index.ts:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "my-weather-server",
version: "1.0.0",
});
server.tool(
"get_weather",
"Get current weather for a city",
{
city: z.string().describe("The city name to get weather for"),
},
async ({ city }) => {
// Using open-meteo — no API key needed
const geocodeUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1`;
const geoRes = await fetch(geocodeUrl);
const geoData = await geoRes.json() as any;
if (!geoData.results?.length) {
return {
content: [{ type: "text", text: `City "${city}" not found.` }],
};
}
const { latitude, longitude, name, country } = geoData.results[0];
const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,wind_speed_10m,weathercode`;
const weatherRes = await fetch(weatherUrl);
const weatherData = await weatherRes.json() as any;
const current = weatherData.current;
return {
content: [{
type: "text",
text: `Weather in ${name}, ${country}:\nTemperature: ${current.temperature_2m}°C\nWind: ${current.wind_speed_10m} km/h`,
}],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
Step 3: Add a run script
Add to package.json:
"scripts": {
"start": "tsx src/index.ts"
}
Step 4: Test it locally
Run it in a terminal: npm start. The server starts and waits. That's correct — stdio servers wait for input. Use the MCP Inspector tool from Anthropic to test it: npx @modelcontextprotocol/inspector npm start. This opens a browser UI where you can call your tools directly.
Step 5: Connect it to Claude Desktop
Add this entry to your claude_desktop_config.json:
{
"mcpServers": {
"weather": {
"command": "npx",
"args": ["tsx", "/absolute/path/to/my-weather-server/src/index.ts"]
}
}
}
Or compile it first (npx tsc) and use the compiled JavaScript:
"args": ["node", "/absolute/path/to/my-weather-server/dist/index.js"]
Restart Claude Desktop. Ask: "What's the weather in Tokyo?" — and Claude will call your get_weather tool with "Tokyo" as the argument and return the current conditions.
What to understand about the SDK pattern
The pattern is the same for any tool you want to build: define the tool name and description (Claude reads these to decide when to use the tool), define the input schema with Zod, and write a handler function that takes those inputs and returns a result.
The SDK handles everything else: the MCP protocol handshake, serialization, error handling, and stdio transport management. You're just writing functions.
Can I build an MCP server in Python?
Yes. The official Python SDK is at github.com/modelcontextprotocol/python-sdk. The pattern is nearly identical — you use @mcp.tool() decorators on functions instead of server.tool() calls. The Python SDK also has excellent FastMCP integration if you prefer a more framework-style approach.
How do I add multiple tools to the same server?
Just call server.tool() multiple times before server.connect(). Each call registers one tool. A single MCP server can expose as many tools as you need — there's no practical limit.
Frequently Asked Questions
It helps but isn't required. The TypeScript SDK examples are readable even if you primarily write JavaScript. There's also an official Python SDK at github.com/modelcontextprotocol/python-sdk if you prefer Python.
Yes. Your server is just a Node.js or Python process — it can make HTTP requests, connect to databases, call any API, and do anything that process has permission to do. The MCP layer is just the interface that exposes those capabilities to Claude.
Publish it to npm as a package — others can then install it with npx. For internal use, share the source code and have people run it with node from the local path. For teams, consider hosting it as an HTTP+SSE server that everyone connects to over the network.
Return an error response from your tool handler: return { content: [{ type: "text", text: "Error: description" }], isError: true }. Claude will see the error response and handle it gracefully in the conversation.