Skip to content

SEP-2557: Stabilizing Tasks#2557

Open
LucaButBoring wants to merge 12 commits intomodelcontextprotocol:mainfrom
LucaButBoring:feat/tasks-stabilization
Open

SEP-2557: Stabilizing Tasks#2557
LucaButBoring wants to merge 12 commits intomodelcontextprotocol:mainfrom
LucaButBoring:feat/tasks-stabilization

Conversation

@LucaButBoring
Copy link
Copy Markdown
Contributor

@LucaButBoring LucaButBoring commented Apr 12, 2026

This proposal builds on 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, we are removing client-hosted elicitation/sampling tasks, as they further complicate the transport-related interactions that SEP-2260 intends to simplify.

Motivation and Context

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 a task-augmented operation.

As we move toward stabilizing tasks in the June specification release, there are a few challenges to resolve to make them easier to implement support for in both the host application and on the server:

  1. Clients are expected to handle cross-request capability negotiation: Tasks expose method-level capabilities which declare if they’re supported at all (e.g. tasks.requests.tools.call signals that tools/call supports task-augmentation) while also having a tool-level execution.taskSupport to declare if a particular tool supports tasks, requiring an initial tools/list call to prime some state that controls if the client should use tasks or not. This is non-obvious behavior and forces clients to do an implicit state warmup.
  2. input_required is a transition point into SSE side-channeling: In Streamable HTTP, tasks rely on some SSE stream being used to deliver elicitations and sampling, but cannot define which stream is used, instead requiring tasks/result to be called prematurely to have a consistent option to open a stream on, from the server’s perspective.
  3. tasks/result is a blocking method: To enable being used as a vehicle for SSE side-channeling, tasks/result is not allowed to return until the entire operation is complete, complicating polling implementations for clients and making servers pay a heavier cost for using elicitation or sampling at all, and undermining the incentives for servers to use tasks in certain cases.

Furthermore, in #2322 (Multi Round-Trip Requests), we identified that we would need to make a breaking change to tasks/result for these changes anyways, but that redesigning the flow within #2322 would be a scope expansion that would derail MRTR discussion. Regardless, MRTR relies heavily on tasks as a solution for "persistent" requests that require server-side state, so these two proposals are somewhat interdependent.

To both improve the adoption of tasks and to reduce their upfront messaging overhead, this proposal simplifies their execution model by allowing peers to raise unsolicited tasks to each other and consolidating the polling lifecycle entirely into the tasks/get method.

How Has This Been Tested?

TBD

Breaking Changes

Several; described in detail in the proposal.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

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

Additional context

Supersedes #2229 and #2339.


AI Use Disclosure: The core SEP document was written entirely by me, but the actual specification and schema changes were written with Claude Code. The LLM-annotated diff validating the specification and schema changes against the SEP requirements is available here.

@LucaButBoring LucaButBoring force-pushed the feat/tasks-stabilization branch from 4215bbd to 554c8bc Compare April 12, 2026 20:44
@LucaButBoring LucaButBoring requested review from a team as code owners April 12, 2026 20:44
@LucaButBoring LucaButBoring changed the title SEP-0000: Stabilizing Tasks SEP-2557: Stabilizing Tasks Apr 12, 2026
@LucaButBoring LucaButBoring force-pushed the feat/tasks-stabilization branch 2 times, most recently from 6c61b6e to c1aa24a Compare April 12, 2026 20:47
@LucaButBoring
Copy link
Copy Markdown
Contributor Author

@CaitieM20 @markdroth @Randgalt moving discussion here


Updated low-level specification language is pending; the SEP document consolidates #2229 and #2339 with additional changes following @markdroth's feedback and discussion with @CaitieM20 a couple of days ago, and factors in the approval of #2260 which makes server-to-client task augmentations invalid (covered in the Rationale section).

This was referenced Apr 12, 2026
@LucaButBoring LucaButBoring added this to the 2026-06-30-RC milestone Apr 13, 2026
@Randgalt
Copy link
Copy Markdown

Seeing as we're breaking things...

Would it be possible to add a requestState field to the Task like we now have in MRTR?

@LucaButBoring LucaButBoring force-pushed the feat/tasks-stabilization branch from 8b2d86d to 6a4a14c Compare April 13, 2026 19:26
@CaitieM20 CaitieM20 self-assigned this Apr 13, 2026
@CaitieM20 CaitieM20 added SEP draft SEP proposal with a sponsor. labels Apr 13, 2026
@CaitieM20 CaitieM20 added the rc-high-priority Related to an upcoming specification release and needs to be addressed with a high priority. label Apr 13, 2026
@LucaButBoring
Copy link
Copy Markdown
Contributor Author

Seeing as we're breaking things...

Would it be possible to add a requestState field to the Task like we now have in MRTR?

Yes, I'll add this in the next round of revisions - once MRTR lands we can potentially even just share most of the same types for those fields.

