Un servidor MCP iniciado por stdio funciona bien cuando el propio cliente crea el proceso. Deja de ser suficiente cuando varios clientes necesitan las mismas tools mediante una URL, cuando el servidor debe estar detrás de un proxy o cuando el equipo quiere publicar una integración sin distribuir un paquete.

Para ese caso, aloja el servidor mediante Streamable HTTP. El SDK actual de MCP para TypeScript separa el servidor del transporte: stdio atiende integraciones locales y Streamable HTTP atiende servidores remotos (MCP TypeScript SDK, "Serve over HTTP", consultado el 10/08/2026). La aplicación sigue registrando las mismas tools, pero el runtime pasa a controlar HTTP, autenticación y cierre.

Este tutorial cubre el diseño mínimo, sus límites de seguridad y un camino de verificación repetible. El ejemplo usa los paquetes del SDK v2. Si tu proyecto todavía usa @modelcontextprotocol/sdk, compara los imports con la guía de migración del SDK antes de copiar el código.

Si todavía necesitas crear el servidor local, empieza por crear un servidor MCP en TypeScript para agentes de código. Aquí el problema es distinto: poner la misma superficie detrás de una red sin confundir transporte con autorización.

Diagrama de un servidor MCP en TypeScript que pasa por un endpoint HTTP, autenticación y verificación de herramientas.

Respuesta rápida

  • Usa stdio cuando el host inicia un proceso local; usa Streamable HTTP para un servidor remoto.
  • Empieza sin estado cuando cada petición pueda sostenerse por sí misma.
  • Valida Host, Origin y autenticación antes de ejecutar una tool.
  • Prueba tools/list y una llamada real con un cliente separado del proceso del servidor.

¿Cuándo debe salir un servidor MCP del proceso local?

Un servidor remoto tiene sentido cuando el cliente no debe gestionar el proceso, cuando varios consumidores comparten la integración o cuando la tool necesita red y credenciales que no deberían copiarse a cada workspace. Un servidor local sigue siendo mejor para tools privadas, rápidas y ligadas al directorio del desarrollador.

El protocolo no convierte una integración local en remota solo por cambiar su URL. La guía del transporte Streamable HTTP define un único endpoint HTTP que recibe mensajes JSON-RPC mediante POST. Cada petición puede recibir un objeto JSON o una respuesta SSE. Esa frontera de red necesita autenticación, límites y observabilidad.

Usa esta decisión:

Situación Transporte inicial Motivo
El IDE o CLI inicia el servidor en el mismo ordenador stdio El proceso y las credenciales permanecen en el host local.
Varios clientes acceden a tools mediante una URL Streamable HTTP El servidor tiene un endpoint compartido.
Las tools no tienen estado y escalan detrás de un proxy Streamable HTTP stateless Cualquier instancia puede atender una petición.
El flujo necesita sesiones, reanudación o notificaciones Streamable HTTP con estado El servidor debe persistir o enrutar el estado.

No elijas HTTP solo porque parezca más nuevo. Si el único cliente es un proceso local y la tool lee archivos de ese workspace, stdio reduce la superficie operativa. El endpoint remoto debe justificar el trabajo extra de TLS, identidad, límites de red y monitorización.

¿Qué cambia Streamable HTTP en el contrato MCP?

Streamable HTTP convierte el servidor en un proceso independiente y usa POST para cada mensaje JSON-RPC. La especificación actual exige que el cliente anuncie soporte para application/json y text/event-stream, y el servidor elige una respuesta JSON o un stream SSE por petición (MCP, "Streamable HTTP", consultado el 10/08/2026). El cliente ya no habla con stdin y stdout.

Eso cambia las pruebas. Debes comprobar el método HTTP, Content-Type, headers de versión, respuesta JSON-RPC, cancelación y comportamiento cuando un proxy cierra la conexión. Una prueba que solo llama al handler de la tool no demuestra que el endpoint remoto cumpla el protocolo.

No trates la versión del protocolo como un detalle permanente. La revisión 2026-07-28 cambió el comportamiento de sesiones y streams respecto a revisiones anteriores. El SDK actual ofrece createMcpHandler para un camino moderno por petición y documenta opciones de compatibilidad para clientes antiguos (SDK, "Supporting protocol revision 2026-07-28", consultado el 10/08/2026).

Registra las versiones de los paquetes, las revisiones aceptadas y los clientes probados. No añadas un fallback para SSE antiguo sin un cliente que lo necesite. Cada transporte extra añade estados y fallos que tendrás que mantener.

¿Cómo montar un servidor MCP remoto en TypeScript?

El SDK actual reduce el camino HTTP a tres piezas: una factory que crea McpServer, un handler que convierte cada petición en una ejecución y un adaptador para el runtime de Node. El ejemplo siguiente es ilustrativo, pero sigue los imports y la composición de los ejemplos oficiales del SDK.

