← Blog
28 de julio, 2026 · 12 min

MCP Tasks: cómo esperan los agentes — la extensión async, a fondo

Un tool call que termina en dos segundos necesita un request y una respuesta. Un tool call que renderiza un video, crawlea diez mil páginas o espera una aprobación humana necesita otra cosa: un handle, un estado y una forma de volver más tarde. La spec de MCP que se finaliza hoy trae ese primitivo — pero no donde vivía antes.

Hoy, 28 de julio de 2026, es el día en que la próxima revisión de la especificación del Model Context Protocol está programada para volverse final, cerrando la ventana de validación de diez semanas que se abrió cuando el release candidate se congeló el 21 de mayo. Cubrimos el cambio principal — el core stateless — cuando salió el RC, y la extensión de autorización enterprise ayer. Esta pieza es sobre la tercera pata del release, y la más directamente relevante para agentes que hacen trabajo real: Tasks, la respuesta del protocolo a las operaciones de larga duración.

Tasks tiene una trayectoria inusual. Salió como feature experimental del core en la revisión 2025-11-25. El uso en producción reveló tanto rediseño pendiente que los maintainers lo sacaron por completo de la especificación y lo reconstruyeron como extensión oficial — SEP-2663, mergeado el 15 de mayo de 2026, viviendo ahora en su propio repositorio ext-tasks bajo el identificador io.modelcontextprotocol/tasks. Esa degradación no es un retroceso. Bajo el framework de extensiones que introduce este release (SEP-2133), las extensiones tienen identificadores reverse-DNS, repositorios dedicados, maintainers delegados y versionado independiente — Tasks ahora puede evolucionar con feedback de implementación sin esperar al tren de releases de la spec. Quien haya construido contra el API experimental de 2025-11-25, eso sí, tiene una migración por delante: el método bloqueante tasks/result desapareció, tasks/list desapareció, y el ciclo de vida se rediseñó alrededor del mismo principio que rediseñó el core — sin sesiones.

Por qué async es el problema del agente, no un nice-to-have

El request/response síncrono encaja mal con las cargas de trabajo de agentes de una forma específica y estructural. Una conexión HTTP mantenida abierta es un recurso en ambos extremos y en cada proxy intermedio. Los timeouts del gateway, del load balancer y del cliente imponen cada uno su propio techo, y el límite efectivo es el mínimo de todos — normalmente bastante por debajo de dos minutos. El trabajo real de un agente excede eso con frecuencia: generación de media, crawls grandes, inferencia batch, pipelines multi-paso y cualquier cosa que se pause por una decisión humana.

Los workarounds pre-Tasks son familiares y todos malos a su manera. Los servidores streamean notificaciones de progreso sobre una conexión que igual no puede sobrevivir a la paciencia de la infraestructura. Los autores de tools inventan patrones de jobs ad-hoc — un tool start_job que devuelve un job ID y un tool check_job para pollearlo — que funciona pero es invisible para el protocolo: sin vocabulario estándar de estados, sin cancelación estándar, nada sobre lo que un cliente o un gateway pueda razonar genéricamente. Tasks estandariza exactamente ese patrón, en la capa de protocolo, con semántica definida para cada paso.

Creación dirigida por el servidor: el servidor decide qué se vuelve tarea

La primera decisión de diseño que vale la pena notar es quién inicia. En la nueva extensión, la creación de tareas es dirigida por el servidor. El cliente no pide una tarea; señala que puede manejarla, incluyendo io.modelcontextprotocol/tasks en las extensiones que anuncia en las capabilities per-request de _meta — el mismo sobre que ahora transporta versión de protocolo e identidad del cliente desde que el handshake initialize desapareció. El servidor entonces decide, por request y a su propia discreción, si responde de forma síncrona o materializa una tarea. La spec es directa: el servidor MAY devolver un CreateTaskResult en lugar de un resultado estándar, y el servidor es el único que decide. Si el servidor necesita la capability y el cliente no la anunció, la llamada falla con el error -32003 y un campo requiredCapabilities nombrando la extensión.

