Skip to main content

Tasks

Neva clients support long-running tasks — an extended way to call tools asynchronously with optional TTL-based cancellation and lifecycle management.

Enabling Tasks on the Client

Use with_tasks() to enable task support:

use std::time::Duration;
use neva::prelude::*;

#[tokio::main]
async fn main() -> Result<(), Error> {
let mut client = Client::new()
.with_options(|opt| opt
.with_tasks()
.with_default_http());

client.connect().await?;

// ...

client.disconnect().await
}

Tasks is an extension under MCP 2026-07-28, and its capability is an empty object — advertising it is the declaration — so with_tasks() takes no closure.

Under legacy-spec

The 2025-11-25 surface applies: with_tasks(|t| t.with_all()) configures a cancel / list / requests sub-tree, and client-hosted tasks exist. See Legacy spec.

Calling a Tool as a Task

Use client.task() to obtain a task builder, then call call_tool() to execute a tool asynchronously as a managed task. This is required when calling a tool that has task_support = "required" on the server side (see the server Tasks guide).

let result = client
.task()
.call_tool("my_long_tool", ()).await;

println!("{:?}", result);

With a TTL

Chain with_ttl() (in milliseconds) to automatically cancel the task if it exceeds the given time limit:

let ttl = 10_000; // 10 seconds
let result = client
.task()
.with_ttl(ttl)
.call_tool("endless_tool", ()).await;

If the TTL expires before the tool completes, the task is cancelled and an appropriate error is returned.

With Arguments

Pass arguments the same way as with call_tool():

let args = [("city1", "London"), ("city2", "Paris")];
let result = client
.task()
.call_tool("generate_weather_report", args).await;

Polling a Task

tasks/get is the single polling method. It returns a DetailedTask — the status plus, depending on it, the outstanding inputRequests, the terminal result, or the error. tasks/update answers those input requests, and tasks/cancel acknowledges with an empty result (cancellation is cooperative, so the outcome is learned by polling).

client.task().call_tool(...) drives that loop for you and resolves to the terminal outcome, so most code never issues the methods directly.

There is no tasks/list

tasks/list and tasks/result were removed in MCP 2026-07-28, and so was Client::list_tasks. A task id is a durable handle the requestor already holds, so enumeration is your job — keep the ids you care about and poll them individually.

On the wire, ttl serializes as ttlMs and poll_interval as pollIntervalMs; ttl is nullable, meaning "unlimited". The status notification is notifications/tasks.

note

In the spec, notifications/tasks is opted into through the subscriptions/listen mechanism. neva implements subscriptions/listen as of 0.5.1, but task status is not one of its filter categories yet — nothing carries the notification, so poll with tasks/get.

Handling Elicitation in Tasks

Task-capable tools may ask for input mid-execution. Register an elicitation handler with the #[elicitation] macro; the framework invokes it when the server-side tool calls ctx.task().elicit() during task execution.

#[elicitation]
async fn elicitation_handler(params: ElicitRequestParams) -> ElicitResult {
match params {
ElicitRequestParams::Url(_url) => ElicitResult::accept(),
ElicitRequestParams::Form(_form) => ElicitResult::decline(),
}
}
No task-augmented sampling

MCP 2026-07-28 removed the server-push sampling/createMessage request, so there is no task-augmented sampling to answer. Sampling now rides the MRTR substrate, which never mixes with the task substrate — the one re-runs, the other suspends.

Learn By Example

Here you may find the full example.