---
title: "Transportes MCP: stdio frente a HTTP, y qué acaba de cambiar"
description: "La mayoría de respuestas a esta pregunta describen una especificación que ya no existe. Las sesiones, el flujo GET y el SSE reanudable han desaparecido. El estado actual."
author: "Matthias Meyer"
published: 2026-08-03
updated: 2026-08-03
language: es
tags: ["mcp", "stdio", "http", "protokoll", "transporte", "spec"]
canonical: "https://studiomeyer.io/es/blog/mcp-stdio-vs-http"
markdown_versions: ["https://studiomeyer.io/de/blog/mcp-stdio-vs-http.md", "https://studiomeyer.io/en/blog/mcp-stdio-vs-http.md", "https://studiomeyer.io/es/blog/mcp-stdio-vs-http.md"]
publisher: "StudioMeyer, https://studiomeyer.io (llms.txt: https://studiomeyer.io/llms.txt)"
---

# Transportes MCP: stdio frente a HTTP, y qué acaba de cambiar

Nuestros propios datos de búsqueda llevan meses diciéndome que escriba esto y los he ignorado. Treinta y una formas distintas de escribir la misma pregunta, cosas como «stdio vs http mcp» y «mcp server stdio vs http», casi trescientas impresiones, varias de ellas en la primera página. Cero clics, porque posicionamos ahí por accidente desde otros artículos y nunca hemos tenido una página que responda de verdad.

Aquí está la página. Y escribirla sacó algo que no esperaba: casi todo lo que encontrarás sobre esta pregunta, incluidos los primeros resultados, describe una versión del protocolo que ya fue reemplazada.

## La respuesta corta

Si un único cliente arranca el servidor en la misma máquina y este toca archivos locales, bases de datos locales o herramientas locales, usa stdio. Si el servidor tiene que ser accesible por red, atender a más de un cliente, o ejecutarse en un sitio donde despliegas en lugar de donde te sientas, usa Streamable HTTP.

Si estás a punto de construir sobre HTTP+SSE, el transporte de dos endpoints de la revisión 2024-11-05, no lo hagas. Está obsoleto desde 2025-03-26, las nuevas implementaciones no deberían adoptarlo, y es candidato a ser eliminado en una revisión futura.

Con eso quedan resueltas la mayoría de decisiones. El resto de este artículo trata de lo que muerde después.

## stdio: una tubería y un fallo que escribe todo el mundo

El cliente arranca tu servidor como subproceso y habla con él por los flujos estándar. El servidor lee JSON-RPC de `stdin` y escribe JSON-RPC en `stdout`, un mensaje por línea, delimitado por saltos de línea, y los mensajes no pueden contener saltos de línea incrustados.

Ahora la regla que rompe más servidores stdio que ninguna otra, y merece la pena citarla porque la gente la pasa por alto al leer en diagonal: el servidor **NO DEBE** escribir en su `stdout` nada que no sea un mensaje MCP válido.

Eso significa que cada `print()`, cada `console.log()`, cada línea de depuración perdida de una biblioteca que importaste va directa al canal de mensajes y corrompe el protocolo. El cliente ve JSON malformado donde esperaba una respuesta. Los síntomas van desde una herramienta que nunca vuelve hasta un servidor que parece conectarse y muere en la primera llamada.

La solución está en el mismo párrafo de la especificación: `stderr` es tuyo. El servidor puede escribir UTF-8 ahí con cualquier fin de registro, y el cliente puede capturarlo, reenviarlo o ignorarlo. La especificación es explícita en que el cliente no debe interpretar la salida por `stderr` como una condición de error. Así que manda tu logging ahí y deja `stdout` en paz.

Dos cosas más que conviene saber. El apagado lo inicia el cliente cerrando tu flujo de entrada, así que un servidor debería terminar con prontitud cuando `stdin` llega al fin de fichero. Esa es la señal principal de cierre ordenado y, según la especificación, la única portable. Y si tu proceso muere inesperadamente, el cliente debería reiniciarlo. Como el protocolo no tiene estado, las peticiones en vuelo simplemente se pierden, y el cliente puede reintentarlas contra el proceso nuevo. Fíjate en la formulación: se pierden, no se reintentan automáticamente. Que haya un reintento lo decide quien llama, no es algo que el transporte haga por ti.

## Streamable HTTP: un endpoint, un POST por mensaje

El servidor expone un único endpoint HTTP que acepta POST. Cada petición o notificación JSON-RPC es su propio POST. Para una petición, el servidor responde con un único objeto JSON o con un flujo SSE acotado a esa petición, que lleva notificaciones de progreso y después la respuesta final. Para una notificación no hay respuesta JSON-RPC en absoluto: una notificación aceptada recibe HTTP `202 Accepted` sin cuerpo.

