MCP Streamable HTTP: cómo crear servidores remotos sin copiar tutoriales antiguos

Streamable HTTP no significa mantener una sesión SSE eterna. La revisión actual de MCP usa un endpoint POST y una respuesta JSON o SSE por petición. Este es el diseño que evita desplegar un servidor remoto con supuestos ya obsoletos.

Compartir
MCP Streamable HTTP: cómo crear servidores remotos sin copiar tutoriales antiguos

La keyword principal es `MCP Streamable HTTP`; la intención es técnica y de implementación: un developer busca exponer un servidor MCP por URL para varios clientes sin confundir el transporte remoto actual con el viejo HTTP+SSE de dos endpoints.

El punto práctico es que HTTP permite TLS, un balanceador, observabilidad y una ruta estable como `https://mcp.ejemplo.com/mcp`. El coste es que ahora tus errores de red, CORS, autenticación, buffering de proxies y despliegue pasan a formar parte del contrato que el cliente debe soportar.

Arquitectura de un servidor MCP remoto con cliente, gateway HTTPS que valida origen y token, endpoint único, ejecutor de herramientas y registro de auditoría; un flujo antiguo de dos endpoints aparece descartado
Cada `POST` tiene su propia respuesta JSON o SSE. La autenticación y la política están delante de la ejecución, no escondidas dentro del prompt.

Checklist

El contrato HTTP que el cliente debe soportar

El cliente manda un único request o notification JSON-RPC UTF-8 por `POST` y anuncia ambos formatos: `Accept: application/json, text/event-stream`. Para una request, el servidor puede responder con un objeto JSON o con SSE. El cliente debe manejar los dos, incluso si tu herramienta hoy parece síncrona: un servidor puede emitir progreso y cerrar con la respuesta final en el stream.

La cancelación moderna es cerrar el stream SSE de esa petición. No envíes `notifications/cancelled` como si fuera stdio. Para cambios de larga vida, el cliente abre `subscriptions/listen`; no reutilices una respuesta de `tools/call` como canal global de notificaciones. En proxies como nginx, desactiva buffering para SSE y usa keep-alives de comentario cuando tengas un stream largo.

La especificación actual también obliga a espejar metadata en headers: `MCP-Protocol-Version` y `Mcp-Method`; `Mcp-Name` para `tools/call`, `resources/read` y `prompts/get`. El servidor que procesa el body debe rechazar discrepancias. No es decoración: permite que gateway y runtime no tomen decisiones distintas sobre qué tool se va a ejecutar.

Código: endpoint remoto moderno en TypeScript

La API actual del SDK TypeScript v2 usa una factory por petición. La factory crea un `McpServer` para el llamador actual; los pools y caches viven fuera, pero la identidad y el servidor no se comparten entre requests. Este ejemplo usa un handler web-standard y después lo adapta a Node.

¿Te está sirviendo? Hay una dosis cada semana

Te resumo herramientas de IA para devs, agentes, MCP, seguridad y workflows en un email de 5 minutos. En español y sin ruido.

Suscribirme gratis

Puntos a revisar

Lo que conviene comprobar

src/mcp.ts
import { createMcpHandler, McpServer } from "@modelcontextprotocol/server";
import { toNodeHandler } from "@modelcontextprotocol/node";
import * as z from "zod/v4";

const handler = createMcpHandler(({ authInfo }) => {
  const actorId = authInfo?.clientId;
  if (!actorId) throw new Error("authenticated caller required");

  const server = new McpServer({ name: "reports", version: "1.0.0" });
  server.registerTool(
    "get-report",
    {
      description: "Read a report owned by the authenticated caller",
      inputSchema: z.object({ reportId: z.string().uuid() }),
    },
    async ({ reportId }) => {
      const report = await reports.findOwnedBy(reportId, actorId);
      if (!report) return { content: [{ type: "text", text: "Not found" }], isError: true };
      return { content: [{ type: "text", text: report.summary }] };
    },
  );
  return server;
});

export const mcp = toNodeHandler(handler); // app.all("/mcp", mcp)

La autenticación no se resuelve con ese `throw`; debe ejecutarse antes del handler y poblar `authInfo` solo tras validar token, audiencia y scopes. La herramienta tampoco confía en `reportId` para elegir tenant: deriva la propiedad del actor autenticado. Este es el límite que evita que un modelo convierta una URL remota en una API de lectura arbitraria.

Protege el endpoint antes de la primera tool

Valida `Origin` en toda conexión entrante; la especificación lo exige para defender servidores locales frente a DNS rebinding. En local, escucha en `127.0.0.1`, no en `0.0.0.0`. El SDK recomienda además validar `Host` delante del handler. CORS no sustituye esa validación: CORS gobierna qué puede leer un navegador; no decide si tu servidor procesa una petición hostil.

Para un servidor remoto protegido, implementa OAuth 2.1 como resource server. El cliente presenta `Authorization: Bearer`; el token no va en query string y se valida contra la audiencia del recurso MCP concreto. La especificación MCP requiere Protected Resource Metadata cuando soportas autorización y permite que una respuesta `401` o `403` explique scopes mínimos mediante `WWW-Authenticate`.

Mantén las herramientas con capacidades pequeñas. Separa `read_report` de `delete_report`; aplica límite por actor y tool; valida IDs y rangos en backend; registra un request ID. Un token válido no convierte cualquier argumento en legítimo. Los schemas de MCP evitan formatos absurdos, pero no sustituyen permisos de negocio ni aislamiento multi-tenant.

La compatibilidad no debe incluir HTTP+SSE por nostalgia. Úsala solo para usuarios identificados y con una ruta aislada. Un nuevo deployment debería arrancar con un único `/mcp` moderno, documentación que nombra la revisión de protocolo y una matriz de clientes probada en CI.

