From 8c434efe8d9f35e4c64390adb11ae5a9c8b35a15 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Mon, 11 May 2026 14:25:28 -0700 Subject: [PATCH] first agentic pass --- CODE_OF_CONDUCT.md | 5 + CONTRIBUTING.md | 16 + LICENSE | 191 ++++ MAINTAINERS.md | 21 + README.md | Bin 54 -> 2497 bytes SECURITY.md | 14 + docs/specification/2025-11-25/tasks.mdx | 905 +++++++++++++++++++ schema/draft/2025-11-25/schema.json | 405 +++++++++ schema/draft/2025-11-25/schema.ts | 425 +++++++++ seps/1686-tasks.md | 1083 +++++++++++++++++++++++ tsconfig.json | 10 + 11 files changed, 3075 insertions(+) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 MAINTAINERS.md create mode 100644 SECURITY.md create mode 100644 docs/specification/2025-11-25/tasks.mdx create mode 100644 schema/draft/2025-11-25/schema.json create mode 100644 schema/draft/2025-11-25/schema.ts create mode 100644 seps/1686-tasks.md create mode 100644 tsconfig.json diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..5932add --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,5 @@ +# Code of Conduct + +This repository follows the same Code of Conduct as the main Model Context Protocol project. + +Please see the [MCP Code of Conduct](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/CODE_OF_CONDUCT.md) for full details. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..77ffdf2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,16 @@ +# Contributing + +This repository follows the same contributing guidelines as the main Model Context Protocol project. + +Please see the [MCP Contributing Guidelines](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/CONTRIBUTING.md) for full details. + +## Extension-Specific Notes + +- The Tasks extension specification lives in `docs/specification/` +- Schema types live in `schema/draft/` +- Changes should be discussed in the **Agents Working Group** prior to submission + +## License + +By contributing, you agree that your contributions will be licensed under the Apache License 2.0. +See the [LICENSE](LICENSE) file for details. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8372134 --- /dev/null +++ b/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2025 The Model Context Protocol Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 0000000..ad41fd7 --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,21 @@ +# Tasks Extension Maintainers + +This document lists the maintainers of the MCP Tasks experimental extension. + +This extension is maintained by the **Agents Working Group**. + +## Extension Maintainers + +- [Peter Alexander](https://github.com/pja-ant) +- [Luca Chang](https://github.com/LucaButBoring) +- [Inna Harper](https://github.com/ihrpr) + +## Sponsor + +- [Caitie McCaffrey](https://github.com/CaitieM20) + +## About This Document + +This document is updated by the extension maintainers and reflects the current +maintainer structure. For more information about MCP governance, see the +[governance documentation](https://modelcontextprotocol.io/community/governance). diff --git a/README.md b/README.md index 0520365ef7241a5941ada98b4973c70730b3637c..2aa171adbd2565c2809f793563597691cbd68ed8 100644 GIT binary patch literal 2497 zcma)8OKu}K5Z&t(1iXW<_qLXx(T;oOc#sD_4em zG?XNfE zAmq%JA^I*&Mow>(nIi>C%eY+`vAK|7#;+y$@J5hTWbR0gJ^EHUcYCb~)qsk`=^3^}BcfgSPQ;4(>o=RVug zQ(kHY>AcZsi#sadmhHtf3T_EtnA&Ag0uN-&bk4*~282=<=pYvl8v{^u4zIlINiH>N zh%nbGEMjq;0W%-%th=`EbFS@LT-*-$Vt)%vDz$DKyM7~bf}gfV3hn^aLIx}t-)JPs zj-Fyr<3R2l1ehw@d9G!;jDw#>PK^%v%)|m)O0AE19tl(f=4-ZP;6N<;^z7}~`}XN+ z`|SNRipjP!1>lcrf)?ZE$}RXIWQZsy$&qIc_hFjZ)OU|Mth+Z}Mcr9uPt!^B8&9?X zDLkbMT1wFzZweZvP8Fe5`SWq3n>m?>Lr>LuCwO>z^~|x4pGG&sZ?pdBt$E!(pMn6R z`$zp-RJ$!6@X=s$&r%iwv){V_^YNt5#!zuBo|VwxKGJWqgDMx>%XuY*wad zi>j`*1oJRBDnO+?BM`rY_y6e5T0*~S2cUN$U4ZaLR-_g>@;__Oa>kJ+>1iwCGpZUd zIGJ+WB2>$Y{(?gCrsfV}fy)VT!Z8+=NlUaS#a^YA)#zabeL_ETljzNxsvY?nTyVcb z%Md-S@Lj?t5^{EFV-u@@eorAf91LHH`R$l)O$sYPEC2}fZl<*Qf(cUfQw8u|tsz`X z3`HJ{NDpeo_LAfRN)^2L8H&IFTsd17QK?Y-D~nz-5?<;W9L(4p!fDr)_`fhg5AMG{ zvLJLz2y57M=R2>htVc#Ce7pD)qvxywDOS=yL{b#f^w0<@q3{n z+pX0Y-L$3K3#q;^%TSU>N&}CRXe5fwLk|xJt}UJxAbprLdpDQ;Pd7h)>fH=4?|Wlz z8E!3pdN?)1;Nl9D7%P}b7xke;oAs!%=_ItUr#P51q!pXvJua literal 54 ycmezWPnki1A(f$mp#Vr0F=R620_i-45{5*E90pymObL(%@rxO + + + +**This document is part of the experimental Tasks extension (`io.modelcontextprotocol/tasks`).** + +Tasks were introduced in version 2025-11-25 of the [MCP specification](https://modelcontextprotocol.io/specification/2025-11-25) +and are currently considered **experimental**. The design and behavior of tasks may evolve in future versions. + +This content is extracted from the core specification to support the extension's incubation. +For the core protocol specification, see [modelcontextprotocol.io](https://modelcontextprotocol.io/specification/2025-11-25). + + + +The Model Context Protocol (MCP) allows requestors — which can be either clients or servers, depending on the direction of communication — to augment their requests with **tasks**. Tasks are durable state machines that carry information about the underlying execution state of the request they wrap, and are intended for requestor polling and deferred result retrieval. Each task is uniquely identifiable by a receiver-generated **task ID**. + +Tasks are useful for representing expensive computations and batch processing requests, and integrate seamlessly with external job APIs. + +## Definitions + +Tasks represent parties as either "requestors" or "receivers," defined as follows: + +- **Requestor:** The sender of a task-augmented request. This can be the client or the server — either can create tasks. +- **Receiver:** The receiver of a task-augmented request, and the entity executing the task. This can be the client or the server — either can receive and execute tasks. + +## User Interaction Model + +Tasks are designed to be **requestor-driven** - requestors are responsible for augmenting requests with tasks and for polling for the results of those tasks; meanwhile, receivers tightly control which requests (if any) support task-based execution and manages the lifecycles of those tasks. + +This requestor-driven approach ensures deterministic response handling and enables sophisticated patterns such as dispatching concurrent requests, which only the requestor has sufficient context to orchestrate. + +Implementations are free to expose tasks through any interface pattern that suits their needs — the protocol itself does not mandate any specific user interaction model. + +## Capabilities + +Servers and clients that support task-augmented requests **MUST** declare a `tasks` capability during initialization. The `tasks` capability is structured by request category, with boolean properties indicating which specific request types support task augmentation. + +### Server Capabilities + +Servers declare if they support tasks, and if so, which server-side requests can be augmented with tasks. + +| Capability | Description | +| --------------------------- | ---------------------------------------------------- | +| `tasks.list` | Server supports the `tasks/list` operation | +| `tasks.cancel` | Server supports the `tasks/cancel` operation | +| `tasks.requests.tools.call` | Server supports task-augmented `tools/call` requests | + +```json +{ + "capabilities": { + "tasks": { + "list": {}, + "cancel": {}, + "requests": { + "tools": { + "call": {} + } + } + } + } +} +``` + +### Client Capabilities + +Clients declare if they support tasks, and if so, which client-side requests can be augmented with tasks. + +| Capability | Description | +| --------------------------------------- | ---------------------------------------------------------------- | +| `tasks.list` | Client supports the `tasks/list` operation | +| `tasks.cancel` | Client supports the `tasks/cancel` operation | +| `tasks.requests.sampling.createMessage` | Client supports task-augmented `sampling/createMessage` requests | +| `tasks.requests.elicitation.create` | Client supports task-augmented `elicitation/create` requests | + +```json +{ + "capabilities": { + "tasks": { + "list": {}, + "cancel": {}, + "requests": { + "sampling": { + "createMessage": {} + }, + "elicitation": { + "create": {} + } + } + } + } +} +``` + +### Capability Negotiation + +During the initialization phase, both parties exchange their `tasks` capabilities to establish which operations support task-based execution. Requestors **SHOULD** only augment requests with a task if the corresponding capability has been declared by the receiver. + +For example, if a server's capabilities include `tasks.requests.tools.call: {}`, then clients may augment `tools/call` requests with a task. If a client's capabilities include `tasks.requests.sampling.createMessage: {}`, then servers may augment `sampling/createMessage` requests with a task. + +If `capabilities.tasks` is not defined, the peer **SHOULD NOT** attempt to create tasks during requests. + +The set of capabilities in `capabilities.tasks.requests` is exhaustive. If a request type is not present, it does not support task-augmentation. + +`capabilities.tasks.list` controls if the `tasks/list` operation is supported by the party. + +`capabilities.tasks.cancel` controls if the `tasks/cancel` operation is supported by the party. + +### Tool-Level Negotiation + +Tool calls are given special consideration for the purpose of task augmentation. In the result of `tools/list`, tools declare support for tasks via `execution.taskSupport`, which if present can have a value of `"required"`, `"optional"`, or `"forbidden"`. + +This is to be interpreted as a fine-grained layer in addition to capabilities, following these rules: + +1. If a server's capabilities do not include `tasks.requests.tools.call`, then clients **MUST NOT** attempt to use task augmentation on that server's tools, regardless of the `execution.taskSupport` value. +1. If a server's capabilities include `tasks.requests.tools.call`, then clients consider the value of `execution.taskSupport`, and handle it accordingly: + 1. If `execution.taskSupport` is not present or `"forbidden"`, clients **MUST NOT** attempt to invoke the tool as a task. Servers **SHOULD** return a `-32601` (Method not found) error if a client attempts to do so. This is the default behavior. + 1. If `execution.taskSupport` is `"optional"`, clients **MAY** invoke the tool as a task or as a normal request. + 1. If `execution.taskSupport` is `"required"`, clients **MUST** invoke the tool as a task. Servers **MUST** return a `-32601` (Method not found) error if a client does not attempt to do so. + +## Protocol Messages + +### Creating Tasks + +Task-augmented requests follow a two-phase response pattern that differs from normal requests: + +- **Normal requests**: The server processes the request and returns the actual operation result directly. +- **Task-augmented requests**: The server accepts the request and immediately returns a `CreateTaskResult` containing task data. The actual operation result becomes available later through `tasks/result` after the task completes. + +To create a task, requestors send a request with the `task` field included in the request params. Requestors **MAY** include a `ttl` value indicating the desired task lifetime duration (in milliseconds) since its creation. + +**Request:** + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "name": "get_weather", + "arguments": { + "city": "New York" + }, + "task": { + "ttl": 60000 + } + } +} +``` + +**Response:** + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "task": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840", + "status": "working", + "statusMessage": "The operation is now in progress.", + "createdAt": "2025-11-25T10:30:00Z", + "lastUpdatedAt": "2025-11-25T10:40:00Z", + "ttl": 60000, + "pollInterval": 5000 + } + } +} +``` + +When a receiver accepts a task-augmented request, it returns a [`CreateTaskResult`](https://modelcontextprotocol.io/specification/2025-11-25/schema#createtaskresult) containing task data. The response does not include the actual operation result. The actual result (e.g., tool result for `tools/call`) becomes available only through `tasks/result` after the task completes. + + + +When a task is created in response to a `tools/call` request, host applications may wish to return control to the model while the task is executing. This allows the model to continue processing other requests or perform additional work while waiting for the task to complete. + +To support this pattern, servers can provide an optional `io.modelcontextprotocol/model-immediate-response` key in the `_meta` field of the `CreateTaskResult`. The value of this key should be a string intended to be passed as an immediate tool result to the model. +If a server does not provide this field, the host application can fall back to its own predefined message. + +This guidance is non-binding and is provisional logic intended to account for the specific use case. This behavior may be formalized or modified as part of `CreateTaskResult` in future protocol versions. + + + +### Getting Tasks + + + +In the Streamable HTTP (SSE) transport, clients **MAY** disconnect from an SSE stream opened by the server in response to a `tasks/get` request at any time. + +While this note is not prescriptive regarding the specific usage of SSE streams, all implementations **MUST** continue to comply with the existing [Streamable HTTP transport specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server). + + + +Requestors poll for task completion by sending [`tasks/get`](https://modelcontextprotocol.io/specification/2025-11-25/schema#tasks%2Fget)requests. +Requestors **SHOULD** respect the `pollInterval` provided in responses when determining polling frequency. + +Requestors **SHOULD** continue polling until the task reaches a terminal status (`completed`, `failed`, or `cancelled`), or until encountering the [`input_required`](#input-required-status) status. Note that invoking `tasks/result` does not imply that the requestor needs to stop polling - requestors **SHOULD** continue polling the task status via `tasks/get` if they are not actively waiting for `tasks/result` to complete. + +**Request:** + +```json +{ + "jsonrpc": "2.0", + "id": 3, + "method": "tasks/get", + "params": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840" + } +} +``` + +**Response:** + +```json +{ + "jsonrpc": "2.0", + "id": 3, + "result": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840", + "status": "working", + "statusMessage": "The operation is now in progress.", + "createdAt": "2025-11-25T10:30:00Z", + "lastUpdatedAt": "2025-11-25T10:40:00Z", + "ttl": 30000, + "pollInterval": 5000 + } +} +``` + +### Retrieving Task Results + + + +In the Streamable HTTP (SSE) transport, clients **MAY** disconnect from an SSE stream opened by the server in response to a `tasks/result` request at any time. + +While this note is not prescriptive regarding the specific usage of SSE streams, all implementations **MUST** continue to comply with the existing [Streamable HTTP transport specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server). + + + +After a task completes the operation result is retrieved via [`tasks/result`](https://modelcontextprotocol.io/specification/2025-11-25/schema#tasks%2Fresult). This is distinct from the initial `CreateTaskResult` response, which contains only task data. The result structure matches the original request type (e.g., `CallToolResult` for `tools/call`). + +To retrieve the result of a completed task, requestors can send a `tasks/result` request: + +While `tasks/result` blocks until the task reaches a terminal status, requestors can continue polling via `tasks/get` in parallel if they are not actively blocked waiting for the result, such as if their previous `tasks/result` request failed or was cancelled. This allows requestors to monitor status changes or display progress updates while the task executes, even after invoking `tasks/result`. + +**Request:** + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "method": "tasks/result", + "params": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840" + } +} +``` + +**Response:** + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "result": { + "content": [ + { + "type": "text", + "text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy" + } + ], + "isError": false, + "_meta": { + "io.modelcontextprotocol/related-task": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840" + } + } + } +} +``` + +### Task Status Notification + +When a task status changes, receivers **MAY** send a [`notifications/tasks/status`](https://modelcontextprotocol.io/specification/2025-11-25/schema#notifications%2Ftasks%2Fstatus) notification to inform the requestor of the change. This notification includes the full task state. + +**Notification:** + +```json +{ + "jsonrpc": "2.0", + "method": "notifications/tasks/status", + "params": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840", + "status": "completed", + "createdAt": "2025-11-25T10:30:00Z", + "lastUpdatedAt": "2025-11-25T10:50:00Z", + "ttl": 60000, + "pollInterval": 5000 + } +} +``` + +The notification includes the full [`Task`](https://modelcontextprotocol.io/specification/2025-11-25/schema#task) object, including the updated `status` and `statusMessage` (if present). This allows requestors to access the complete task state without making an additional `tasks/get` request. + +Requestors **MUST NOT** rely on receiving this notifications, as it is optional. Receivers are not required to send status notifications and may choose to only send them for certain status transitions. Requestors **SHOULD** continue to poll via `tasks/get` to ensure they receive status updates. + +### Listing Tasks + +To retrieve a list of tasks, requestors can send a [`tasks/list`](https://modelcontextprotocol.io/specification/2025-11-25/schema#tasks%2Flist) request. This operation supports pagination. + +**Request:** + +```json +{ + "jsonrpc": "2.0", + "id": 5, + "method": "tasks/list", + "params": { + "cursor": "optional-cursor-value" + } +} +``` + +**Response:** + +```json +{ + "jsonrpc": "2.0", + "id": 5, + "result": { + "tasks": [ + { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840", + "status": "working", + "createdAt": "2025-11-25T10:30:00Z", + "lastUpdatedAt": "2025-11-25T10:40:00Z", + "ttl": 30000, + "pollInterval": 5000 + }, + { + "taskId": "abc123-def456-ghi789", + "status": "completed", + "createdAt": "2025-11-25T09:15:00Z", + "lastUpdatedAt": "2025-11-25T10:40:00Z", + "ttl": 60000 + } + ], + "nextCursor": "next-page-cursor" + } +} +``` + +### Cancelling Tasks + +To explicitly cancel a task, requestors can send a [`tasks/cancel`](https://modelcontextprotocol.io/specification/2025-11-25/schema#tasks%2Fcancel) request. + +**Request:** + +```json +{ + "jsonrpc": "2.0", + "id": 6, + "method": "tasks/cancel", + "params": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840" + } +} +``` + +**Response:** + +```json +{ + "jsonrpc": "2.0", + "id": 6, + "result": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840", + "status": "cancelled", + "statusMessage": "The task was cancelled by request.", + "createdAt": "2025-11-25T10:30:00Z", + "lastUpdatedAt": "2025-11-25T10:40:00Z", + "ttl": 30000, + "pollInterval": 5000 + } +} +``` + +## Behavior Requirements + +These requirements apply to all parties that support receiving task-augmented requests. + +### Task Support and Handling + +1. Receivers that do not declare the task capability for a request type **MUST** process requests of that type normally, ignoring any task-augmentation metadata if present. +1. Receivers that declare the task capability for a request type **MAY** return an error for non-task-augmented requests, requiring requestors to use task augmentation. + +### Task ID Requirements + +1. Task IDs **MUST** be a string value. +1. Task IDs **MUST** be generated by the receiver when creating a task. +1. Task IDs **MUST** be unique among all tasks controlled by the receiver. + +### Task Status Lifecycle + +1. Tasks **MUST** begin in the `working` status when created. +1. Receivers **MUST** only transition tasks through the following valid paths: + 1. From `working`: may move to `input_required`, `completed`, `failed`, or `cancelled` + 1. From `input_required`: may move to `working`, `completed`, `failed`, or `cancelled` + 1. Tasks with a `completed`, `failed`, or `cancelled` status are in a terminal state and **MUST NOT** transition to any other status + +**Task Status State Diagram:** + +```mermaid +stateDiagram-v2 + [*] --> working + + working --> input_required + working --> terminal + + input_required --> working + input_required --> terminal + + terminal --> [*] + + note right of terminal + Terminal states: + • completed + • failed + • cancelled + end note +``` + +### Input Required Status + + + +With the Streamable HTTP (SSE) transport, servers often close SSE streams after delivering a response message, which can lead to ambiguity regarding the stream used for subsequent task messages. + +Servers can handle this by enqueueing messages to the client to side-channel task-related messages alongside other responses. + +Servers have flexibility in how they manage SSE streams during task polling and result retrieval, and clients **SHOULD** expect messages to be delivered on any SSE stream, including the HTTP GET stream. +One possible approach is maintaining an SSE stream on `tasks/result` (see notes on the `input_required` status). +Where possible, servers **SHOULD NOT** upgrade to an SSE stream in response to a `tasks/get` request, as the client has indicated it wishes to poll for a result. + +While this note is not prescriptive regarding the specific usage of SSE streams, all implementations **MUST** continue to comply with the existing [Streamable HTTP transport specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server). + + + +1. When the task receiver has messagesfor the requestor that are necessary to complete the task, the receiver **SHOULD** move the task to the `input_required` status. +1. The receiver **MUST** include the `io.modelcontextprotocol/related-task` metadata in the request to associate it with the task. +1. When the requestor encounters the `input_required` status, it **SHOULD** preemptively call `tasks/result`. +1. When the receiver receives all required input, the task **SHOULD** transition out of `input_required` status (typically back to `working`). + +### TTL and Resource Management + +1. Receivers **MUST** include a `createdAt` [ISO 8601](https://datatracker.ietf.org/doc/html/rfc3339#section-5)-formatted timestamp in all task responses to indicate when the task was created. +1. Receivers **MUST** include a `lastUpdatedAt` [ISO 8601](https://datatracker.ietf.org/doc/html/rfc3339#section-5)-formatted timestamp in all task responses to indicate when the task was last updated. +1. Receivers **MAY** override the requested `ttl` duration. +1. Receivers **MUST** include the actual `ttl` duration (or `null` for unlimited) in `tasks/get` responses. +1. After a task's `ttl` lifetime has elapsed, receivers **MAY** delete the task and its results, regardless of the task status. +1. Receivers **MAY** include a `pollInterval` value (in milliseconds) in `tasks/get` responses to suggest polling intervals. Requestors **SHOULD** respect this value when provided. + +### Result Retrieval + +1. Receivers that accept a task-augmented request **MUST** return a `CreateTaskResult` as the response. This result **SHOULD** be returned as soon as possible after accepting the task. +1. When a receiver receives a `tasks/result` request for a task in a terminal status (`completed`, `failed`, or `cancelled`), it **MUST** return the final result of the underlying request, whether that is a successful result or a JSON-RPC error. +1. When a receiver receives a `tasks/result` request for a task in any other non-terminal status (`working` or `input_required`), it **MUST** block the response until the task reaches a terminal status. +1. For tasks in a terminal status, receivers **MUST** return from `tasks/result` exactly what the underlying request would have returned, whether that is a successful result or a JSON-RPC error. + +### Associating Task-Related Messages + +1. All requests, notifications, and responses related to a task **MUST** include the `io.modelcontextprotocol/related-task` key in their `_meta` field, with the value set to an object with a `taskId` matching the associated task ID. + 1. For example, an elicitation that a task-augmented tool call depends on **MUST** share the same related task ID with that tool call's task. +1. For the `tasks/get`, `tasks/result`, and `tasks/cancel` operations, the `taskId` parameter in the request **MUST** be used as the source of truth for identifying the target task. Requestors **SHOULD NOT** include `io.modelcontextprotocol/related-task` metadata in these requests, and receivers **MUST** ignore such metadata if present in favor of the RPC method parameter. + Similarly, for the `tasks/get`, `tasks/list`, and `tasks/cancel` operations, receivers **SHOULD NOT** include `io.modelcontextprotocol/related-task` metadata in the result messages, as the `taskId` is already present in the response structure. + +### Task Notifications + +1. Receivers **MAY** send `notifications/tasks/status` notifications when a task's status changes. +1. Requestors **MUST NOT** rely on receiving the `notifications/tasks/status` notification, as it is optional. +1. When sent, the `notifications/tasks/status` notification **SHOULD NOT** include the `io.modelcontextprotocol/related-task` metadata, as the task ID is already present in the notification parameters. + +### Task Progress Notifications + +Task-augmented requests support progress notifications as defined in the [progress](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/progress) specification. The `progressToken` provided in the initial request remains valid throughout the task lifetime. + +### Task Listing + +1. Receivers **SHOULD** use cursor-based pagination to limit the number of tasks returned in a single response. +1. Receivers **MUST** include a `nextCursor` in the response if more tasks are available. +1. Requestors **MUST** treat cursors as opaque tokens and not attempt to parse or modify them. +1. If a task is retrievable via `tasks/get` for a requestor, it **MUST** be retrievable via `tasks/list` for that requestor. + +### Task Cancellation + +1. Receivers **MUST** reject cancellation requests for tasks already in a terminal status (`completed`, `failed`, or `cancelled`) with error code `-32602` (Invalid params). +1. Upon receiving a valid cancellation request, receivers **SHOULD** attempt to stop the task execution and **MUST** transition the task to `cancelled` status before sending the response. +1. Once a task is cancelled, it **MUST** remain in `cancelled` status even if execution continues to completion or fails. +1. The `tasks/cancel` operation does not define deletion behavior. However, receivers **MAY** delete cancelled tasks at their discretion at any time, including immediately after cancellation or after the task `ttl` expires. +1. Requestors **SHOULD NOT** rely on cancelled tasks being retained for any specific duration and should retrieve any needed information before cancelling. + +## Message Flow + +### Basic Task Lifecycle + +```mermaid +sequenceDiagram + participant C as Client (Requestor) + participant S as Server (Receiver) + Note over C,S: 1. Task Creation + C->>S: Request with task field (ttl) + activate S + S->>C: CreateTaskResult (taskId, status: working, ttl, pollInterval) + deactivate S + Note over C,S: 2. Task Polling + C->>S: tasks/get (taskId) + activate S + S->>C: working + deactivate S + Note over S: Task processing continues... + C->>S: tasks/get (taskId) + activate S + S->>C: working + deactivate S + Note over S: Task completes + C->>S: tasks/get (taskId) + activate S + S->>C: completed + deactivate S + Note over C,S: 3. Result Retrieval + C->>S: tasks/result (taskId) + activate S + S->>C: Result content + deactivate S + Note over C,S: 4. Cleanup + Note over S: After ttl period from creation, task is cleaned up +``` + +### Task-Augmented Tool Call With Elicitation + +```mermaid +sequenceDiagram + participant U as User + participant LLM + participant C as Client (Requestor) + participant S as Server (Receiver) + + Note over LLM,C: LLM initiates request + LLM->>C: Request operation + + Note over C,S: Client augments with task + C->>S: tools/call (ttl: 3600000) + activate S + S->>C: CreateTaskResult (task-123, status: working) + deactivate S + + Note over LLM,C: Client continues processing other requests
while task executes in background + LLM->>C: Request other operation + C->>LLM: Other operation result + + Note over C,S: Client polls for status + C->>S: tasks/get (task-123) + activate S + S->>C: working + deactivate S + + Note over S: Server needs information from client
Task moves to input_required + + Note over C,S: Client polls and discovers input_required + C->>S: tasks/get (task-123) + activate S + S->>C: input_required + deactivate S + + Note over C,S: Client opens result stream + C->>S: tasks/result (task-123) + activate S + S->>C: elicitation/create (related-task: task-123) + activate C + C->>U: Prompt user for input + U->>C: Provide information + C->>S: elicitation response (related-task: task-123) + deactivate C + deactivate S + + Note over C,S: Client closes result stream and resumes polling + + Note over S: Task continues processing...
Task moves back to working + + C->>S: tasks/get (task-123) + activate S + S->>C: working + deactivate S + + Note over S: Task completes + + Note over C,S: Client polls and discovers completion + C->>S: tasks/get (task-123) + activate S + S->>C: completed + deactivate S + + Note over C,S: Client retrieves final results + C->>S: tasks/result (task-123) + activate S + S->>C: Result content + deactivate S + C->>LLM: Process result + + Note over S: Results retained for ttl period from creation +``` + +### Task-Augmented Sampling Request + +```mermaid +sequenceDiagram + participant U as User + participant LLM + participant C as Client (Receiver) + participant S as Server (Requestor) + + Note over S: Server decides to initiate request + + Note over S,C: Server requests client operation (task-augmented) + S->>C: sampling/createMessage (ttl: 3600000) + activate C + C->>S: CreateTaskResult (request-789, status: working) + deactivate C + + Note over S: Server continues processing
while waiting for result + + Note over S,C: Server polls for result + S->>C: tasks/get (request-789) + activate C + C->>S: working + deactivate C + + Note over C,U: Client may present request to user + C->>U: Review request + U->>C: Approve request + + Note over C,LLM: Client may involve LLM + C->>LLM: Request completion + LLM->>C: Return completion + + Note over C,U: Client may present result to user + C->>U: Review result + U->>C: Approve result + + Note over S,C: Server polls and discovers completion + S->>C: tasks/get (request-789) + activate C + C->>S: completed + deactivate C + + Note over S,C: Server retrieves result + S->>C: tasks/result (request-789) + activate C + C->>S: Result content + deactivate C + + Note over S: Server continues processing + + Note over C: Results retained for ttl period from creation +``` + +### Task Cancellation Flow + +```mermaid +sequenceDiagram + participant C as Client (Requestor) + participant S as Server (Receiver) + + Note over C,S: 1. Task Creation + C->>S: tools/call (request ID: 42, ttl: 60000) + activate S + S->>C: CreateTaskResult (task-123, status: working) + deactivate S + + Note over C,S: 2. Task Processing + C->>S: tasks/get (task-123) + activate S + S->>C: working + deactivate S + + Note over C,S: 3. Client Cancellation + Note over C: User requests cancellation + C->>S: tasks/cancel (taskId: task-123) + activate S + + Note over S: Server stops execution (best effort) + Note over S: Task moves to cancelled status + + S->>C: Task (status: cancelled) + deactivate S + + Note over C: Client receives confirmation + + Note over S: Server may delete task at its discretion +``` + +## Data Types + +### Task + +A task represents the execution state of a request. The task state includes: + +- `taskId`: Unique identifier for the task +- `status`: Current state of the task execution +- `statusMessage`: Optional human-readable message describing the current state (can be present for any status, including error details for failed tasks) +- `createdAt`: ISO 8601 timestamp when the task was created +- `ttl`: Time in milliseconds from creation before task may be deleted +- `pollInterval`: Suggested time in milliseconds between status checks +- `lastUpdatedAt`: ISO 8601 timestamp when the task status was last updated + +### Task Status + +Tasks can be in one of the following states: + +- `working`: The request is currently being processed. +- `input_required`: The receiver needs input from the requestor. The requestor should call `tasks/result` to receive input requests, even though the task has not reached a terminal state. +- `completed`: The request completed successfully and results are available. +- `failed`: The associated request did not complete successfully. For tool calls specifically, this includes cases where the tool call result has `isError` set to true. +- `cancelled`: The request was cancelled before completion. + +### Task Parameters + +When augmenting a request with task execution, the `task` field is included in the request parameters: + +```json +{ + "task": { + "ttl": 60000 + } +} +``` + +Fields: + +- `ttl` (number, optional): Requested duration in milliseconds to retain task from creation + +### Related Task Metadata + +All requests, responses, and notifications associated with a task **MUST** include the `io.modelcontextprotocol/related-task` key in `_meta`: + +```json +{ + "io.modelcontextprotocol/related-task": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840" + } +} +``` + +This associates messages with their originating task across the entire request lifecycle. + +For the `tasks/get`, `tasks/list`, and `tasks/cancel` operations, requestors and receivers **SHOULD NOT** include this metadata in their messages, as the `taskId` is already present in the message structure. +The `tasks/result` operation **MUST** include this metadata in its response, as the result structure itself does not contain the task ID. + +## Error Handling + +Tasks use two error reporting mechanisms: + +1. **Protocol Errors**: Standard JSON-RPC errors for protocol-level issues +1. **Task Execution Errors**: Errors in the underlying request execution, reported through task status + +### Protocol Errors + +Receivers **MUST** return standard JSON-RPC errors for the following protocol error cases: + +- Invalid or nonexistent `taskId` in `tasks/get`, `tasks/result`, or `tasks/cancel`: `-32602` (Invalid params) +- Invalid or nonexistent cursor in `tasks/list`: `-32602` (Invalid params) +- Attempt to cancel a task already in a terminal status: `-32602` (Invalid params) +- Internal errors: `-32603` (Internal error) + +Additionally, receivers **MAY** return the following errors: + +- Non-task-augmented request when receiver requires task augmentation for that request type: `-32600` (Invalid request) + +Receivers **SHOULD** provide informative error messages to describe the cause of errors. + +**Example: Task augmentation required** + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "error": { + "code": -32600, + "message": "Task augmentation required for tools/call requests" + } +} +``` + +**Example: Task not found** + +```json +{ + "jsonrpc": "2.0", + "id": 70, + "error": { + "code": -32602, + "message": "Failed to retrieve task: Task not found" + } +} +``` + +**Example: Task expired** + +```json +{ + "jsonrpc": "2.0", + "id": 71, + "error": { + "code": -32602, + "message": "Failed to retrieve task: Task has expired" + } +} +``` + + + +Receivers are not required to retain tasks indefinitely. It is compliant behavior for a receiver to return an error stating the task cannot be found if it has purged an expired task. + + + +**Example: Task cancellation rejected (already terminal)** + +```json +{ + "jsonrpc": "2.0", + "id": 74, + "error": { + "code": -32602, + "message": "Cannot cancel task: already in terminal status 'completed'" + } +} +``` + +### Task Execution Errors + +When the underlying request does not complete successfully, the task moves to the `failed` status. This includes JSON-RPC protocol errors during request execution, or for tool calls specifically, when the tool result has `isError` set to true. The `tasks/get` response **SHOULD** include a `statusMessage` field with diagnostic information about the failure. + +**Example: Task with execution error** + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "result": { + "taskId": "786512e2-9e0d-44bd-8f29-789f820fe840", + "status": "failed", + "createdAt": "2025-11-25T10:30:00Z", + "lastUpdatedAt": "2025-11-25T10:40:00Z", + "ttl": 30000, + "statusMessage": "Tool execution failed: API rate limit exceeded" + } +} +``` + +For tasks that wrap tool call requests, when the tool result has `isError` set to `true`, the task should reach `failed` status. + +The `tasks/result` endpoint returns exactly what the underlying request would have returned: + +- If the underlying request resulted in a JSON-RPC error, `tasks/result` **MUST** return that same JSON-RPC error. +- If the request completed with a JSON-RPC response, `tasks/result` **MUST** return a successful JSON-RPC response containing that result. + +## Security Considerations + +### Task Isolation and Access Control + +Task IDs are the primary mechanism for accessing task state and results. Without proper access controls, any party that can guess or obtain a task ID could potentially access sensitive information or manipulate tasks they did not create. + +When an authorization context is provided, receivers **MUST** bind tasks to said context. + +Context-binding is not practical for all applications. Some MCP servers operate in environments without authorization, such as single-user tools, or use transports that don't support authorization. +In these scenarios, receivers **SHOULD** document this limitation clearly, as task results may be accessible to any requestor that can guess the task ID. +If context-binding is unavailable, receivers **MUST** generate cryptographically secure task IDs with enough entropy to prevent guessing and should consider using shorter TTL durations to reduce the exposure window. +Furthermore, receivers that cannot identify requestors **SHOULD NOT** declare the `tasks.list` capability, as listing tasks would expose task metadata to any requestor regardless of task ID entropy. + +If context-binding is available, receivers **MUST** reject `tasks/get`, `tasks/result`, and `tasks/cancel` requests for tasks that do not belong to the same authorization context as the requestor. For `tasks/list` requests, receivers **MUST** ensure the returned task list includes only tasks associated with the requestor's authorization context. + +Additionally, receivers **SHOULD** implement rate limiting on task operations to prevent denial-of-service and enumeration attacks. + +### Resource Management + +1. Receivers **SHOULD**: + 1. Enforce limits on concurrent tasks per requestor + 1. Enforce maximum `ttl` durations to prevent indefinite resource retention + 1. Clean up expired tasks promptly to free resources + 1. Document maximum supported `ttl` duration + 1. Document maximum concurrent tasks per requestor + 1. Implement monitoring and alerting for resource usage + +### Audit and Logging + +1. Receivers **SHOULD**: + 1. Log task creation, completion, and retrieval events for audit purposes + 1. Include auth context in logs when available + 1. Monitor for suspicious patterns (e.g., many failed task lookups, excessive polling) +1. Requestors **SHOULD**: + 1. Log task lifecycle events for debugging and audit purposes + 1. Track task IDs and their associated operations diff --git a/schema/draft/2025-11-25/schema.json b/schema/draft/2025-11-25/schema.json new file mode 100644 index 0000000..fa1d2ba --- /dev/null +++ b/schema/draft/2025-11-25/schema.json @@ -0,0 +1,405 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "MCP Tasks Extension Schema. Contains only task-related definitions from the MCP 2025-11-25 specification. Base types (Result, JSONRPCRequest, JSONRPCNotification, etc.) are defined in the core MCP schema.", + "$comment": "Extension Identifier: io.modelcontextprotocol/tasks. Core MCP types (Result, JSONRPCRequest, etc.) are structurally inlined in this schema for portability. The authoritative definitions are in the core schema at https://github.com/modelcontextprotocol/modelcontextprotocol/tree/main/schema/2025-11-25", + "$defs": { + "TaskStatus": { + "description": "The status of a task.", + "enum": ["working", "input_required", "completed", "failed", "cancelled"], + "type": "string" + }, + "TaskMetadata": { + "description": "Metadata for augmenting a request with task execution.\nInclude this in the `task` field of the request parameters.", + "properties": { + "ttl": { + "description": "Requested duration in milliseconds to retain task from creation.", + "type": "integer" + } + }, + "type": "object" + }, + "RelatedTaskMetadata": { + "description": "Metadata for associating messages with a task.\nInclude this in the `_meta` field under the key `io.modelcontextprotocol/related-task`.", + "properties": { + "taskId": { + "description": "The task identifier this message is associated with.", + "type": "string" + } + }, + "required": ["taskId"], + "type": "object" + }, + "Task": { + "description": "Data associated with a task.", + "properties": { + "taskId": { + "description": "The task identifier.", + "type": "string" + }, + "status": { + "$ref": "#/$defs/TaskStatus", + "description": "Current task state." + }, + "statusMessage": { + "description": "Optional human-readable message describing the current task state.\nThis can provide context for any status, including:\n- Reasons for \"cancelled\" status\n- Summaries for \"completed\" status\n- Diagnostic information for \"failed\" status (e.g., error details, what went wrong)", + "type": "string" + }, + "createdAt": { + "description": "ISO 8601 timestamp when the task was created.", + "type": "string" + }, + "lastUpdatedAt": { + "description": "ISO 8601 timestamp when the task was last updated.", + "type": "string" + }, + "ttl": { + "description": "Actual retention duration from creation in milliseconds, null for unlimited.", + "type": ["integer", "null"] + }, + "pollInterval": { + "description": "Suggested polling interval in milliseconds.", + "type": "integer" + } + }, + "required": ["taskId", "status", "createdAt", "lastUpdatedAt", "ttl"], + "type": "object" + }, + "CreateTaskResult": { + "description": "A response to a task-augmented request.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See the core MCP specification for notes on `_meta` usage.", + "type": "object" + }, + "task": { + "$ref": "#/$defs/Task" + } + }, + "required": ["task"], + "type": "object" + }, + "TaskAugmentedRequestParams": { + "description": "Common params for any task-augmented request.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See the core MCP specification for notes on `_meta` usage.", + "properties": { + "progressToken": { + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications.", + "type": ["string", "integer"] + } + }, + "type": "object" + }, + "task": { + "$ref": "#/$defs/TaskMetadata", + "description": "If specified, the caller is requesting task-augmented execution for this request.\nThe request will return a CreateTaskResult immediately, and the actual result can be retrieved later via tasks/result.\n\nTask augmentation is subject to capability negotiation - receivers MUST declare support for task augmentation of specific request types in their capabilities." + } + }, + "type": "object" + }, + "GetTaskRequest": { + "description": "A request to retrieve the state of a task.", + "properties": { + "id": { + "description": "A unique identifier for the request.", + "type": ["string", "integer"] + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "tasks/get", + "type": "string" + }, + "params": { + "properties": { + "taskId": { + "description": "The task identifier to query.", + "type": "string" + } + }, + "required": ["taskId"], + "type": "object" + } + }, + "required": ["id", "jsonrpc", "method", "params"], + "type": "object" + }, + "GetTaskResult": { + "description": "The response to a tasks/get request.", + "allOf": [ + { + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See the core MCP specification for notes on `_meta` usage.", + "type": "object" + } + }, + "type": "object" + }, + { + "$ref": "#/$defs/Task" + } + ] + }, + "GetTaskPayloadRequest": { + "description": "A request to retrieve the result of a completed task.", + "properties": { + "id": { + "description": "A unique identifier for the request.", + "type": ["string", "integer"] + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "tasks/result", + "type": "string" + }, + "params": { + "properties": { + "taskId": { + "description": "The task identifier to retrieve results for.", + "type": "string" + } + }, + "required": ["taskId"], + "type": "object" + } + }, + "required": ["id", "jsonrpc", "method", "params"], + "type": "object" + }, + "GetTaskPayloadResult": { + "description": "The response to a tasks/result request.\nThe structure matches the result type of the original request.\nFor example, a tools/call task would return the CallToolResult structure.", + "additionalProperties": {}, + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See the core MCP specification for notes on `_meta` usage.", + "type": "object" + } + }, + "type": "object" + }, + "CancelTaskRequest": { + "description": "A request to cancel a task.", + "properties": { + "id": { + "description": "A unique identifier for the request.", + "type": ["string", "integer"] + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "tasks/cancel", + "type": "string" + }, + "params": { + "properties": { + "taskId": { + "description": "The task identifier to cancel.", + "type": "string" + } + }, + "required": ["taskId"], + "type": "object" + } + }, + "required": ["id", "jsonrpc", "method", "params"], + "type": "object" + }, + "CancelTaskResult": { + "description": "The response to a tasks/cancel request.", + "allOf": [ + { + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See the core MCP specification for notes on `_meta` usage.", + "type": "object" + } + }, + "type": "object" + }, + { + "$ref": "#/$defs/Task" + } + ] + }, + "ListTasksRequest": { + "description": "A request to retrieve a list of tasks.", + "properties": { + "id": { + "description": "A unique identifier for the request.", + "type": ["string", "integer"] + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "tasks/list", + "type": "string" + }, + "params": { + "properties": { + "cursor": { + "description": "An opaque token representing the current pagination position.", + "type": "string" + } + }, + "type": "object" + } + }, + "required": ["id", "jsonrpc", "method"], + "type": "object" + }, + "ListTasksResult": { + "description": "The response to a tasks/list request.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See the core MCP specification for notes on `_meta` usage.", + "type": "object" + }, + "nextCursor": { + "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", + "type": "string" + }, + "tasks": { + "items": { + "$ref": "#/$defs/Task" + }, + "type": "array" + } + }, + "required": ["tasks"], + "type": "object" + }, + "TaskStatusNotificationParams": { + "description": "Parameters for a `notifications/tasks/status` notification.", + "allOf": [ + { + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See the core MCP specification for notes on `_meta` usage.", + "type": "object" + } + }, + "type": "object" + }, + { + "$ref": "#/$defs/Task" + } + ] + }, + "TaskStatusNotification": { + "description": "An optional notification from the receiver to the requestor, informing them that a task's status has changed. Receivers are not required to send these notifications.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/tasks/status", + "type": "string" + }, + "params": { + "$ref": "#/$defs/TaskStatusNotificationParams" + } + }, + "required": ["jsonrpc", "method", "params"], + "type": "object" + }, + "TaskServerCapabilities": { + "description": "Task-related server capabilities. Include this in the server's capabilities object during initialization.", + "properties": { + "tasks": { + "properties": { + "list": { + "description": "Whether this server supports tasks/list.", + "type": "object" + }, + "cancel": { + "description": "Whether this server supports tasks/cancel.", + "type": "object" + }, + "requests": { + "properties": { + "tools": { + "properties": { + "call": { + "description": "Whether the server supports task-augmented tools/call requests.", + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "TaskClientCapabilities": { + "description": "Task-related client capabilities. Include this in the client's capabilities object during initialization.", + "properties": { + "tasks": { + "properties": { + "list": { + "description": "Whether this client supports tasks/list.", + "type": "object" + }, + "cancel": { + "description": "Whether this client supports tasks/cancel.", + "type": "object" + }, + "requests": { + "properties": { + "sampling": { + "properties": { + "createMessage": { + "description": "Whether the client supports task-augmented sampling/createMessage requests.", + "type": "object" + } + }, + "type": "object" + }, + "elicitation": { + "properties": { + "create": { + "description": "Whether the client supports task-augmented elicitation/create requests.", + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "ToolTaskSupport": { + "description": "Task support declaration for individual tools.\n- \"forbidden\": Tool does not support task-augmented execution (default when absent)\n- \"optional\": Tool may support task-augmented execution\n- \"required\": Tool requires task-augmented execution", + "enum": ["forbidden", "optional", "required"], + "type": "string" + } + } +} diff --git a/schema/draft/2025-11-25/schema.ts b/schema/draft/2025-11-25/schema.ts new file mode 100644 index 0000000..8f58da1 --- /dev/null +++ b/schema/draft/2025-11-25/schema.ts @@ -0,0 +1,425 @@ +/** + * MCP Tasks Extension Schema + * Extension Identifier: io.modelcontextprotocol/tasks + * + * This file contains only the task-related types from the MCP 2025-11-25 specification. + * Base types (Result, JSONRPCRequest, JSONRPCNotification, etc.) are defined in the + * core MCP schema at https://github.com/modelcontextprotocol/modelcontextprotocol + * + * @see https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks + */ + +// ============================================================================ +// Base type stubs (defined in core MCP schema) +// ============================================================================ +// +// The following types are minimal stubs of core MCP types, provided solely so +// that this file compiles. They are NOT part of the Tasks extension API. +// Implementations MUST use the authoritative definitions from the core schema: +// https://github.com/modelcontextprotocol/modelcontextprotocol/tree/main/schema/2025-11-25 +// ============================================================================ + +/** + * @internal Core MCP type stub — see core schema for authoritative definition. + */ +type ProgressToken = string | number; + +/** + * @internal Core MCP type stub — see core schema for authoritative definition. + */ +type Cursor = string; + +/** + * @internal Core MCP type stub — see core schema for authoritative definition. + */ +type RequestId = string | number; + +/** + * @internal Core MCP type stub — see core schema for authoritative definition. + */ +interface RequestParams { + _meta?: { + progressToken?: ProgressToken; + [key: string]: unknown; + }; +} + +/** + * @internal Core MCP type stub — see core schema for authoritative definition. + */ +interface NotificationParams { + _meta?: { [key: string]: unknown }; +} + +/** + * @internal Core MCP type stub — see core schema for authoritative definition. + */ +interface Result { + _meta?: { [key: string]: unknown }; + [key: string]: unknown; +} + +/** + * @internal Core MCP type stub — see core schema for authoritative definition. + */ +interface Request { + method: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + params?: { [key: string]: any }; +} + +/** + * @internal Core MCP type stub — see core schema for authoritative definition. + */ +interface Notification { + method: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + params?: { [key: string]: any }; +} + +/** + * @internal Core MCP type stub — see core schema for authoritative definition. + */ +interface JSONRPCRequest extends Request { + jsonrpc: "2.0"; + id: RequestId; +} + +/** + * @internal Core MCP type stub — see core schema for authoritative definition. + */ +interface JSONRPCNotification extends Notification { + jsonrpc: "2.0"; +} + +/** + * @internal Core MCP type stub — see core schema for authoritative definition. + */ +interface PaginatedRequest extends JSONRPCRequest { + params?: RequestParams & { + cursor?: Cursor; + }; +} + +/** + * @internal Core MCP type stub — see core schema for authoritative definition. + */ +interface PaginatedResult extends Result { + nextCursor?: Cursor; +} + +/* Tasks */ + +/** + * The status of a task. + * + * @category `tasks` + */ +export type TaskStatus = + | "working" // The request is currently being processed + | "input_required" // The task is waiting for input (e.g., elicitation or sampling) + | "completed" // The request completed successfully and results are available + | "failed" // The associated request did not complete successfully. For tool calls specifically, this includes cases where the tool call result has `isError` set to true. + | "cancelled"; // The request was cancelled before completion + +/** + * Metadata for augmenting a request with task execution. + * Include this in the `task` field of the request parameters. + * + * @category `tasks` + */ +export interface TaskMetadata { + /** + * Requested duration in milliseconds to retain task from creation. + */ + ttl?: number; +} + +/** + * Metadata for associating messages with a task. + * Include this in the `_meta` field under the key `io.modelcontextprotocol/related-task`. + * + * @category `tasks` + */ +export interface RelatedTaskMetadata { + /** + * The task identifier this message is associated with. + */ + taskId: string; +} + +/** + * Data associated with a task. + * + * @category `tasks` + */ +export interface Task { + /** + * The task identifier. + */ + taskId: string; + + /** + * Current task state. + */ + status: TaskStatus; + + /** + * Optional human-readable message describing the current task state. + * This can provide context for any status, including: + * - Reasons for "cancelled" status + * - Summaries for "completed" status + * - Diagnostic information for "failed" status (e.g., error details, what went wrong) + */ + statusMessage?: string; + + /** + * ISO 8601 timestamp when the task was created. + */ + createdAt: string; + + /** + * ISO 8601 timestamp when the task was last updated. + */ + lastUpdatedAt: string; + + /** + * Actual retention duration from creation in milliseconds, null for unlimited. + * @nullable + */ + ttl: number | null; + + /** + * Suggested polling interval in milliseconds. + */ + pollInterval?: number; +} + +/** + * A response to a task-augmented request. + * + * @category `tasks` + */ +export interface CreateTaskResult extends Result { + task: Task; +} + +/* Task Operations */ + +/** + * A request to retrieve the state of a task. + * + * @category `tasks/get` + */ +export interface GetTaskRequest extends JSONRPCRequest { + method: "tasks/get"; + params: { + /** + * The task identifier to query. + */ + taskId: string; + }; +} + +/** + * The response to a tasks/get request. + * + * @category `tasks/get` + */ +export type GetTaskResult = Result & Task; + +/** + * A request to retrieve the result of a completed task. + * + * @category `tasks/result` + */ +export interface GetTaskPayloadRequest extends JSONRPCRequest { + method: "tasks/result"; + params: { + /** + * The task identifier to retrieve results for. + */ + taskId: string; + }; +} + +/** + * The response to a tasks/result request. + * The structure matches the result type of the original request. + * For example, a tools/call task would return the CallToolResult structure. + * + * @category `tasks/result` + */ +export interface GetTaskPayloadResult extends Result { + [key: string]: unknown; +} + +/** + * A request to cancel a task. + * + * @category `tasks/cancel` + */ +export interface CancelTaskRequest extends JSONRPCRequest { + method: "tasks/cancel"; + params: { + /** + * The task identifier to cancel. + */ + taskId: string; + }; +} + +/** + * The response to a tasks/cancel request. + * + * @category `tasks/cancel` + */ +export type CancelTaskResult = Result & Task; + +/** + * A request to retrieve a list of tasks. + * + * @category `tasks/list` + */ +export interface ListTasksRequest extends PaginatedRequest { + method: "tasks/list"; +} + +/** + * The response to a tasks/list request. + * + * @category `tasks/list` + */ +export interface ListTasksResult extends PaginatedResult { + tasks: Task[]; +} + +/* Task Notifications */ + +/** + * Parameters for a `notifications/tasks/status` notification. + * + * @category `notifications/tasks/status` + */ +export type TaskStatusNotificationParams = NotificationParams & Task; + +/** + * An optional notification from the receiver to the requestor, informing them that a task's status has changed. Receivers are not required to send these notifications. + * + * @category `notifications/tasks/status` + */ +export interface TaskStatusNotification extends JSONRPCNotification { + method: "notifications/tasks/status"; + params: TaskStatusNotificationParams; +} + +/* Task-Augmented Request Params */ + +/** + * Common params for any task-augmented request. + * Extends the base RequestParams to include an optional `task` field. + * + * @category `tasks` + */ +export interface TaskAugmentedRequestParams extends RequestParams { + /** + * If specified, the caller is requesting task-augmented execution for this request. + * The request will return a CreateTaskResult immediately, and the actual result can be + * retrieved later via tasks/result. + * + * Task augmentation is subject to capability negotiation - receivers MUST declare support + * for task augmentation of specific request types in their capabilities. + */ + task?: TaskMetadata; +} + +/* Capability Additions */ + +/** + * Task-related server capabilities. + * Include this in the server's `capabilities` object during initialization. + * + * @category `tasks` + */ +export interface TaskServerCapabilities { + tasks?: { + /** + * Whether this server supports tasks/list. + */ + list?: Record; + /** + * Whether this server supports tasks/cancel. + */ + cancel?: Record; + /** + * Specifies which request types can be augmented with tasks. + */ + requests?: { + /** + * Task support for tool-related requests. + */ + tools?: { + /** + * Whether the server supports task-augmented tools/call requests. + */ + call?: Record; + }; + }; + }; +} + +/** + * Task-related client capabilities. + * Include this in the client's `capabilities` object during initialization. + * + * @category `tasks` + */ +export interface TaskClientCapabilities { + tasks?: { + /** + * Whether this client supports tasks/list. + */ + list?: Record; + /** + * Whether this client supports tasks/cancel. + */ + cancel?: Record; + /** + * Specifies which request types can be augmented with tasks. + */ + requests?: { + /** + * Task support for sampling-related requests. + */ + sampling?: { + /** + * Whether the client supports task-augmented sampling/createMessage requests. + */ + createMessage?: Record; + }; + /** + * Task support for elicitation-related requests. + */ + elicitation?: { + /** + * Whether the client supports task-augmented elicitation/create requests. + */ + create?: Record; + }; + }; + }; +} + +/* Tool-Level Task Support */ + +/** + * Task support declaration for individual tools. + * Include this in the tool's definition returned by tools/list. + * + * - "forbidden": Tool does not support task-augmented execution (default when absent) + * - "optional": Tool may support task-augmented execution + * - "required": Tool requires task-augmented execution + * + * @category `tasks` + */ +export type ToolTaskSupport = "forbidden" | "optional" | "required"; diff --git a/seps/1686-tasks.md b/seps/1686-tasks.md new file mode 100644 index 0000000..62bd932 --- /dev/null +++ b/seps/1686-tasks.md @@ -0,0 +1,1083 @@ +# SEP-1686: Tasks + +- **Status**: Final +- **Type**: Standards Track +- **Created**: 2025-10-20 +- **Author(s)**: Surbhi Bansal, Luca Chang +- **Issue**: #1686 + +## Abstract + +This SEP improves support for task-based workflows in the Model Context Protocol (MCP). It introduces both the **task primitive** and the associated **task ID**, which can be used to query the state and results of a task, up to a server-defined duration after the task has completed. This primitive is designed to augment other requests (such as tool calls) to enable call-now, fetch-later execution patterns across all requests for servers that support this primitive. + +## Motivation + +The current MCP specification supports tool calls that execute a request and eventually receive a response, and tool calls can be passed a progress token to integrate with MCP’s progress-tracking functionality, enabling host applications to receive status updates for a tool call via notifications. However, there is no way for a client to explicitly request the status of a tool call, resulting in states where it is possible for a tool call to have been dropped on the server, and it is unknown if a response or a notification may ever arrive. Similarly, there is no way for a client to explicitly retrieve the result of a tool call after it has completed — if the result was dropped, clients must call the tool again, which is undesirable for tools expected to take minutes or more. This is particularly relevant for MCP servers abstracting existing workflow-based APIs, such as AWS Step Functions, Workflows for Google Cloud, or APIs representing CI/CD pipelines, among other applications. + +Today, it is possible for individual MCP servers to represent tools in a way that enables this, with certain compromises. For example, a server may expose a `long_running_tool` and wish to support this pattern, splitting it into three separate tools to accommodate this: + +1. `start_long_running_tool`: This would start the work represented by `long_running_tool` and return a tracking token of some kind, such as a job ID. +2. `get_long_running_tool_status(token)`: This would accept the tracking token and return the current status of the tool call, informing the caller that the operation is still ongoing. +3. `get_long_running_tool_result(token)`: This would accept the tracking token and return the result of the tool call, if it is available. + +Representing a tool in this way seems to solve for the use case, but it introduces a new problem: Tools are generally-expected to be orchestrated by an agent, and agent-driven polling is both unnecessarily expensive and inconsistent — it relies on prompt engineering to steer an agent to poll at all. In the original `long_running_tool` case, the client had no way of knowing if a response would ever be received, while in the `start_long_running_tool` case, the application has no way of knowing if the agent will orchestrate tools according to the specific contract of the server. + +It is also impossible for the host application to take ownership of this orchestration, as this tool-splitting is both conventions-based and may be implemented in different ways across MCP servers — one server may have three tools for one conceptual operation (as in our example), or it may have more, in the case of more complex, multi-step operations. + +On the other hand, if active task polling is not needed, existing MCP servers can fully-wrap a workflow API in a single tool call that polls for a result, but this introduces an undesirable implementation cost: an MCP server wrapping an existing workflow API is a server that only exists for polling other systems. + +**Affected Customer Use Cases** +These concerns are backed by real use cases that Amazon has seen both internally and with their external customers (identities redacted where non-public): + +**1. Healthcare & Life Sciences Data Analysis** +**_Challenge:_** Amazon’s customers in the healthcare and life sciences industry are attempting to use MCP to wrap existing computational tools to analyze molecular properties and predict drug interactions, processing hundreds of thousands of data points per job from chemical libraries through multiple inference models simultaneously. These complex, multi-step workflows require a way to actively check statuses, as they take upwards of several hours, making retries undesirable. +**_Current Workaround:_** Not yet determined. +**_Impact:_** Cannot integrate with real-time research workflows, prevents interactive drug discovery platforms, and blocks automated research pipelines. These customers are looking for best practices for workflow-based tool calls and have noted the lack of first-class support in MCP as a concern. If these customers do not have a solution for long-running tool calls, they will likely forego MCP and continue using their existing platforms. +**_Ideal:_** Concurrent and poll-able tool calls as an answer for operations executing in the range of a few minutes, and some form of push notification system to avoid blocking their agents on long analyses on the order of hours. This SEP supports the former use case, and offers a framework that could extend to support the latter. + +**2. Enterprise Automation Platforms** +**_Challenge:_** Amazon’s large enterprise customers are looking to develop internal MCP platforms to automate SDLC processes across their organizations, extending to sales, customer service, legal, HR, and cross-divisional teams. They have noted they have long-running agent and agent-tool interactions, supporting complex business process automation. +**_Current Workaround:_** Not yet determined. Considering an application-level system outside of MCP backed by webhooks. +**_Impact:_** Limitations related to the host application being unaware of tool execution state prevent complex business process automation and limit sophisticated multi-step operations. These customers want to dispatch processes concurrently and collect their results later, and are noting the lack of explicit late-retrieval as a concern — and are considering involved application-level notification systems as a possible workaround. +**_Ideal:_** Built-in mechanisms for actively checking the status of ongoing work to avoid needing to implement notification systems specific to their own tool conventions themselves. + +**3. Code Migration Workflows** +**_Challenge_:** Amazon has automated code migration and transformation tools to perform upgrades across its own codebases and those of external customers, and is attempting to wrap those tools in MCP servers. These migrations analyze dependencies, transform code to avoid deprecated runtime features, and validate changes across multiple repositories. These migrations range from minutes to hours depending on migration scope, complexity, and validation requirements. +**_Current Workaround:_** Developers implement manual tracking by splitting a job into `create` and `get` tools, forcing models to manage state and repeatedly poll for completion. +**_Impact:_** Poor developer experience due to needing to replicate this hand-rolled polling mechanism across many tools. One team had to debug an issue where the model would hallucinate job names if it hadn’t listed them first. Validating that this does not happen across many tools in a large toolset is time-consuming and error-prone. +**_Ideal:_** Support natively polling tool state at the data layer to support pushing a tool to the background and avoiding blocking other tasks in the chat session, while still supporting deterministic polling and result retrieval. The team needs the same pattern across many tools in their MCP servers, and wants a common solution across them, which this SEP directly supports. + +**4. Test Execution Platforms** +**_Challenge:_** Amazon’s internal test infrastructure executes comprehensive test suites including thousands of cases, integration tests across services, and performance benchmarks. They have built an MCP server wrapping this existing infrastructure. +**_Current Workaround:_** For streaming test logs, the MCP server exposes a tool that can read a range of log lines, as it cannot effectively notify the client when the execution is complete. There is not yet any workaround for executing test runs. +**_Impact:_** Cannot run a test suite and stream its logs simultaneously without a single hours-long tool call, which would time out on either the client or the server. This prevents agents from looking into test failures in an incomplete test run until the entire test suite has completed, potentially hours later. +**_Ideal:_** Support host application-driven tool polling for intermediate results, so a client can be notified when a long-running tool is complete. This SEP does not fully-support this use case (it does enable polling), but the Task execution model can be extended to do so, as discussed in the “Future Work” section. + +**5. Deep Research** +**_Challenge:_** Deep research tools spawn multiple research agents to gather and summarize information about topics, going through several rounds of search and conversation turns internally to produce a final result for the caller application. The tool takes an extended amount of time to execute, and it is not always clear if the tool is still executing. +**_Current Workaround:_** The research tool is split into a separate `create` tool to create a report job and a `get` tool to get the status/result of that job later. +**_Impact:_** When using this with host applications, the agent sometimes runs into issues calling the `get` tool repeatedly — in particular, it calls the tool once before ending its conversation turn, claiming to be "waiting" before calling the tool again. It cannot resume until receiving a new user message. This also complicates expiration times, as it is not possible to predict when the client will retrieve the result when this occurs. It is possible to work around this by adding a `wait` tool for the model, but this prevents the model from doing anything else concurrently. +**_Ideal:_** Support polling a tool call’s state in a deterministic way and notify the model when a result is ready, so the tool result can be immediately retrieved and deleted from the server. Other than notifying the model (a host application concern), this SEP fully supports this use case. + +**6. Agent-to-Agent Communication (Multi-Agent Systems)** +**_Challenge:_** One of Amazon’s internal multi-agent systems for customer question answering faces scenarios where agents require significant processing time for complex reasoning, research, or analysis. When agents communicate through MCP, slow agents cause cascading delays throughout this system, as agents are forced to wait on their peers to complete their work. +**_Current Workaround:_** Not yet determined. +**_Impact:_** Communication pattern creates cascading delays, prevents parallel agent processing, and degrades system responsiveness for other time-sensitive interactions. +**_Ideal:_** Some method to allow agents to perform other work concurrently and get notified once long-running tasks complete. This SEP supports this use case by enabling host applications to implement background polling for select tool calls without blocking agents. + +These use cases demonstrate that a mechanism to actively track tool calls and defer results is a real requirement for these types of MCP deployments in production environments. + +**Integration with Existing Architectures** +Many workflow-driven systems already provide active execution-tracking capabilities with built-in status metadata, monitoring, and data retention policies. This proposal enables MCP servers to expose these existing APIs with thin MCP wrappers while maintaining their existing reliability. + +**Benefits for Existing Architectures:** + +- **Leverage Existing State Management:** Systems like AWS Step Functions, Workflows for Google Cloud, and CI/CD platforms already maintain execution state, logs, and results. MCP servers can expose these systems' existing APIs without pushing the responsibility of polling to a fallible agent. +- **Preserve Native Monitoring:** Existing monitoring, alerting, and observability tools continue to work unchanged. The execution happens almost entirely within the existing workflow-management system. +- **Reduce Implementation Overhead:** Server implementers don't need to build new state management, persistence, or monitoring infrastructure. They can focus on the MCP protocol mapping of their existing APIs to tasks. + +This SEP simplifies integration with existing workflows and allows workflow services to continue to manage their own state while delivering a quality customer experience, rather than offloading to agent-polling or building MCP servers that do nothing but poll other services. + +## Specification + +This SEP introduces a mechanism for requestors (which can be either clients or servers, depending on the direction of communication) to augment their requests with **tasks**. Tasks are durable state machines that carry information about the underlying execution state of the request they wrap, and are intended for requestor polling and deferred result retrieval. Each task is uniquely identifiable by a requestor-generated **task ID**. + +### 1. User Interaction Model + +Tasks are designed to be **application-driven**—receivers tightly-control which requests (if any) support task-based execution and manage the lifecycles of those tasks; meanwhile, requestors own the responsibility for augmenting requests with tasks, and for polling on the results of those tasks. + +Implementations are free to expose tasks through any interface pattern that suits their needs—the protocol itself does not mandate any specific user interaction model. + +### 2. Capabilities + +Servers and clients that support task-augmented requests **MUST** declare a `tasks` capability during initialization. The `tasks` capability is structured by request category, with boolean properties indicating which specific request types support task augmentation. + +Refer to https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1732 for details. + +### 3. Protocol Messages + +#### 3.1. Creating Tasks + +To create a task, requestors send a request with the `modelcontextprotocol.io/task` key included in `_meta`, with a `taskId` value representing the task ID. Requestors **MAY** include a `keepAlive`, with a value representing how long after completion the requestor would like the task results to be kept for. + +**Request:** + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "some_method", + "params": { + "_meta": { + "modelcontextprotocol.io/task": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840", + "keepAlive": 60000 + } + } + } +} +``` + +#### 3.2. Getting Tasks + +To retrieve the state of a task, requestors send a `tasks/get` request: + +**Request:** + +```json +{ + "jsonrpc": "2.0", + "id": 3, + "method": "tasks/get", + "params": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840", + "_meta": { + "modelcontextprotocol.io/related-task": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840" + } + } + } +} +``` + +**Response:** + +```json +{ + "jsonrpc": "2.0", + "id": 3, + "result": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840", + "keepAlive": 30000, + "pollFrequency": 5000, + "status": "submitted", + "_meta": { + "modelcontextprotocol.io/related-task": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840" + } + } + } +} +``` + +#### 3.3. Retrieving Task Results + +To retrieve the result of a completed task, requestors send a `tasks/result` request: + +**Request:** + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "method": "tasks/result", + "params": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840", + "_meta": { + "modelcontextprotocol.io/related-task": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840" + } + } + } +} +``` + +**Response:** + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "result": { + "content": [ + { + "type": "text", + "text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy" + } + ], + "isError": false, + "_meta": { + "modelcontextprotocol.io/related-task": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840" + } + } + } +} +``` + +#### 3.4. Task Creation Notification + +When a receiver creates a task, it **MUST** send a `notifications/tasks/created` notification to inform the requestor that the task has been created and polling can begin. + +**Notification:** + +```json +{ + "jsonrpc": "2.0", + "method": "notifications/tasks/created", + "params": { + "_meta": { + "modelcontextprotocol.io/related-task": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840" + } + } + } +} +``` + +The task ID is conveyed through the `modelcontextprotocol.io/related-task` metadata key. The notification parameters are otherwise empty. + +This notification resolves the race condition where a requestor might attempt to poll for a task before the receiver has finished creating it. By sending this notification immediately after task creation, the receiver signals that the task is ready to be queried via `tasks/get`. + +Receivers that do not support tasks (and thus ignore task metadata in requests) will not send this notification, allowing requestors to fall back to waiting for the original request response. + +#### 3.5. Listing Tasks + +To retrieve a list of tasks, requestors send a `tasks/list` request. This operation supports pagination. + +**Request:** + +```json +{ + "jsonrpc": "2.0", + "id": 5, + "method": "tasks/list", + "params": { + "cursor": "optional-cursor-value" + } +} +``` + +**Response:** + +```json +{ + "jsonrpc": "2.0", + "id": 5, + "result": { + "tasks": [ + { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840", + "status": "working", + "keepAlive": 30000, + "pollFrequency": 5000 + }, + { + "taskId": "abc123-def456-ghi789", + "status": "completed", + "keepAlive": 60000 + } + ], + "nextCursor": "next-page-cursor" + } +} +``` + +#### 3.6 Deleting Tasks + +To explicitly delete a task and its associated results, requestors send a `tasks/delete` request. + +**Request:** + +```json +{ + "jsonrpc": "2.0", + "id": 6, + "method": "tasks/delete", + "params": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840", + "_meta": { + "modelcontextprotocol.io/related-task": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840" + } + } + } +} +``` + +**Response:** + +```json +{ + "jsonrpc": "2.0", + "id": 6, + "result": { + "_meta": { + "modelcontextprotocol.io/related-task": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840" + } + } + } +} +``` + +### 4. Behavior Requirements + +These requirements apply to all parties that support receiving task-augmented requests. + +#### 4.1. Task Support and Handling + +1. Receivers that do not support task augmentation on a request **MUST** process the request normally, ignoring any task metadata in `_meta`. +2. Receivers that support task augmentation **MAY** choose which request types support tasks. + +#### 4.2. Task ID Requirements + +1. Task IDs **MUST** be a string value. +2. Task IDs **SHOULD** be unique across all tasks controlled by the receiver. +3. The receiver of a request with a task ID in its `_meta` **MAY** validate that the provided task ID has not already been associated with a task controlled by that receiver. + +#### 4.3. Task Status Lifecycle + +1. Tasks **MUST** begin in the `submitted` status when created. +2. Receivers **MUST** only transition tasks through the following valid paths: + 1. From `submitted`: may move to `working`, `input_required`, `completed`, `failed`, `cancelled`, or `unknown` + 2. From `working`: may move to `input_required`, `completed`, `failed`, `cancelled`, or `unknown` + 3. From `input_required`: may move to `working`, `completed`, `failed`, `cancelled`, or `unknown` + 4. Tasks in `completed`, `failed`, `cancelled`, or `unknown` status **MUST NOT** transition to any other status (terminal states) +3. Receivers **MAY** move directly from `submitted` to `completed` if execution completes immediately. +4. The `unknown` status is a terminal fallback state for unexpected error conditions. Receivers **SHOULD** use `failed` with an error message instead when possible. + +**Task Status State Diagram:** + +```mermaid +stateDiagram-v2 + [*] --> submitted + + submitted --> working + submitted --> terminal + + working --> input_required + working --> terminal + + input_required --> working + input_required --> terminal + + terminal --> [*] + + note right of terminal + Terminal states: + • completed + • failed + • cancelled + • unknown + end note +``` + +#### 4.4. Input Required Status + +1. When a receiver sends a request associated with a task (e.g., elicitation, sampling), the receiver **MUST** move the task to the `input_required` status. +2. The receiver **MUST** include the `modelcontextprotocol.io/related-task` metadata in the request to associate it with the task. +3. When the receiver receives all required responses, the task **MAY** transition out of `input_required` status (typically back to `working`). +4. If multiple related requests are pending, the task **SHOULD** remain in `input_required` status until all are resolved. + +#### 4.5. Keep-Alive and Resource Management + +1. Receivers **MAY** override the requested `keepAlive` duration. +2. Receivers **MUST** include the actual `keepAlive` duration (or `null` for unlimited) in `tasks/get` responses. +3. After a task reaches a terminal status (`completed`, `failed`, or `cancelled`) and its `keepAlive` duration has elapsed, receivers **MAY** delete the task and its results. +4. Receivers **MAY** include a `pollFrequency` value (in milliseconds) in `tasks/get` responses to suggest polling intervals. Requestors **SHOULD** respect this value when provided. + +#### 4.6. Result Retrieval + +1. Receivers **MUST** only return results from `tasks/result` when the task status is `completed`. +2. Receivers **MUST** return an error if `tasks/result` is called for a task in any other status. +3. Requestors **MAY** call `tasks/result` multiple times for the same task while it remains available. + +#### 4.7. Associating Task-Related Messages + +1. All requests, notifications, and responses related to a task **MUST** include the `modelcontextprotocol.io/related-task` key in their `_meta`, with the value set to an object with a `taskId` matching the associated task ID. +2. For example, an elicitation that a task-augmented tool call depends on **MUST** share the same related task ID with that tool call's task. + +#### 4.8. Task Cancellation + +1. When a receiver receives a `notifications/cancelled` notification for the JSON-RPC request ID of a task-augmented request, the receiver **SHOULD** immediately move the task to the `cancelled` status and cease all processing associated with that task. +2. Due to the asynchronous nature of notifications, receivers **MAY** not cancel task processing instantaneously. Receivers **SHOULD** make a best-effort attempt to halt execution as quickly as possible. +3. If a `notifications/cancelled` notification arrives after a task has already reached a terminal status (`completed`, `failed`, `cancelled`, or `unknown`), receivers **SHOULD** ignore the notification. +4. After a task reaches `cancelled` status and its `keepAlive` duration has elapsed, receivers **MAY** delete the task and its metadata. +5. Requestors **MAY** send `notifications/cancelled` at any time during task execution, including when the task is in `input_required` status. If a task is cancelled while in `input_required` status, receivers **SHOULD** also disregard any pending responses to associated requests. +6. Because notifications do not provide confirmation of receipt, requestors **SHOULD** continue to poll with `tasks/get` after sending a cancellation notification to confirm the task has transitioned to `cancelled` status. If the task does not transition to `cancelled` within a reasonable timeframe, requestors **MAY** assume the cancellation was not processed. + +#### 4.9. Task Listing + +1. Receivers **SHOULD** use cursor-based pagination to limit the number of tasks returned in a single response. +2. Receivers **MUST** include a `nextCursor` in the response if more tasks are available. +3. Requestors **MUST** treat cursors as opaque tokens and not attempt to parse or modify them. +4. If a task is retrievable via `tasks/get` for a requestor, it **MUST** be retrievable via `tasks/list` for that requestor. + +#### 4.10 Task Deletion + +1. Receivers **MAY** accept or reject delete requests for any task at their discretion. +1. If a receiver accepts a delete request, it **SHOULD** delete the task and all associated results and metadata. +1. Receivers **MAY** choose not to support deletion at all, or only support deletion for tasks in certain statuses (e.g., only terminal statuses). +1. Requestors **SHOULD** delete tasks containing sensitive data promptly rather than relying solely on `keepAlive` expiration for cleanup. + +### 5. Message Flow + +https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1686#issuecomment-3452378176 + +### 6. Data Types + +#### Task + +A task represents the execution state of a request. The task metadata includes: + +- `taskId`: Unique identifier for the task +- `keepAlive`: Time in milliseconds that results will be kept available after completion +- `pollFrequency`: Suggested time in milliseconds between status checks +- `status`: Current state of the task execution + +#### Task Status + +Tasks can be in one of the following states: + +- `submitted`: The request has been received and queued for execution +- `working`: The request is currently being processed +- `input_required`: The request is waiting on additional input from the requestor +- `completed`: The request completed successfully and results are available +- `failed`: The task lifecycle itself encountered an error, unrelated to the associated request logic +- `cancelled`: The request was cancelled before completion +- `unknown`: A terminal fallback state for unexpected error conditions when the receiver cannot determine the actual task state + +#### Task Metadata + +When augmenting a request with task execution, the `modelcontextprotocol.io/task` key is included in `_meta`: + +```json +{ + "modelcontextprotocol.io/task": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840", + "keepAlive": 60000 + } +} +``` + +Fields: + +- `taskId` (string, required): Client-generated unique identifier for the task +- `keepAlive` (number, optional): Requested duration in milliseconds to retain results after completion + +#### Task Creation Notification + +When a receiver creates a task, it sends a `notifications/tasks/created` notification to signal that the task is ready for polling. The notification has empty params, with the task ID conveyed through the `modelcontextprotocol.io/related-task` metadata key: + +```json +{ + "jsonrpc": "2.0", + "method": "notifications/tasks/created", + "params": { + "_meta": { + "modelcontextprotocol.io/related-task": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840" + } + } + } +} +``` + +This notification enables requestors to begin polling without encountering race conditions where the task might not yet exist on the receiver. + +#### Task Get Request + +The `tasks/get` request retrieves the current state of a task: + +```typescript +{ + taskId: string; // The task identifier to query +} +``` + +#### Task Get Response + +The `tasks/get` response includes: + +```typescript +{ + taskId: string; // The task identifier + status: TaskStatus; // Current task state + keepAlive: number | null; // Actual retention duration in milliseconds, null for unlimited + pollFrequency?: number; // Suggested polling interval in milliseconds + error?: string; // Error message if status is "failed" +} +``` + +#### Task Result Request + +The `tasks/result` request retrieves the result of a completed task: + +```typescript +{ + taskId: string; // The task identifier to retrieve results for +} +``` + +#### Task Result Response + +The `tasks/result` response returns the original result that would have been returned by the request: + +```typescript +{ + // The structure matches the result type of the original request + // For example, a tools/call task would return CallToolResult structure + [key: string]: unknown; +} +``` + +The result structure depends on the original request type. The receiver returns the same result structure that would have been returned if the request had been executed without task augmentation. + +#### Task List Request + +The `tasks/list` request retrieves a list of tasks: + +```typescript +{ + cursor?: string; // Optional cursor for pagination +} +``` + +#### Task List Response + +The `tasks/list` response includes: + +```typescript +{ + tasks: Array<{ + taskId: string; // The task identifier + status: TaskStatus; // Current task state + keepAlive: number | null; // Retention duration in milliseconds, null for unlimited + pollFrequency?: number; // Suggested polling interval in milliseconds + error?: string; // Error message if status is "failed" + }>; + nextCursor?: string; // Cursor for next page, absent if no more results +} +``` + +#### Related Task Metadata + +All requests, responses, and notifications associated with a task **MUST** include the `modelcontextprotocol.io/related-task` key in `_meta`: + +```json +{ + "modelcontextprotocol.io/related-task": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840" + } +} +``` + +This associates messages with their originating task across the entire request lifecycle. + +### 7. Error Handling + +Tasks use two error reporting mechanisms: + +1. **Protocol Errors**: Standard JSON-RPC errors for protocol-level issues +2. **Task Execution Errors**: Errors in the underlying request execution, reported through task status + +#### 7.1. Protocol Errors + +Receivers **MUST** return standard JSON-RPC errors for the following protocol error cases: + +- Invalid or nonexistent `taskId` in `tasks/get`, `tasks/list`, or `tasks/result`: `-32602` (Invalid params) +- Invalid or nonexistent cursor in `tasks/list`: `-32602` (Invalid params) +- Request with a `taskId` that was already used for a different task (if the receiver validates task ID uniqueness): `-32602` (Invalid params) +- Attempting to retrieve result when task is not in `completed` status: `-32602` (Invalid params) +- Internal errors: `-32603` (Internal error) + +Receivers **SHOULD** provide informative error messages to describe the cause of errors. + +**Example: Task not found** + +```json +{ + "jsonrpc": "2.0", + "id": 70, + "error": { + "code": -32602, + "message": "Failed to retrieve task: Task not found" + } +} +``` + +**Example: Task expired** + +```json +{ + "jsonrpc": "2.0", + "id": 71, + "error": { + "code": -32602, + "message": "Failed to retrieve task: Task has expired" + } +} +``` + +> NOTE: Receivers are not obligated to retain task metadata indefinitely. It is compliant behavior for a receiver to return a "not-found" error if it has purged an expired task. + +**Example: Result requested for incomplete task** + +```json +{ + "jsonrpc": "2.0", + "id": 72, + "error": { + "code": -32602, + "message": "Cannot retrieve result: Task status is 'working', not 'completed'" + } +} +``` + +**Example: Duplicate task ID (if receiver validates uniqueness)** + +```json +{ + "jsonrpc": "2.0", + "id": 73, + "error": { + "code": -32602, + "message": "Task ID already exists: 786512e2-9e0d-44bd-8f29-789f320fe840" + } +} +``` + +#### 7.2. Task Execution Errors + +When the underlying request fails during execution, the task moves to the `failed` status. The `tasks/get` response **SHOULD** include an `error` field with details about the failure: + +```typescript +{ + taskId: string; + status: "failed"; + keepAlive: number | null; + pollFrequency?: number; + error?: string; // Description of what went wrong +} +``` + +**Example: Task with execution error** + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "result": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840", + "status": "failed", + "keepAlive": 30000, + "error": "Tool execution failed: API rate limit exceeded" + } +} +``` + +For tasks that wrap requests with their own error semantics (like `tools/call` with `isError: true`), the task should still reach `completed` status, and the error information is conveyed through the result structure of the original request type. + +### 8. Security Considerations + +#### 8.1. Task Isolation and Access Control + +1. Receivers **SHOULD** scope task IDs to prevent unauthorized access: + 1. Bind tasks to the session that created them (if sessions are supported) + 2. Bind tasks to the authentication context (if authentication is used) + 3. Reject `tasks/get`, `tasks/list`, or `tasks/result` requests for tasks from different sessions or auth contexts +2. Receivers that do not implement session or authentication binding **SHOULD** document this limitation clearly, as task results may be accessible to any requestor that can guess the task ID. +3. Receivers **SHOULD** implement rate limiting on: + 1. Task creation to prevent resource exhaustion + 2. Task status polling to prevent denial of service + 3. Task result retrieval attempts + 4. Task listing requests to prevent denial of service + +#### 8.2. Resource Management + +> WARNING: Task results may persist longer than the original request execution time. For sensitive operations, requestors should carefully consider the security implications of extended result retention and may want to retrieve results promptly and request shorter `keepAlive` durations. + +1. Receivers **SHOULD**: + 1. Enforce limits on concurrent tasks per requestor + 2. Enforce maximum `keepAlive` durations to prevent indefinite resource retention + 3. Clean up expired tasks promptly to free resources +2. Receivers **SHOULD**: + 1. Document maximum supported `keepAlive` duration + 2. Document maximum concurrent tasks per requestor + 3. Implement monitoring and alerting for resource usage + +#### 8.3. Audit and Logging + +1. Receivers **SHOULD**: + 1. Log task creation, completion, and retrieval events for audit purposes + 2. Include session/auth context in logs when available + 3. Monitor for suspicious patterns (e.g., many failed task lookups, excessive polling) +2. Requestors **SHOULD**: + 1. Log task lifecycle events for debugging and audit purposes + 2. Track task IDs and their associated operations + +## Rationale + +### Design Decision: Generic Task Primitive + +The decision to implement tasks as a generic request augmentation mechanism (rather than tool-specific or method-specific) was made to maximize protocol simplicity and flexibility. + +Tasks are designed to work with any request type in the MCP protocol, not just tool calls. This means that `resources/read`, `prompts/get`, `sampling/createMessage`, and any future request types can all be augmented with task metadata. This approach provides significant benefits over a tool-specific design. + +From a protocol perspective, this design eliminates the need for separate task implementations per request type. Instead of defining different async patterns for tools versus resources versus prompts, a single set of task management methods (`tasks/get` and `tasks/result`) works uniformly across all request types. This uniformity reduces cognitive load for implementers and creates a consistent experience for applications using the protocol. + +The generic design also provides implementation flexibility. Servers can choose which requests support task augmentation without requiring protocol changes or version negotiation. If a server doesn't support tasks for a particular request type, it simply ignores the task metadata and processes the request normally. This allows servers to add task support to requests incrementally, starting with high-value operations and expanding over time based on actual usage patterns. + +Architecturally, tasks are treated as metadata rather than a separate execution model. They augment existing requests rather than replacing them. The original request/response flow remains intact—the request still gets a response eventually. Tasks simply provide an additional polling-based mechanism for result retrieval. This design ensures that related messages (such as elicitations during task execution) can be associated consistently via the `modelcontextprotocol.io/related-task` metadata key, regardless of the underlying request type. + +### Design Decision: Metadata-Based Augmentation + +Using `_meta` for task information rather than dedicated request parameters was chosen to maintain a clear separation of concerns between request semantics and execution tracking. + +Task information is fundamentally orthogonal to request semantics. The task ID and keepAlive duration don't affect what the request does—they only affect how the result is retrieved and retained. A `tools/call` request performs the same operation whether or not it includes task metadata. The task metadata simply provides an alternative mechanism for accessing the result. + +By placing task information in `_meta`, we create a clear architectural boundary between "what to execute" (request parameters) and "how to track execution" (task metadata). This boundary makes it easier for implementers to reason about the protocol. Request parameters define the operation being performed, while metadata provides orthogonal concerns like progress tracking, task management, and other execution-related information. + +This approach also provides natural backward compatibility. Servers that don't support tasks can ignore the `_meta` content without breaking request processing. The request parameters remain valid and complete, so the operation can proceed normally. This means no protocol version negotiation is required—the new functionality is purely additive and non-disruptive. + +SDKs can provide ergonomic abstractions over the task primitive while maintaining the separation of concerns, for example: + +```typescript +// === MCP SDK (Pseudocode based loosely on modelcontextprotocol/typescript-sdk) === + +/** + * NEW: A request that resolves to a result, either directly or by polling a task. + */ +class PendingRequest { + constructor(readonly protocol: Protocol, readonly result: Promise, readonly taskId?: string) {} + + /** + * Waits for a result, calling onTaskStatus if provided and a task was created. + */ + async result({ onTaskStatus }): Promise => { + if (!onTaskStatus || !this.taskId) { + // No task listener or task ID provided, just block for the result + return await result; + } + + // Whichever is successful first (or a failure if all fail) is returned. + return Promise.any([ + result, // Blocks for result + (async () => { + // Blocks for a notifications/tasks/created with the provided task ID + await this.protocol.waitForTask(this.taskId); + return await taskHandler(this.taskId); + })(), + ]); + } + + /** + * Encapsulates polling for a result, calling onTaskStatus after querying the task. + */ + private async taskHandler({ onTaskStatus }): Promise => { + // Poll for completion + let task: Task; + do { + task = await this.protocol.getTask(this.taskId); + await onTaskStatus(task); + await sleep(task.pollFrequency ?? DEFAULT_POLLING_INTERNAL); + } while (!task.isTerminal()); + + // Process result + return await this.protocol.getTaskResult(this.taskId); + } +} + +/** + * Simplified/partial client session implementation for illustration purposes. + * Extends a base class it shares with the server. + */ +class Client extends Protocol { + /** + * Existing request method, but with most implementation refactored to beginCallTool + */ + async callTool( + params: CallToolRequest['params'], + resultSchema: Schema, + ) { + // Existing request methods can be changed to reuse new methods exposed for + // separating request/response flows. + const request = await this.beginCallTool(params, resultSchema); + return request.result(); + } + + /** + * NEW: Low-level method that starts a tool call and returns a PendingRequest + * object for more granular control. + */ + async beginCallTool( + params: CallToolRequest['params'], + resultSchema: Schema, + ) { + const request = await this.beginRequest({ method: 'tools/call', params }, resultSchema, options); + return request; + } +} + +// === HOST APPLICATION === + +// Begin a tool call with task support +const pending: PendingRequest = await client.beginCallTool( + { + name: "analyze_dataset", + arguments: { dataset: "large_file.csv" }, + }, + CallToolResultSchema, + { + keepAlive: 3600000, + }, +); + +// Client code can assume tasks are supported, and the fallback case can be handled internally +const result = await pending.result({ + onTaskStatus: async (task) => { + await sendLatestStateSomewhere(task); + }, +}); +``` + +As the design does not alter the basic request semantics, the existing form would continue to work as well: + +```typescript +const result = await client.callTool( + { + name: "analyze_dataset", + arguments: { dataset: "large_file.csv" }, + }, + CallToolResultSchema, +); +``` + +### Design Decision: Client-Generated Task IDs + +The choice to have clients generate task IDs rather than having servers assign them provides several critical benefits: + +**Idempotency and Fault Tolerance:** +The primary benefit is enabling idempotent task creation. When a client generates the task ID, it can safely retry a task-augmented request if it doesn't receive a response, knowing that the server will recognize the duplicate task ID and return an error. This is essential for reliable operation over unreliable networks: + +- If a request times out, the client can safely retry without creating duplicate tasks +- If a connection drops before the response arrives, the client can reconnect and retry +- The server validates task ID uniqueness and returns an error for duplicates, confirming whether the task was created + +With server-generated task IDs, a timeout or connection failure creates uncertainty—the client doesn't know whether the task was created, and has no safe way to retry without potentially creating duplicate tasks. + +**Simplicity for Clients:** +Client-generated task IDs simplify the client's implementation by eliminating the need to correlate the initial response with a task identifier. The client can immediately begin polling for task status using the task ID it generated, without needing to parse the response to extract a server-assigned identifier. This is particularly valuable for asynchronous programming models where the client may want to store the task ID before the response arrives. + +**Trade-offs for Servers:** +The main trade-off is that servers wrapping existing workflow systems with their own task identifiers will generally handle this by maintaining a mapping between the client-provided task IDs and the underlying system's identifiers. For example, an MCP server wrapping AWS Step Functions might receive a client-generated task ID like `"client-abc-123"` and need to track that it corresponds to Step Functions execution ARN `"arn:aws:states:...:exec-xyz"`. + +This requires: + +- Persistent storage for the task ID mapping (typically a simple key-value store) +- Maintaining the mapping for the task's keepAlive duration +- Handling mapping lookups for task status and result retrieval + +However, this complexity is typically minor compared to the overall work of integrating an existing workflow system into MCP. Most workflow systems already require state management for tracking execution, and maintaining a task ID mapping is a straightforward addition. The mapping structure is simple (client task ID maps to an internal identifier), and can be implemented using existing databases or key-value stores such a server likely already uses for other state management. + +### Design Decision: Task Creation Notification + +The decision to use a `notifications/tasks/created` notification rather than altering the response semantics (as #1391 proposed) acknowledges the asynchronous nature of task creation and enables efficient race patterns between task-based polling and traditional request/response flows. + +When a server creates a task, it must signal to the client that the task is ready for polling. There are at least two possible approaches: (1) the initial request could return synchronously with task metadata, or (2) the server could send a notification. This proposal uses notifications for several key reasons: + +1. Notifications enable fire-and-forget request processing. The server can accept the request, begin processing it, and send the notification once the task is created, without needing to block the initial request/response cycle. This is particularly important for servers that dispatch work to background systems or queues—they can acknowledge the request immediately and send the notification once the background system confirms task creation. +2. Notifications support the race pattern that enables graceful degradation. Clients can race between waiting for the original request's response and waiting for the `notifications/tasks/created` notification. If the server doesn't support tasks, no notification arrives and the original response wins. If the server does support tasks, the notification typically arrives first (or approximately simultaneously), enabling polling to begin. A synchronous response would force clients to wait for the response before knowing whether to poll or not. +3. Notifications avoid ambiguity with existing protocol semantics. If the initial request response included task metadata and the client then polled for results, it would change the implied meaning of existing notification types: + 1. **Progress notifications**: The current MCP specification requires that progress notifications reference tokens that "are associated with an in-progress operation." While "operation" is not formally defined, the implied understanding is that an operation is bounded by a request/response pair—progress notifications stop when the response is sent. With a synchronous response containing task metadata, progress notifications would need to continue while the task executes, expanding the implied meaning of "operation" to include asynchronous tasks that outlive the original request/response cycle. The notification-based approach avoids this semantic expansion by keeping progress notifications tied to the initial request's lifecycle, while future task-based progress can be cleanly associated via `modelcontextprotocol.io/related-task` metadata. We recommend that a future SEP clarify the definition of "operation" in the progress specification. + 2. **Cancellation semantics**: With the notification-based approach, `notifications/cancelled` clearly targets the original request ID and causes the associated task to move to `cancelled` status, maintaining a clean separation between request cancellation and task lifecycle management. + +While the notification is required by the specification for servers that create tasks, there are edge cases where it may be unavailable: + +- **sHTTP without stream support**: In environments where either the client or the server does not support SSE streams, notifications cannot be delivered. In such cases, clients may choose to proactively poll with `tasks/get` using exponential backoff, though this is nonstandard and may result in unnecessary polling attempts if the server doesn't support tasks. +- **Degraded connection scenarios**: If the notification is lost in transit, clients should implement reasonable timeout behavior and fall back to the original response. + +The standard and recommended approach is to wait for the `notifications/tasks/created` notification before beginning polling. Proactive polling without waiting for the notification should be considered a fallback mechanism for constrained environments only. + +### Design Decision: No Capabilities Declaration + +Unlike other protocol features such as tools, resources, and prompts, tasks do not require capability negotiation. This decision was made to enable graceful degradation and per-request flexibility. + +Task support can be determined implicitly through usage rather than explicitly through capability declarations. When a client sends a task-augmented request, the server will process it according to its capabilities. If the server doesn't support tasks for that request type, it simply ignores the task metadata and returns the result normally through the original request/response flow. The client can then detect the lack of task support by attempting to call `tasks/get` and handling any errors that result. + +This approach eliminates the need for complex handshakes or feature detection protocols. Clients can optimistically try task augmentation and gracefully fall back to direct response handling if needed. This makes the protocol more resilient and easier to implement. + +Additionally, this design provides per-request flexibility that would be difficult to express through capabilities. A server might support tasks on some request types but not others, or support might vary based on runtime conditions such as resource availability or load. Requiring granular capability declarations per request type would significantly complicate the protocol without providing substantial benefits. The implicit detection model is simpler and more flexible. + +### Alternative Designs Considered + +**Tool-Specific Async Execution:** +An earlier version of this proposal (#1391) focused specifically on tool calls, introducing an `invocationMode` field on tool definitions to mark tools as supporting synchronous, asynchronous, or both execution modes. This approach would have added dedicated fields to the tool call request and response structures, with server-side capability declarations to indicate support for async tool execution. + +While this design would have addressed the immediate need for long-running tool calls, it was rejected in favor of the more general task primitive for several reasons. First, it artificially limited the async execution pattern to tools when other request types have similar needs. Resources can be expensive to read, prompts can require complex processing, and sampling requests may involve lengthy user interactions. Creating separate async patterns for each request type would lead to protocol fragmentation and inconsistent implementation patterns. + +Second, the tool-specific approach required more complex capability negotiation and version handling. Servers would need to filter tool lists based on client capabilities, and SDKs would need to manage different invocation patterns for sync versus async tools. This complexity would ripple through every layer of the implementation stack. + +Finally, the tool-specific design didn't address the broader architectural need for deferred result retrieval across all MCP request types. By generalizing to a task primitive that augments any request, this proposal provides a consistent pattern that can be applied uniformly across the protocol. More importantly, this foundation is extensible to future protocol messages and features such as subtasks, making it a more appropriate building block for the protocol's evolution. + +**Transport-Layer Solutions:** +An alternative approach would be to solve for this purely at the transport layer, without introducing a new data-layer primitive. Several proposals (#1335, #1442, #1597) address transport-specific concerns such as connection resilience, request retry semantics, and stream management for sHTTP. These are valuable improvements that can mitigate many scaling and reliability challenges associated with requests that may take extended time to complete. + +However, transport-layer solutions alone are insufficient for the use cases this SEP addresses. Even with perfect transport-layer reliability, several data-layer concerns remain: + +First, servers and clients need a way to communicate expectations about execution patterns. Without this, host applications cannot make informed decisions about UX patterns—should they block, show a spinner, or allow the user to continue working? An annotation alone could signal that a request might take extended time, but provides no mechanism to actively check status or retrieve results later. + +Second, transport-layer solutions cannot provide visibility into the execution state of a request that is still in progress. If a request stops sending progress notifications, the client cannot distinguish between "the server is doing expensive work" and "the request was lost." Transport-level retries can confirm the connection is alive, but cannot answer "is this specific request still executing?" This visibility is critical for operations where users need confidence their work is progressing. + +Third, different transports would require different mechanisms for these concerns. The sHTTP proposals adjust stream management and retry semantics to fulfill these requirements, but stdio has no equivalent extension points. This creates transport-specific fragmentation where implementers must solve the same problems differently depending on their choice of transport. Data-layer operations provides consistent semantics across all transports. + +Finally, deferred result retrieval and active status checks are data-layer concerns that cannot be addressed by transport improvements alone. The ability to retrieve a result multiple times, specify retention duration, and handle cleanup is orthogonal to how the underlying messages are delivered. + +**Resource-Based Approaches:** +Another possible approach would be to leverage existing MCP resources for tracking long-running operations. For example, a tool could return a linked resource that communicates operation status, and clients could subscribe to that resource to receive updates when the operation completes. This would allow servers to represent task state using the resource primitive, potentially with annotations for suggested polling frequency. + +While this approach is technically feasible and servers remain free to adopt such conventions, it suffers from similar limitations as the tool-splitting pattern described in the Motivation section. Like the `start_tool` and `get_tool` convention, a resource-based tracking system would be convention-based rather than standardized, creating several challenges: + +The most fundamental issue is the lack of a consistent way for clients to distinguish between ordinary resources (meant to be exposed to models) and status-tracking resources (meant to be polled by the application). Should a status resource be presented to the model? How should the client correlate a returned resource with the original tool call? Without standardization, different servers would implement different conventions, forcing clients/hosts/models to handle each server's particular approach. Extending resources with task-like semantics (such as polling frequency, keepalive durations, and explicit status states) would create a new and distinct purpose for resources that would be difficult to distinguish from their existing purpose as model-accessible content. + +The resource subscription model has one additional issue: as it is push-based, it requires clients to wait for notifications of resource changes rather than actively polling for status. While this works for some use cases, it doesn't address scenarios where clients need to actively check status—for example, proactively and deterministically checking if work is still progressing, which is the original intent of this proposal. + +The task primitive addresses these concerns by providing a standardized, protocol-level mechanism specifically designed for this use case, with consistent semantics that any client can leverage without host applications needing to understand server-specific conventions. While resource-based tracking remains possible for servers that prefer it and/or are already using it, this SEP provides a first-class alternative that solves the broader set of requirements identified previously. + +### Backward Compatibility + +This SEP introduces **no backward incompatibilities**. All existing MCP functionality remains unchanged: + +**Compatibility Guarantees:** + +- Existing requests work identically with or without task metadata +- Servers that don't understand tasks process requests normally +- No protocol version negotiation required +- No capability declarations needed + +**Graceful Degradation:** + +- Clients race between waiting for the original request's response and waiting for the `notifications/tasks/created` notification followed by polling +- Whichever completes first (original response or task-based retrieval) is used by the client +- If a server doesn't support tasks, no `notifications/tasks/created` is sent, and the original request's response is used +- If a server supports tasks, the `notifications/tasks/created` notification is sent, enabling the client to begin polling for results +- This race pattern ensures graceful degradation without requiring capability negotiation or version detection +- Partial support is possible—servers can support tasks on some requests but not others + +**Adoption Path:** + +- Servers can implement task support incrementally, starting with high-value request types +- Clients can opportunistically use tasks where supported +- No coordination required between client and server updates + +## Future Work + +The task primitive introduced in this SEP provides a foundation for several important extensions that will enhance MCP's workflow capabilities. + +### Push Notifications + +While this SEP focuses on client-driven polling, future work could introduce server-initiated notifications for task state changes. This would be particularly valuable for operations that take hours or longer, where continuous polling becomes impractical. + +A notification-based approach would allow servers to proactively inform clients when: + +- A task completes or fails +- A task reaches a milestone or significant state transition +- A task requires input (complementing the `input_required` status) + +This could be implemented through webhook-style mechanisms or persistent notification channels, depending on the transport capabilities. The proposed task ID and status model provides the necessary infrastructure for servers to identify which tasks warrant notifications and for clients to correlate notifications with their outstanding tasks. + +### Intermediate Results + +The current task model returns results only upon completion. Future extensions could enable tasks to report intermediate results or progress artifacts during execution. This would support use cases where servers can produce partial outputs before final completion, such as: + +- Streaming analysis results as they become available +- Reporting completed phases of multi-step operations +- Providing preview data while full processing continues + +Intermediate results would build on the proposed task ID association mechanism, allowing servers to send multiple result notifications or response messages tied to the same task ID throughout its lifecycle. + +### Nested Task Execution + +A significant future enhancement is support for hierarchical task relationships, where a task can spawn subtasks as part of its execution. This would enable complex, multi-step workflows orchestrated by the server. + +In a nested task model, a server could: + +- Create subtasks in response to a parent task reaching a state that requires additional operations +- Communicate subtask requirements to the client, potentially including required tool calls or sampling requests +- Track subtask completion and use subtask results to advance the parent task +- Maintain provenance through task ID hierarchies, showing the relationship between parent and child tasks + +For example, a complex analysis task might spawn several subtasks for data gathering, each represented by its own task ID but associated with the parent task. The parent task would remain in a pending state (potentially in a new `tool_required` status) until all required subtasks complete. + +This hierarchical model would support sophisticated server-controlled workflows while maintaining the client's ability to monitor and retrieve results at any level of the task tree. + +
+ +Example nested task flow + +```mermaid +sequenceDiagram + participant C as Client + participant S as Server + + Note over C,S: Client Creates Parent Task + C->>S: tools/call "deploy_application"
_meta: {taskId: "deploy-123"} + S--)C: notifications/tasks/created + + C->>S: tasks/get (taskId: "deploy-123") + S->>C: status: working + + Note over S: Server determines subtasks needed + + Note over C,S: Server Responds with Subtask Requirements + C->>S: tasks/get (taskId: "deploy-123") + S->>C: status: working
childTasks: [{
taskId: "build-456",
toolName: "run_build",
arguments: {...}
}, {
taskId: "test-789",
toolName: "run_tests",
arguments: {...}
}] + + Note over C: Client initiates subtasks + + C->>S: tools/call "run_build"
_meta: {taskId: "build-456", parentTaskId: "deploy-123"} + S--)C: notifications/tasks/created + + C->>S: tools/call "run_tests"
_meta: {taskId: "test-789", parentTaskId: "deploy-123"} + S--)C: notifications/tasks/created + + Note over C: Client polls subtasks + + C->>S: tasks/get (taskId: "build-456") + S->>C: status: completed + + C->>S: tasks/get (taskId: "test-789") + S->>C: status: completed + + Note over S: All subtasks complete, parent continues + + C->>S: tasks/get (taskId: "deploy-123") + S->>C: status: completed + + C->>S: tasks/result (taskId: "deploy-123") + S->>C: Deployment complete +``` + +**Potential Data Model Extensions:** +The task status response could be extended to include parent and child task relationships: + +```typescript +{ + taskId: string; + status: TaskStatus; + keepAlive: number | null; + pollFrequency?: number; + error?: string; + + // Extensions for nested tasks + parentTaskId?: string; // ID of parent task, if this is a subtask + childTasks?: Array<{ // Subtasks required by this task + taskId: string; // Pre-generated task ID for the subtask + toolName: string; // Tool to call for this subtask + arguments?: object; // Arguments for the tool call + }>; +} +``` + +This would allow clients to: + +- Discover subtasks required by a parent task through the `childTasks` array +- Initiate the required subtask tool calls using the pre-generated task IDs and provided arguments +- Navigate the task hierarchy by following parent/child relationships via `parentTaskId` +- Monitor all subtasks by polling each child task ID +- Wait for all subtasks to complete before checking parent task completion + +The existing task metadata and status lifecycle are designed to be forward-compatible with these extensions. + +
diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f6efb51 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "strict": true, + "noEmit": true, + "target": "ES2020", + "module": "ES2020", + "moduleResolution": "node" + }, + "include": ["schema/**/*.ts"] +}