Instala los paquetes y fija sus versiones en el lockfile:

npm install @modelcontextprotocol/server @modelcontextprotocol/node zod
npm install --save-dev typescript tsx @types/node

Crea una factory pequeña. Debe registrar tools estrechas y no leer credenciales directamente del prompt. El contexto autenticado pertenece a la capa HTTP, no a una cadena que el modelo pueda modificar.

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

export function buildServer(): McpServer {
  const server = new McpServer({
    name: "remote-notes",
    version: "1.0.0",
  });

  server.registerTool(
    "find_note",
    {
      description: "Find a note by its public identifier",
      inputSchema: z.object({
        id: z.string().min(1).max(80),
      }),
    },
    async ({ id }) => ({
      content: [{ type: "text", text: `Requested note: ${id}` }],
    }),
  );

  return server;
}

Una tool real debe consultar una fuente autorizada y validar también su resultado. El retorno anterior es deliberadamente pequeño: deja visible el contrato sin fingir que existe una API de notas, una base de datos o un sistema de tickets en este ejemplo.

Ahora crea una entrada HTTP stateless. createMcpHandler construye el servidor desde la factory por petición y toNodeHandler lo adapta a node:http. El código también aplica las protecciones de Host y Origin del paquete Node.

// src/http.ts
import { createServer } from "node:http";
import {
  localhostHostValidation,
  localhostOriginValidation,
  toNodeHandler,
} from "@modelcontextprotocol/node";
import { createMcpHandler } from "@modelcontextprotocol/server";
import { buildServer } from "./mcp-server.js";

const handler = toNodeHandler(createMcpHandler(buildServer));
const validateHost = localhostHostValidation();
const validateOrigin = localhostOriginValidation();

const httpServer = createServer((req, res) => {
  if (!validateHost(req, res) || !validateOrigin(req, res)) return;
  void handler(req, res);
});

httpServer.listen(3000, "127.0.0.1", () => {
  console.error("MCP endpoint listening on http://127.0.0.1:3000/mcp");
});

process.on("SIGTERM", () => {
  httpServer.close(() => process.exit(0));
});

El bind de loopback es apropiado para desarrollo local. En producción, usa un framework HTTP o un adaptador de runtime con una lista de hosts permitidos, escucha en la dirección del entorno y coloca la autenticación antes del handler. No copies 127.0.0.1 a un contenedor que deba recibir tráfico externo.

El SDK usa la misma separación en su ejemplo de gateway: createMcpHandler controla el protocolo y toNodeHandler lo adapta a Node (SDK, "Gateway example", consultado el 10/08/2026). Así puedes probar el handler sin abrir un puerto y probar el proceso completo por HTTP.

¿Cómo proteger el endpoint antes de la primera tool call?

Protege el endpoint por capas. El servidor MCP debe rechazar un host u origen inválido, el proxy debe terminar TLS y la capa de identidad debe validar el token antes de entregar la petición al handler. El tutorial de autorización de MCP recomienda OAuth para servidores HTTP alojados de forma remota, mientras que los servidores stdio pueden usar credenciales locales (MCP, "Understanding Authorization", consultado el 10/08/2026).

Para un servicio Node público, la forma es esta. verifier es un adaptador para tu proveedor de identidad. No pongas un token real en el repositorio ni trates este recorte como una implementación completa de OAuth.

import { createMcpExpressApp } from "@modelcontextprotocol/express";
import { toNodeHandler } from "@modelcontextprotocol/node";
import {
  createMcpHandler,
  requireBearerAuth,
} from "@modelcontextprotocol/server";
import { buildServer } from "./mcp-server.js";

const app = createMcpExpressApp({
  host: "0.0.0.0",
  allowedHosts: ["mcp.example.com"],
});

const verifier = createVerifierFromYourIdentityProvider();
const auth = requireBearerAuth({
  verifier,
  requiredScopes: ["mcp:tools"],
});

const handler = toNodeHandler(createMcpHandler(buildServer));

app.all("/mcp", auth, (req, res) => {
  void handler(req, res, req.body);
});

app.listen(8080, () => {
  console.error("MCP endpoint listening on port 8080");
});

La documentación del SDK describe requireBearerAuth, requiredScopes y el desafío WWW-Authenticate en esta frontera (SDK, "Authorization", consultado el 10/08/2026). Una tool todavía debe aplicar autorización de dominio: un token válido no significa que el caller pueda leer cada nota, base de datos o repositorio.

Sustituye mcp.example.com por el host real y conserva una lista explícita. La especificación también exige validar Origin para evitar DNS rebinding. Si montas el servidor con node:http sin una factory de framework, coloca los guards correspondientes antes del handler.

