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.

Respuesta rápida
- Usa
stdiocuando 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/listy 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:
- Stateless: el handler crea el servidor por petición y cualquier instancia puede atender cualquier llamada.
- Estado externo: sesiones, eventos o resultados viven en almacenamiento compartido y cualquier nodo puede atender la petición.
- 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:
- El cliente completa
initializey negocia una revisión compatible. tools/listmuestrafind_notecon el schema publicado.- Un identificador válido devuelve el contenido esperado.
- Un identificador vacío falla la validación antes de la consulta real.
- 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
stdioen la red. - [ ] La ruta
/mcpestá 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
- Model Context Protocol, "Streamable HTTP", consultado el 10/08/2026
- Model Context Protocol, "Understanding Authorization in MCP", consultado el 10/08/2026
- MCP TypeScript SDK, "Serve over HTTP", consultado el 10/08/2026
- MCP TypeScript SDK, "Authorization", consultado el 10/08/2026
- MCP TypeScript SDK, "Sessions, state, and scaling", consultado el 10/08/2026
- MCP TypeScript SDK, "Supporting protocol revision 2026-07-28", consultado el 10/08/2026
- MCP TypeScript SDK, "Upgrade to v2", consultado el 10/08/2026
- MCP Inspector, documentación oficial, consultado el 10/08/2026