In this article
Quick summary
An MCP server is a program that exposes tools to Claude Code. You build it in TypeScript or Python, it doesn't need a web server, and you distribute it as an npm package. In this tutorial we build a complete weather MCP from scratch.
What is an MCP server
An MCP (Model Context Protocol) server is a process that exposes capabilities to Claude Code. Think of it as a plugin: you give Claude Code access to tools it doesn't have by default.
Example: a weather MCP lets Claude Code check current weather. A database MCP lets it run SQL queries. A Slack MCP lets it send messages.
The key concepts of the protocol:
- Tools: Functions that Claude Code can execute. They have a name, description, JSON Schema parameters and a handler function.
- Resources: Data that Claude Code can read. URIs with text or binary content.
- Prompts: Predefined prompt templates that the user can invoke.
To better understand the ecosystem, read the complete MCP guide.
Architecture: tools, resources, prompts
An MCP server has 3 types of capabilities:
Tools (most used)
Tools are functions that Claude Code can call. Each tool has:
{
name: "get_weather",
description: "Obtener el clima actual de una ciudad",
inputSchema: {
type: "object",
properties: {
city: { type: "string", description: "Nombre de la ciudad" }
},
required: ["city"]
}
}
Resources
Resources are static or dynamic data that Claude Code can read. Example: a configuration file, a list of available endpoints, documentation.
Prompts
Prompt templates that the user can invoke with arguments. Example: an "analyze-logs" prompt that receives a time period and generates a structured analysis.
Project setup
# Crear proyecto
mkdir mcp-weather && cd mcp-weather
npm init -y
# Instalar dependencias
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
# Configurar TypeScript
npx tsc --init --target ES2022 --module Node16 \
--moduleResolution Node16 --outDir dist \
--declaration true
Project structure:
mcp-weather/
├── src/
│ └── index.ts # Entry point del servidor
├── package.json
└── tsconfig.json
Tutorial: weather MCP step by step
We'll build an MCP server that queries current weather using the free Open-Meteo API (no API key needed).
Step 1: Create the server
// 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: "weather",
version: "1.0.0",
description: "Consulta el clima actual de cualquier ciudad"
});
// Tool: get_weather
server.tool(
"get_weather",
"Obtener el clima actual de una ciudad",
{
city: z.string().describe("Nombre de la ciudad"),
units: z.enum(["celsius", "fahrenheit"])
.default("celsius")
.describe("Unidades de temperatura")
},
async ({ city, units }) => {
// Geocoding: ciudad -> coordenadas
const geoRes = await fetch(
`https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1`
);
const geoData = await geoRes.json();
if (!geoData.results?.length) {
return {
content: [{ type: "text", text: `Ciudad "${city}" no encontrada.` }]
};
}
const { latitude, longitude, name } = geoData.results[0];
// Clima actual
const unit = units === "fahrenheit" ? "fahrenheit" : "celsius";
const weatherRes = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,wind_speed_10m,relative_humidity_2m&temperature_unit=${unit}`
);
const weather = await weatherRes.json();
const current = weather.current;
return {
content: [{
type: "text",
text: `Clima en ${name}: ${current.temperature_2m}°${unit === "celsius" ? "C" : "F"}, viento ${current.wind_speed_10m} km/h, humedad ${current.relative_humidity_2m}%`
}]
};
}
);
// Iniciar servidor
const transport = new StdioServerTransport();
await server.connect(transport);
Step 2: Configure package.json
{
"name": "mcp-weather",
"version": "1.0.0",
"type": "module",
"bin": { "mcp-weather": "dist/index.js" },
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}
Step 3: Build and test
npm run build
Step 4: Configure in Claude Code
// ~/.claude/settings.json
{
"mcpServers": {
"weather": {
"command": "node",
"args": ["/ruta/a/mcp-weather/dist/index.js"]
}
}
}
Now you can ask Claude Code: "What's the weather in Madrid?" and it will use your MCP to respond.
Testing with MCP Inspector
MCP Inspector is an official tool for testing servers without Claude Code:
npx @modelcontextprotocol/inspector node dist/index.js
It opens a web dashboard where you can:
- See all registered tools, resources and prompts
- Execute tools with test parameters
- View request/response JSON
- Detect schema or execution errors
Publishing to npm
# Build final
npm run build
# Login en npm (primera vez)
npm login
# Publicar
npm publish
Once published, anyone can use it with:
{
"mcpServers": {
"weather": {
"command": "npx",
"args": ["-y", "mcp-weather"]
}
}
}
Python alternative
If you prefer Python, the official SDK is also available:
pip install mcp
from mcp.server import Server
from mcp.server.stdio import stdio_server
app = Server("weather")
@app.tool()
async def get_weather(city: str, units: str = "celsius") -> str:
# ... lógica del clima
return f"Clima en {city}: ..."
async def main():
async with stdio_server() as (read, write):
await app.run(read, write)
import asyncio
asyncio.run(main())
The concept is identical. Only the syntax changes.
Best practices
- Clear descriptions: Claude Code chooses which tool to use based on the description. Be specific.
- Zod validation: Use strict schemas for parameters. Prevents errors.
- Informative errors: When something fails, return a useful message, not a stack trace.
- Timeout: External API calls should have a timeout. Claude Code waits but not indefinitely.
- Stateless: MCP servers are stateless by default. Don't store state between calls unless you explicitly need to.
- Security: Never expose credentials in responses. Read MCP security best practices.
FAQ
What language do you use to create an MCP server?
TypeScript (most popular, best docs) or Python. Both have an official Anthropic SDK.
How long does it take?
A basic MCP with 2-3 tools: 1-2 hours. With tests and npm publication: half a day.
Do I need a web server?
No. MCP servers are local stdio processes. No port, domain or hosting needed.
Can I publish to npm?
Yes. It's a normal npm package. npm publish and done.
How do I test before using?
With MCP Inspector: npx @modelcontextprotocol/inspector node dist/index.js
From MCP consumer to creator
Learn MCP from scratch in IAcademy's free modules. From first use to building your own server.
Start free