Es la asignación correcta del conocimiento. Solo el servidor sabe si este tools/call en particular va a tomar 800 milisegundos o 40 minutos — y puede no saberlo hasta haber empezado. La creación dirigida por el servidor permite que el mismo tool responda inputs pequeños de forma síncrona e inputs grandes de forma asíncrona, sin que el cliente adivine por adelantado. Por ahora tools/call es el único método que soporta ejecución task-augmented; la spec deja explícitamente la puerta abierta a extenderlo a otros tipos de request.

Cuando el servidor sí elige una tarea, la respuesta es un CreateTaskResult discriminado por resultType: "task":

{
  "resultType": "task",
  "task": {
    "taskId": "7f3d2a9c46e1b8...",
    "status": "working",
    "createdAt": "2026-07-28T09:00:00Z",
    "ttlMs": 86400000,
    "pollIntervalMs": 5000
  }
}

Una línea normativa carga aquí con la mayor parte del peso de confiabilidad: un servidor MUST NOT devolver CreateTaskResult hasta que la tarea esté creada de forma durable — hasta que un tasks/get para ese taskId ya resolvería. No hay ventana en la que el cliente sostenga un handle hacia la nada. Si tu task store es Postgres o Redis, el write commitea antes de que salga la respuesta. Es la misma disciplina que los facilitators de x402 aplican a los registros de settlement, y es lo que hace que el handle sea seguro de persistir y retomar tras un crash — cosa que la spec dice que los clientes SHOULD hacer.

Cinco estados, una máquina

El ciclo de vida es una máquina de cinco estados. Dos son no terminales: working e input_required. Tres son terminales e irreversibles: completed, failed y cancelled. Toda tarea empieza en working.

La compresión de ese primer estado es deliberada. Ya sea que el job esté ejecutándose activamente o esperando en una cola detrás de cien más, el cliente ve working. La posición en la cola, la asignación de workers y los internals de retry son asunto del servidor; el campo opcional statusMessage existe para color legible por humanos, pero el vocabulario de estados se mantiene mínimo para que cada cliente y cada gateway implementen el mismo loop. En completed, el result de la tarea contiene exactamente lo que el request original habría devuelto de forma síncrona — el código downstream del caller no cambia. En failed, error transporta el error JSON-RPC.

El lado del cliente en el contrato es el polling. Llama a tasks/get con el taskId, respetando el pollIntervalMs sugerido por el servidor, hasta que llegue un estado terminal. Lecturas y escrituras están deliberadamente separadas en dos métodos — tasks/get nunca muta, lo que lo mantiene idempotente, seguro de reintentar y cacheable — mientras tasks/update transporta todo lo que cambia estado. Para despliegues que prefieren push en vez de poll, los servidores pueden emitir notifications/tasks a clientes que optaron via subscriptions/listen, cada notificación con el mismo estado completo de la tarea que devolvería un tasks/get. Las notificaciones son una optimización, no un reemplazo: el loop de polling es la base que funciona a través de cualquier intermediario.

La retención se gobierna con ttlMs — time-to-live desde la creación, mutable durante la vida de la tarea, null significa ilimitado. La semántica es honesta sobre lo que es un TTL: un backstop, no una promesa. Pasado el TTL, el servidor MAY marcar la tarea como failed y luego borrarla por completo, punto en el cual tasks/get devuelve -32602 como si la tarea nunca hubiera existido. Un cliente que deja de pollear por un día debería tratar un TTL expirado como un handle que quedó obsoleto.

input_required: elicitation sin conexión viva

El estado más interesante es input_required, porque resuelve un problema que el modelo viejo no podía: ¿cómo le pregunta un servidor algo al cliente en medio de un job cuando no hay sesión y posiblemente no hay conexión abierta?

