Skip to content

SEP-2663: Tasks Extension #1013

Open

Description

@localden

Tracking implementation of SEP-2663 for the 2026-07-28 MCP spec release.

Activity

  1. added
    enhancementNew feature or request
    extensionsWork relating to MCP extensions - see https://modelcontextprotocol.io/extensions/overview
    P2Moderate issues affecting some users, edge cases, potentially valuable feature
    on Jun 23, 2026
  2. Kehrlann commented on Jun 24, 2026

    @Kehrlann
    Contributor

    We will prioritize extensions in the SDK after we have the core spec covered.

    If you need hooks to provide your own implementation, please open a new issue and link to this one.

  3. added this to the 3.x Planning milestone on Jun 25, 2026
  4. anushree1808 commented on Sep 1, 2026

    @anushree1808

    Hi. What is the estimated timeline for the new protocol version and task extensions to be available?

  5. liotur commented on Oct 12, 2026

    @liotur

    I'd like to pick up the Tasks extension for the Java SDK. Some of the tools on the MCP servers I run in production need a human approval before they execute, so I'm also looking at SEP-2848, which builds on Tasks. @chemicL should this wait for #1170 to land, or is it fine to start against the current server API?

  6. chemicL commented on Oct 15, 2026

    @chemicL
    Member

    @liotur thanks. #1170 is just the first step, we need to follow up with the client implementation. From then on we can think of implementing tasks. Please do contribute here in the form of ideas around the design for the extension. In the modern server primitives I tried to come up with more flexible interfaces for pluggability in mind so feedback on that will be very valuable.

  7. modified the milestones: 2.2.0-M1, 2.2.0-M2 on Oct 16, 2026
  8. liotur commented on Oct 16, 2026

    @liotur

    @chemicL thanks. I read through #1170 with Tasks in mind. Most of it fits as a plain feature: a TasksFeature can serve tasks/get, tasks/update and tasks/cancel, advertise the extension and check ctx.clientCapabilities().extensions(), and since CallToolOutcome is open, tools/call can already return a CreateTaskResult. A few questions:

    1. Where should the decision to create a task live? Today it's either a feature that wraps ToolsFeature, or the repository returning CreateTaskResult. With the repository route, the result that tasks/get serves later skips the outputSchema check, since that's private to ToolsFeature. Either way, it's up to each implementation not to return a task to a client that didn't declare the extension on that request. McpServer already rejects InputRequired from methods that don't support it. Would a similar check for task results fit? (Add ability to intercept tool call executions. #720 looks related.)

    2. Whatever runs the tool after CreateTaskResult can only reuse the original McpRequestContext (final, package-private constructor, no attributes). So there's no way to hand the tool anything task-specific, like a cancel flag for blocking code. Is reusing the original context the intended pattern?

    3. Every tasks/* call needs an auth check, but the only principal hook is the extractor on HmacRequestStateCodec, and it defaults to null. Would a server-level principal resolver that features can read make sense? SEP-2848 would need the same principal in requestState and in the task.

    Smaller: SEP-2663 has clients send Mcp-Name set to the taskId on tasks/*, but the transport checks Mcp-Name only for three hard-coded methods, so an extension can't opt in.

    Happy to open separate issues for any of these if you'd rather track them outside #1013.

  9. modified the milestones: 2.2.0-M2, 2.2.0-M3 on Nov 2, 2026
  10. Kehrlann commented on Nov 6, 2026

    @Kehrlann
    Contributor

    @liotur

    1. I think your intuition is good: you'd wrap the ToolsFeature. Returning a CreateTaskResultcan be in a normal handler in the tools repo. However, everything else, including task/get, needs to be in a feature. In there, you can then validate the output schema of task/get's result. We haven't planned interceptors, as this can be modeled with features directly. The API is low level and needs a bit of Reactor, but your feature will probably do something like:
    public class CustomTasksFeature implements McpFeature {
    
    	private final ToolsFeature delegate = null; // todo: inject the real thing
    
    	@Override
    	public Mono<? extends McpAsyncResponse<? extends McpSchema.Result>> handle(McpRequestContext ctx, Object params) {
    		return preProcess(ctx, params)
    			// then optionally call the tool
    			.flatMap(preProcessed -> {
    				// you can do if/else here, or in a deferred mono
    				if (preProcessed.someDecision()) {
    					return Mono.just(McpAsyncResponse.result(McpSchema.CallToolResult.builder().isError(true).build()));
    				}
    				else {
    					return delegate.handle(ctx, params);
    				}
    			})
    			// post-processing
    			.map(response -> {
    				if (response instanceof McpAsyncResponse.Result<? extends McpSchema.Result> result
    						&& result.result() instanceof McpSchema.CallToolResult toolResult) {
                        // here you could validate the output schema
    					var updatedResult = postProcess(toolResult);
    					return McpAsyncResponse.result(updatedResult);
    				}
    				return response;
    			});
    	}
    
    	private McpSchema.CallToolResult postProcess(McpSchema.CallToolResult toolResult) {
    		return null;
    	}
    
    	private Mono<PreProcessed> preProcess(McpRequestContext ctx, Object params) {
    		return null;
    	}
    
    	record PreProcessed(McpRequestContext ctx, Object params, Object otherStuff) {
    		boolean someDecision() {
    			return true;
    		}
    	}
    
    	// ...
    
    }
    1. Correct, context is known at task creation time and that's about it - if the client doesn't poll, we don't know anything else. Note that context is mostly information about the request, e.g. client capabilities, name and version. The context, in turn, context an McpTransportContext which can hold arbitrary data injected through an McpContextExtractor. This allows you to pass, say, the principal executing the task, or a tenant header, etc, but that's request level. I'm not sure what you mean by cancel flag; but I imagine you want to pass "extra arguments" to the tool. In that case, in the "preprocessing" step above, you change the CallToolRequest to have more arguments, maybe gated in _meta or something.
    2. Weeeeelllll tasks are vague about auth. SEP says "Servers MUST perform authentication and authorization checks" but in STDIO there is no concept of auth. The hook for getting a principal is the contextExtractor, as stated above. It's server-level (transport-level, technically), and ends up in McpRequestContext.transportContext() under whatever key you choose for your principal.

    Mcp-Name: that's a gap indeed. That's an annoying abstraction leak between the transport layer and server layer - what about tasks on STDIO ?
    You could solve it again with a transport extractor + feature-level validation, but the whole idea of a feature is to self-contain extensions.
    I think for Mcp-Name we'll probably take a shortcut and carry it forward. Opened #1317 for this purpose.

  11. liotur commented on Nov 8, 2026

    @liotur

    @Kehrlann thanks, that's exactly what I needed. I went through the sketch against main and it works; three things I ran into that are worth writing down for whoever does this next.

    The delegate. ToolsFeature.ofAsync(repository) is public, so there's nothing to inject — the wrapper builds its own and is registered in place of it. McpServer.Builder.build() doesn't add a ToolsFeature of its own, and byMethod.putIfAbsent throws when two features claim the same method, so registering both isn't an option anyway. That's also what makes the wrapper reliably in the path rather than something a request can go around.

    What the wrapper has to forward. methods() and handle are the only abstract members, so a wrapper that implements just those compiles and silently drops the rest: capabilities (ToolsFeature does builder.tools(false), so tools stop being advertised) and inputRequiredMethods. Both are defaults on McpFeature, so nothing warns. Forwarding them to the delegate is enough.

    Output schema. Validating in the wrapper after delegate.handle only covers the synchronous result. For the result tasks/get serves later the wrapper has to validate on the way in to the store, since by then there's no CallToolResult coming back through it — which lines up with keeping tasks/get in a feature.

    On 2 and 3, that's clear — request-level context, and extra arguments go in by rewriting the CallToolRequest during pre-processing. For the principal it's McpTransportContextExtractor into transportContext(), which works for both tasks and the SEP-2848 case since they read the same context.

    Mcp-Name: carrying it forward sounds right to me. Happy to open a separate issue for just the transport-level part — the check is hard-coded to three methods today, so an extension can't opt in — if you'd like it tracked outside this one. I'll leave 1–3 here since they're answered.

  12. chemicL commented on Nov 9, 2026

    @chemicL
    Member

    @liotur thanks, that's a useful perspective!

    I'd prefer Tasks to be a feature in its own right that replaces ToolsFeature instead of wrapping it.

    The Tasks feature would take two repositories: the regular tool repository you already have, and a new one for task tools. Regular tools are served as they are today. A task tool is still a tool to the client, listed in tools/list, but its handler receives a task handle (cancel signal, input requests) and returns a task. The feature serves tools/list, tools/call and tasks/*, so deciding to create a task, checking the client's capability, and validating the stored result all happen in one place. Migrating a tool to a task means moving it to the task repository.

    For that to work without duplicating ToolsFeature, we can make its building blocks reusable. Separately, Mcp-Name validation should work for extension methods too, so tasks/* can be checked against taskId.

    An extensible subscriptions/listen for taskIds and notifications/tasks is already part of my client work for #1011.

    @Kehrlann if you agree with this direction, we can create an issue for making the ToolsFeature building blocks reusable (not needed for GA).

  13. Kehrlann commented on Nov 9, 2026

    @Kehrlann
    Contributor

    @Kehrlann if you agree with this direction, we can create an issue for making the ToolsFeature building blocks reusable (not needed for GA).

    Agreed, opened #1320

  14. modified the milestones: 2.2.0, 2.3.0 on Nov 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Moderate issues affecting some users, edge cases, potentially valuable featureenhancementNew feature or requestextensionsWork relating to MCP extensions - see https://modelcontextprotocol.io/extensions/overview

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions