Build Your Own MCP Server: Step by Step Guide

By Ricardo Gutierrez · · 22 min read

In this article

  1. What is an MCP server
  2. Architecture: tools, resources, prompts
  3. Project setup
  4. Tutorial: weather MCP step by step
  5. Testing with MCP Inspector
  6. Publishing to npm
  7. Python alternative
  8. Best practices
  9. FAQ

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:

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:

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

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