Skip to content
Merged
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
253 changes: 245 additions & 8 deletions seps/2557-tasks-stabilization.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
- **Status**: Draft
- **Type**: Standards Track
- **Created**: 2026-04-12
- **Author(s)**: Luca Chang (@LucaButBoring)
- **Author(s)**: Luca Chang (@LucaButBoring), Caitie McCaffrey (@CaitieM20)
- **Sponsor**: Caitie McCaffrey (@CaitieM20)
- **PR**: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2557

## Abstract

This proposal builds on [tasks](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks) by introducing several simplifications to the original functionality to prepare the feature for stabilization, following implementation and usage feedback since the initial experimental release. In particular, this proposal allows tasks to be returned in response to non-task requests to remove unneeded stateful handshakes, and it collapses `tasks/result` into `tasks/get`, removing the error-prone interaction between the `input_required` task status and the `tasks/result` method. Additionally, following the acceptance of [SEP-2260: Require Server requests to be associated with a Client request](./2260-Require-Server-requests-to-be-associated-with-Client-requests.md), we are removing client-hosted elicitation/sampling tasks, as they further complicate the transport-related interactions that SEP-2260 intends to simplify.
This proposal builds on [tasks](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks) by introducing several simplifications to the original functionality to prepare the feature for stabilization, following implementation and usage feedback since the initial experimental release. In particular, this proposal allows tasks to be returned in response to non-task requests to remove unneeded stateful handshakes, and it collapses `tasks/result` into `tasks/get`, removing the error-prone interaction between the `input_required` task status and the `tasks/result` method.

This SEP also incorporates changes into `Tasks` necessary following the acceptance of:
- [SEP-2260: Require Server requests to be associated with a Client request](./2260-Require-Server-requests-to-be-associated-with-Client-requests.md)
- [SEP-2322: Multi Round-Trip Requests](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322)
- [SEP-2243: Http Standardization](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243)

## Motivation

Expand Down Expand Up @@ -49,12 +54,11 @@ To both improve the adoption of tasks and to reduce their upfront messaging over
## Specification

The following changes will be made to the tasks specification:

1. With respect to task creation:
<1. With respect to task creation:>
1. We will deprecate the following capability declarations:
1. Client capabilities:
1. `tasks` (the entire capability, given that all supported client methods are now invalid, per [SEP-2260](./2260-Require-Server-requests-to-be-associated-with-Client-requests.md))
1. Server capabilities:
1. `tasks` (the entire capability, and all sub-capabilities, given that all supported client methods are now invalid, per [SEP-2260](./2260-Require-Server-requests-to-be-associated-with-Client-requests.md))
. Server capabilities:
1. `tasks.cancel` (only the capability declaration is deprecated; _not_ the `tasks/cancel` method itself)
1. `tasks.requests.tools.call`
1. We will deprecate the `execution.taskSupport` field from the `Tool` shape.
Expand All @@ -72,7 +76,101 @@ The following changes will be made to the tasks specification:
1. We may expand task support to additional client-to-server request types in the future, and implementors are still advised against implementing tasks as a tool-specific protocol operation.
1. We will require `tasks/cancel` to be supported even if a server is incapabable or unwillling of offering actual task cancellation, similar to `notifications/cancelled` (it should instead return an error).

Below (collapsed) is an example of a tool call with an unsolicited task-augmentation, with an elicitation during the request:
### Task Capabilities Changes Summary
The below table summarizes the changes to the task-related capabiliteis:
| Role | Capability | Status | Description |
| --- | --- | --- | --- |
|Server | Tasks.Reqeusts.Tools.Call | removed |
|Server | Task.Cancel | removed | |
|Server | Tasks.List | still supported | |
|Client | Tasks.Requests.Sampling.CreateMessage | removed | no longer supported SEP-2260 |
|Client | Tasks.Requests.Elicitation.Create | removed | no longer supported SEP-2260 |
| Client| Tasks.Cancel | removed | no longer needed |
| Client| Tasks.List | removed | no longer needed |

### Task Methods Changes Summary
The below table summarizes the changes to the task-related methods:
| Method | Status | Description |
| --- | --- | --- |
| tasks/get | still supported | Consolidates the entire polling lifecycle into a single method. |
| tasks/result | removed | No longer needed; results are inlined into `tasks/get`. |
| tasks/input_resposne | removed | No longer needed; results are inlined into `tasks/get`. |
| tasks/cancel | still supported | Required to be supported even if actual cancellation is not possible. |
| tasks/list | still supported | Some open questions on how this should be implemented without sessions. |

