Skip to content

mcp: add tasks extension capability negotiation - #755

Open
JoannaaKL wants to merge 4 commits into
modelcontextprotocol:mainfrom
JoannaaKL:add-tasks-api
Open

mcp: add tasks extension capability negotiation#755
JoannaaKL wants to merge 4 commits into
modelcontextprotocol:mainfrom
JoannaaKL:add-tasks-api

Conversation

@JoannaaKL

@JoannaaKL JoannaaKL commented Jan 16, 2026

Copy link
Copy Markdown

Adds capability negotiation for the MCP Tasks extension (io.modelcontextprotocol/tasks). First step of #626; task execution follows in later PRs.

Why

Tasks moved out of the core protocol and into an official extension in 2026-07-28 (SEP-2663), now specified in modelcontextprotocol/ext-tasks. The in-core design this PR originally implemented (SEP-1686, 2025-11-25) is gone from the current schema. The branch is rewritten against the extension, and scoped to capabilities only.

This also fixes a live bug. A CreateTaskResult decoded into an empty CallToolResult without error, so a tool call against any server that returns a task silently looked like it succeeded and returned nothing.

What

  • ExtensionTasks: the extension identifier.
  • HasExtension on ClientCapabilities and ServerCapabilities — the read-side counterpart to the existing AddExtension, which had no equivalent for testing whether a peer declared an extension. Generic, not tasks-specific.
  • Reserve the "task" resultType discriminator and reject it while decoding, returning UnsupportedTaskResultError with the task ID.

Negotiation is then one line on each side, over plumbing that already exists:

req.ClientCapabilities().HasExtension(mcp.ExtensionTasks)              // server
cs.InitializeResult().Capabilities.HasExtension(mcp.ExtensionTasks)    // client

The guard lives in UnmarshalJSON rather than in CallTool or a middleware: a middleware can be disabled via MultiRoundTrip.Disabled, which is the wrong shape for a correctness check. tools/call is the only task-eligible method today, but the spec asks implementations to accommodate more in future revisions, so the guard also covers prompts/get and resources/read. It adds no extra parsing, since all three result types already decode resultType.

Nothing is declared by default, on either side. Declaring the extension obliges a client to poll task handles and a server to serve tasks/*, neither of which the SDK implements yet; users with their own implementation can still declare it via AddExtension.

Note for whoever implements tasks/get: the error code for "Missing Required Client Capability" is -32021, so this SDK's existing CodeMissingRequiredClientCapabilities is already correct. The ext-tasks prose briefly said -32003 — a slip introduced when SEP-2663 was ported into that repo, where the SEP itself, the core 2026-07-28 schema and the TypeScript SDK all use -32021. Fixed upstream in modelcontextprotocol/ext-tasks#15, so the spec and this SDK now agree.

@jba

jba commented Jan 21, 2026

Copy link
Copy Markdown
Contributor

Please delete the guidelines and write a proper commit message. Link to the part of the spec that defines tasks.

@jba

jba commented Jan 21, 2026

Copy link
Copy Markdown
Contributor

Also link to our issue about tasks.

@jba

jba commented Jan 21, 2026

Copy link
Copy Markdown
Contributor

I just skimmed the spec. It is huge. I think we should implement it in multiple PRs. Let's start with the relatively easy stuff. How about a PR for capabilities first? And maybe one for the task data structure and related code, like generating the task ID?

@jba

jba commented Jan 21, 2026

Copy link
Copy Markdown
Contributor

I don't quite understand how calling a tool with a task works. It looks like the tool itself doesn't have to know about tasks. Where is the code that returns the task result immediately and then handles task lifetime management?

I think everything else is pretty straightforward, but this part of the design needs to be discussed on our tasks issue.

@JoannaaKL

Copy link
Copy Markdown
Author

Hey @jba pr is a draft and I assumed it's clear that it's not ready for a review yet. Body would be filled once the implementation is done.
Regarding the the tool calling - happy to move to the main issue and discuss there.

Add ExtensionTasks, the identifier of the io.modelcontextprotocol/tasks
extension, and nil-safe HasExtension accessors on ClientCapabilities and
ServerCapabilities, so both peers can negotiate the extension defined by
SEP-2663. AddExtension already covered declaring an extension; there was
no supported way to test for one.

Task execution is not implemented, and neither NewClient nor NewServer
declares the extension by default: declaring it obliges a client to
handle a task handle in place of any result, and a server to serve
tasks/get.

Reserve the "task" resultType discriminator and reject it while decoding.
Previously a CreateTaskResult decoded into an empty CallToolResult with
no error, so a tool call against a task-returning server silently
appeared to succeed and return nothing. It now fails with
UnsupportedTaskResultError, which carries the task ID.

Tasks moved out of the core protocol into an extension in 2026-07-28;
the earlier in-core design (SEP-1686, 2025-11-25) is not implemented.

For modelcontextprotocol#626
@JoannaaKL JoannaaKL changed the title First implementation of tasks mcp: add tasks extension capability negotiation Aug 17, 2026
SEP-2663 has merged, and the tasks extension now has a published
specification document. Point at both rather than at the SEP pull
request and the ext-tasks repository root.
@JoannaaKL

JoannaaKL commented Aug 20, 2026

Copy link
Copy Markdown
Author

Hey @jba, this is the capabilities-only pr you suggested, out of draft now.

Scope is just negotiation: ExtensionTasks, HasExtension on both capability types, and reserving the "task" resultType.

On your question about where task lifetime is handled - it isn't. Nothing is declared by default on either side, so we never promise a peer we can serve tasks. Happy to discuss the execution design in #626.

Two things changed since you last looked. Tasks moved out of core into an extension (SEP-2663) so I rewrote the branch against ext-tasks. And this fixes a bug: a CreateTaskResult decodes into an empty CallToolResult with no error today, so a tool call against a task-returning server looks like it succeeded and returned nothing.

Also ci has never run here, I think it needs your approval.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants