Introducción al desarrollo de MCP Server: crea tu primer servicio MCP desde cero

Introducción: Cuando escribes código en Cursor, ¿alguna vez quisiste que la IA consultara directamente la última versión de las dependencias de tu proyecto? ¿O al analizar datos en Claude, que pudiera leer información de tu base de datos? Si lo implementas por separado, tendrías que escribir código de adaptación para cada herramienta de IA. Con un MCP Server, lo escribes una vez y todos los clientes compatibles con MCP pueden usarlo. Este artículo te guía desde cero para escribir a mano un MCP Server completo con TypeScript.
¿Qué es MCP? Entiende los conceptos clave en 3 minutos
La historia de un puerto USB
Si usaste productos digitales hace unos años, recordarás esa época incómoda: el ratón con conector redondo, el teclado cuadrado, la impresora con puerto paralelo — cada dispositivo exigía su propio conector. Luego llegó USB y un solo puerto resolvió todo.
MCP (Model Context Protocol) está convirtiéndose en el «estándar USB» del mundo de las herramientas de IA.
Sin MCP, si quieres que la IA acceda a una fuente de datos, debes escribir una capa de adaptación para cada herramienta: un plugin para Claude, una extensión para Cursor, otra para Windsurf… La complejidad es N × M (N fuentes de datos × M herramientas de IA).
Con MCP, solo escribes un MCP Server y todos los clientes compatibles pueden llamarlo directamente. La complejidad baja a N + M.
La arquitectura de tres capas es sencilla:
+-------------+ +-------------+ +-------------+
| Host | -> | Client | -> | Server |
| (Claude) | | (Cliente MCP)| | (Tu servicio)|
+-------------+ +-------------+ +-------------+
- Host: la aplicación de IA en sí, como Claude Desktop o Cursor
- Client: el cliente MCP, responsable de comunicarse con el Host
- Server: el servicio que escribes tú, que ofrece funcionalidad concreta
Las tres capacidades de un MCP Server
Un MCP Server puede ofrecer tres tipos de funcionalidad:
| Capacidad | Uso | Ejemplo |
|---|---|---|
| Tools (herramientas) | Ejecutar acciones | Consultar el tiempo, enviar mensajes, leer base de datos |
| Resources (recursos) | Proporcionar datos | Contenido de archivos, respuestas de API, configuración |
| Prompts (indicaciones) | Plantillas predefinidas | Plantilla de revisión de código, plantilla de informe diario |
Piensa en Tools como «funciones» — la IA las invoca para ejecutar una acción; Resources como «fuentes de datos» — la IA puede leer su contenido; Prompts como «plantillas» — ayudan a la IA a entender la tarea más rápido.
Diferencia con otros artículos: Si has visto otros tutoriales de MCP, quizá conoces versiones con Python y FastMCP. Este artículo usa el SDK nativo de TypeScript, más adecuado para desarrolladores frontend y full-stack. Ambas implementaciones son equivalentes; elige el lenguaje que domines.
"https://modelcontextprotocol.io"
Preparación del entorno de desarrollo
Requisitos previos
Este artículo asume que ya:
- Tienes Node.js 18+ o Bun 1.0+ instalado
- Has escrito TypeScript y conoces
interfaceyasync/await - Tienes Claude Desktop u otro cliente compatible con MCP (Cursor, Windsurf, etc.)
Si no has usado Bun, te recomiendo probarlo — es mucho más rápido que npm e incluye soporte TypeScript integrado, sin configurar ts-node.
Inicializar el proyecto
# Crear directorio del proyecto
mkdir mcp-weather-server && cd mcp-weather-server
# Inicializar (con Bun o npm)
bun init -y
# o npm init -y
# Instalar MCP TypeScript SDK
bun add @modelcontextprotocol/sdk zod
# o npm install @modelcontextprotocol/sdk zod
Aquí usamos dos dependencias:
@modelcontextprotocol/sdk: SDK oficial de MCP para TypeScriptzod: validación de tipos en tiempo de ejecución para definir el schema de parámetros de herramientas
Puntos clave de la configuración TypeScript
Si usas bun init, tsconfig.json ya viene configurado. Si lo haces a mano, fíjate en estas opciones:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"strict": true
}
}
moduleResolution: "bundler" es importante para módulos ESM; si no, puedes encontrarte con errores del tipo «xxx is not defined».
Práctica: escribir un MCP Server de consulta del tiempo
Este tutorial te lleva a completar un MCP Server que:
- Recibe solicitudes de invocación de la IA
- Consulta la API de OpenWeatherMap para obtener el tiempo en tiempo real
- Devuelve resultados formateados
Diseño de la estructura del proyecto
mcp-weather-server/
+-- src/
| +-- index.ts # Archivo de entrada
| +-- weather.ts # Implementación de la herramienta del tiempo
| +-- resources.ts # Definición de recursos
+-- package.json
+-- tsconfig.json
El código puede ir todo en index.ts (como en este artículo), pero dividirlo en módulos facilita el mantenimiento.
Paso 1: crear el esqueleto del MCP Server
Empecemos por lo más simple — un MCP Server que arranque:
// src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Crear instancia del servidor
const server = new McpServer({
name: "weather-service",
version: "1.0.0",
});
// Registrar herramienta (Tools)
server.tool(
"get_weather",
"Obtener información meteorológica actual de una ciudad",
{
city: z.string().describe("Nombre de la ciudad, ej.: Beijing, Shanghai"),
},
async ({ city }) => {
// La implementación se desarrolla en la siguiente sección
return { content: [{ type: "text", text: `Consultando el tiempo en ${city}...` }] };
}
);
// Iniciar el servidor
const transport = new StdioServerTransport();
await server.connect(transport);
McpServer es la clase principal del SDK; debes pasar name y version. El método tool() registra una herramienta: primer argumento el nombre, segundo la descripción, tercero el schema de parámetros y por último la función de ejecución.
Paso 2: implementar la herramienta de consulta del tiempo (código principal)
Ahora hagamos que la herramienta funcione de verdad. Usamos la API gratuita de OpenWeatherMap:
// src/weather.ts
import { z } from "zod";
// Definir tipo de respuesta de OpenWeatherMap API
interface WeatherResponse {
name: string;
main: { temp: number; feels_like: number; humidity: number };
weather: [{ description: string }];
wind: { speed: number };
}
// Implementación de la herramienta de consulta del tiempo
server.tool(
"get_weather",
"Obtener información meteorológica actual de una ciudad",
{
city: z.string().describe("Nombre de la ciudad, ej.: Beijing, Shanghai"),
},
async ({ city }) => {
const API_KEY = process.env.OPENWEATHER_API_KEY;
const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric&lang=zh_cn`;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Error en la solicitud API: ${response.status}`);
}
const data: WeatherResponse = await response.json();
// Devolver resultado formateado
return {
content: [
{
type: "text",
text: JSON.stringify({
city: data.name,
temperature: `${data.main.temp}°C`,
feels_like: `${data.main.feels_like}°C`,
description: data.weather[0].description,
humidity: `${data.main.humidity}%`,
wind_speed: `${data.wind.speed} m/s`,
}, null, 2),
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Consulta fallida: ${error instanceof Error ? error.message : 'Error desconocido'}`,
},
],
isError: true,
};
}
}
);
Ten en cuenta:
- API Key desde variable de entorno: nunca escribas la clave directamente en el código
- Manejo de errores: devuelve
isError: truepara que el cliente sepa que la invocación falló - Definición de tipos: la interfaz
WeatherResponsepermite que TypeScript verifique la estructura de datos
Regístrate en OpenWeatherMap para obtener una API Key gratuita y configura la variable de entorno:
export OPENWEATHER_API_KEY=your_api_key_here
Paso 3: añadir Resources (opcional pero recomendado)
Los Resources permiten que tu Server ofrezca datos de solo lectura. Por ejemplo, un recurso con el estado del servidor:
// src/resources.ts
// Proporcionar información del estado del servidor
server.resource(
"server-status",
"status://server",
async (uri) => ({
contents: [
{
uri: uri.href,
text: JSON.stringify({
name: "Weather Service",
version: "1.0.0",
status: "running",
timestamp: new Date().toISOString(),
}, null, 2),
},
],
})
);
// Proporcionar documentación de la API
server.resource(
"api-docs",
"docs://api",
async (uri) => ({
contents: [
{
uri: uri.href,
text: `
# Weather MCP Server API
## Tools
- get_weather(city: string): Obtener el tiempo de una ciudad
## Resources
- status://server - Estado del servidor
- docs://api - Documentación de la API
`.trim(),
},
],
})
);
Los dos primeros argumentos de resource() son el nombre del recurso y el URI; el tercero es la función de lectura. El URI puede usar cualquier scheme, como status:// o docs://, siempre que puedas distinguirlos.
Paso 4: añadir Prompts (funcionalidad avanzada)
Los Prompts son plantillas de conversación predefinidas. Por ejemplo, una plantilla de «informe meteorológico» que la IA rellena automáticamente con el nombre de la ciudad:
// Plantilla predefinida de informe meteorológico
server.prompt(
"weather_report",
"Generar un informe meteorológico formateado",
{
city: z.string().describe("Nombre de la ciudad"),
include_tips: z.boolean().optional().describe("Incluir consejos de vestimenta"),
},
({ city, include_tips }) => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `Genera un informe meteorológico para ${city}. ${include_tips ? "Incluye también consejos de vestimenta." : ""}`,
},
},
],
})
);
El valor de retorno de prompt() es un array de mensajes, cada uno con role y content. Así la IA obtiene el contexto predefinido al usarlo.
Paso 5: completar el archivo de entrada
Integra todo el código anterior en src/index.ts y añade manejo de errores:
// src/index.ts (versión completa)
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-service",
version: "1.0.0",
});
// Registrar todas las herramientas, recursos e indicaciones
// ... (código anterior)
// Manejo de errores
process.stdin.on("error", (err) => {
console.error("Error en entrada estándar:", err);
process.exit(1);
});
process.stdout.on("error", (err) => {
console.error("Error en salida estándar:", err);
process.exit(1);
});
// Salida elegante
process.on("SIGINT", async () => {
await server.close();
process.exit(0);
});
// Iniciar el servidor
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP Weather Server iniciado, esperando conexión...");
StdioServerTransport usa entrada y salida estándar para comunicarse, por lo que el manejo de errores de stdin/stdout es importante. El manejo de SIGINT permite detener el servicio con Ctrl+C de forma limpia.
Ejecuta bun run src/index.ts; si ves el mensaje «iniciado», todo está bien.
Configurar el cliente: que Claude use tu Server
El Server está listo; ahora hay que hacer que Claude Desktop o Cursor puedan invocarlo.
Configuración de Claude Desktop
Localiza el archivo de configuración:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Añade la configuración de tu Server:
{
"mcpServers": {
"weather": {
"command": "bun",
"args": ["run", "/absolute/path/to/mcp-weather-server/src/index.ts"],
"env": {
"OPENWEATHER_API_KEY": "tu API Key"
}
}
}
}
Nota: la ruta en args debe ser absoluta. Una ruta relativa hará que el Server falle al arrancar.
Configuración de Cursor / Windsurf
Cursor y Windsurf se configuran de forma similar: en los ajustes del IDE, busca la configuración MCP y añade la entrada del servidor (mismo formato).
El archivo de configuración de Cursor suele estar en:
- macOS:
~/Library/Application Support/Cursor/User/globalStorage/state.vscdb - O dentro del IDE: Ajustes -> AI -> MCP -> Añadir servidor
Probar tu Server
- Reinicia Claude Desktop / Cursor
- En el diálogo escribe: «Consulta el tiempo en Beijing»
- Claude debería invocar automáticamente tu MCP Server
Si ves una salida similar a esta, ha funcionado:
{
"city": "Beijing",
"temperature": "18°C",
"feels_like": "16°C",
"description": "nublado",
"humidity": "65%",
"wind_speed": "3.2 m/s"
}
Solución de problemas frecuentes
| Problema | Posible causa | Solución |
|---|---|---|
| Server no conectado | Ruta incorrecta | Comprueba la ruta absoluta en args |
| API Key inválida | Variable de entorno no pasada | Confirma que env está bien configurado |
| Sin respuesta | Error de compilación TypeScript | Compila primero con bun build o tsc |
| Error de permisos | Permisos del archivo de configuración | Asegúrate de que el archivo sea legible |
"https://github.com/modelcontextprotocol/typescript-sdk"
Extensiones y recomendaciones de despliegue
Añadir más herramientas
La consulta del tiempo es solo el comienzo. También puedes:
- Consulta histórica del tiempo: llamar a una API de datos históricos
- Comparación multi-ciudad: consultar varias ciudades y devolver una tabla comparativa
- Suscripción a alertas meteorológicas: comprobar si hay avisos de mal tiempo
El registro de estas herramientas es idéntico al de get_weather; solo cambia la lógica de implementación.
Comparación de opciones de despliegue
Si quieres compartir el Server con tu equipo, la transmisión stdio local no basta. Aquí tienes varias formas de desplegarlo:
| Forma de despliegue | Escenario | Ventajas | Desventajas |
|---|---|---|---|
| stdio local | Uso personal, pruebas | Simple, seguro | No compartible |
| HTTP/SSE | Equipo, multiusuario | Acceso remoto | Requiere autenticación |
| Serverless | Producción | Escalado automático | Latencia de arranque en frío |
Consideraciones para producción
Autenticación: en despliegue HTTP debes implementar autenticación. MCP soporta OAuth 2.1; también puedes usar una API Key simple:
// Comprobar API Key en el encabezado de la solicitud
const apiKey = request.headers.get("Authorization");
if (apiKey !== `Bearer ${process.env.API_KEY}`) {
return new Response("Unauthorized", { status: 401 });
}
Limitación de tasa: evita llamadas maliciosas que agoten tu cuota de API. Puedes usar express-rate-limit o el rate limiting integrado de Cloudflare Workers.
Registros: usa pino o winston para registrar invocaciones de herramientas y facilitar la depuración:
import pino from "pino";
const logger = pino();
server.tool("get_weather", /* ... */, async ({ city }) => {
logger.info({ city }, "Consultando el tiempo");
// ...
});
Monitorización: rastrea tasa de éxito y tiempo de respuesta. Prometheus + Grafana es una combinación habitual.
Resumen
Este artículo explica cómo escribir un MCP Server desde cero con TypeScript. Los contenidos incluyen:
- Entender los conceptos clave de MCP y la arquitectura de tres capas
- Crear un servidor con el MCP TypeScript SDK
- Implementar la herramienta de consulta del tiempo (Tools)
- Añadir recursos de estado del servidor (Resources)
- Definir plantillas de informe meteorológico (Prompts)
- Configurar Claude Desktop / Cursor para invocar el Server
Ahora puedes:
- Construir wrappers MCP para las APIs que uses a diario (GitHub, Slack, Notion, etc.)
- Crear interfaces MCP para sistemas internos (CRM, bases de datos)
- Explorar lo que ya existe en la comunidad MCP
Recursos para profundizar:
Si quieres profundizar en los principios del protocolo MCP, lee Guía profunda del protocolo MCP.
FAQ
¿Qué conocimientos necesito para desarrollar un MCP Server?
¿Cuál es la diferencia entre MCP Server y FastMCP?
¿Cómo pruebo si el MCP Server funciona correctamente?
¿Se puede desplegar un MCP Server en un servidor remoto?
11 min de lectura · Publicado el: 19 mar 2026 · Actualizado el: 21 ago 2026
Guía práctica de MCP
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
¿Herramientas de IA incompatibles? El protocolo MCP conecta todo sin fricción (con tutorial práctico)
Análisis profundo de cómo el protocolo MCP resuelve la interoperabilidad entre herramientas de IA, con un caso práctico para construir un servicio de consulta del clima con FastMCP. Incluye código completo, guía de configuración y soluciones a problemas frecuentes para empezar con MCP en 5 minutos.
Parte 1 de 4
Siguiente
Tutorial práctico de MCP: guía completa para que Cursor consulte bases de datos y llame APIs
Te enseñamos paso a paso a configurar un MCP Server para que Cursor y Claude consulten bases de datos SQLite/PostgreSQL y llamen APIs. Con ejemplos de código completos y soluciones a problemas comunes, listo en 15 minutos.
Parte 3 de 4



Comentarios
Inicia sesión con GitHub para dejar un comentario