SEP-2557: Stabilizing Tasks#2557
SEP-2557: Stabilizing Tasks#2557LucaButBoring wants to merge 12 commits intomodelcontextprotocol:mainfrom
Conversation
4215bbd to
554c8bc
Compare
6c61b6e to
c1aa24a
Compare
|
@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). |
|
Seeing as we're breaking things... Would it be possible to add a |
8b2d86d to
6a4a14c
Compare
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. |
| * 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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
My initial take is 0 support for the previous task version as it was marked as experimental and not fully supported.
cc: @felixweinberger
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
cc: @Kehrlann @chemicL (this affects modelcontextprotocol/java-sdk#755)
- Removed `task` nesting on `GetTaskResult` - Clarified header handling in the context of SEP-2243 - Clarified error semantics for tool calls with isError: true
|
Added |
|
Updated spec/schema language to reflect latest SEP document updates; new annotated diff here. |
| * when the task is in `input_required` state. | ||
| */ | ||
| inputResponses?: InputResponses; | ||
| }; |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
on the initial tool call that returns a task there is no request state? My thoughts regarding requestState:
- maybe it could be called
taskStateand put directly in theTaskmodel' - 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/taskStatemechanism and be managed by clients completely removing the need for server managed storage.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Sorry, I didn't mean to imply there would be no server context. But, task specific state could be managed in this additional carrier.
There was a problem hiding this comment.
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/getis 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
There was a problem hiding this comment.
Thanks, that's helpful to get a better understading.
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/resultintotasks/get, removing the error-prone interaction between theinput_requiredtask status and thetasks/resultmethod. 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-25specification 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:
input_requiredis 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.tasks/resultis a blocking method: To enable being used as a vehicle for SSE side-channeling,tasks/resultis 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/resultfor 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/getmethod.How Has This Been Tested?
TBD
Breaking Changes
Several; described in detail in the proposal.
Types of changes
Checklist
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.