From 8323521e458f1d928fb3295839df8fbbcbb0c4da Mon Sep 17 00:00:00 2001 From: Luca Chang Date: Fri, 30 Jan 2026 19:52:25 -0800 Subject: [PATCH 1/5] feat(sep): tasks are progress --- proposals/0000-tasks-are-progress.md | 76 ++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 proposals/0000-tasks-are-progress.md diff --git a/proposals/0000-tasks-are-progress.md b/proposals/0000-tasks-are-progress.md new file mode 100644 index 0000000..5ccf6fa --- /dev/null +++ b/proposals/0000-tasks-are-progress.md @@ -0,0 +1,76 @@ +# SEP-0000: Tasks are Progress + +- **Status**: Proposal +- **Type**: Standards Track +- **Created**: 2026-01-30 +- **Author(s)**: Luca Chang (@LucaButBoring) +- **Sponsor**: None +- **PR**: https://github.com/modelcontextprotocol/transports-wg/pull/{NUMBER} + +## Abstract + +**Tasks** were introduced in an experimental state in the `2025-11-25` specification release, serving as an alternate execution mode for certain request types (tool calls, elicitation, and sampling) to enable polling for the result of an augmented operation. Conversely, **progress** exists to receive information about the status of a long-running operation via JSON-RPC notifications. This proposal consolidates progress notifications into tasks, creating a unified and cohesive way for clients and servers to share updates with each other. + +## Motivation + +*Note: For ease of understanding, we use client/server terminology throughout this proposal. In reality, this is a peer-to-peer flow in which either party may dispatch a request. The Tasks and Progress specifications use "requestor" and "receiver" to decouple themselves from the assumption that these features apply only to client requests.* + +Today, tasks and progress notifications represent mirrored execution models: + +- **Tasks** represent a client-driven, polling-based execution model, where the client needs to be active intermittently to execute polling requests to check the status of an operation, and to eventually retrieve the final result. +- **Progress** represents a server-driven, push-based execution model, where the client and server share a continuous, long-running response stream for the server to send notifications on, prior to the server returning a final result. + +Each protocol feature can be used independently of the other; they can also be used together. Tasks were given a `statusMessage` field that mirrors progress's `message` field, but were deliberately not given an equivalent to progress's `progress` and `total` fields to attempt to avoid creating ambiguity between their use cases. It would have been a mistake for the two to differ _only_ by execution model when tasks also imply a degree of state management around results that progress notifications lack. + +However, [Multi Round-Trip Requests (MRTR)](https://github.com/modelcontextprotocol/transports-wg/pull/7) violate the assumptions that progress notifications are built on, and under MRTR semantics it becomes impossible for progress notifications to continue to represent meaningful state (as every request becomes stateless). Being able to report progress updates is still useful for UX purposes such as suggesting how many longer a slow operation may take, and that use case should not be invalidated. Instead, MRTR creates an opportunity for MCP to commit to tasks as a unified solution for all persistent operations by interpreting progress notifications as a subset of this and removing them entirely. + +## Specification + +`Task`, which is returned in the `task` field of `CreateTaskResponse`, as the complete result of `tasks/get`, and in the optional task notifications, will gain optional `progress` and `progressTotal` fields: + +```json +{ + "taskId": "bdc2fd8b-442e-40ff-abdb-f33986513750", + "status": "working", + "statusMessage": "Reticulating splines...", + "progress": 6, + "progressTotal": 7, + "createdAt": "2026-01-30T18:22:00Z", + "lastUpdatedAt": "2026-01-30T18:30:00Z", + "ttl": 30000, + "pollInterval": 5000 +} +``` + +As with progress notifications, `progress` **MUST** be monotonically increasing, and `progressTotal` will remain optional as the total amount of work may be unknown. Both values may be floating-point numbers. *In addition* to these properties, we will define the following new behavior for `progressTotal`, as it was previously underspecified: + +1. `progressTotal` **MAY** be omitted, but if it is provided it **MUST** be greater than or equal to `progress`. +2. `progressTotal` **MAY** change between requests, but it **MUST** be monotonically increasing (similar to `progress`). A changing total may represent new work being discovered, for example. + +The Progress specification, the associated `ProgressNotification` and `ProgressNotificationParams` types, and the reserved `_meta.progressToken` field will be completely removed from the protocol. + +## Rationale + +### Why get rid of something that exists? + +It has been previously-suggested to build progress support into tasks (for example [here](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1732#discussion_r2501534339)), and the two features have always served recognizably-similar roles if you squint hard enough, with progress representing the simple use case of "I have a long-running operation and want to show a completion rate for it in my UI" and tasks representing the more complex use case of "and specifically, I want to dispatch background work that I'll choose to fetch the actual result for sometime after it completes." + +Tasks serve a use case that progress could not meet in its current form, as notifications fundamentally imply some sort of active process on the server that is able to asynchronously dispatch those notifications in the first place. With tasks, an entire operation flow can be executed on stateless infrastructure (for example, a serverless function) by offloading execution to another system (such as an external job manager) without wasting compute by forcing a single instance to actively poll the upstream system internally to e.g. a tool call. + +At the time that tasks were proposed, the distinctions between progress and tasks made it unreasonable to force tasks into the paradigm of progress, or to add progress fields to tasks (which would have rendered progress somewhat redundant). While we could have taken the latter approach and proactively removed progress, the experimental nature of tasks combined with a reluctance to make any breaking changes led to a "safer" solution that favored making tasks less useful to allow other existing features to exist largely unchanged. Regardless, there wasn't much of a need to "change what worked," as it were — even if something could be removed, there was never any urgent need to remove it. + +With the shift to MRTR, this changes: Progress notifications no longer work as originally-designed, so MCP is forced to either somehow shim their logic on top of MRTR semantics or remove them altogether. The former option would be both surprising and semantically-incorrect (progress resets after responding to an elicitation/sampling request). Consolidating progress into tasks respects the use cases of progress without being forced to maintain it in a vestigal state. + +## Backward Compatibility + +From a protocol standpoint, this proposal introduces no backwards-incompatible behavior. No special behavior is necessary for the removal of Progress; clients should cease to send it, and if a server implements this specification and encounters a request with `_meta.progressToken`, it will simply ignore it. This is consistent with how progress notifications were already handled: The client could not assume that it would actually receive progress notifications just because it provided a progress token in its request. + +From an application-developer's standpoint, however, this is obviously breaking — progress notifications won't exist anymore. Applications leveraging progress notifications as a signaling mechanism are encouraged to adopt tasks instead. As an interim implementation on the client side, applications can treat existing progress notifications and task progress as equivalent, wiring task progress into existing UI components etc., with progress handlers being fully-removed at a later point in time. + +## Security Implications + +No new security implications are introduced by this proposal. + +## Reference Implementation + +To be provided. From 38616838faee8e7d9e141be9942b93d7a48bf41c Mon Sep 17 00:00:00 2001 From: Luca Chang Date: Thu, 5 Feb 2026 14:45:21 -0800 Subject: [PATCH 2/5] chore: update PR link in progress SEP --- proposals/0000-tasks-are-progress.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/0000-tasks-are-progress.md b/proposals/0000-tasks-are-progress.md index 5ccf6fa..a50db78 100644 --- a/proposals/0000-tasks-are-progress.md +++ b/proposals/0000-tasks-are-progress.md @@ -5,7 +5,7 @@ - **Created**: 2026-01-30 - **Author(s)**: Luca Chang (@LucaButBoring) - **Sponsor**: None -- **PR**: https://github.com/modelcontextprotocol/transports-wg/pull/{NUMBER} +- **PR**: https://github.com/modelcontextprotocol/transports-wg/pull/11 ## Abstract From 1e546ddbae554123db1851c361e5e18fad054918 Mon Sep 17 00:00:00 2001 From: Luca Chang Date: Wed, 11 Feb 2026 14:37:58 -0800 Subject: [PATCH 3/5] chore: rewrite "tasks are progress" to just be "task progress" --- proposals/0000-tasks-are-progress.md | 76 ---------------------------- proposals/xxxx-task-progress.md | 75 +++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 76 deletions(-) delete mode 100644 proposals/0000-tasks-are-progress.md create mode 100644 proposals/xxxx-task-progress.md diff --git a/proposals/0000-tasks-are-progress.md b/proposals/0000-tasks-are-progress.md deleted file mode 100644 index a50db78..0000000 --- a/proposals/0000-tasks-are-progress.md +++ /dev/null @@ -1,76 +0,0 @@ -# SEP-0000: Tasks are Progress - -- **Status**: Proposal -- **Type**: Standards Track -- **Created**: 2026-01-30 -- **Author(s)**: Luca Chang (@LucaButBoring) -- **Sponsor**: None -- **PR**: https://github.com/modelcontextprotocol/transports-wg/pull/11 - -## Abstract - -**Tasks** were introduced in an experimental state in the `2025-11-25` specification release, serving as an alternate execution mode for certain request types (tool calls, elicitation, and sampling) to enable polling for the result of an augmented operation. Conversely, **progress** exists to receive information about the status of a long-running operation via JSON-RPC notifications. This proposal consolidates progress notifications into tasks, creating a unified and cohesive way for clients and servers to share updates with each other. - -## Motivation - -*Note: For ease of understanding, we use client/server terminology throughout this proposal. In reality, this is a peer-to-peer flow in which either party may dispatch a request. The Tasks and Progress specifications use "requestor" and "receiver" to decouple themselves from the assumption that these features apply only to client requests.* - -Today, tasks and progress notifications represent mirrored execution models: - -- **Tasks** represent a client-driven, polling-based execution model, where the client needs to be active intermittently to execute polling requests to check the status of an operation, and to eventually retrieve the final result. -- **Progress** represents a server-driven, push-based execution model, where the client and server share a continuous, long-running response stream for the server to send notifications on, prior to the server returning a final result. - -Each protocol feature can be used independently of the other; they can also be used together. Tasks were given a `statusMessage` field that mirrors progress's `message` field, but were deliberately not given an equivalent to progress's `progress` and `total` fields to attempt to avoid creating ambiguity between their use cases. It would have been a mistake for the two to differ _only_ by execution model when tasks also imply a degree of state management around results that progress notifications lack. - -However, [Multi Round-Trip Requests (MRTR)](https://github.com/modelcontextprotocol/transports-wg/pull/7) violate the assumptions that progress notifications are built on, and under MRTR semantics it becomes impossible for progress notifications to continue to represent meaningful state (as every request becomes stateless). Being able to report progress updates is still useful for UX purposes such as suggesting how many longer a slow operation may take, and that use case should not be invalidated. Instead, MRTR creates an opportunity for MCP to commit to tasks as a unified solution for all persistent operations by interpreting progress notifications as a subset of this and removing them entirely. - -## Specification - -`Task`, which is returned in the `task` field of `CreateTaskResponse`, as the complete result of `tasks/get`, and in the optional task notifications, will gain optional `progress` and `progressTotal` fields: - -```json -{ - "taskId": "bdc2fd8b-442e-40ff-abdb-f33986513750", - "status": "working", - "statusMessage": "Reticulating splines...", - "progress": 6, - "progressTotal": 7, - "createdAt": "2026-01-30T18:22:00Z", - "lastUpdatedAt": "2026-01-30T18:30:00Z", - "ttl": 30000, - "pollInterval": 5000 -} -``` - -As with progress notifications, `progress` **MUST** be monotonically increasing, and `progressTotal` will remain optional as the total amount of work may be unknown. Both values may be floating-point numbers. *In addition* to these properties, we will define the following new behavior for `progressTotal`, as it was previously underspecified: - -1. `progressTotal` **MAY** be omitted, but if it is provided it **MUST** be greater than or equal to `progress`. -2. `progressTotal` **MAY** change between requests, but it **MUST** be monotonically increasing (similar to `progress`). A changing total may represent new work being discovered, for example. - -The Progress specification, the associated `ProgressNotification` and `ProgressNotificationParams` types, and the reserved `_meta.progressToken` field will be completely removed from the protocol. - -## Rationale - -### Why get rid of something that exists? - -It has been previously-suggested to build progress support into tasks (for example [here](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1732#discussion_r2501534339)), and the two features have always served recognizably-similar roles if you squint hard enough, with progress representing the simple use case of "I have a long-running operation and want to show a completion rate for it in my UI" and tasks representing the more complex use case of "and specifically, I want to dispatch background work that I'll choose to fetch the actual result for sometime after it completes." - -Tasks serve a use case that progress could not meet in its current form, as notifications fundamentally imply some sort of active process on the server that is able to asynchronously dispatch those notifications in the first place. With tasks, an entire operation flow can be executed on stateless infrastructure (for example, a serverless function) by offloading execution to another system (such as an external job manager) without wasting compute by forcing a single instance to actively poll the upstream system internally to e.g. a tool call. - -At the time that tasks were proposed, the distinctions between progress and tasks made it unreasonable to force tasks into the paradigm of progress, or to add progress fields to tasks (which would have rendered progress somewhat redundant). While we could have taken the latter approach and proactively removed progress, the experimental nature of tasks combined with a reluctance to make any breaking changes led to a "safer" solution that favored making tasks less useful to allow other existing features to exist largely unchanged. Regardless, there wasn't much of a need to "change what worked," as it were — even if something could be removed, there was never any urgent need to remove it. - -With the shift to MRTR, this changes: Progress notifications no longer work as originally-designed, so MCP is forced to either somehow shim their logic on top of MRTR semantics or remove them altogether. The former option would be both surprising and semantically-incorrect (progress resets after responding to an elicitation/sampling request). Consolidating progress into tasks respects the use cases of progress without being forced to maintain it in a vestigal state. - -## Backward Compatibility - -From a protocol standpoint, this proposal introduces no backwards-incompatible behavior. No special behavior is necessary for the removal of Progress; clients should cease to send it, and if a server implements this specification and encounters a request with `_meta.progressToken`, it will simply ignore it. This is consistent with how progress notifications were already handled: The client could not assume that it would actually receive progress notifications just because it provided a progress token in its request. - -From an application-developer's standpoint, however, this is obviously breaking — progress notifications won't exist anymore. Applications leveraging progress notifications as a signaling mechanism are encouraged to adopt tasks instead. As an interim implementation on the client side, applications can treat existing progress notifications and task progress as equivalent, wiring task progress into existing UI components etc., with progress handlers being fully-removed at a later point in time. - -## Security Implications - -No new security implications are introduced by this proposal. - -## Reference Implementation - -To be provided. diff --git a/proposals/xxxx-task-progress.md b/proposals/xxxx-task-progress.md new file mode 100644 index 0000000..83d8247 --- /dev/null +++ b/proposals/xxxx-task-progress.md @@ -0,0 +1,75 @@ +# SEP-XXXX: Task Progress + +- **Status**: Proposal +- **Type**: Standards Track +- **Created**: 2026-01-30 +- **Author(s)**: Luca Chang (@LucaButBoring) +- **Sponsor**: None +- **PR**: https://github.com/modelcontextprotocol/transports-wg/pull/11 + +## Abstract + +**Tasks** were introduced in an experimental state in the `2025-11-25` specification release, serving as an alternate execution mode for certain request types (tool calls, elicitation, and sampling) to enable polling for the result of an augmented operation. Conversely, **progress** exists to receive information about the status of a long-running operation via JSON-RPC notifications. While their use cases are partially-overlapping, they are often mutually-exclusive, and using progress notifications on top of tasks adds unnecessary complexity to implementations, which now need to consider that progress notifications may span multiple requests in task operations. To simplify implementations, this proposal reintroduces request-scoped progress notifications and adds progress fields to task metadata, reducing the need to combine the two features. + +## Motivation + +*Note: For ease of understanding, we use client/server terminology throughout this proposal. In reality, this is a peer-to-peer flow in which either party may dispatch a request. The Tasks and Progress specifications use "requestor" and "receiver" to decouple themselves from the assumption that these features apply only to client requests.* + +Today, tasks and progress notifications represent mirrored execution models: + +- **Tasks** represent a client-driven, polling-based execution model, where the client needs to be active intermittently to execute polling requests to check the status of an operation, and to eventually retrieve the final result. +- **Progress** represents a server-driven, push-based execution model, where the client and server share a continuous, long-running response stream for the server to send notifications on, prior to the server returning a final result. + +Each protocol feature can be used independently of the other; they can also be used together. Tasks were given a `statusMessage` field that mirrors progress's `message` field, but were deliberately not given an equivalent to progress's `progress` and `total` fields to attempt to avoid creating ambiguity between their use cases. It would have been a mistake for the two to differ _only_ by execution model when tasks also imply a degree of state management around results that progress notifications lack. + +Additionally, a change was made to progress notifications to specify that in the case of task-augmented execution, progress tokens remain active for the entire polling lifecycle of a task, rather than only for the duration of the initial request (which may return either `CreateTaskResult` or a standard result type). This was intended to allow progress notifications to remain unique and compatible with tasks, even though tasks also benefit from a notion of progress but conflict with the progress feature's execution model. + +Looking forward towards the stabilization of tasks as a protocol feature, there are opportunities to simplify their execution model by introducing a notion of progress into tasks directly. This enables tasks to communicate progress when polled without requiring its association with a separate notification message. + +## Specification + +`Task`, which is returned in the `task` field of `CreateTaskResult`, as the complete result of `tasks/get`, and in the optional task notifications, will gain optional `progress` and `progressTotal` fields: + +```json +{ + "taskId": "bdc2fd8b-442e-40ff-abdb-f33986513750", + "status": "working", + "statusMessage": "Reticulating splines...", + "progress": 6, + "progressTotal": 7, + "createdAt": "2026-01-30T18:22:00Z", + "lastUpdatedAt": "2026-01-30T18:30:00Z", + "ttl": 30000, + "pollInterval": 5000 +} +``` + +As with progress notifications, `progress` **MUST** be monotonically increasing, and `progressTotal` will remain optional as the total amount of work may be unknown. Both values may be floating-point numbers. *In addition* to these properties, we will define the following new behavior for `progressTotal` (and `total` in the progress specification), as it was previously underspecified: + +1. `progressTotal` **MAY** be omitted, but if it is provided it **MUST** be greater than or equal to `progress`. +2. `progressTotal` **MAY** change between requests, but it **MUST** be monotonically increasing (similar to `progress`). A changing total may represent new work being discovered, for example. + +To support use cases that leverage the existing task status notifications, we will modify the notification behavior requirements to allow them to be emitted on any task metadata update - not just when the `status` is changed: + +```diff +- When a task status changes, receivers **MAY** send a `notifications/tasks/status` notification to inform the requestor of the change. This notification includes the full task state. ++ When a task's metadata changes, receivers **MAY** send a `notifications/tasks/status` notification to inform the requestor of the change. This notification includes the full task state. +``` + +## Rationale + +### Why duplicate progress into tasks? + +TODO + +## Backward Compatibility + +This change has no backwards-compatibility concerns. + +## Security Implications + +No new security implications are introduced by this proposal. + +## Reference Implementation + +To be provided. From b0e9ea959c1ddec754aab641b5721f70d461a3fb Mon Sep 17 00:00:00 2001 From: Luca Chang Date: Wed, 11 Feb 2026 15:13:10 -0800 Subject: [PATCH 4/5] chore: update rationale for duplicating progress into tasks --- proposals/xxxx-task-progress.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/proposals/xxxx-task-progress.md b/proposals/xxxx-task-progress.md index 83d8247..5517eb8 100644 --- a/proposals/xxxx-task-progress.md +++ b/proposals/xxxx-task-progress.md @@ -22,9 +22,9 @@ Today, tasks and progress notifications represent mirrored execution models: Each protocol feature can be used independently of the other; they can also be used together. Tasks were given a `statusMessage` field that mirrors progress's `message` field, but were deliberately not given an equivalent to progress's `progress` and `total` fields to attempt to avoid creating ambiguity between their use cases. It would have been a mistake for the two to differ _only_ by execution model when tasks also imply a degree of state management around results that progress notifications lack. -Additionally, a change was made to progress notifications to specify that in the case of task-augmented execution, progress tokens remain active for the entire polling lifecycle of a task, rather than only for the duration of the initial request (which may return either `CreateTaskResult` or a standard result type). This was intended to allow progress notifications to remain unique and compatible with tasks, even though tasks also benefit from a notion of progress but conflict with the progress feature's execution model. +However, in Streamable HTTP, combining these features creates ambiguous behavioral tradeoffs regarding the `input_required` status, which is not specified to be used with notifications in the first place - only with requests. If a server wishes to send progress notifications to a client, it must decide if those notifications should be sent on the GET stream used for background messages, side-channeled on the SSE stream during the `tasks/get` operation, or side-channeled during the `tasks/result` operation. In the case of the background stream and `tasks/result`, this forces the server to keep an active handler for the full duration of the task lifetime, and in the case of `tasks/get`, the server must be able to dequeue server-to-client events in a consistent order from any invocation of the `tasks/get` method. -Looking forward towards the stabilization of tasks as a protocol feature, there are opportunities to simplify their execution model by introducing a notion of progress into tasks directly. This enables tasks to communicate progress when polled without requiring its association with a separate notification message. +To sidestep this complexity, this proposal introduces progress fields directly into tasks, allowing servers to simply always have a mechanism for communicating progress changes without navigating stream selection at all. ## Specification @@ -60,7 +60,11 @@ To support use cases that leverage the existing task status notifications, we wi ### Why duplicate progress into tasks? -TODO +It has been previously-suggested to build progress support into tasks (for example [here](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1732#discussion_r2501534339)), and the two features have always served recognizably-similar roles if you squint hard enough, with progress representing the simple use case of "I have a long-running operation and want to show a completion rate for it in my UI" and tasks representing the more complex use case of "and specifically, I want to dispatch background work that I'll choose to fetch the actual result for sometime after it completes." + +Tasks serve a use case that progress could not meet in its current form, as notifications fundamentally imply some sort of active process on the server that is able to asynchronously dispatch those notifications in the first place. With tasks, an entire operation flow can be executed on stateless infrastructure (for example, a serverless function) by offloading execution to another system (such as an external job manager) without wasting compute by forcing a single instance to actively poll the upstream system internally to e.g. a tool call. + +At the time that tasks were proposed, the distinctions between progress and tasks made it unreasonable to force tasks into the paradigm of progress, or to add progress fields to tasks, which would have rendered progress somewhat redundant. However, in light of the fact that communicating progress for a task is useful, combined with the complexities around streaming progress notifications back to the client from within a task (described in the Motivation section), it is clear now that duplicating a notion of progress into tasks would not render progress notifications redundant at all, and indeed there are use cases where progress is relevant despite not using tasks at all. ## Backward Compatibility From 1c346fb14aecd934820a6b14f9cecbae00768dc3 Mon Sep 17 00:00:00 2001 From: Luca Chang Date: Wed, 11 Feb 2026 15:16:11 -0800 Subject: [PATCH 5/5] chore: rename task progress SEP again --- proposals/xxxx-task-progress.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/xxxx-task-progress.md b/proposals/xxxx-task-progress.md index 5517eb8..e9ce061 100644 --- a/proposals/xxxx-task-progress.md +++ b/proposals/xxxx-task-progress.md @@ -1,4 +1,4 @@ -# SEP-XXXX: Task Progress +# SEP-XXXX: Tasks are (not) Progress - **Status**: Proposal - **Type**: Standards Track