No apruebes tools por nombre solo porque el servidor pasó la autenticación. Separa lecturas de mutaciones, limita el alcance por usuario y registra qué identidad llamó a cada tool. El artículo sobre allowlists de MCP para agentes de código cubre la política de tools que queda por encima de la autenticación.

¿El servidor debe ser stateless o tener sesiones?

Empieza stateless cuando cada petición pueda reconstruir el servidor y acceder al almacenamiento por sí misma. La guía de escalado del SDK explica que createMcpHandler crea una instancia por petición, por lo que los servidores stateless pueden escalar detrás de un balanceador sin afinidad de sesión (SDK, "Sessions, state, and scaling", consultado el 10/08/2026).

Este modelo funciona bien para tools que reciben todos los argumentos, consultan una fuente externa y devuelven un resultado. El estado de la aplicación debe estar en una base de datos, cache o cola, no en el objeto McpServer creado para una sola petición.

Usa estado persistente cuando el flujo deba reanudar un stream, seguir notificaciones, mantener una suscripción o ejecutar trabajo entre llamadas. No dejes que un Map en memoria sea la única copia de una sesión si el servicio puede tener dos réplicas o reiniciarse.

Las opciones prácticas son:

  1. Stateless: el handler crea el servidor por petición y cualquier instancia puede atender cualquier llamada.
  2. Estado externo: sesiones, eventos o resultados viven en almacenamiento compartido y cualquier nodo puede atender la petición.
  3. Afinidad de sesión: el estado queda en la memoria de un nodo y el proxy fija el cliente a él. Es sencillo, pero complica fallos y escalado.

La guía de escalado del SDK también describe un bus compartido para notificaciones entre nodos. Úsalo solo cuando una función necesite comunicación entre instancias. Una tool de consulta simple no necesita ese coste el primer día.

La separación útil no es "HTTP con sesión" frente a "HTTP sin sesión". Separa el estado que exige el dominio del estado introducido por accidente por el transporte. Una tarea larga necesita persistencia porque el trabajo existe. Un mapa en memoria copiado de un ejemplo es deuda operativa.

¿Cómo verificar un servidor MCP remoto sin confiar en su proceso?

Verifica el servidor por capas: el proceso inicia, el endpoint responde, el cliente descubre las tools, una llamada válida produce un resultado, una llamada inválida se rechaza y una petición sin autorización no produce efectos. El MCP Inspector sirve para depuración interactiva, pero el check del CI debe iniciar o llamar al endpoint como un cliente real.

Durante el desarrollo, ejecuta el proceso y apunta el Inspector al endpoint:

npx @modelcontextprotocol/inspector http://127.0.0.1:3000/mcp

Confirma esta secuencia en el panel y en los logs del servidor:

  1. El cliente completa initialize y negocia una revisión compatible.
  2. tools/list muestra find_note con el schema publicado.
  3. Un identificador válido devuelve el contenido esperado.
  4. Un identificador vacío falla la validación antes de la consulta real.
  5. Un origen, host o credencial inválidos fallan antes del handler de la tool.

No trates una respuesta 200 como prueba suficiente. La capa HTTP puede responder mientras el contrato de la tool es incorrecto. El tutorial de pruebas de contrato para servidores MCP en TypeScript muestra cómo automatizar descubrimiento, llamada válida, entrada inválida y errores de transporte para un servidor local. En este spoke, cambia stdio por la URL HTTP y añade casos de autenticación y proxy.

Una prueba de humo puede llamar al endpoint desde una red de prueba, nunca con una credencial de producción. Captura status, Content-Type, cuerpo JSON-RPC, tiempo de respuesta y logs redactados. Si el endpoint usa SSE, comprueba también que el proxy no almacene la respuesta en buffer.

Este artículo no afirma un benchmark de producción ni un resultado de despliegue propio. La prueba propuesta es pequeña a propósito: un cliente separado descubre la tool, ejecuta un caso válido, fuerza uno inválido y confirma que la barrera de identidad detiene la ejecución. Es más honesto y útil que llamar al handler directamente y declarar listo el servidor remoto.

¿Qué fallos aparecen primero en producción?

Los primeros fallos suelen estar en la frontera, no en el razonamiento del modelo. Una ruta incorrecta devuelve 404; un token ausente devuelve 401; un alcance insuficiente devuelve 403; un origen rechazado puede devolver 403; un cliente incompatible falla durante la negociación; y un proxy mal configurado retiene o cierra un stream. Registra el error sin guardar tokens, prompts ni datos de las tools.