### Task Schema Changes
The `Task` schema defining the task metadata remains unchanged.

### Client Reqeuests for `task/get`
```typescript
interface GetTaskRequest extends JSONRPCRequest {
method "tasks/get";
params: {
/**
* The task identifier to query.
*/
taskId: string;
/**
* Optional field to allow the client to respond to a server's request for more information
* when the task is in `input_required` state.
*/
inputResponses?: InputResponses;
};
}
```
### Server Response for `task/get`
```typescript
interface GetTaskResult extends Result
{
/**
* Required field containing the Task Metadata Object.
*/
task: Task;
/**
* Optional field containing the InputRequests that specify the additional information needed from the client. Present only when task status is `input_required`.
*/
inputRequests?: InputRequests;
/**
* Optional field containing the Result of a Task. Present only when task status is `completed`.
*/
result?: JSONObject;

/**
* Optional field containing the error that caused the task to fail. Present only when task status is `failed`.
*/
error?: JSONObject;
Comment on lines +133 to +141
Copy link
Copy Markdown
Owner

@LucaButBoring LucaButBoring Apr 13, 2026

Choose a reason for hiding this comment

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

These get inlined into Task, not GetTaskResult (that is, these changes affect every request/notification that uses the Task type).

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.

Does that make sense though? For example CreateTaskResult is now

interface CreateTaskResult extends Result
{
    task: Task
} 

So with having these in-lined with task you could return an error or result which doesn't really make sense.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Yeah, after further discussion, I think we should use a separate type instead of Task here and in tasks/list - naming TBD

}
```

### `ResultType`
The ResultType field was introduced in [SEP-2322: Multi Round-Trip Requests](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322) to handle polymorphic results. `Tasks` has the same issue where a server may return a `ToolCallResult` or a `Task`. To address this, we propose the addition of the `task` ResultType to indicate that a Response contains a `Task` object.

```typescript
type ResultType = "complete" | "incomplete" | "task"
```

For backwards compatibility ResultType is inferred to be `complete`. Therefore all calls which return a `Task` (i.e.`task/get`, `task/cancel`) calls must set `task` as the ResultType moving forward.

### Example Task Flow
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

nit: might want to specify the result types (that is, to clarify tools/call returns CreateTaskResult)

```mermaid
sequenceDiagram
participant U as User
participant C as Client
participant S as Server
C->>S: tools/call (id: 1)
S-->>C: Result (id: 1, taskId: 123, status: working)
C->>S: tasks/get (taskId: 123)
S-->>C: Result (taskId: 123, status: input_required, inputRequests: {...})
C->>U: Prompt User for Input
U-->>C: Provided Input
C->>S: tasks/get (taskId: 123, inputResponses: {...})
S-->>C: Result (taskId: 123, status: working)
C-->>S: tasks/get (taskId: 123)
S-->>C: Result (taskId: 123, status: completed, result: {...})


```
Below (collapsed) is the full json example of a tool call with an unsolicited task-augmentation that matches the diagram above:

<details>

