Crear un servidor MCP en TypeScript con el SDK v2 (y que hable 2026-07-28)
Si buscas «MCP TypeScript» vas a encontrar sobre todo código de 2025. Ese código usa el paquete único @modelcontextprotocol/sdk, que es la v1 del SDK, y sus métodos server.tool(), server.resource() y server.prompt() no e
Si buscas «MCP TypeScript» vas a encontrar sobre todo código de 2025. Ese código usa el paquete único @modelcontextprotocol/sdk, que es la v1 del SDK, y sus métodos server.tool(), server.resource() y server.prompt() no existen en la v2. Para un servidor nuevo, el que habla la especificación MCP 2026-07-28, necesitas el SDK oficial v2, que se reparte en paquetes separados.
Este artículo es un extracto de una guía más larga. Montamos nortia-pedidos, un servidor que consulta pedidos de Nortia Logística (la empresa ficticia del curso de DesarrolloMCP), y lo servimos por stdio y por Streamable HTTP sin estado. Todo el código se ejecutó con @modelcontextprotocol/server 2.3.0, zod 4.6.5 y Node 24.12.
Lo que no te cuentan: instalar la v2 no basta
La guía oficial del SDK lo dice así: «Nothing in v2 puts a 2026-07-28 byte on the wire by default». La versión del protocolo la decide el punto de entrada con el que sirves el servidor, no el paquete que instalas. Por eso el servidor se construye dentro de una función, una fábrica que devuelve un McpServer: los puntos de entrada que hablan 2026-07-28 reciben la fábrica, no una instancia, y en HTTP la ejecutan en cada petición.
El proyecto
mkdir nortia-pedidos && cd nortia-pedidos
npm init -y && npm pkg set type=module
npm i @modelcontextprotocol/server @modelcontextprotocol/client \
@modelcontextprotocol/node @modelcontextprotocol/express express zod
npm i -D typescript tsx @types/node @types/express
La fábrica crearServidor() registra una tool (buscar_pedido, con entrada y salida validadas con Zod 4), un resource fijo, otro con plantilla (pedidos://pedido/{pedidoId}) y un prompt. En la guía completa está entera y explicada decisión a decisión: por qué un pedido inexistente es un resultado con isError: true y no una excepción, y por qué hace falta filtrar los campos de salida aunque declares un outputSchema.
Servirlo por stdio
import { serveStdio } from '@modelcontextprotocol/server/stdio';
import { dependencias } from './entorno.js';
import { crearServidor } from './servidor.js';
const deps = dependencias('stdio'); // falla al arrancar si la configuración es inválida
const handle = serveStdio(() => crearServidor(deps));
console.error(`nortia-pedidos (${deps.inquilino}) escuchando por stdio`);
process.on('SIGINT', () => void handle.close());
serveStdio(fábrica) es lo que hace que el servidor hable 2026-07-28, y por defecto también atiende a clientes de las versiones con handshake. Fíjate en que todo se escribe con console.error: en stdio, la especificación prohíbe escribir en stdout nada que no sea un mensaje MCP válido.
Servirlo por Streamable HTTP, sin estado
import { createMcpExpressApp } from '@modelcontextprotocol/express';
import { toNodeHandler } from '@modelcontextprotocol/node';
import { createMcpHandler } from '@modelcontextprotocol/server';
import { dependencias } from './entorno.js';
import { crearServidor } from './servidor.js';
const deps = dependencias('http');
// La fábrica se ejecuta una vez por petición HTTP: no hay nada que recordar entre dos POST.
const handler = createMcpHandler(() => crearServidor(deps));
const app = createMcpExpressApp(); // express.json() y validación de Host y Origin
const node = toNodeHandler(handler);
app.all('/mcp', (req, res) => void node(req, res, req.body));
const PORT = Number(process.env.PORT ?? 3000);
const httpServer = app.listen(PORT, '127.0.0.1', () => {
console.error(`nortia-pedidos (${deps.inquilino}) en http://127.0.0.1:${PORT}/mcp`);
});
process.on('SIGTERM', async () => {
await handler.close();
httpServer.close();
});
createMcpHandler devuelve un handler web estándar y toNodeHandler lo adapta a Express. No hay sesiones ni cabecera Mcp-Session-Id: cada POST se resuelve con lo que trae. Como la fábrica se ejecuta en cada petición, lo caro (pools de base de datos, cachés, clientes de otras APIs) tiene que vivir fuera, a nivel de módulo.
Probarlo sin engañarte
El MCP Inspector usa por defecto las versiones con handshake. Si no le pides --protocol-era modern (o el campo "protocolEra": "modern" dentro de la entrada del servidor en su archivo de configuración), la prueba pasa en verde sin haber probado 2026-07-28. La señal de que sí se negoció la versión actual es io.modelcontextprotocol/serverInfo dentro de _meta en la respuesta.
La guía completa, con el resto del código (los datos detrás de una interfaz, la tool, los resources y el prompt), la petición curl con su _meta, la prueba con el Inspector y cómo pedirle a Claude Code que lo construya fijando el SDK v2, está en DesarrolloMCP: crear un servidor MCP en TypeScript.
Si estás aprendiendo MCP en español, el curso de DesarrolloMCP tiene el nivel Básico gratis (14 lecciones, sin tarjeta), actualizado a la especificación 2026-07-28 y con todo el código probado.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.