feat: implement support for SEP-1686 Tasks#755
feat: implement support for SEP-1686 Tasks#755LucaButBoring wants to merge 11 commits intomodelcontextprotocol:mainfrom
Conversation
4cf98e9 to
82ccfa1
Compare
|
Nice to have this in, the api seems quite the same as typescript sdk |
|
@LucaButBoring Thanks. I'll try to review the PR. In the meantime, please rebase against |
82ccfa1 to
8baba5e
Compare
|
Rebased - this is (unfortunately) a very large PR, but I recommend starting with the code related to the key design decisions, and there's also a new integration test ( |
|
I'm testing with our server implementation. The protocol version in the initialize request is |
Will fix this.
That's expected for tool calls from the client - the client only declares a I'll update the documentation to try and make this more clear. |
I ran into an issue with this. I've been developing server support for tasks for a while now. I was testing with the current version of the MCP Inspector which reports protocol version |
|
The current version of the inspector doesn't support Tasks, that's being worked on here: modelcontextprotocol/inspector#1013 |
Yeah, I know. But it still sends protocol version |
|
It is, I'm testing my fix for that right now - looks like #733 added the constant for it but the corresponding constant for the latest version wasn't changed to it. |
|
@Randgalt That requires changing a lot of tests, so I'm considering making that change in a separate PR, is that fine? The protocol version change is technically needed for all of the 11/25 spec features, so I don't think this PR should include it (it should've been done independently first, really). For testing right now, you can declare the supported protocol versions when constructing the transport, like this: var transport = HttpClientStreamableHttpTransport // or WebClientStreamableHttpTransport
.builder("http://localhost:" + PORT) // use WebClient.builder().baseUrl() here for WebClientStreamableHttpTransport
.supportedProtocolVersions(List.of(ProtocolVersions.MCP_2025_11_25))
.build(); |
|
Opened #758 to bump the declared protocol version |
|
OK - I pulled both PRs and made a temp branch that has both of them and did some testing. I still don't understand how Here's my code: The above stays at |
|
I just tried with
That's it, we don't see the elicit result nor another |
|
Looks like elicitation/sampling aren't wired up to the message queue correctly, I'm adjusting the implementation/tests to address this and properly detect issues with it (this was also an oversight I made in TS at first but the API differences tripped me up when verifying it). Will update once that's resolved. |
|
Should be fixed, now - wired up the queue properly (and trimmed its API surface down a fair bit) and updated the tests to avoid internal blocking, which was masking the issue before |
It's still behaving somewhat as before. I call Then If I use |
| @@ -1590,7 +2656,7 @@ public record CallToolResult( // @formatter:off | |||
| @JsonProperty("content") List<Content> content, | |||
| @JsonProperty("isError") Boolean isError, | |||
| @JsonProperty("structuredContent") Object structuredContent, | |||
| @JsonProperty("_meta") Map<String, Object> meta) implements Result { // @formatter:on | |||
| @JsonProperty("_meta") Map<String, Object> meta) implements ServerTaskPayloadResult { // @formatter:on | |||
There was a problem hiding this comment.
public record CallToolResult( // @formatter:off
@JsonProperty("content") List<Content> content,
@JsonProperty("isError") Boolean isError,
@JsonProperty("structuredContent") Object structuredContent,
@JsonProperty("task") Task task,
@JsonProperty("_meta") Map<String, Object> meta) implements Result, GetTaskPayloadResult {I think something like this would be better.
There was a problem hiding this comment.
You mean so that CallToolResult implicitly satisfies CreateTaskResult? It seems unintuitive for SDK consumers to have an ambiguous union like that in method signatures.
There was a problem hiding this comment.
Yes,I implemented as this, which is simpler
|
@Randgalt I pushed a working client/server example to a separate branch (client; server) - how closely does this match your repro? Trying to figure out where our setups diverge so I can diagnose this effectively. |
This helps, thank you. I found one bug in our server and what I think is a bug in your client. We're expecting |
872aff7 to
f3aff3e
Compare
|
@chemicL Updated, how does |
|
@LucaButBoring Any updates on this? |
|
Not yet, I'm looking for at least some additional feedback on the implementation approach from maintainers right now. |
ff34b60 to
3e054f8
Compare
Also added taskId convenience overloads.
This refactor splits most Task orchestration logic out of McpAsyncServer, enabling custom task lifecycle handlers and improving maintainability. Key changes: **TaskManager abstraction:** - TaskManager interface defines task lifecycle operations - DefaultTaskManager implements full task orchestration logic - NullTaskManager provides no-op implementation for serverless deployments - TaskManagerHost interface enables server-to-task-manager communication **Custom handler support:** - invokeCustomTaskHandler() allows tools to provide custom getTask/getTaskResult handlers - TaskAwareAsyncToolSpecification.getTaskHandler() for custom task status - TaskAwareAsyncToolSpecification.getTaskResultHandler() for custom results - Handlers integrate seamlessly with existing task infrastructure **Architecture improvements:** - RequestTaskStore manages task-to-request mapping for side-channeling - TaskManagerMessageProcessorAdapter bridges session and task manager - McpSessionMessageProcessor interface for decoupled message processing
3e054f8 to
8534220
Compare
|
Another month... we're waiting on this PR so we can test our changes. |
|
Any update on this? Are we waiting for reviews? Is it possible to break it into phases or atleast just merge the toolexecution/taskstore into schemas so we run our own forks for now and later on converge when the reference implementation catches up? |
|
Hey all. We are working on this in the background. We're going to try and land it as soon as possible. |
|
@LucaButBoring and @Kehrlann, thanks so much for your work on this! Do you have a rough estimate for when this change might land? We're trying to unblock an urgent need for Tasks support on our end. If it's going to take longer than a week, no worries at all, we'll just fork the SDK temporarily. Please let me know, thank you. |
|
@YousefHaggy Sorry for the repeated delays on this 😓 I discussed this with @chemicL in person during the MCP Dev Summit a couple of weeks ago, and we identified a few (substantial) concrete adjustments to make to this PR, and I'm also working on the stabilization proposal (modelcontextprotocol/modelcontextprotocol#2557) in parallel, which would introduce some breaking changes that would basically immediately stack on top of this (next spec release is tentatively 6/30) - I need to discuss that complication with the Spring folks as well. Given your degree of urgency I agree that a temporary fork is your most viable option, as this will definitely take at least another week to sort out. For full context, the specific changes @chemicL suggested after we reviewed this together were:
|
|
@LucaButBoring I understand the planned release is for June 30th, but is there an estimation on when this one will be merged? as well as the stabilization proposal? Thanks for the hard work! |
Implements SEP-1686 (Tasks), from the latest specification release.
Motivation and Context
Tasks address several protocol gaps:
start_tool,get_status,get_resulttools; a single task-aware tool handles the full lifecycleUsage
Server: Defining a Task-Aware Tool
Server: Using CreateTaskContext for Lifecycle Management
TaskSupportMode options:
REQUIRED(default): Must have task metadata; returns error otherwiseOPTIONAL: Works with or without task metadata; auto-polling shim provides backward compatibilityFORBIDDEN: No task support (regular tools)Client: Streaming API (Recommended)
Drop-in replacement for
callToolthat handles polling automatically:Client: Task-Based API (For Explicit Control)
For consumers who need custom polling behavior, cancellation logic, or batched task management:
Similar patterns exist for sampling (
createMessageStream/createMessageTask) and elicitation (createElicitationStream/createElicitationTask).Server: Bidirectional Task Flows
Servers can send task-augmented requests to clients, assuming the client has configured its own
TaskStore:Key Design Decisions
Experimental namespace - All task APIs are in
io.modelcontextprotocol.experimental.tasks, signaling that the API may change (matches TypeScript/Python SDKs)TaskStore abstraction - Interface for pluggable storage;
InMemoryTaskStoreprovided for development and testing. The originating request (e.g.,CallToolRequest) is stored alongside the task, so tool routing can be derived from stored context rather than maintained as separate mapping state.Auto-polling shim -
OPTIONALmode tools work transparently for non-task-aware clientsDefense-in-depth session isolation - Session ID required on all TaskStore operations; enforced at both server and storage layers to prevent cross-session task access
nullforsessionIdbypasses validation (single-tenant mode). This is used byMcpAsyncClientsince clients are inherently single-tenant - there's only one session, so cross-session isolation doesn't apply.How Has This Been Tested?
Breaking Changes
None
Types of changes
Checklist
Additional context
Closes #668
This PR also includes a tweak to how
202 Acceptedis handled by the client implementation, which was done to handle how the TypeScript server SDK configures its response headers when accepting JSON-RPC responses and notifications from the client - in particular, sendingInitializeNotificationproduced an exception in the Java SDK client before this, which made testing compatibility with the existing TS SDK's Tasks implementation rather difficult.