Skip to content

feat: Implement SEP-2663 Tasks Extension#1020

Merged
jamadeo merged 13 commits into
mainfrom
jamadeo/tasks-extension
Jul 22, 2026
Merged

feat: Implement SEP-2663 Tasks Extension#1020
jamadeo merged 13 commits into
mainfrom
jamadeo/tasks-extension

Conversation

@jamadeo

@jamadeo jamadeo commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Implement SEP-2663 Tasks Extension

Implements the MCP Tasks extension (io.modelcontextprotocol/tasks) per SEP-2663, replacing the experimental 2025-11-25 tasks feature, which is removed. Closes #868.

Server runtime

New TaskManager replaces OperationProcessor:

  • Tasks are durably observable before CreateTaskResult is returned.
  • Mid-task elicitation/sampling via TaskContext::request_input(), surfaced as inputRequests and resumed through tasks/update (partial fulfillment supported, unknown keys ignored).
  • Cooperative cancellation: tasks/cancel acks immediately and signals intent (TaskContext::cancelled() / is_cancel_requested()), but the operation decides its own terminal state — a task may still finish as completed, per spec.
  • TTL enforcement and eviction: overdue tasks are failed, and terminal tasks are evicted after one retention window, swept opportunistically from every entry point. Late polls return -32602 as specified.

