Phase 13: Tools & Protocols

Extensión de las tareas del MCP: trabajo duradero en un núcleo sin estatus

El MCP sin estatus no significa que cada operación debe terminar en una sola solicitud. La extensión oficial de tareas da a los trabajos de larga duración un mango duradero explícito. Un servidor puede devolver ese mango desde tools/call, cualquier instancia puede responder .tasks/get, y la entrada del cliente llega a través de tasks/updateSin reanudar las sesiones de protocolo.

Type: Build

Languages: Python

Prerequisites: Phase 13 · 09 (transports), Phase 13 · 11 (stateless MRTR), Phase 13 · 12 (elicitation)

Time: ~90 minutes

Objetivos de aprendizaje

  • Distinguir el transporte de protocolos sin estado de origen del estado de tareas de aplicación duraderas.
  • Negociar el acuerdoio.modelcontextprotocol/tasksextensión de las capacidades por solicitud y server/discover¿ Qué ?
  • Regresa un mensaje dirigido al servidorCreateTaskResultconresultType: "task"sólo después de una creación duradera.
  • Encuesta con tasks/get, cumple las tareas introducidas con tasks/update, y solicitar la cancelación de la cooperación con tasks/cancel¿ Qué ?
  • Retira el viejo .tasks/status¿ Qué ?tasks/result, y tasks/listlas suposiciones.
  • Suscribirse a las notificaciones opcionales de tareas a través de subscriptions/listenen una corriente de respuesta POST SSE.
  • Expirado de tarea modelo, reinicio de recuperación, deduplicación de la clave de entrada y errores de ejecución correctamente.

Por qué las tareas son una extensión

Las tareas aparecieron por primera vez como una característica central experimental en 2025-11-25.io.modelcontextprotocol/tasksextensión para que los clientes y servidores puedan optar por el ciclo de vida extra sin expandir el protocolo principal para todos.

La especificación de extensión sigue siendo un borrador de superficie a pesar de que es el hogar oficial actual de tareas. Enlazar la versión de extensión compatible con su SDK, ejecutar escenarios de conformidad y aislar los adaptadores de cable de su dominio de trabajo y almacenamiento.

Utilice una tarea cuando la operación tenga una o más de estas propiedades:

  • Puede sobrevivir a un tiempo de espera normal.
  • Una cola de trabajadores o un sistema de trabajo externo ya posee la ejecución.
  • El cliente necesita recuperarse después de su propio reinicio.
  • La operación se detiene para la entrada del usuario o modelo durante la ejecución.
  • La cancelación y la recuperación de resultados duraderos son requisitos del producto.

No crea una tarea para una búsqueda determinista barata. Un manejo, persistencia, votación, vencimiento y cancelación son una complejidad real.

El núcleo de los apátridas, la aplicación estatal

MCP 2026-07-28 se elimina initialize¿ Qué ?notifications/initialized, sesiones de protocolo, y Mcp-Session-IdEso no prohíbe los productos de estado.

Una identificación de tarea es el estado de aplicación explícito:

  • El servidor lo insiste antes de devolverlo.
  • El cliente puede almacenarlo y volver a hacer una encuesta después de reiniciar.
  • La identificación puede ser enviada a cualquier réplica respaldada por la misma tienda duradera.
  • La autorización se verifica en cada método de tarea.
  • La expiración y eliminación se definen por campos de tareas, no por un período de vida del transporte.

Esto es operativamente diferente del estado oculto conectado a una conexión.

Mantenga cuatro vidas separadas:

StateLifetimeWhere it belongs
Protocol metadataOne requestparams._meta, validated again on every call
Transport workOne stdio request or HTTP responseIn-flight coordinator with a bounded deadline
MRTR continuationOne retry sequenceIntegrity-protected requestState, plus replay controls when needed
Durable taskAcross requests, replicas, restarts, and reconnectsShared application store keyed by an authorized taskId

El traslado de un registro de tareas a la memoria de proceso no hace que MCP sea estado.tasks/getPersiste antes de devolver el mango, luego haga que cada método de tarea resuelva el mismo registro compartido bajo los controles del inquilino y del principal.

Negociación de la capacidad

El cliente anuncia apoyo en cada solicitud elegible:

json{
  "_meta": {
    "io.modelcontextprotocol/protocolVersion": "2026-07-28",
    "io.modelcontextprotocol/clientCapabilities": {
      "extensions": {
        "io.modelcontextprotocol/tasks": {}
      }
    },
    "io.modelcontextprotocol/clientInfo": {
      "name": "lesson-client",
      "version": "1.0.0"
    }
  }
}

El servidor devuelve exactamente supportedVersions, capacidades,ttlMs, y cacheScopede laserver/discoverLa aplicación de las tecnologías de la información y de la información es una de las principales modalidades de aplicación de las tecnologías de la información y de la información.tools/listEse resultado devuelve un deterministagenerate_reportdescriptor, objeto válido inputSchema¿ Qué ?resultType: "complete", metadatos de identidad del servidor, y pistas de caché público.

Un método de tarea de un cliente que no declaró las devoluciones de extensión -32021, Falta de capacidad requerida para el cliente, con data.requiredCapabilitiesse fija en {"extensions":{"io.modelcontextprotocol/tasks":{}}}Una cadena de protocolo no soportada devuelve .-32022con exactitudsupportedy requesteddatos; una versión que no está disponible o no está en cadena se devuelve -32602¿ Qué ?

Un sobre sin un JSON-RPC idEl receptor puede procesarlo, pero no emite ningún resultado o error JSON-RPC.202 Acceptedsin organismo para una notificación aceptada.

En la actualidad, sólotools/callsoporta la ejecución de tareas aumentadas. Diseñe su abstracción interna para que los tipos de solicitudes futuros no requieran reescribir almacenamiento.

Creación de tareas dirigidas por servidores

La vieja bandera del cliente .params._meta.task.requiredEl cliente declara soporte de extensión, luego el servidor decide si un determinado tools/callse convierte en una tarea.

Solicitud:

json{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "generate_report",
    "arguments": {"size": "large"},
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {
        "extensions": {
          "io.modelcontextprotocol/tasks": {}
        }
      }
    }
  }
}

Respuesta:

json{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resultType": "task",
    "taskId": "tsk_786512e29e0d",
    "status": "working",
    "statusMessage": "Preparing report outline.",
    "createdAt": "2026-08-21T10:30:00Z",
    "lastUpdatedAt": "2026-08-21T10:30:00Z",
    "ttlMs": 900000,
    "pollIntervalMs": 1000
  }
}

El servidor no debe devolver este mango hasta que un tasks/getEn una tienda de almacenamiento consistente, espere a la visibilidad de lectura antes de responder. de lo contrario un cliente puede recibir un ID de aspecto válido y recibir inmediatamente "no se encuentra".

Una respuesta a la tarea no se solicita en el sentido de que el cliente no solicita el modo de tarea.

La forma de la tarea

Cada tarea lleva consigo:

  • taskId: identificador estable generado por el servidor;
  • status¿ Qué es esto ?working¿ Qué ?input_required¿ Qué ?completed¿ Qué ?cancelled, ofailedEl artículo 1
  • createdAty lastUpdatedAt: sello de tiempo ISO 8601;
  • ttlMs: duración de caducidad desde la creación, o nullsin límite anunciado;
  • opcional pollIntervalMs: la cadencia mínima de encuestas sugerida del servidor actual;
  • opcional statusMessage: contexto orientado al usuario o al modelo.

Los campos específicos de estado aparecen sólo cuando sean relevantes:

  • input_requiredincluye inputRequests¿ Qué ?
  • completedincluye la solicitud original result¿Qué forma tiene?
  • failedincluye un JSON-RPC errorObjeto.

El cliente debe honrar .pollIntervalMsUn servidor puede limitar las tasas de encuestas más agresivas y puede cambiar el intervalo durante la vida de la tarea.

Encuesta con tasks/get

El cliente pide una instantánea actual:

httpPOST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tasks/get
Mcp-Name: tsk_786512e29e0d
json{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tasks/get",
  "params": {
    "taskId": "tsk_786512e29e0d",
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {
        "extensions": {
          "io.modelcontextprotocol/tasks": {}
        }
      }
    }
  }
}

tasks/getEl resultado siempre ha sido el mismo.resultType: "complete"La tarea anida todavía puede tenerstatus: "working"o status: "input_required"¿ Qué ?

Esta distinción evita un error común del parser:

textresult.resultType = complete    means the tasks/get RPC finished
result.status = working        means the represented job is still running