Escala sin convertir SSE en estado

Un handler por request y herramientas idempotentes hacen que escalar horizontalmente sea bastante normal: cualquier réplica puede atender el siguiente POST. Para una tarea larga, devuelve un identificador de trabajo y ofrece una tool de consulta o usa las utilidades de tareas de la versión que soportes. No dependas de que la misma conexión TCP, pod o stream permanezca vivo.

SSE sí exige cuidado operacional. Desactiva buffering del proxy (`X-Accel-Buffering: no` cuando aplique), revisa timeouts de idle y propaga desconexión para cancelar cálculo desperdiciado. Pero no abras SSE por defecto: para una lectura rápida, JSON reduce recursos y hace observabilidad más simple. Escoge streaming cuando hay progreso, pasos intermedios o latencia real que explicar.

Registra protocolo, method, tool name, actor pseudonimizado, status, latencia, bytes, tipo de respuesta JSON/SSE y motivo de denegación. No metas argumentos completos ni resultados sensibles en las trazas. Es suficiente para detectar una tool lenta o un cliente antiguo sin convertir logs en una fuga de datos.

Checklist de despliegue

  • El endpoint `/mcp` está detrás de TLS y no comparte una ruta ambigua con una API pública genérica.
  • El runtime valida Origin y Host; el proceso local se liga a loopback cuando no debe ser remoto.
  • OAuth valida firma, issuer, expiración, audiencia del recurso y scopes; no acepta tokens por query string.
  • Cada tool deriva tenant y actor del contexto autenticado y comprueba permisos en backend.
  • Cliente y servidor soportan JSON y SSE por request; existe cancelación y el proxy no bufferiza streams.
  • Los headers MCP requeridos coinciden con el body y se rechazan mismatches en borde y servidor.
  • La política de compatibilidad enumera versiones, clientes, ruta legacy si existe y fecha de retirada.
  • Hay tests de contrato, seguridad, aislamiento de tenants, cancelación y despliegue tras un proxy real.

Preguntas frecuentes

¿Qué es MCP Streamable HTTP?

Es el transporte MCP para un servidor remoto. Cada mensaje JSON-RPC se envía por POST a un endpoint MCP y la respuesta es un JSON o un stream SSE vinculado a esa petición.

¿Streamable HTTP sustituye a stdio?

No. `stdio` sigue siendo apropiado para servidores locales lanzados por el host. Streamable HTTP resuelve un servidor compartido o remoto; requiere controles de red, autenticación y operaciones adicionales.

¿Necesito Mcp-Session-Id en 2026?

No para la revisión `2026-07-28`. El GET stream y las sesiones de protocolo se retiraron. Modela el estado de negocio con tus propios IDs persistentes y asociados a la identidad autenticada.

¿Por qué el servidor debe aceptar JSON y SSE?

El cliente anuncia ambos formatos y el servidor elige por petición. JSON funciona para resultados breves; SSE permite notificaciones de progreso y una respuesta final de un trabajo que necesita streaming.

¿CORS protege un MCP remoto?

No. Necesitas validar Origin, autenticar cada request, autorizar la tool y sus argumentos en backend, aplicar TLS y comprobar aislamiento de tenant. CORS es solo una política del navegador.

¿Puedo mantener el transporte SSE antiguo?

Solo como compatibilidad explícita para clientes que realmente lo requieran. Es un transporte deprecado; las implementaciones nuevas deben preferir Streamable HTTP y fijar una retirada medible del puente legacy.

Cómo migrar un servidor MCP local a Streamable HTTP moderno

  1. Decidir si debe ser remoto. Mantén stdio si la tool es personal; usa HTTP cuando haya clientes compartidos, operación gestionada o acceso por red justificado.
  2. Fijar la revisión. Declara clientes y versiones soportadas; evita mezclar GET streams, sesiones heredadas y el endpoint actual por accidente.
  3. Crear el endpoint. Expón un único `/mcp` que procese POST y pueda devolver JSON o SSE por request mediante el SDK actual.
  4. Montar controles de borde. Valida Origin y Host, termina TLS, limita tamaño y rate, y enlaza loopback para desarrollo local.
  5. Autenticar y autorizar. Valida bearer token, audiencia y scopes antes del handler; deriva actor y tenant de esa identidad en cada tool.
  6. Validar el contrato. Comprueba `MCP-Protocol-Version`, `Mcp-Method` y `Mcp-Name` frente al body y rechaza mismatches.
  7. Modelar trabajo durable. Guarda tareas largas y estado de negocio con IDs propios, TTL y control de propietario; no uses una conexión como base de datos.
  8. Probar clientes reales. Cubre discover, tools/list, JSON, SSE, cancelación, proxy, scopes, tenant cruzado y el fallback legacy que prometas.
  9. Publicar con observabilidad. Mide era de protocolo, tool, resultado, latencia y denegaciones; elimina rutas heredadas cuando su uso llegue a cero.

Fuentes y referencias

También te puede interesar

OAuth 2.1 para proteger servidores MCP remotosMCP Inspector: testing y depuración de servidoresMCP en producción: seguridad, permisos y supply chainMCP Registry: publicar y descubrir servidoresMCP outputSchema y structuredContent para agentes

Recibe una lectura semanal de herramientas IA para devs

Cada semana te resumo herramientas de IA para devs, agentes, MCP, seguridad y workflows en un email de 5 minutos. En español y sin ruido.

Suscribirme gratis

Lo mejor de la IA para desarrolladores, cada martes

Newsletter en español, gratis. Las herramientas, modelos y trucos de IA para devs que de verdad importan — sin ruido.