The SDK dispatch gates tasks/* on the per-request client capability, returning -32021 (Missing Required Client Capability) with requiredCapabilities data when the server advertises the extension but the client didn't declare it. A legacy task param on tools/call is tolerated and ignored.

Conformance & tests

  • The conformance server implements all required task fixtures; all 9 runnable Tasks-extension scenarios pass (35/35 checks) and were removed from the expected-failures baseline.

Known limitation

notifications/tasks push via subscriptions/listen is not yet wired (SubscriptionFilter has no taskIds field); the corresponding upstream conformance check is itself still skipped. Clients observe task state by polling tasks/get.

Breaking change: removes the experimental 2025-11-25 tasks API (TaskMetadata, TasksCapability, with_task(), tasks/list, tasks/result, #[task_handler], tool execution(task_support)). Clients that don't declare the extension always receive synchronous results.

How Has This Been Tested?

Only the conformance and unit tests.

Breaking Changes

The experimental version of tasks is removed.

Types of changes

  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

@github-actions github-actions Bot added T-documentation Documentation improvements T-dependencies Dependencies related changes T-test Testing related changes T-config Configuration file changes T-core Core library changes T-examples Example code changes T-handler Handler implementation changes T-macros Macro changes T-model Model/data structure changes T-service Service layer changes T-transport Transport layer changes T-CI Changes to CI/CD workflows and configuration labels Jul 21, 2026
@jamadeo jamadeo changed the title Jamadeo/tasks extension feat: Implement SEP-2663 Tasks Extension Jul 21, 2026
@jamadeo
jamadeo marked this pull request as ready for review July 21, 2026 19:00
@jamadeo
jamadeo requested a review from a team as a code owner July 21, 2026 19:00
@jamadeo
jamadeo requested a review from Copilot July 21, 2026 19:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements the SEP-2663 MCP Tasks extension (io.modelcontextprotocol/tasks) across the rmcp model layer, server/client service plumbing, macros, conformance fixtures, and examples—replacing and removing the prior experimental 2025-11-25 task API surface.

Changes:

  • Replaces the old “task-augmented request params + OperationProcessor” runtime with a SEP-2663-aligned TaskManager plus tasks/get, tasks/update, tasks/cancel (including capability gating and cooperative cancellation semantics).
  • Updates the wire model (task shapes, resultType: "task", notifications, schemas) and removes deprecated/experimental task APIs/macros (TaskMetadata, tasks/list, tasks/result, #[task_handler], tool execution.taskSupport).
  • Refreshes conformance server fixtures and examples/tests to exercise the new lifecycle and negotiation rules.

Reviewed changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
README.md Updates top-level documentation and code snippets to describe SEP-2663 Tasks extension usage.
examples/servers/src/common/task_demo.rs Migrates the task demo server to use TaskManager and CallToolResponse::Task.
examples/servers/src/common/counter.rs Removes legacy task-handler wiring and old long-task enqueue test.
examples/servers/README.md Updates examples documentation from SEP-1319 flow to SEP-2663 flow.
examples/clients/src/task_stdio.rs Updates client example to declare tasks extension and poll tasks/get for inline results.
examples/clients/README.md Updates client docs for SEP-2663 lifecycle and capability declaration.
crates/rmcp/tests/test_tool_macros.rs Adjusts tests to the new “tasks as extension” capability plumbing.
crates/rmcp/tests/test_task.rs Replaces legacy OperationProcessor tests with end-to-end SEP-2663 task lifecycle tests.
crates/rmcp/tests/test_task_support_validation.rs Removes tests tied to removed tool-level execution.taskSupport and legacy task param behavior.
crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema.json Updates server JSON-RPC schema snapshots to SEP-2663 task shapes and methods.
crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema_current.json Updates “current” server schema snapshot to SEP-2663 task shapes and methods.
crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema.json Updates client JSON-RPC schema snapshot to remove legacy task param and add tasks/update.
crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema_current.json Updates “current” client schema snapshot similarly.
crates/rmcp/tests/test_deserialization.rs Updates untagged ServerResult deserialization regression tests after task-result model changes.
crates/rmcp/src/transport/common/mcp_headers.rs Routes Mcp-Name for tasks methods from params.taskId for intermediaries.
crates/rmcp/src/task_manager.rs Introduces the new TaskManager runtime for durable task lifecycle/state, TTL, updates, and cancellation.
crates/rmcp/src/service/server.rs Updates subscription filtering/error messaging for renamed task notification method.
crates/rmcp/src/service/client.rs Adds client helpers for tasks/get, tasks/update, tasks/cancel and task-aware call handling.
crates/rmcp/src/model/tool.rs Removes legacy per-tool execution/task-support metadata from Tool model.
crates/rmcp/src/model/task.rs Adds SEP-2663 task types (DetailedTask, TaskPayload, CreateTaskResult, TaskAckResult, etc.).
crates/rmcp/src/model/serde_impl.rs Updates model serde tests to reflect removal of legacy task field in requests.
crates/rmcp/src/model/mrtr.rs Extends CallToolResponse to include task responses and adds wire-equality for InputRequest.
crates/rmcp/src/model/meta.rs Removes legacy task-augmented request meta trait and updates extension wiring for new task requests.
crates/rmcp/src/model/capabilities.rs Moves tasks support to extensions and adds supports_tasks() helpers + enable builder methods.
crates/rmcp/src/model.rs Removes legacy task params/methods and adds SEP-2663 methods/types and ResultType::TASK.
crates/rmcp/src/handler/server/tool.rs Removes legacy task metadata from tool call context and guards error branches against task responses.
crates/rmcp/src/handler/server/router/tool/tool_traits.rs Removes tool execution/task-support attribute plumbing from router traits.
crates/rmcp/src/handler/server/router/tool.rs Updates router tests/fixtures for removed legacy task param.
crates/rmcp/src/handler/server.rs Adds tasks capability gating (-32021) and removes legacy tool-level taskSupport validation/handlers.
crates/rmcp/src/handler/client.rs Updates client handler signature to new task notification params type.
crates/rmcp/Cargo.toml Adds uuid dependency behind server feature for server-side task IDs.
crates/rmcp-macros/src/tool.rs Removes macro support for tool-level execution.task_support.
crates/rmcp-macros/src/tool_handler.rs Removes task_handler sibling detection from capability auto-generation.
crates/rmcp-macros/src/task_handler.rs Deletes the legacy task-handler macro implementation.
crates/rmcp-macros/src/lib.rs Removes task_handler proc-macro export and module wiring.
crates/rmcp-macros/README.md Updates macros README to remove #[task_handler] from documented attributes.
conformance/src/bin/server.rs Adds SEP-2663 tasks fixtures and server-side TaskManager integration for conformance scenarios.
conformance/expected-failures-extensions.yaml Removes tasks scenarios from expected failures baseline now that they pass.

Comment thread crates/rmcp/src/task_manager.rs
Comment thread crates/rmcp/src/task_manager.rs Outdated
Comment thread examples/servers/src/common/task_demo.rs
Comment thread README.md
jamadeo added a commit that referenced this pull request Jul 21, 2026
- TaskManager: drop the JoinHandle as soon as the operation settles instead
  of retaining it for the whole retention window, and only store it at spawn
  time if the task is still non-terminal (avoids keeping a completed handle
  that the completion path could never clear).
- Use RequestContext::client_capabilities() (which applies the initialize-time
  fallback for session peers) instead of raw context.meta.client_capabilities()
  in the task_demo example, the README snippet, and the test server — the
  meta-only form would wrongly treat session-declared tasks clients as
  unsupported.

All 9 Tasks extension conformance scenarios remain green (35/35 checks).
@jamadeo
jamadeo requested a review from Copilot July 21, 2026 20:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 2 comments.

Comment thread crates/rmcp/src/task_manager.rs
Comment thread crates/rmcp/src/handler/server.rs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 2 comments.

Comment thread crates/rmcp/src/task_manager.rs
Comment thread crates/rmcp/src/task_manager.rs

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 2 comments.

Comment thread conformance/src/bin/server.rs Outdated
Comment thread crates/rmcp/src/model/task.rs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 2 comments.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 1 comment.

Comment thread examples/servers/src/common/counter.rs

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 1 comment.

Comment thread crates/rmcp/src/task_manager.rs Outdated
jamadeo added 12 commits July 22, 2026 12:58
…2025-11-25 tasks design

Reshape tasks from the experimental SEP-1319/1686 core-protocol feature into
the official io.modelcontextprotocol/tasks extension (SEP-2663):

Model:
- Re-model Task (statusMessage, ttlMs nullable, pollIntervalMs) and add
  DetailedTask with status-discriminated payloads (inputRequests/result/error
  inlined per spec)
- CreateTaskResult now flattens Task with resultType: "task";
  add ResultType::TASK and CallToolResponse::Task
- Add tasks/update (UpdateTaskParams with MRTR InputResponses); tasks/get and
  tasks/cancel reworked; remove tasks/list, tasks/result
- notifications/tasks now carries a full DetailedTask; removed from client
  notifications (SEP-2260)

Capabilities:
- Remove core TasksCapability et al; tasks are declared via the extensions
  map (enable_tasks() builders, supports_tasks() accessors)
- Remove tool-level execution.taskSupport and the _meta.task /
  TaskMetadata / with_task() opt-in: task creation is server-directed and
  gated on the per-request client capability

Runtime:
- Replace OperationProcessor with TaskManager: durable-before-response task
  creation, input_required round-trips via tasks/update, cooperative
  cancellation, TTL expiry
- Client peer helpers get_task/update_task/cancel_task
- Emit Mcp-Name routing header from params.taskId for tasks/* methods
  (SEP-2243/2663)

Macros:
- Remove #[task_handler] and the tool execution() attribute

Tests/examples/docs updated; schema snapshots regenerated.

BREAKING CHANGE: the 2025-11-25 experimental tasks API is removed without a
compatibility shim. Clients that do not declare the tasks extension always
receive synchronous results.
…SEP-2663)

When the server advertises io.modelcontextprotocol/tasks but the client did
not declare it (per-request _meta clientCapabilities, or initialize-time
capabilities in session mode), tasks/get, tasks/update, and tasks/cancel now
return -32021 Missing Required Client Capability with the required extension
in the error data, instead of falling through to the handler's -32601
default. Servers that do not advertise the extension keep returning -32601.

Also add regression tests confirming that unknown taskIds yield -32602 and
that a legacy 2025-11-25 'task' param on tools/call is silently ignored.
Conformance fixtures (conformance/src/bin/server.rs):
- Add the required fixture tools: greet (sync-only), slow_compute,
  failing_job (task support: required), protocol_error_job, confirm_delete,
  multi_input, and test_tool_with_task (MRTR -> task escalation), all backed
  by TaskManager with server-directed task creation gated on the client's
  tasks-extension capability
- Advertise io.modelcontextprotocol/tasks in server capabilities and wire
  get_task/update_task/cancel_task handlers
- Task-required tools reject with -32021 when the client did not declare the
  extension

SDK wire-shape fixes surfaced by the suite:
- Add resultType: "complete" to GetTaskResult and introduce TaskAckResult
  so tasks/update and tasks/cancel acks carry the SEP-2322 discriminator
  (spec: every non-CreateTaskResult response on the tasks surface is
  resultType complete); dispatch now returns ServerResult::task_ack
- CreateTaskResult gets a strict deserializer requiring resultType: "task"
  so it does not shadow task-shaped results in untagged unions
- Client update_task/cancel_task accept both TaskAckResult and EmptyResult

All 9 runnable Tasks extension server scenarios now pass (35/35 checks;
tasks-status-notifications remains upstream-skipped), so the corresponding
entries are removed from conformance/expected-failures-extensions.yaml.
2025-11-25 server suite (40/40), 2026-07-28 server suite (114/114), draft
client suite, and extensions client suite all pass their baselines.
…rative cancel

- DetailedTask's JsonSchema now derives from the actual flattened wire shape
  (DetailedTaskWire: base Task + optional inputRequests/result/error) instead
  of approximating with the base Task schema, so generated schemas for
  GetTaskResult and notifications/tasks document the status-specific payload
  fields. Golden message schemas regenerated.

- TaskManager::cancel_task no longer aborts the underlying future. It records
  the observable cancelled state and acks immediately (spec's eventually
  consistent semantics), but lets the operation keep running so it can observe
  is_cancel_requested() or the error from a woken request_input() and perform
  cleanup; any late result is discarded. New unit tests cover both the
  cooperative-cleanup path and waking parked input requests.
- TaskManager::cancel_task no longer forces terminal 'cancelled'. It records
  the cancellation intent, acks immediately, and wakes parked request_input
  awaits, but the operation decides its own terminal state: a post-cancel
  error settles as 'cancelled', while an operation that finishes its work
  settles as 'completed' — per the spec, 'the task may still reach a
  non-cancelled terminal status'.
- Add TaskContext::cancelled(), a watch-based await for use with
  tokio::select! as the cooperative cancellation exit path.
- Correct the unsupported-notification method string from
  'notifications/tasks/status' to 'notifications/tasks' and document that
  task status notifications are not yet routable through subscriptions/listen
  (SubscriptionFilter has no taskIds field; upstream check still skipped).
- Update conformance slow_compute fixture, task_demo example, and tests to
  honor cancellation via ctx.cancelled(); lifecycle conformance still 8/8
  (all 9 scenarios remain green, 35/35 checks).
TTL handling previously only ran from get_task and only flipped overdue
non-terminal tasks to 'failed', never removing entries — an unbounded leak
for long-lived servers, and it made ttl_ms: None = 'unlimited retention'
meaningless since everything was retained forever.

- Rename expire_overdue to sweep_expired and run it from every entry point
  (spawn, get_task, update_task, cancel_task).
- Track terminal_at on each entry; terminal tasks are evicted after being
  retained for one further ttl_ms window past their terminal transition, so
  well-behaved pollers can observe the final state before late tasks/get
  calls return -32602 (spec: servers may delete expired tasks at any time,
  and returning task-not-found for purged tasks is compliant behavior).
- ttl_ms: None entries are never evicted (spec: unlimited retention);
  document the retention model on TaskManager, including that there is no
  background sweeper.
- New tests: retention-window eviction, sweep of abandoned tasks via other
  entry points, unlimited-TTL retention, and error-code assertions
  (-32602) for unknown ids across get/update/cancel.

Reviewed the second feedback item (dedicated task-not-found error code)
against SEP-2663 §Protocol Errors and it is incorrect: the SEP explicitly
specifies -32602 (Invalid params) for invalid or nonexistent taskIds, which
is what unknown_task already returns. Kept -32602; strengthened tests to
assert the code.
- TaskManager: drop the JoinHandle as soon as the operation settles instead
  of retaining it for the whole retention window, and only store it at spawn
  time if the task is still non-terminal (avoids keeping a completed handle
  that the completion path could never clear).
- Use RequestContext::client_capabilities() (which applies the initialize-time
  fallback for session peers) instead of raw context.meta.client_capabilities()
  in the task_demo example, the README snippet, and the test server — the
  meta-only form would wrongly treat session-declared tasks clients as
  unsupported.

All 9 Tasks extension conformance scenarios remain green (35/35 checks).
A handler could return CallToolResponse::Task without checking whether the
request declared the io.modelcontextprotocol/tasks extension, sending a
CreateTaskResult to a client that cannot parse it. The SDK dispatch now
rejects that case with -32021 Missing Required Client Capability before the
response leaves the server, using RequestContext::client_capabilities()
(per-request _meta with initialize-time fallback).

Adds a regression test with a deliberately misbehaving handler that always
materializes a task; all Tasks extension conformance scenarios remain green
(35/35 checks).
Copilot review: ttlMs: 0 never expired immediately because phase 1 used a
strict 'elapsed > ttl_ms' comparison, and phase 2 retained terminal tasks
at exactly the TTL boundary ('elapsed <= ttl_ms'). Treat elapsed >= ttl_ms
as expired in phase 1 and evict when elapsed >= ttl_ms in phase 2, so both
phases agree at the boundary and ttlMs: 0 expires/evicts on the first sweep.
…tency

- TaskAckResult carried only resultType (+ optional _meta) with a derived
  Deserialize, so inside the untagged ServerResult union it greedily matched
  any result object containing a resultType key, shadowing CustomResult and
  losing data (verified with a probe). Replace with a strict deserializer:
  deny_unknown_fields and require resultType == "complete". Regression
  tests cover both the non-matching shapes and the genuine ack shape.

- Conformance fixtures: confirm_delete and multi_input rejected non-tasks
  clients inline, contradicting the TASK_REQUIRED_TOOLS doc/constant. Move
  them into TASK_REQUIRED_TOOLS (they park on in-task elicitation and have
  no synchronous fallback) so the upfront -32021 gate is the single source
  of truth, and drop the now-unreachable inline checks.

All 9 Tasks extension conformance scenarios remain green (35/35 checks).
After tasks/cancel, any operation error was coerced to terminal
'cancelled', masking real failures (e.g. an unrelated error landing just
after a late cancel request) and contradicting the documented 'operation
decides its own terminal state' contract.

Change TaskFuture's error type from McpError to a new TaskExit enum:

- TaskExit::Cancelled — an explicit cooperative-cancellation exit;
  settles as terminal 'cancelled'.
- TaskExit::Error(McpError) — a real failure; settles as terminal
  'failed' with the error inlined, even after tasks/cancel was received.

From<McpError> for TaskExit keeps '?' ergonomic in task bodies, and
request_input() now returns TaskExit directly (its wake-on-cancel path
yields TaskExit::Cancelled), so parked operations that propagate it with
'?' settle as 'cancelled' automatically.

Update the conformance fixtures, task_demo example, and tests; add a
regression test asserting a post-cancel unrelated error settles as
'failed' with its error payload preserved.

All 9 Tasks extension conformance scenarios remain green (35/35 checks).
@jamadeo
jamadeo force-pushed the jamadeo/tasks-extension branch from 85d2d8b to 8f0980b Compare July 22, 2026 16:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated no new comments.

…TTL retention docs

Addresses branch-review findings:

- spawn() raced with shutdown(): if shutdown drained the task map between
  the entry insert and the JoinHandle store, the handle was dropped and the
  operation kept running detached. If the entry is gone at store time, the
  handle is now aborted instead.
- running_task_count() now runs the TTL sweep like every other entry point,
  so it no longer reports overdue tasks as running (matching the documented
  sweep-on-every-entry-point behavior). Regression test added.
- Document that terminal-task retention intentionally extends one ttl_ms
  window past the terminal transition (observation grace period) beyond the
  creation-based lifetime ttlMs advertises on the wire — compliant since
  SEP-2663 allows deleting expired tasks at any time after the TTL.
Comment thread crates/rmcp/src/model.rs
/// The request used a protocol version the server does not support.
pub const UNSUPPORTED_PROTOCOL_VERSION: Self = Self(-32022);
/// Processing the request requires a client capability that was not declared.
pub const MISSING_REQUIRED_CLIENT_CAPABILITY: Self = Self(-32021);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't in this diff but a goose review surfaced for me that the error code for this should be -32003

From the spec: https://modelcontextprotocol.io/seps/2663-tasks-extension.md

* Missing required client capabilities: `-32003` (Missing Required Client Capability)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jamadeo
jamadeo merged commit 07abfdf into main Jul 22, 2026
21 checks passed
@jamadeo
jamadeo deleted the jamadeo/tasks-extension branch July 22, 2026 18:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-CI Changes to CI/CD workflows and configuration T-config Configuration file changes T-core Core library changes T-dependencies Dependencies related changes T-documentation Documentation improvements T-examples Example code changes T-handler Handler implementation changes T-macros Macro changes T-model Model/data structure changes T-service Service layer changes T-test Testing related changes T-transport Transport layer changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement SEP-2663: Tasks Extension

3 participants