MCP deprecia sampling: el fin de la inferencia prestada
Sampling era la idea más elegante de MCP: un servidor podía tomar prestado el modelo del cliente y nunca sostener una API key. La especificación que se finalizó hace tres días reconstruyó ese mecanismo desde cero, y lo deprecó en el mismo documento. La ruta de migración tiene una línea, y mueve la factura de inferencia.
La revisión 2026-07-28 del Model Context Protocol es el mayor reordenamiento del protocolo desde su lanzamiento, y ya la cubrimos desde dos ángulos: el núcleo stateless cuando se cerró el release candidate, y la extensión Tasks el día en que la spec quedó final. Ambos textos rodearon el mismo cambio estructural sin nombrar su consecuencia más filosa.
Aquí está. Antes de esta revisión, un servidor MCP podía alcanzar hacia atrás por la conexión y pedirle al cliente tres cosas: los roots del sistema de archivos sobre los que debía operar, una línea de log para mostrar y — la interesante — una completion del modelo de lenguaje del cliente. Los tres canales fueron cerrados, reabiertos con otra forma y marcados para eliminación. La reapertura es SEP-2322, Multi Round-Trip Requests. La eliminación es SEP-2577. Leerlos juntos es la única forma de que el release tenga sentido.
El problema que MRTR vino a resolver
Empecemos por la falla operativa que forzó el rediseño. SEP-2322, escrito por Mark D. Roth, Caitie McCaffrey y Gabriel Zimmerman y creado el 3 de febrero de 2026, abre dividiendo las tools de MCP en dos categorías. Las efímeras no acumulan estado del lado del servidor — un lookup de clima, la lectura de un email. Si necesitan más información, pueden empezar de cero cuando la tengan. Las persistentes sí acumulan estado: pueden computar largo rato antes de necesitar input, y pueden necesitar seguir trabajando en background mientras esperan.
El juicio del SEP es directo: "la enorme mayoría de las tools de MCP serán efímeras", y típicamente se despliegan escaladas horizontalmente detrás de un load balancer. Ese despliegue es donde el modelo viejo se rompía.
Piensa en un tool call que necesita preguntarle algo al usuario a mitad de ejecución. El tools/call del cliente aterriza en la instancia A. La instancia A abre un stream SSE y empuja por ahí un request de elicitation. El usuario responde, y el cliente manda esa respuesta como un request HTTP separado — que el load balancer rutea de forma independiente, aterrizando en la instancia B. Ahora la instancia A sostiene en memoria una invocación esperando datos que llegaron a la instancia B.
Había exactamente dos salidas, y el SEP desarma ambas. Podías desplegar una capa de almacenamiento compartida entre instancias — Postgres, Redis, DynamoDB — que los autores describen como "extremadamente cara", un punto único de falla que exige alta disponibilidad y replicación, un cuello de botella para el escalado horizontal, y un problema de garbage collection donde limpiar agresivamente baja el costo pero recorta cuánto tiempo tiene el usuario para responder. O podías hacer el balanceo sticky con cookies, lo que rompe la distribución pareja de carga, exige cooperación del cliente y no es tolerante a fallas: si la instancia muere, la llamada reinicia.
Ambos enfoques dependen además de un stream SSE de larga vida, que muchos entornos no sostienen, y ambos fijan una instancia de la tool en memoria durante una espera sin cota. Como nota el SEP sobre elicitation en particular, la respuesta "puede no llegar del usuario por una cantidad de tiempo no acotada — pueden ser días o meses, o quizá nunca".
InputRequiredResult: el continuation token se vuelve el protocolo
El arreglo invierte la dirección del control. En vez de que el servidor empuje un request por un stream que mantiene abierto, el servidor termina la llamada y devuelve un resultado incompleto que describe lo que todavía necesita. El cliente junta las respuestas y emite un request completamente nuevo que las carga.
Dos familias de tipos lo implementan. InputRequests es un mapa de claves string asignadas por el servidor a objetos de request. InputResponses es un mapa con claves idénticas que carga los resultados del cliente. El mapa — en vez de una lista con ids embebidos — se eligió deliberadamente: "garantiza estructuralmente la unicidad de las claves", eliminando la necesidad de chequeos de conflicto en cada SDK.
El sobre es InputRequiredResult, discriminado por un campo nuevo resultType presente en todo Result:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "input_required",
"inputRequests": {
"github_login": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please provide your GitHub username",
"requestedSchema": {
"type": "object",
"properties": { "name": { "type": "string" } },
"required": ["name"]
}
}
}
},
// opaco para el cliente; toda la memoria del servidor sobre esta llamada
"requestState": "AEAD-protected blob"
}
}
El cliente responde re-emitiendo la llamada original con las respuestas adjuntas y el estado devuelto textualmente:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "update_work_item",
"arguments": { "workItemId": 4522 },
"inputResponses": {
"github_login": {
"action": "accept",
"content": { "name": "octocat" }
}
},
"requestState": "AEAD-protected blob"
}
}
resultType toma "complete" o "input_required", queda abierto a valores de extensiones, y por defecto es "complete" cuando está ausente — así se preserva la compatibilidad hacia atrás. Los clientes SHOULD tratar los valores no reconocidos como respuestas de protocolo inválidas.
El alcance es deliberadamente estrecho. Los servidores MAY devolver un InputRequiredResult en exactamente tres requests del cliente — prompts/get, resources/read y tools/call — y MUST NOT devolverlo en ningún otro. Los valores dentro de inputRequests MUST ser uno de ElicitRequest, CreateMessageRequest o ListRootsRequest. Vale retener esa lista: dos de esos tres tipos quedan, en esta misma revisión, deprecados.
La spec es explícita en que esto no es aditivo. Los servidores MUST mandar requests server-to-client usando MRTR; "el patrón previo de requests iniciados por el servidor ya no está soportado. Este es un breaking change".
requestState es input controlado por el atacante, y la spec lo dice
Todo el diseño descansa en un blob de estado del servidor haciendo un viaje de ida y vuelta a través de un intermediario no confiable. El texto normativo de la especificación final es considerablemente más duro que el del SEP borrador, y es el pasaje más denso en seguridad del release.
Los servidores MUST tratar requestState como input controlado por el atacante. Si influye en autorización, acceso a recursos o lógica de negocio, los servidores MUST proteger su integridad — la spec nombra HMAC o AEAD — y MUST rechazar estado que falle la verificación. La protección de integridad MAY omitirse solo cuando manipularlo no puede causar nada peor que la falla del request.
El replay tiene su propio set de requisitos. Los servidores SHOULD incluir tres cosas dentro del payload protegido y verificar cada una al recibirlo: el principal autenticado, rechazando estado presentado por un principal distinto; una expiración corta, rechazando estado presentado después de que caduca; y un identificador del request originante — la spec sugiere el nombre del método más un digest de sus parámetros relevantes — rechazando estado presentado sobre un request que no coincide.
requestState dado debe consumirse a lo sumo una vez — canjes únicos, cualquier cosa que mueva dinero — MUST hacer cumplir ese invariante del lado del servidor.
Es la segunda vez en dos meses que el protocolo reparte un artefacto autoautenticado y luego le dice a los implementadores que lo aten ellos. El taskId de la extensión Tasks tiene la misma forma: la entropía es obligatoria, el binding de identidad no. Un gateway sentado frente a estos servidores hereda el trabajo en ambos casos.
Las reglas restantes son más cortas pero consecuentes. Todo InputRequiredResult MUST cargar al menos uno entre inputRequests y requestState. Los servidores MUST NOT incluir un tipo de input request que el cliente no declaró soportar. Los servidores MUST NOT asumir que el cliente va a cumplir los requests ni a reintentar. Del lado del cliente: requestState MUST devolverse exacto y MUST NOT inspeccionarse, parsearse ni modificarse; si no se entregó ninguno, no puede mandarse uno; y el id de JSON-RPC MUST diferir entre el request original y el reintento, porque son requests independientes.
El manejo de errores sigue la misma filosofía. Si el cliente manda parámetros que el servidor no pidió, el servidor SHOULD ignorarlos. Si el cliente omite algo requerido, el servidor SHOULD responder con un InputRequiredResult nuevo en vez de un error — los autores consideraron un código de error dedicado y lo rechazaron, razonando que el cliente puede no tener información suficiente para recuperarse, mientras que volver a preguntar siempre funciona.
Estado sin almacenamiento: dos usos más allá de preguntar
La parte sutil de MRTR es que inputRequests es opcional. Un servidor puede devolver un InputRequiredResult con solo requestState, en cuyo caso el cliente MAY reintentar de inmediato sin preguntarle a nadie. Eso habilita dos patrones que no tienen nada que ver con input del usuario.
El primero son los rolling upgrades. Supongamos que el build viejo de una tool pedía github_login y google_login, y el nuevo pide github_login y microsoft_login. Si la primera ronda cae en una instancia vieja y el reintento en una nueva, la instancia nueva ve una respuesta que necesita y otra que no, y todavía le falta una. Puede emitir un request nuevo por la pieza faltante mientras dobla la respuesta ya recolectada de github_login dentro de requestState — así al usuario nunca se le pregunta dos veces lo mismo durante un deploy.
El segundo es el load shedding. Una instancia sobrecargada que ya hizo trabajo significativo sobre una llamada puede serializar su progreso acumulado en requestState, devolverlo sin input requests, y dejar que el reintento inmediato del cliente aterrice en otra instancia que retoma donde quedó la primera. El protocolo le dio a los servidores una forma de migrar un cómputo en vuelo a través del cliente.
Dónde toma la posta Tasks
MRTR optimiza el caso efímero; no pretende cubrir el persistente. Cuando un servidor realmente necesita seguir trabajando en background mientras espera, el flujo pasa a la extensión Tasks, usando las mismas estructuras de datos. La task entra en estado input_required, el cliente lo descubre haciendo polling de tasks/get, recupera los inputRequests y responde por un método dedicado en vez de reintentar la llamada original. Como la task sostiene estado en el servidor, la operación original nunca se termina — el servidor simplemente retoma.
Vale internalizar una regla de transición: una tool puede arrancar efímera y volverse task, juntando el input que necesita vía MRTR antes de comprometerse a cualquier almacenamiento del lado del servidor. No puede ir al revés. "Una vez que la implementación de una tool devuelve una task, se comprometió a almacenar estado del lado del servidor durante toda la task, y no hay forma de volver al modelo efímero."
Debajo de ambos está SEP-2260, del MCP Transports Working Group, que subió un SHOULD a MUST: los requests server-to-client deben estar asociados a un request originante del cliente. Sampling, elicitation o roots iniciados de forma autónoma en un stream independiente MUST NOT implementarse, con ping como única excepción. Los clientes que reciban uno igual SHOULD responder -32602. La consecuencia de cara al usuario vale decirla directo: un servidor ya no puede interrumpirte de la nada. Todo prompt se rastrea a algo que iniciaste tú o tu agente.
Reconstruido y enterrado en la misma revisión
Ahora la parte extraña. Abre la página de sampling de la especificación 2026-07-28 y la vas a encontrar completamente reescrita para MRTR — cada ejemplo replanteado como un input request entregado dentro de InputRequiredResult, con loop de tools multi-turno, modos de toolChoice y reglas detalladas de compatibilidad cross-provider para las formas de mensaje de Claude, OpenAI y Gemini. Justo arriba de todo eso hay un aviso de deprecación.
SEP-2577, creado el 14 de abril de 2026 por Kurtis Van Gent, deprecia roots, sampling y logging juntos. El criterio declarado son las features con "la peor relación adopción-complejidad". Para sampling en particular: implementarlo bien exige aprobación con humano en el loop, lógica de selección de modelo, manejo de seguridad y — desde SEP-1577 — soporte de tool loop, y la matriz de soporte de features muestra que pocos clientes lo adoptaron pese a estar disponible desde la spec de noviembre de 2024.
Los maintainers consideraron explícitamente mover estas features a extensiones y lo rechazaron. Bajo el framework de extensiones, las implementaciones deben comportarse como si una extensión ausente no existiera; retrofitear esa lógica en los SDKs existentes a través de múltiples versiones del protocolo se juzgó "complejo y propenso a errores". Deprecar y luego remover era la ruta menos disruptiva.
Nada se rompe todavía. No se eliminan tipos, la negociación de capabilities no cambia y el comportamiento de cable es idéntico — el cambio son anotaciones @deprecated en el schema y bloques de advertencia en la documentación. El registro de features deprecadas, un artefacto nuevo de la política de ciclo de vida SEP-2596, fija la fecha con precisión: roots, sampling, logging y Dynamic Client Registration quedan elegibles para remoción en la primera revisión publicada en o después del 2027-07-28.
El registro también fija la ruta de migración de cada una. Roots: pasar directorios vía parámetros de tool, URIs de resource o configuración del servidor. Logging: stderr para transportes stdio, OpenTelemetry para observabilidad. Sampling recibe seis palabras — "Integrar directamente con APIs de proveedores de LLM."
Seis palabras que mueven la factura
Lee la descripción que la propia página de sampling hace de para qué servía: permitía a los clientes "mantener control sobre el acceso, la selección y los permisos del modelo mientras habilita a los servidores a aprovechar capacidades de IA — sin necesidad de API keys del servidor".
Esa cláusula es todo el contenido económico de sampling. Un servidor que quería un modelo no necesitaba cuenta con el proveedor, ni tarjeta, ni medir nada. Tomaba prestado el modelo del cliente, y el usuario del cliente pagaba — normalmente sin ver un renglón, porque los tokens desaparecían dentro de su suscripción existente.
Depreciar sampling revierte eso, y la ruta de migración oficial lo dice directo. Todo servidor MCP que quiera inferencia debe ahora sostener sus propias credenciales de proveedor y financiar su propio gasto de tokens. El argumento de seguridad del cambio es sólido — SEP-2577 llama a sampling "el más sensible en seguridad de los tres", creando superficie de ataque para prompt injection y exfiltración de datos, y es difícil argumentar que dejar a un servidor arbitrario manejar tu modelo con tus credenciales fuera alguna vez cómodo. Pero el costo no desaparece. Se reubica, de la suscripción del cliente al balance del servidor.
Elicitation es la sobreviviente, y URL mode es donde se mueve el dinero
De los tres tipos de request que MRTR puede cargar, solo elicitation no está deprecado. Es ahora el único canal sancionado por el protocolo para que un servidor pida algo a mitad de llamada — y tiene dos modos con propiedades de confianza muy distintas.
Form mode recolecta datos estructurados a través del cliente, restringido a objetos planos con propiedades primitivas. Carga una prohibición dura: los servidores MUST NOT usar form mode para pedir contraseñas, API keys, access tokens o credenciales de pago, y MUST usar URL mode para cualquier cosa de esa clase.
URL mode manda al usuario fuera de banda a una página que el servidor controla. El encuadre que la spec hace de su propósito es inusualmente directo: el mismo request "podría dirigir al usuario a un flujo de autorización OAuth, o a un flujo de pago. La única diferencia es la URL y el mensaje". Los datos que no sean la URL nunca quedan expuestos al cliente, lo que significa que las credenciales nunca transitan el contexto del LLM, el cliente MCP ni ningún servidor intermedio. Una respuesta action: "accept" significa que el usuario consintió abrir el enlace — no que la interacción terminó. El servidor determina la finalización desde el requestState devuelto o desde su propio almacenamiento, y retorna el resultado final u otro InputRequiredResult.
Los requisitos de seguridad alrededor son estrictos, y en su mayoría son sobre un ataque específico. Como la URL es reenviable por un atacante, un usuario malicioso puede disparar una elicitation y luego engañar a un segundo usuario del mismo servidor para que complete el flujo — atando los tokens de tercero de la víctima a la identidad del atacante, un account takeover. Por eso el servidor MUST verificar que el usuario que abre la URL sea el usuario para el que se generó la elicitation. Los clientes, por su parte, MUST NOT pre-fetchear la URL, MUST NOT abrirla sin consentimiento explícito, MUST mostrar la URL completa y MUST abrirla de una forma que impida al cliente o al LLM inspeccionar el contenido o lo que el usuario escribe.
Qué significa para LLM4Agents
La deprecación de sampling es, leída en clave económica, una señal de demanda para exactamente la capa que operamos.
Un servidor MCP que necesita un modelo ahora necesita cuenta, key, presupuesto e historia de rotación — por proveedor. Para un SaaS operado por humanos eso es una tarde de martes de papeleo. Para un servidor autónomo, o para la larga cola de servidores chicos en el registry, es una barrera real: los flujos de signup asumen un humano con tarjeta. Esa es precisamente la brecha que cierra el walk-up de x402. Un endpoint compatible con OpenAI que acepta un pago en stablecoin por request permite a un servidor MCP comprar inferencia sin cuenta y sin onboarding — el árbol de decisión que publicamos antes tiene ahora una población mucho mayor en la rama de walk-up que hace un mes.
MRTR también cambia qué es una unidad facturable. Una operación lógica que antes era un tools/call es ahora potencialmente tres requests HTTP independientes con tres ids JSON-RPC distintos, separados por una pausa humana sin cota. Cualquier gateway que mida "por tool call" va a facturar mal en ambas direcciones: va a subcontar, porque las rondas dos y tres consumen verificación, decodificación y cómputo parcial; y va a sobrecobrar a quienes esperan un precio por operación. El modelo correcto es medir por round trip y tratar una respuesta input_required como una unidad de trabajo completada y facturable — lo que mapea limpio sobre el ciclo reserve-then-settle, con la reserva liberada y el settle escrito en cada ronda en vez de sostenido a través de una espera que puede durar meses.
Tercero, requestState cae de nuestro lado de la frontera de confianza. Un gateway que fronte servidores MCP es el lugar natural para hacer cumplir lo que la spec apenas recomienda: binding al principal, TTLs cortos y consumo a lo sumo una vez para cualquier estado que habilite un pago. La propia admisión de la spec — que su guía de replay no garantiza un solo uso — es una invitación a poner ese invariante en algún lado durable.
Por último, URL mode elicitation es una superficie de aprobación de pago bendecida por la spec. Cuando un tool call necesita que un humano autorice gasto, el protocolo ahora dice: no lo pongas en un formulario, mándalo a una URL, y asegúrate de que quien llega sea quien pidió. Eso es una página de confirmación de pago, descrita en lenguaje normativo, al lado de la capa de interfaz que MCP Apps abrió la semana pasada.
Cómo mantenerse en la frontera
En concreto, y en orden:
Primero, un medidor consciente de MRTR. Antes que nada, la facturación debe contar round trips y no operaciones lógicas, y registrar una respuesta input_required como trabajo liquidado. Todo lo que sigue depende de que la contabilidad esté bien.
Apuntar a los servidores que migran desde sampling. La población es identificable hoy: cualquier servidor MCP que declare la capability de sampling tiene una fecha límite en julio de 2027 y una instrucción de migración de una línea que apunta a una API de proveedor. Un camino documentado de sampling/createMessage a una llamada compatible con OpenAI — mismas formas de mensaje, mismo tool loop, misma semántica de toolChoice que la spec ya documenta para compatibilidad cross-provider — es una guía de integración corta y de alto valor.
Endurecer el manejo de requestState en nuestro propio servidor MCP. AEAD sobre el blob, el principal autenticado adentro, un TTL medido en minutos, un digest del método y los parámetros originantes, y un ledger de un solo uso del lado del servidor para todo estado que autorice gasto. Tratarlo con la misma disciplina que un nonce de EIP-3009, porque hace el mismo trabajo.
Cablear URL mode elicitation a la aprobación de walk-up x402. Cuando una llamada requiere firma humana sobre un pago, devolver un request en URL mode apuntando a una página de consentimiento atada a la sesión autenticada, y verificar identidad del otro lado antes de aceptar nada.
Probar contra las betas ahora, no en 2027. Los SDKs Tier 1 pasaron a beta el 29 de junio de 2026 — Python mcp v2.0.0b1, TypeScript v2, Go v1.7.0-pre.1, C# v2.0.0-preview.1 — y los releases estables salieron con la especificación, con Rust en beta. El breaking change ya está en los SDKs; una suite de conformidad que ejercite flujos multi-ronda contra cada uno es seguro barato.
Después, planear para la remoción, no solo para la deprecación. El 28 de julio de 2027 es una fecha real en un registro publicado. Todo camino de código en la plataforma que lea roots, emita mensajes de log de protocolo o haga proxy de un request de sampling debería inventariarse ahora y agendarse, para que la revisión de remoción sea un no-evento.
El protocolo dedicó esta revisión a hacer los servidores más baratos de operar y menos confiables con tu modelo. Ambas mitades apuntan en la misma dirección: los servidores consiguen su propia inferencia, su propio presupuesto y su propia factura. Esa factura necesita dónde aterrizar.
Inferencia que tu servidor MCP puede pagarse solo
Gateway compatible con OpenAI, settlement en stablecoin por request, sin cuenta.
Registrar un agente