@CaitieM20 CaitieM20 removed the rc-high-priority Related to an upcoming specification release and needs to be addressed with a high priority. label Apr 13, 2026
Comment thread schema/draft/schema.ts Outdated
* The structure matches the result type of the original request.
* For example, a {@link CallToolRequest | tools/call} task would return the {@link CallToolResult} structure.
*/
result?: JSONObject;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

See my PR on your branch. What do you think about having the Task Schema remain unchanged as a metadata object, but we create a GetTaskResult object that act as an envelope for the Task, inputRequests, results & error?

Feels a bit cleaner to not glom all this stuff into the Task Metadata object since that is light weight and can be passed around or copied. As is could be represented as a struct.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated the SEP text (spec is next), I'm a bit uncertain on the name but I'm calling it DetailedTask for now, and both tasks/get and notifications/tasks/status will use it. Representing the type like so:

interface InputRequiredTask extends Task {
  status: "input_required";
  /**
   * Field containing the InputRequests that specify the additional information needed from the client. Present
   * only when task status is `input_required` (see SEP-2322).
   */
  inputRequests: InputRequests;
}

interface CompletedTask extends Task {
  status: "completed";
  /**
   * The final result of the task. Present only when status is "completed".
   * The structure matches the result type of the original request.
   * For example, a {@link CallToolRequest | tools/call} task would return the {@link CallToolResult} structure.
   */
  result: JSONObject;
}

interface FailedTask extends Task {
  status: "failed";
  /**
   * The error that caused the task to fail. Present only when status is "failed".
   */
  error: JSONObject;
}

type DetailedTask = Task | InputRequiredTask | CompletedTask | FailedTask;

That makes DetailedTask a discriminated union of a regular task and our special statuses with more data.

add diagrams, schemas, http headers, and specify Gettask Behavior.

### `tasks/result`

The removal of `tasks/result` is not backwards-compatible. At a protocol level, this is handled according to the protocol version. Under the `2025-11-25` protocol version, `tasks/result` **MUST** be supported if the `tasks` capability is advertised, but under subsequent protocol versions, requests **MUST** be rejected with a `-32601` (Method Not Found) error.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Throwing out a thought: since Tasks are experimental, how much do you think SDKs should attempt to retain backwards compatibility? Curious how much complexity it introduces on SDKs and wondering what the blast radius is.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FWIW in our server we won't don't currently support tasks given the lack of client support. We will add support for this version of tasks so backwards compatibility isn't an issue for us.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

My initial take is 0 support for the previous task version as it was marked as experimental and not fully supported.

cc: @felixweinberger

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Additional point of information: Go SDK doesn't support tasks at all yet. No problem from our side with introducing breaking changes to achieve a better API.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

My 2cents is we said this was experimental so there should be no expected backwards compatibility here. I think we should be able to make a clean break. @halter73 can you weigh in here? I think this aligns with some brief discussions we also had.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

- Removed `task` nesting on `GetTaskResult`
- Clarified header handling in the context of SEP-2243
- Clarified error semantics for tool calls with isError: true
@LucaButBoring
Copy link
Copy Markdown
Contributor Author

Added requestState to the SEP text, will update the spec/schema async

@LucaButBoring
Copy link
Copy Markdown
Contributor Author

Updated spec/schema language to reflect latest SEP document updates; new annotated diff here.

* when the task is in `input_required` state.
*/
inputResponses?: InputResponses;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is missing requestState?: string;

The `Task` schema defining the task metadata remains unchanged. However, we introduce new derived types that inline `result`/`error`/`inputRequests`, to be used by `tasks/get` and `notifications/tasks/status`. This allows us to avoid introducing redundant/bloated fields in `CreateTaskResult` and in `ListTasksResult`.

```typescript
interface InputRequiredTask extends Task {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

on the initial tool call that returns a task there is no request state? My thoughts regarding requestState:

  • maybe it could be called taskState and put directly in the Task model'
  • currently, our server has to have a storage mechanism to keep track of the task's running state
  • all of a tasks state can be moved into the requestState/taskState mechanism and be managed by clients completely removing the need for server managed storage.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

completely removing the need for server managed storage

I thought the whole point of Tasks was to introduce a persistence layer for long running calls? How do you handle a situation when a worker that is executing the call is killed? How do you handle task/get if the request lands at a different server than the one the worker is executing on?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry, I didn't mean to imply there would be no server context. But, task specific state could be managed in this additional carrier.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Here's a real world example:

  • Our server might have a tool that initiates a long running query in Trino
  • When the tool is called we start the query
  • It would be nice if we could associate an object with the task. Something like:
{
  "queryId": "XXXXX",
  "userId": "XXXXXX",
  ... etc. ...
}
  • When tasks/get is called the MCP server can check with Trino directly to see if the query is complete and return the results when done
  • This way, the "task" that is operating has no need to know that it's part of an MCP call. Our MCP server can manage the independently operating process

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, that's helpful to get a better understading.

@dsp-ant dsp-ant added the roadmap/agents Roadmap: Agent Communication (Tasks lifecycle) label Apr 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

draft SEP proposal with a sponsor. roadmap/agents Roadmap: Agent Communication (Tasks lifecycle) SEP

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

7 participants