Expand All @@ -98,6 +196,7 @@ The server determines (via bespoke logic) that it wants to create a task to repr
{
"jsonrpc": "2.0",
"id": 2,
"result_type": "task",
"result": {
"task": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
Expand Down Expand Up @@ -130,6 +229,7 @@ On each request while the task is in a `"working"` status, the server returns a
{
"jsonrpc": "2.0",
"id": 3,
"result_type": "task",
"result": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"status": "working",
Expand Down Expand Up @@ -158,6 +258,7 @@ Eventually, the server reaches the point at which it needs to send an elicitatio
{
"id": 4,
"jsonrpc": "2.0",
"result_type": "task",
"result": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"status": "input_required",
Expand Down Expand Up @@ -202,6 +303,7 @@ For thoroughness, let's consider a case where the client happens to poll `tasks/
{
"id": 5,
"jsonrpc": "2.0",
"result_type": "task",
"result": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"status": "input_required",
Expand Down Expand Up @@ -284,6 +386,7 @@ Eventually, the server completes the request, so it stores the final `CallToolRe
{
"jsonrpc": "2.0",
"id": 7,
"result_type": "task",
"result": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"status": "completed",
Expand All @@ -300,12 +403,146 @@ Eventually, the server completes the request, so it stores the final `CallToolRe
],
"isError": false
}
},
}
```

</details>

### Tasks/Get Behavior by Task State
A `Task` can be in one of the following states: `working`, `completed`, `failed`, `canceled`, or `input_required`. This section defines the expected behavior of a call to `tasks/get` when in each state.

All responses to `task/get` MUST include the `task` object.

#### Working
The response MUST include the `task` object with `working` status.
<details>
``` json
{
"jsonrpc": "2.0",
"id": 1,
"result_type": "task",
"result": {
"task": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"status": "working",
"statusMessage": "The operation is in progress.",
"createdAt": "2025-11-25T10:30:00Z",
"lastUpdatedAt": "2025-11-25T10:40:00Z",
"ttl": 60000,
"pollInterval": 5000
}
}
}
```
</details>

#### Completed
When a task is in the `completed` state, a call to `tasks/get` MUST return the `Task` with status `completed` and include the final result of the task.
<details>
```json
{
"jsonrpc": "2.0",
"id": 1,
"result_type": "task",
"result": {
"task": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"status": "completed",
"statusMessage": "The operation has completed successfully.",
"createdAt": "2025-11-25T10:30:00Z",
"lastUpdatedAt": "2025-11-25T10:40:00Z",
"ttl": 60000,
"pollInterval": 5000
},
"content": [
{
"type": "text",
"text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
}
],
"isError": false
}
}
```
</details>

#### Failed
When a task is in the `failed` state, a call to `tasks/get` should return an error in the `result` field indicating the reason for the failure.

TBD On what this looks like.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

At least right now this would be a JSONRPCError, though the question stands of how we want to handle failed tool calls


#### Canceled
When a task is in the `canceled` state, a call to `tasks/get` MUST return the `Task` with status `canceled`.
<details>
```json
{
"jsonrpc": "2.0",
"id": 1,
"result_type": "task",

"result": {
"task": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"status": "cancelled",
"statusMessage": "The operation has been cancelled.",
"createdAt": "2025-11-25T10:30:00Z",
"lastUpdatedAt": "2025-11-25T10:40:00Z",
"ttl": 60000,
"pollInterval": 5000
},
}
}
```
</details>

#### `input_required`
If Server Task Status is `input_required` this indicates that the `Task` requires additional input from the client before it can proceed. The server MUST return the `Task` with status set to `input_required` and a `IncompleteResult` from [SEP-2322: Multi Round-Trip Requests](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322).
<details>
```json
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"task": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"status": "input_required",
"statusMessage": "The operation requires additional input.",
"createdAt": "2025-11-25T10:30:00Z",
"lastUpdatedAt": "2025-11-25T10:40:00Z",
"ttl": 60000,
"pollInterval": 5000
},
"inputRequests": {
"github_login": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please provide your GitHub username",
"requestedSchema": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": ["name"]
}
}
}
}
}
}
```
</details>

### HTTP Streamable Transport Headers
[SEP-2243](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243) introduces standard headers in the Streamable HTTP Transport to facilitate more efficient routing. Routing on `TaskId`is also desirable since there is often state associated with a specific Task that needs to be consistently routed to the same server instance.

For Tasks the following Headers MUST be set by the client when making requests over the Streamable HTTP Transport:
- `Mcp-Method`: `tasks/get`, `tasks/cancel`
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Should we also mention tasks/list here? It wouldn't have an Mcp-Name but the consistency would be useful

- `Mcp-Name`: should contain the `taskId` of the Task being requested or cancelled.

## Rationale

### Removing `tasks/result`
Expand Down Expand Up @@ -346,7 +583,7 @@ The removal of `tasks/result` is not backwards-compatible. At a protocol level,

The following adjustments related to unsolicited tasks are breaking changes:

1. We will allow `CreateTaskResult` to be returned in response to `CallToolRequest` when no `task` field is present in the request.
1. We will allow `CreateTaskResult` to be returned in response to `CallToolRequest` when no `task` field is present in the client request.
1. We will allow `CallToolResult` to be returned in response to `CallToolRequest` even when the `task` field is present in the request.

At a protocol level, this should be handled according to the protocol version. Under the `2025-11-25` protocol version, these cases **SHOULD** be handled as malformed responses, but under subsequent protocol versions, they **MUST** be treated as valid per the updated specification language.
Expand Down
Loading