La respuesta es hacer que las preguntas sean parte del estado de la tarea. Cuando el servidor necesita input, la tarea transiciona a input_required y la respuesta de tasks/get incorpora un mapa inputRequests — entradas con clave, cada una con el método y los params de lo que de otro modo sería un request standalone servidor-a-cliente, siendo una elicitation el caso canónico. El cliente responde a través de tasks/update, entregando un mapa inputResponses con claves coincidentes. El servidor MAY aceptar un set parcial de respuestas; la tarea permanece en input_required hasta que llegue todo lo pendiente, y entonces típicamente vuelve a working.

Las claves hacen trabajo real. Cada clave MUST ser única durante la vida de la tarea, un servidor MUST NOT reusar una clave después de que su respuesta fue entregada, y los clientes SHOULD trackear claves para no responder dos veces — lo que convierte las claves en tokens de idempotencia. Un tasks/update reintentado no puede duplicar una aprobación, por la misma razón que un nonce EIP-3009 reusado no puede duplicar el gasto de una autorización. Quien haya construido rails de pago reconoce la forma.

La spec también cierra explícitamente un agujero de confianza: una elicitation que aparece via inputRequests MUST ser tratada por el host exactamente como un elicitation/create directo — mismo modelo de confianza, mismo comportamiento de cara al usuario. Un servidor no debe poder contrabandear un prompt de menor escrutinio hacia el usuario solo porque llegó dentro del sobre de una tarea en vez de como request en vivo.

La cancelación es un pedido, no una orden

La cancelación es cooperativa por diseño. tasks/cancel señala intención; el servidor la reconoce con un resultado vacío y decide si la honra y cuándo. La transición eventual a cancelled no está garantizada — el trabajo puede completarse o fallar antes, y una tarea que ya pagó sus efectos secundarios puede estar más allá del punto de detenerse. Los clientes MAY descartar su estado local en el momento en que envían la cancelación y seguir adelante.

Eso suena débil hasta que consideras lo que costaría una garantía de hard-cancel: cada servidor necesitaría workers preemptibles y rollback transaccional de efectos secundarios arbitrarios. El protocolo en cambio dice la verdad sobre el trabajo distribuido — puedes dejar de prestar atención, pero no siempre puedes detener el mundo. Las implicaciones de facturación se siguen directamente, y volvemos a ellas más abajo.

Qué se borró, y por qué los borrados son el diseño

Contra el API experimental de 2025-11-25, destacan tres remociones. tasks/result — la llamada bloqueante que estacionaba una conexión hasta que la tarea terminara — desapareció; reintroducía por la puerta trasera exactamente la conexión de larga vida que Tasks existe para eliminar. El parámetro de opt-in per-request desapareció a favor del anuncio de capability más la discreción del servidor. Y tasks/list desapareció por una razón que vale la pena citar completa: sin sesiones, no hay forma segura de acotar "¿las tareas de quién?". Un endpoint de listado necesita un dueño, el protocolo ya no tiene una sesión para definirlo, y antes que shippear una superficie de enumeración con semántica de autorización difusa, la extensión lo borró. El descubrimiento de tareas ahora es trabajo del cliente: persiste tus taskIds de forma durable, porque nadie te los va a recordar.

Ese borrado apunta al filo más agudo de la extensión. Un taskId es, en palabras de la propia spec, usable como bearer token: los servidores MUST generar IDs con suficiente entropía como para que un tercero no pueda enumerarlos ni adivinarlos. Pero más allá de la entropía, la especificación no vincula una tarea a la identidad que la creó. Si el token presentado en tasks/get debe coincidir con el que creó la tarea queda a criterio de la implementación. Un despliegue serio debería vincular tareas al principal autenticado — el subject OAuth de la capa de autorización, o la API key en el gateway — y tratar la entropía como defensa en profundidad, no como el modelo de control de acceso.

Estado de implementación — los cuatro SDKs Tier 1 publicaron betas para la revisión 2026-07-28 el 29 de junio: Python mcp v2.0.0b1, TypeScript v2 (dividido en @modelcontextprotocol/server y @modelcontextprotocol/client), Go v1.7.0-pre.1 y C# v2.0.0-preview.1, según el post oficial de betas de SDKs. Las betas cubren el core stateless y los multi round-trip requests; como extensión, Tasks versiona de forma independiente — verifica el soporte de extensiones de cada SDK antes de depender de él.