Síntoma Causa probable Comprobación
404 al conectar La URL no apunta a /mcp o falta la ruta Confirma la ruta final y el método POST.
401 antes de tools/list Falta el token, está caducado o el issuer no coincide Revisa WWW-Authenticate y usa un token de prueba.
403 en localhost El guard rechazó Host u Origin Prueba el origen explícito y no abras todas las interfaces sin allowlist.
No aparecen tools Falló la negociación o la factory no registró ninguna Lee la respuesta de initialize y el log de construcción.
Funciona en una réplica y falla en otra La sesión vive solo en memoria Usa stateless, almacenamiento compartido o afinidad deliberada.
La tool tarda y se corta la conexión Timeout del proxy, servidor o cliente Distingue cancelación de fallo de la tool y mide cada capa.

El caso más peligroso es repetir una mutación después de una desconexión. Una conexión caída no demuestra que el servidor no aplicara el efecto. Para escrituras, usa una idempotency key, una consulta de confirmación o una operación compensatoria. Las lecturas suelen ser más fáciles de repetir, pero también necesitan un límite.

Otro límite: Streamable HTTP no autoriza una tool por sí mismo. El transporte entrega mensajes, la autenticación identifica al caller y la política de la aplicación decide si ese caller puede actuar. Mantén esas decisiones separadas para que un cambio de transporte no amplíe por accidente la agencia del agente.

Checklist antes de publicar el endpoint

Antes de entregar la URL a un cliente, recorre esta lista:

  • [ ] El servidor usa Streamable HTTP para el acceso remoto y no expone stdio en la red.
  • [ ] La ruta /mcp está documentada y probada por un cliente externo.
  • [ ] TLS termina en el proxy o en el proceso, según el entorno.
  • [ ] Host y Origin tienen validación explícita, sin * como configuración permanente.
  • [ ] La autenticación valida issuer, audience, expiración y scope.
  • [ ] Cada tool aplica autorización de dominio, no solo autenticación global.
  • [ ] El modo stateless o con estado se eligió según el flujo real.
  • [ ] Los logs redactan tokens, prompts, datos personales y resultados sensibles.
  • [ ] El CI prueba descubrimiento, schema, éxito, rechazo, transporte y cierre.
  • [ ] Las mutaciones tienen idempotencia o confirmación del efecto antes del retry.

Si necesitas desplegar en Cloud Run, trata el servicio como el proceso HTTP que recibe el endpoint MCP. El artículo sobre servicio o worker pool para agentes de larga duración en Cloud Run ayuda a decidir el modelo de ejecución. No sustituye las pruebas de protocolo, auth y escalado de este artículo.

Conclusión

Un servidor MCP remoto fiable solo parece un servicio HTTP normal. El transporte define cómo llegan los mensajes, pero Host, Origin, identidad, permisos, estado y verificación deciden si la integración puede operar con seguridad. Empieza stateless cuando el dominio lo permita y añade persistencia solo por un requisito concreto.

En flujos largos de Claude Code y Codex, uso RemoteCode como herramienta del autor para continuar el trabajo entre sesiones. Ayuda a conservar la continuidad, pero no sustituye la autenticación, las pruebas de contrato ni la decisión sobre qué tools puede ejecutar un caller.

Preguntas frecuentes

¿Puede el mismo servidor MCP admitir stdio y Streamable HTTP?

Sí. Registra las tools en una factory compartida y elige una entrada de transporte por entorno. stdio sirve un proceso local, mientras que createMcpHandler o un transporte HTTP sirve peticiones remotas. Prueba ambos caminos porque su ciclo de vida, autenticación y cierre son distintos.

¿Streamable HTTP necesita una sesión en memoria?

No. El SDK actual permite un handler stateless por petición. Usa una sesión o estado externo cuando el flujo necesite reanudación, notificaciones o trabajo que cruce llamadas. Un Map local sirve en un ejemplo, pero no como única fuente de verdad de un servicio que escala o se reinicia.

¿Puedo proteger MCP solo con una API key?

Una API key puede funcionar para una integración sencilla, pero debe validarse antes del handler y limitarse al conjunto mínimo de tools. Para datos de usuario, consentimiento, auditoría o scopes distintos, usa un sistema de identidad adecuado y valida issuer, audience, expiración y permisos del servicio.

¿El MCP Inspector sustituye las pruebas de contrato?

No. El Inspector acelera la investigación manual de conexiones, schemas y llamadas. El CI debe repetir esos casos con un cliente automatizado y fallar si cambia la superficie publicada. La prueba debe usar el mismo transporte que producción, no solo llamar a una función interna.

¿Tengo que aceptar clientes MCP antiguos?

Solo si todavía los necesitas. El SDK actual documenta compatibilidad entre revisiones, pero cada camino adicional amplía la matriz de pruebas. Declara la revisión aceptada, registra los clientes probados y elimina el fallback legado cuando ya no atienda un caso real.

Fuentes consultadas