You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Session-level client extensions overwrite the per-request Tasks opt-in: CallToolAsTaskAsync never creates a task #1920
On the 2026-07-28 revision, a client whose session capabilities declare any extension (for example MCP Apps, io.modelcontextprotocol/ui) never gets a task back from CallToolAsTaskAsync. The server answers synchronously, as if the client had not opted in to Tasks.
McpTasksClientExtensions.CallToolAsTaskAsync writes _meta["io.modelcontextprotocol/clientCapabilities"].extensions["io.modelcontextprotocol/tasks"] on the request. McpSessionHandler.InjectRequestMeta then merges the session capabilities one top-level key at a time:
When the session capabilities carry extensions, that key replaces the request's whole extensions object, and the tasks entry is lost. The comment above this block says per-request extensions such as io.modelcontextprotocol/tasks must be preserved, so the shallow merge contradicts the intent.
Reproduced on 2.0.0. The same code ships in 2.2.0 and is on main.
To Reproduce
[TestMethod][DataRow(false)]// passes[DataRow(true)]// fails: IsTask is false, negotiated 2026-07-28publicasyncTaskCallToolAsTaskAsync_CreatesATask(booldeclareSessionExtension){varbuilder=WebApplication.CreateSlimBuilder();builder.WebHost.UseUrls("http://127.0.0.1:0");builder.Services.AddMcpServer(options =>options.ToolCollection=[McpServerTool.Create(async(CancellationTokenct)=>{awaitTask.Delay(200,ct);return"done";},newMcpServerToolCreateOptions{Name="slow"}),]).WithHttpTransport().WithTasks(newInMemoryMcpTaskStore());awaitusingvarapp=builder.Build();app.MapMcp("/mcp");awaitapp.StartAsync();varoptions=newMcpClientOptions();if(declareSessionExtension){options.Capabilities=newClientCapabilities{Extensions=newDictionary<string,object>{["io.modelcontextprotocol/ui"]=newJsonObject()},};}awaitusingvarclient=awaitMcpClient.CreateAsync(newHttpClientTransport(newHttpClientTransportOptions{Endpoint=newUri($"{app.Urls.First()}/mcp")}),options);varanswer=awaitclient.CallToolAsTaskAsync(newCallToolRequestParams{Name="slow"});Assert.IsTrue(answer.IsTask);}
Expected behavior
The request declares both the session extensions and the per-request io.modelcontextprotocol/tasks extension, and the server answers a CreateTaskResult. extensions (and any other object-valued capability) should be merged key by key, not replaced.
Workaround
Declare io.modelcontextprotocol/tasks in the session capabilities, next to the other extensions.
Environment
ModelContextProtocol 2.0.0 (reproduced); the merge code is unchanged in 2.2.0 and on main
ModelContextProtocol.Extensions.Tasks 2.0.0
.NET 10, Streamable HTTP, stateless server
Note
This issue was AI-generated (Claude Code). The repro was run locally against 2.0.0.
Activity