No hay ninguna .tasks/resultCuando la tarea se complete, el siguiente tasks/getLa respuesta se enmarca en el original CallToolResulten elresult¿Qué es esto ?

json{
  "resultType": "complete",
  "taskId": "tsk_786512e29e0d",
  "status": "completed",
  "createdAt": "2026-08-21T10:30:00Z",
  "lastUpdatedAt": "2026-08-21T10:34:12Z",
  "ttlMs": 900000,
  "result": {
    "resultType": "complete",
    "content": [
      {"type": "text", "text": "Generated large report with approved outline."}
    ],
    "structuredContent": {"size": "large", "approved": true},
    "isError": false,
    "_meta": {
      "io.modelcontextprotocol/serverInfo": {
        "name": "tasks-demo",
        "version": "1.0.0"
      }
    }
  },
  "_meta": {
    "io.modelcontextprotocol/serverInfo": {
      "name": "tasks-demo",
      "version": "1.0.0"
    }
  }
}

El exterior .resultTypedice el tasks/getEl RPC completado.result.resultTypedice que la llamada de herramienta original se completó. ese discriminador anidado es necesario.CallToolResultTambién debe llevar su propio io.modelcontextprotocol/serverInfoEsta lección incluye en lugar de almacenar una carga útil no tipificada.

No hay ninguna .tasks/listLos servidores sin sesión no pueden inferir con seguridad qué tareas pertenecen a una lista escaneada por conexión. Las aplicaciones que necesitan historial deben exponer una herramienta de dominio autorizado con filtros explícitos y reglas de propiedad.

Ingreso durante la ejecución de tareas

La entrada de tarea y el MRTR central se parecen, pero utilizan continuidades diferentes.

Entrada necesaria antes de crear tareas

El núcleo de retorno resultType: "input_required"del original tools/callEl cliente lo cumple y vuelve a intentar la llamada original.

Entrada necesaria después de la creación de tareas

Establezca la tarea para input_required- ¿ Qué ?tasks/getexpone lo sobresaliente inputRequests, y el cliente envía respuestas a través de tasks/updateEl cliente no vuelve a intentar el original .tools/call¿ Qué ?

Instantánea:

json{
  "resultType": "complete",
  "taskId": "tsk_786512e29e0d",
  "status": "input_required",
  "createdAt": "2026-08-21T10:30:00Z",
  "lastUpdatedAt": "2026-08-21T10:31:00Z",
  "ttlMs": 900000,
  "inputRequests": {
    "approve_outline": {
      "method": "elicitation/create",
      "params": {
        "mode": "form",
        "message": "Approve the generated report outline?",
        "requestedSchema": {
          "type": "object",
          "properties": {"approved": {"type": "boolean"}},
          "required": ["approved"]
        }
      }
    }
  }
}

Actualización:

httpPOST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tasks/update
Mcp-Name: tsk_786512e29e0d
json{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tasks/update",
  "params": {
    "taskId": "tsk_786512e29e0d",
    "inputResponses": {
      "approve_outline": {
        "action": "accept",
        "content": {"approved": true}
      }
    },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {
        "extensions": {
          "io.modelcontextprotocol/tasks": {}
        }
      }
    }
  }
}

La respuesta de éxito es un reconocimiento vacío más resultType: "complete"El cambio de estado puede ser consistente, así que el cliente continúa haciendo encuestas o escuchando.

Cada uno .inputRequestsLa clave debe ser única durante toda la vida de la tarea.tasks/getLas imágenes instantáneas pueden mostrar la misma clave pendiente; los clientes deduplican la interfaz de usuario y los servidores ignoran las respuestas de las claves desconocidas, reemplazadas o ya cumplidas.input_requiredhasta que se contesten todas las llaves requeridas.

La cancelación es cooperativa

tasks/cancelEl trabajo puede terminar primero, ignorar la cancelación o la transición más tarde.

httpPOST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tasks/cancel
Mcp-Name: tsk_786512e29e0d
json{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tasks/cancel",
  "params": {
    "taskId": "tsk_786512e29e0d",
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {
        "extensions": {
          "io.modelcontextprotocol/tasks": {}
        }
      }
    }
  }
}

Para los tres métodos de tarea,Mcp-Nameespejosparams.taskId. No repite el nombre del método JSON-RPC. code/main.pycentraliza esta regla en make_http_request¿ Qué ?