Tres requisitos del lado del cliente se pasan por alto con facilidad. La cabecera `Accept` debe listar tanto `application/json` como `text/event-stream`, porque el servidor elige en cada petición cuál envía y el cliente debe manejar ambos. El cuerpo debe ser una única petición o notificación JSON-RPC, nunca una respuesta. Y las cabeceras de metadatos, a las que llego enseguida, son obligatorias.

Para notificaciones de larga duración del servidor al cliente hay ahora un mecanismo propio: envías una petición `subscriptions/listen`, y su flujo de respuesta permanece abierto llevando solo los tipos de notificación a los que te suscribiste. Las notificaciones ligadas a una petición, como el progreso, no viajan por ese flujo; van por el flujo de respuesta de la petición a la que pertenecen.

## Qué cambió en la revisión 2026-07-28

Esta es la parte que deja obsoletas casi todas las explicaciones existentes, incluidas bastantes guías publicadas este año.

**Las sesiones a nivel de protocolo han desaparecido.** Revisiones anteriores permitían al servidor asignar una sesión mediante una cabecera `Mcp-Session-Id`, terminada con un HTTP DELETE. Ese mecanismo no forma parte de la revisión actual. Un servidor que implemente solo la nueva revisión debería ignorar por completo una cabecera `Mcp-Session-Id` entrante y no debe emitir ni devolver identificadores de sesión.

**El flujo GET independiente ha desaparecido.** Los clientes abrían antes un flujo SSE separado con una petición GET para recibir mensajes iniciados por el servidor. Eliminado. Un servidor que solo soporte esta revisión debería responder a GET o DELETE en el endpoint MCP con `405 Method Not Allowed`; los servidores que además siguen hablando las revisiones antiguas obviamente las siguen atendiendo.

**Los flujos reanudables han desaparecido.** `Last-Event-ID` no está soportado. Un servidor construido solo para esta revisión debería ignorar esa cabecera, igual que cualquier `Mcp-Session-Id` entrante.

**Los servidores ya no envían sus propias peticiones.** Cuando un servidor necesita algo del cliente, es decir sampling, elicitation o roots, antes enviaba una petición JSON-RPC por un flujo SSE. Ahora devuelve un `InputRequiredResult` y el cliente reintenta la llamada original con las respuestas adjuntas. Se llama Multi Round-Trip Requests y es un cambio real en el flujo de control, no un cambio de nombre.

**Y el propio handshake `initialize` es ahora la vía heredada.** Las revisiones modernas llevan la versión del protocolo, las capacidades y la identidad del cliente en cada petición, en campos `_meta.io.modelcontextprotocol/*`, en lugar de fijarlos una sola vez en un handshake ligado a la conexión.

Si has leído algo de esto en una guía que todavía muestra contabilidad de `Mcp-Session-Id` y un flujo GET, esa guía describe la forma de 2025-03-26 a 2025-11-25. No es historia equivocada, y muchos servidores desplegados la siguen hablando, pero no es contra lo que deberías construir ahora.

## Tres cabeceras que ahora son obligatorias

Streamable HTTP refleja campos seleccionados del cuerpo en cabeceras HTTP para que balanceadores, pasarelas y herramientas de observabilidad puedan enrutar e inspeccionar peticiones sin parsear el cuerpo.

Todo POST al endpoint MCP debe llevar `MCP-Protocol-Version`, por ejemplo `MCP-Protocol-Version: 2026-07-28`. `Mcp-Method` es obligatoria en todas las peticiones, y `Mcp-Name` en `tools/call`, `resources/read` y `prompts/get`, llevando el nombre de la herramienta, la URI del recurso o el nombre del prompt respectivamente.

Y aquí está la trampa: el valor de la cabecera debe coincidir con el valor correspondiente del cuerpo. Si no coincide, el servidor debe rechazar la petición con `400 Bad Request` y el error JSON-RPC `-32020`, llamado `HeaderMismatch`. El motivo es una preocupación de seguridad real y no pedantería. Si un balanceador enruta según la cabecera mientras el servidor ejecuta según el cuerpo, y ambos discrepan, tienes una petición que va a la infraestructura de un inquilino y ejecuta la llamada de otro.

Los valores que no pueden escribirse de forma segura como ASCII plano, es decir cualquiera con caracteres no ASCII, caracteres de control o espacios al principio o al final, deben codificarse en Base64 con el formato centinela `=?base64?VALOR?=`. Los servidores lo decodifican antes de compararlo con el cuerpo.

## Las reglas de seguridad para HTTP, que no son opcionales

Tres requisitos, y el primero es el que se salta la gente.

