Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions proposals/xxxx-task-progress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# SEP-XXXX: Tasks are (not) 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.

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.

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.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

To be clear: we are proposing that there will be no way to get real-time progress notifications for tasks? (or any other streamed information that we may -- and almost certainly will -- want to add in future)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@pja-ant Not necessarily, just that this will be a better option for progress specifically. Other notifications still exist and have these problems, but progress is one we feel is important enough to create an easier alternative for.


## 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.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: but I don't think we need the increasing constraint. It may be rare, but it may be possible that some work becomes no-longer required during operation (e.g. the task is to work through a todo-list and an item gets cancelled mid-task).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

That creates some odd edge cases where progress and progressTotal are no longer consistent with each other, I think? There is already a requirement that progress is monotonically increasing, meaning that without this requirement, there's an edge that looks like this:

  1. A tool begins with 5 work items. The first 3 are essential; the last 2 are not.
  2. The first 4 items complete, putting progress at 4/5.
  3. The last two items are no longer necessary and are both dropped, putting progress at 4/3.


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?

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

This change has no backwards-compatibility concerns.

## Security Implications

No new security implications are introduced by this proposal.

## Reference Implementation

To be provided.