Задачи
Neva поддерживает долгосрочные задачи — способ асинхронного вызова инструментов с управлением их жизненным циклом. Задачи позволяют клиентам выполнять инструменты, которые могут занимать много времени или требуют дополнительных взаимодействий, с опциональной отменой по истечении TTL.
В MCP 2026-07-28 Tasks — это расширение
(modelcontextprotocol/ext-tasks),
объявляемое через capabilities.extensions["io.modelcontextprotocol/tasks"].
Neva регистрирует его через трейт Extension, а with_tasks() — тонкая
обёртка над этой регистрацией.
Включение задач на сервере
Используйте with_tasks() для включения поддержки задач:
use neva::prelude::*;
fn main() {
App::new()
.with_options(|opt| opt
.with_default_http()
.with_tasks())
.run_blocking();
}
Возможность расширения представлена пустым объектом — само её объявление
и есть декларация, — поэтому with_tasks() не принимает замыкание.
legacy-specДействует поверхность 2025-11-25: поддерево возможностей
cancel / list / requests, настраиваемое через
with_tasks(|t| t.with_all()), а также tasks/list, tasks/result и задачи
на стороне клиента. См. Легаси-спецификация.
Объявление инструмента с поддержкой задач
Пометьте инструмент как задачу, указав task_support = "required" в атрибутном макросе #[tool]:
#[tool(task_support = "required")]
async fn endless_tool() {
loop {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
}
Инструмент, помеченный task_support = "required", должен вызываться как задача (через client.task().call_tool() на стороне клиента). Вызов его как обычного инструмента будет отклонён.
Методы задач
| Метод | Что делает |
|---|---|
tasks/get | Единственный метод опроса. Возвращает DetailedTask: статус и, в зависимости от него, незакрытые inputRequests, финальный result либо error |
tasks/update | Клиент отвечает на input-запросы задачи, опираясь на то, что показал tasks/get |
tasks/cancel | Подтверждает пустым результатом — отмена кооперативная, поэтому итог узнаётся опросом |
tasks/list и tasks/result отсутствуют. Идентификатор задачи — это
долговременный дескриптор, который у запросившей стороны уже есть, поэтому
перечисление — её собственная задача.
CreateTaskResult теперь плоский (Result & Task) и несёт
resultType: "task" — поля задачи лежат на верхнем уровне, а не во
вложенном объекте task. В протоколе Task::ttl сериализуется как ttlMs
(теперь Option<usize> — под допустимый по схеме случай «без ограничения»),
а poll_interval — как pollIntervalMs. Уведомление о статусе называется
notifications/tasks.
Каждый метод задач также несёт params.taskId в заголовке маршрутизации
Mcp-Name, чтобы промежуточный узел мог направить вызовы задачи на тот
экземпляр, где хранится её состояние.
Комбинирование задач с получением данных
Инструмент с поддержкой задач может дождаться ввода пользователя прямо во
время выполнения — через ctx.task():
#[tool(task_support = "required")]
async fn tool_with_elicitation(mut ctx: Context, task: Meta<RelatedTaskMetadata>) -> String {
let params = ElicitRequestParams::form("Are you sure to proceed?")
.with_related_task(task);
// Задача не перезапускается — она действительно приостанавливается.
// Поэтому, в отличие от MRTR-вызова `ctx.elicit(key, params)`,
// replay-ключ здесь не нужен.
let res = ctx.task().elicit(params.into()).await;
format!("{:?}", res.unwrap().action)
}
Meta<RelatedTaskMetadata> несёт контекст задачи, автоматически внедряемый фреймворком. Он передаётся в with_related_task(), чтобы клиент мог связать запрос на получение данных с выполняющейся задачей.
В MCP 2026-07-28 нет сэмплирования, расширенного задачей. Сэмплирование лишилось серверного запроса к клиенту и теперь живёт на подложке MRTR, которая никогда не смешивается с подложкой задач: одна приостанавливается, другая перезапускается. Получение данных — единственный вид ввода, которого может дождаться задача.
Обучение на примерах
Полный пример доступен здесь.