El trabajador de la lección honra la cancelación inmediatamente, haciendo llamadas repetidas idempotentes.

No se utilice notifications/cancelledLa notificación pertenece a la solicitud de cancelación, no a tareas duraderas.

La distinción es importante en el límite de enrutamiento. La cancelación de solicitudes se dirige a una operación JSON-RPC en vuelo o a su respuesta HTTP escaneada por la solicitud.tools/callYa ha regresado .resultType: "task", que la solicitud está completa y que el cierre de su transporte no puede nombrar o detener el trabajo duradero. tasks/cancelEl nuevo RPC está autorizado.params.taskId, refleja esa identidad enMcp-Name, resuelve el backend de la tarea, registra la intención de cancelación de la cooperativa y devuelve un reconocimiento sin afirmar que el trabajador ha parado.

Por lo tanto, una puerta de entrada debe mantener los coordinadores de las solicitudes y las rutas de tareas en diferentes tablas. La tabla de las solicitudes puede desaparecer cuando finalice la respuesta. La ruta de tareas debe sobrevivir hasta que expire el estado terminal y la retención. Lesson 29: MCP Reliability, Cancellation, and Flow Controlconstruye la carrera, el tiempo de espera, la impotencia, la presión y retomar las reglas para ambos caminos.

Notificaciones opcionales

Las encuestas son la línea de base. Un cliente que quiere actualizaciones push envíasubscriptions/listenCon ID de tarea. Para Streamable HTTP, este es un POST cuya respuesta es un flujo de SSE escaneado por solicitud. No hay un flujo de eventos GET independiente y ninguna sesión de protocolo para mantenerlo vivo.

El servidor reconoce las identidades aceptadas con notifications/subscriptions/acknowledgedy luego puede enviar instantáneas completas a través de notifications/tasks. El reconocimiento y cada notificación de tareaio.modelcontextprotocol/subscriptionIdEn el_meta, igual al subscriptions/listenCada notificación de tarea es equivalente a lo que tasks/getregresaría en ese momento.

Los clientes deben declarar la extensión de tareas. Deben volver a conectarse y reanudar desde ID de tareas duraderas en lugar de depender de la repetición de eventos o Last-Event-ID¿ Qué ?

Semántica del fracaso

Utilice las dos capas de error correctamente.

Error de protocolo

Parámetros de método inválidos o un id de tarea desconocido devuelven un error JSON-RPC, comúnmente -32602. Falta de declaraciones de apoyo a la extensión -32021con el objeto de capacidad requerido.

Resultados de la ejecución de tareas

  • Un resultado normal de la herramienta con isError: truees todavía un completedla tarea porque la llamada de herramienta produjo su resultado definido.
  • Un error JSON-RPC durante la ejecución diferida hace la tarea failedy almacena ese error JSON-RPC en error¿ Qué ?
  • El rechazo del usuario puede producir cancelled, un resultado de rechazo completado, u otro resultado seguro específico del dominio.

Durabilidad, vencimiento y propiedad

Persiste al menos el ID de tarea, estado, timestamps, ttl, intervalo de encuestas, propiedad original de la operación, resultado o error, solicitudes de entrada pendientes y todas las entradas emitidas.

La clave de almacenamiento debe incluir o resolver un inquilino y un principal autorizado.tasks/get¿ Qué ?tasks/update¿ Qué ?tasks/cancel, y suscripción.

ttlMsEl cliente puede tratarlo como un respaldo cuando una tarea ha dejado de producir actualizaciones observables. Un servidor puede fallar y luego eliminar una tarea expirada. No lo describa como una promesa de conservar un resultado completado durante tantos milisegundos después de la finalización.

El curso escribe un archivo temporal y renombre automáticamente. Un servicio de réplica múltiple debe utilizar una tienda duradera compartida y un contrato de arrendamiento de trabajadores o control de concurrencia equivalente.

Construye el mismo

