Cómo crear tu primer servidor MCP con TypeScript desde cero (y hostearlo gratis)
Tutorial con código real y errores resueltos: crea tu primer servidor MCP con TypeScript, pruébalo con Postman y despliégalo gratis en Railway con SSEServerTransport.
Jesús Blanco
Autor
23 min
En el artículo anterior explicamos qué es MCP y por qué se está convirtiendo en el estándar de conexión entre modelos de IA y sistemas reales. Ahora vamos al código.
En este tutorial vas a construir un servidor MCP funcional desde cero usando TypeScript. El ejemplo es un servidor de consulta de clima en tiempo real que cualquier persona puede probar con Claude Desktop, Cursor o incluso Postman — sin necesidad de API keys ni cuentas de pago. Al final también te mostramos cómo hostearlo en Railway para que sea accesible remotamente.
⚠️ Nota de producción: este artículo incluye los ajustes reales que tuvimos que hacer para que el servidor funcionara con clientes MCP estándar. Si en otros tutoriales ves código diferente y no funciona, probablemente están usando un transporte incorrecto. Aquí va la versión que sí corre.
Lo que necesitas antes de empezar:
Node.js 18 o superior
Conocimiento básico de TypeScript / JavaScript
Claude Desktop o Postman instalado (para probar el servidor localmente)
¿Qué vamos a construir?
Un servidor MCP con 3 tools:
Tool
Qué hace
get_current_weather
Consulta temperatura, viento y lluvia en cualquier ciudad
get_weekly_forecast
Devuelve el pronóstico de 7 días
compare_cities_weather
Compara el clima entre dos ciudades
Usamos la API pública de Open-Meteo — sin API key, completamente gratis — y su API de geocodificación para convertir nombres de ciudades a coordenadas.
Este es exactamente el tipo de servidor que en fencode.dev construimos para clientes que necesitan que sus agentes tomen decisiones basadas en datos externos en tiempo real.
Paso 1: Inicializa el proyecto
Crea una carpeta nueva e inicializa el proyecto:
bash
mkdir mcp-weather-server
cd mcp-weather-server
npm init -y
💡 Usa la ruta absoluta. En macOS ejecuta pwd dentro de la carpeta del proyecto para obtenerla.
Reinicia Claude Desktop y prueba preguntando: "¿Qué clima hace hoy en Monterrey?"
Paso 7: Servidor remoto con transporte SSE
Aquí viene el punto más importante del artículo — y donde la mayoría de los tutoriales te van a fallar.
¿Por qué SSE y no un POST simple?
El estándar MCP sobre red requiere un "apretón de manos" inicial: el cliente (Claude, Postman, Cursor) hace primero un GET para abrir un flujo de eventos (Server-Sent Events). Solo después empieza a enviar mensajes por POST. Si tu servidor solo acepta POST, el cliente recibe un 404 en ese primer GET y nunca llega a conectarse.
Hay otro error sutil muy común: usar app.use(express.json()) junto con el SDK de MCP. Express lee el body de la petición para parsearlo, pero el SDK también necesita leer ese mismo stream para procesar JSON-RPC. Un stream solo se puede leer una vez, así que el SDK lanza stream is not readable. La solución es simple: no uses express.json() — el SDK lo maneja directamente.
Crea src/server-http.ts:
typescript
// src/server-http.tsimport express from"express";
import { McpServer } from"@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from"@modelcontextprotocol/sdk/server/sse.js";
import { registerTools } from"./tools.js";
const app = express();
// ⚠️ NO uses app.use(express.json()) aquí.// Express consumiría el stream del body antes de que el SDK pueda leerlo.// El SDK de MCP se encarga de parsear el JSON-RPC directamente.// ─── Singleton del servidor MCP ─────────────────────────────────// Se instancia una sola vez a nivel global, no dentro de cada request.// Evita problemas de memoria y de estado con múltiples conexiones.const mcpServer = newMcpServer({
name: "fencode-weather-server",
version: "1.0.0",
});
registerTools(mcpServer);
// Mapa de transportes activos por sesiónconst transports = newMap();
// ─── GET /sse — El cliente abre el flujo de eventos ────────────// Este es el "apretón de manos" inicial del protocolo MCP.// Sin este endpoint, cualquier cliente MCP estándar falla con 404.
app.get("/sse", async (req, res) => {
const transport = newSSEServerTransport("/messages", res);
transports.set(transport.sessionId, transport);
res.on("close", () => {
transports.delete(transport.sessionId);
console.log(`🔌 Sesión cerrada: ${transport.sessionId}`);
});
await mcpServer.connect(transport);
console.log(`🔗 Nueva sesión SSE: ${transport.sessionId}`);
});
// ─── POST /messages — El cliente envía mensajes JSON-RPC ────────
app.post("/messages", async (req, res) => {
const sessionId = req.query.sessionIdasstring;
const transport = transports.get(sessionId);
if (!transport) {
res.status(404).json({ error: "Sesión no encontrada. Conecta primero al endpoint /sse." });
return;
}
await transport.handlePostMessage(req, res);
});
// ─── GET /health — Para Railway y monitoreo ─────────────────────
app.get("/health", (_req, res) => {
res.json({
status: "ok",
server: "fencode-weather-mcp",
version: "1.0.0",
activeSessions: transports.size,
});
});
constPORT = process.env.PORT ?? 3000;
app.listen(PORT, () => {
console.log(`✅ Servidor MCP SSE corriendo en puerto ${PORT}`);
console.log(`🔗 Endpoint SSE: http://localhost:${PORT}/sse`);
console.log(`📩 Endpoint Messages: http://localhost:${PORT}/messages`);
console.log(`❤️ Health check: http://localhost:${PORT}/health`);
});
Inicia en modo desarrollo:
bash
npm run dev:http
Deberías ver en consola:
text
✅ Servidor MCP SSE corriendo en puerto 3000
🔗 Endpoint SSE: http://localhost:3000/sse
📩 Endpoint Messages: http://localhost:3000/messages
❤️ Health check: http://localhost:3000/health
Paso 8: Prueba tu servidor con Postman
Antes de desplegar en la nube, valida que todo funciona localmente. Postman soporta SSE de forma nativa y es la forma más rápida de verificar tu servidor sin necesitar Claude Desktop.
Con este servidor tienes en práctica todos los conceptos clave de MCP:
Registrar tools con Zod para validación de inputs tipada
Manejar errores de forma que el modelo los entienda
Usar transporte stdio para Claude Desktop en local
Usar transporte SSE con los endpoints correctos para producción
Probar tu servidor con Postman o MCP Inspector sin necesitar un cliente MCP
Desplegar en Railway con Docker en minutos
El patrón es idéntico sin importar qué quieras conectar: sustituye las llamadas a Open-Meteo por tu base de datos Supabase, tu ERP, tu CRM o cualquier API interna. La arquitectura no cambia.
En el próximo artículo conectamos este mismo patrón con Supabase para que un agente de IA pueda consultar y actualizar datos de tu aplicación en tiempo real.
¿Ya lo probaste? Cuéntanos en los comentarios qué ciudad consultaste primero 👇
Código completo en GitHub
Puedes clonar el repositorio completo de este tutorial en: https://github.com/Fencode-dev/mcp-weather-server