Qué significa para LLM4Agents

LLM4Agents opera un gateway OpenAI-compatible donde los agentes pagan por llamada en stablecoins, y expone su propio servidor MCP con familias de tools que son exactamente las cargas de trabajo para las que se construyó Tasks: generación de video y media, ingesta de workspace, inferencia batch. Hoy esos tools de larga duración o estiran timeouts síncronos o usan job tools ad-hoc. La extensión Tasks le da a ese patrón una forma estándar, y se siguen tres consecuencias.

Primero, la pregunta de facturación se vuelve más filosa y mejor a la vez. Nuestro modelo de settlement reserva fondos al momento del request y liquida por el uso real — la misma forma que el scheme upto de x402, donde verify autoriza un techo y settle cobra el monto real. Una llamada con forma de tarea mapea limpio sobre eso: reservar en el CreateTaskResult, liquidar en el estado terminal. completed liquida por uso; failed a nivel de protocolo libera la reserva; y la cancelación cooperativa es precisamente la razón por la que reserve-then-settle es el primitivo correcto — un tasks/cancel después de gastar las horas de GPU igual liquida por el trabajo hecho, y la honestidad del protocolo al respecto le gana a un modelo de facturación que finge que cancelar es gratis.

Segundo, el MUST de durabilidad es un regalo para un gateway de facturación. Como una tarea debe estar creada de forma durable antes de devolver el handle, el registro de la tarea y la reserva de pago pueden commitear en la misma transacción. Cada taskId que le entregamos a un agente está respaldado por una fila de job y una retención de fondos — sin cargos huérfanos, sin jobs fantasma.

Tercero, el hueco de identidad es nuestro para cerrar. La spec se detiene en la entropía; un gateway que autentica a cada caller puede hacerlo mejor vinculando cada tarea a la identidad del agente que paga y rechazando tasks/get de cualquier otro. La identidad anclada en pagos — el tema que corre desde nuestra auditoría de ERC-8004 en adelante — aplica también aquí: la entidad que pagó por la tarea es la dueña natural de su handle.

Cómo mantenerse en la frontera

Secuencia concreta, en orden. Uno: adoptar la extensión del lado servidor — implementar tasks/get, tasks/update y tasks/cancel en el servidor MCP de LLM4Agents, y migrar las familias de tools de larga duración (video, batch, ingesta) a creación de tareas dirigida por el servidor, manteniendo respuestas síncronas para inputs pequeños. Dos: cablear el ciclo de vida a la facturación — reservar en la creación de la tarea en la misma transacción que el write durable, liquidar en el estado terminal, liberar en fallo a nivel de protocolo, y documentar la política de la-cancelación-liquida-el-trabajo-hecho antes de la primera disputa, no después. Tres: vincular tareas a la identidad del caller en el gateway y acotar cada método de tasks al principal creador, tratando la entropía del ID como defensa secundaria. Cuatro: exponer un índice de tareas acotado para nuestros propios callers — la spec borró tasks/list porque no tiene sesión con la que acotar, pero un gateway que autentica agentes puede ofrecer "mis tareas" con seguridad, como valor agregado que el protocolo crudo no puede dar. Cinco: seguir la cadencia de releases independiente de la extensión y el soporte de extensiones en los SDKs, y pinear versiones — una extensión que itera más rápido que la spec es el punto, y también el riesgo operativo.

El hilo conductor del release 2026-07-28 es que MCP dejó de asumir una conversación y empezó a asumir infraestructura. El core stateless hizo los requests ruteables; Tasks hace el trabajo durable. Para agentes que pagan por lo que consumen, esa segunda propiedad es la que importa: un job del que puedes sostener un handle es un job que puedes medir, liquidar y probar. Los protocolos están convergiendo en lo que los rails de pago aprendieron hace tiempo — el recibo es el producto.

Dale a tus agentes trabajo que sobreviva al request

Un gateway, 345+ modelos, facturación en stablecoins por llamada — construido para el stack async de agentes.

Registra tu agente