Los servidores **DEBEN** validar la cabecera `Origin` en todas las conexiones entrantes para prevenir ataques de DNS rebinding, y deben responder a un Origin inválido con `403 Forbidden`. Sin eso, una web que tu usuaria visite en el navegador puede alcanzar un servidor MCP local en su máquina y manejarlo. El ataque funciona precisamente porque el servidor es local y de confianza.

En ejecución local, los servidores **DEBERÍAN** escuchar solo en `127.0.0.1` en lugar de `0.0.0.0`. Escuchar en todas las interfaces en un portátil pone tu servidor de herramientas en cada red a la que ese portátil se conecte, wifi de hotel incluido.

Y los servidores **DEBERÍAN** implementar autenticación adecuada en todas las conexiones. El transporte en sí no prescribe un mecanismo, así que cuál usas es una decisión que tomas deliberadamente y no una que el transporte tome por ti. En la práctica eso significa la autorización que la especificación MCP define aparte, o una credencial bearer que tú controles.

Nada de esto aplica a stdio, y esa es buena parte del motivo por el que stdio sigue siendo la opción correcta para trabajo local. No hay puerto, no hay origin y no hay superficie de red a la escucha, así que la frontera del proceso es la frontera de seguridad del transporte.

Hay una cosa que stdio no te da, y conviene decirlo porque se asume mucho: no es una garantía de que tus datos se queden en la máquina. El transporte es local, el comportamiento del servidor no. Un servidor stdio es un proceso corriente que puede abrir cualquier conexión saliente que le apetezca, y bastantes existen precisamente para llamar a una API remota en tu nombre. stdio te dice cómo habla el cliente con el servidor. No te dice nada sobre a dónde manda el servidor las cosas después.

## La decisión en forma de tabla

| | stdio | Streamable HTTP |
|---|---|---|
| Quién arranca el servidor | el cliente, como subproceso | tú, de forma independiente |
| Accesible para | solo ese cliente | cualquiera que alcance la URL |
| Superficie de red | ninguna | un puerto, con todo lo que conlleva |
| Cancelación | `notifications/cancelled` | cerrar el flujo de la petición |
| Autenticación | frontera del proceso | mecanismo propio más validación de Origin |
| Uso programado y desatendido | necesita un proceso que lo arranque | encaje natural |
| Caso típico | archivos locales, base de datos local, herramientas de IDE | API alojada, servidor de equipo, SaaS |

En la práctica la decisión rara vez está reñida. Lo que sí merece reflexión es el caso en que un proveedor ofrece ambos, cada vez más frecuente: un paquete local que instalas y un endpoint alojado al que apuntas. Ahí la pregunta decisiva no es la elegancia técnica sino quién guarda la credencial y qué hace el servidor con ella. Un paquete local mantiene la credencial en tu máquina, y esa es una diferencia real. No mantiene ahí tus datos, como dice la sección anterior. Si esa distinción importa en tu caso, la respuesta está en el comportamiento del servidor y en sus condiciones de privacidad, no en su transporte.

## Dos cosas que comprobaría antes de construir

Comprueba qué revisión habla realmente tu cliente, no cuál describe la documentación. La especificación incluye un procedimiento explícito de negociación de versión y repliegue precisamente porque el campo está ahora repartido entre revisiones, y recomienda sondear antes de la primera petición real incluso para clientes que solo soportan versiones modernas. La ganancia es que un desajuste falla de forma predecible en lugar de que un servidor antiguo procese tu llamada en silencio con otra semántica.

Y si escribes un servidor stdio, ponle una regla de lint o un test que falle en cuanto algo llegue a `stdout` fuera del escritor de mensajes. Es la protección más barata contra el único fallo que le cuesta una tarde a todo el mundo, y se añade en menos tiempo del que dura la primera sesión de depuración que evita.

Los transportes en sí no son complicados. Lo que hace confuso este tema es que el terreno se movió hace poco y la mayor parte de lo escrito no se ha puesto al día. Si te llevas una sola cosa de este artículo, llévate la costumbre de mirar la fecha de revisión de todo lo que leas.

## Seguir leyendo, Claude + Claude Code

- [Servidor MCP de Ahrefs: configuración para Claude, Codex y los demás](https://studiomeyer.io/es/blog/ahrefs-mcp-server-setup.md)
- [Leer y analizar documentos con Claude](https://studiomeyer.io/es/blog/analyze-documents-with-claude.md)
- [Automatizar las partes repetitivas de tu semana con Claude](https://studiomeyer.io/es/blog/automating-tasks-with-claude.md)
- [Claude en 2026: modelos, apps, Claude Code y la API](https://studiomeyer.io/es/blog/claude-guide-2026.md)