code/main.pyImplementa un servicio de tareas deterministas:

  • server/discoverretorno supportedVersions, las pistas de caché, y la extensión de tareas.
  • tools/listdevuelve un determinista, cachéable generate_reportDescriptor con un esquema de entrada válido.
  • tools/callcrea y persiste la tarea antes de regresar resultType: "task"¿ Qué ?
  • Una nueva instancia de servicio recarga la misma tarea, demostrando la recuperación de reinicio.
  • tasks/getdevuelve instantáneas completas de tareas.
  • El trabajador se mueve de working¿ Qué ?input_required¿ Qué ?
  • tasks/updateacepta una respuesta en el formulario y devuelve un reconocimiento completo vacío.
  • El trabajador almacena un nidoCallToolResultcon su propio resultTypey la identidad del servidor, luego las transiciones a completed¿ Qué ?
  • tasks/cancelEl Consejo de Ministros de la Unión Europea ha adoptado una decisión en el marco de la cual se ha adoptado una decisión.
  • Los conjuntos de constructor HTTP Mcp-Name¿ Qué ?params.taskIdportasks/get¿ Qué ?tasks/update, y tasks/cancel¿ Qué ?
  • Los asistentes de notificación utilizan notifications/subscriptions/acknowledgedy notifications/tasks, ambos etiquetados con la solicitud de escucha ID.
  • Las notificaciones sin ID no producen respuesta JSON-RPC.

El trabajador avanza explícitamente en lugar de dormir en un hilo de fondo. Eso hace que cada transición de estado sea determinista y mantiene el ejemplo de protocolo separado de la mecánica de cola.

Usalo

Desde la raíz del repositorio:

bashcd phases/13-tools-and-protocols/13-mcp-async-tasks/code
python3 main.py
python3 -m unittest discover tests -v

Secuencia de resultados esperada:

textid=0 resultType=complete status=ack
id=1 resultType=task status=working
id=2 resultType=complete status=working
id=3 resultType=complete status=input_required
id=4 resultType=complete status=ack
id=5 resultType=complete status=completed

También verifique quetasks/status¿ Qué ?tasks/result, y tasks/listmétodo de devolución no encontrado en el servicio moderno.

Verifique eso .tools/listes determinista y cada método de tarea HTTP actual refleja su ID de tarea a través de Mcp-Name¿ Qué ?

Envío

outputs/skill-task-store-designer.mdAhora produce un diseño consciente de la extensión: negociación de capacidad, creación duradera antes de regreso, métodos actuales, flujo de actualización de entrada, propiedad, vencimiento, cancelación, suscripción y migración de los métodos experimentales eliminados.

Los ejercicios

  1. Añadir una segunda clave de entrada pendiente. Envía una parcial tasks/updatey demostrar que la tarea sigue .input_requiredHasta que se respondan las dos llaves.
  2. Añadir la propiedad del inquilino a la tienda y rechazar un ID de tarea válido presentado por el principal autenticado incorrecto.
  3. Añadir un contrato de arrendamiento de trabajadores con vencimiento. Demostrar que dos instancias de servicio no pueden completar la misma tarea simultáneamente.
  4. Implementar un adaptador SSE de respuesta POST para subscriptions/listenNo agregue GET,Last-Event-ID, o un encabezado de sesión.
  5. Añadir limpieza de vencimiento. Distinguir una tarea vencida de una identificación de tarea malformada sin filtración de existencia entre los inquilinos.

Términos clave

TermMeaning in the current extension
Tasks extensionOptional io.modelcontextprotocol/tasks capability for durable async work
CreateTaskResultServer-directed resultType: "task" response to an eligible request
tasks/getPoll a full current task snapshot, including terminal result or pending input
tasks/updateSubmit responses to a task's outstanding inputRequests
tasks/cancelAcknowledge cooperative cancellation intent
input_requiredTask status indicating client input is outstanding
pollIntervalMsServer-suggested minimum delay before another poll
ttlMsExpiry duration measured from task creation
Durable-before-returnRule that the task id must resolve before its handle is sent
notifications/tasksOptional full task snapshot delivered on a subscribed SSE response

Compatibilidad con el legado

La superficie experimental 2025-11-25 utilizó el aumento de tareas solicitadas por el cliente, tasks/status¿ Qué ?tasks/result, y opcionales tasks/listUn cliente actual utiliza la capacidad de extensión, acepta manipulaciones dirigidas por servidores, encuestas tasks/get, suministra entrada con tasks/update, y lee el resultado final de la foto de tarea.

Leer más

This free lesson is part of the AI Engineering from Scratch curriculum. Read the full explanation, run the lesson code, and verify the result in the interactive reader or from the repository source.

Browse the complete course catalog or open this lesson on GitHub.