Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 25 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -971,21 +971,33 @@ and [client](examples/clients/src/subscriptions_streamhttp.rs) examples.

## Tasks (long-running tool invocations)

`rmcp` supports the [task-based tool invocation](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks)
flow defined in SEP-1319. Annotate a tool with `execution(task_support = "required" | "optional")`
and add `#[task_handler]` to your `ServerHandler` impl — `enqueue_task`, `tasks/list`, `tasks/get`,
`tasks/result`, and `tasks/cancel` are generated for you on top of an `OperationProcessor`.
`rmcp` implements the [MCP Tasks extension](https://modelcontextprotocol.io/extensions/tasks/overview)
(SEP-2663, `io.modelcontextprotocol/tasks`). A client declares the extension in its
capabilities; the server then decides per request whether to materialize a `tools/call`
as a task, returning a `CreateTaskResult` (`resultType: "task"`). The client polls
`tasks/get`, answers in-task input requests via `tasks/update`, and may request
cooperative cancellation via `tasks/cancel`. Use `rmcp::task_manager::TaskManager`
to manage task lifecycles server-side.

```rust, ignore
#[tool(
description = "Sum two numbers after a 2-second delay",
execution(task_support = "required")
)]
async fn slow_sum(/* ... */) -> Result<CallToolResult, McpError> { /* ... */ }

#[tool_handler]
#[task_handler]
impl ServerHandler for TaskDemo {}
// Client: declare the tasks extension capability.
let caps = ClientCapabilities::builder().enable_tasks().build();

// Server: decide per request whether to materialize a task.
async fn call_tool(&self, request: CallToolRequestParams, context: RequestContext<RoleServer>)
-> Result<CallToolResponse, McpError>
{
let client_supports_tasks = context
.client_capabilities()
.is_some_and(|caps| caps.supports_tasks());
Comment thread
Copilot marked this conversation as resolved.
if client_supports_tasks {
let task = self.tasks.spawn(TaskOptions::default(), move |_ctx| {
Box::pin(async move { /* long-running work -> Ok(CallToolResult) */ })
});
return Ok(CallToolResponse::Task(CreateTaskResult::new(task)));
}
// ... fall back to synchronous execution
}
```

See [`servers_task_stdio`](examples/servers/src/task_stdio.rs) and the matching
Expand Down
18 changes: 5 additions & 13 deletions conformance/expected-failures-extensions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,11 @@
# When bumping DRAFT_CONFORMANCE_VERSION, review the available extension and
# pending scenarios and update this file deliberately.

server:
# SEP-2663 Tasks Extension, tracked in #868.
# `tasks-status-notifications` is intentionally absent: the upstream check is
# currently skipped, and CI should fail if it becomes active but does not pass.
- tasks-lifecycle
- tasks-capability-negotiation
- tasks-wire-fields
- tasks-request-state-removal
- tasks-mrtr-input
- tasks-request-headers
- tasks-dispatch-and-envelope
- tasks-required-task-error
- tasks-mrtr-composition
# The SEP-2663 Tasks Extension server scenarios (tracked in #868) all pass and
# were removed from this baseline. `tasks-status-notifications` remains
# upstream-skipped pending the subscriptions/listen rewrite; CI will fail if it
# becomes active but does not pass.
server: []

client:
# Informational OAuth extension scenarios.
Expand Down
Loading