-
Notifications
You must be signed in to change notification settings - Fork 667
Tool returning McpTask is double-wrapped when client sends task-augmented request #1480
Description
Describe the bug
When a tool explicitly creates its own task via IMcpTaskStore and returns Task<McpTask> (the pattern documented in docs/concepts/tasks/tasks.md), the SDK creates a second task wrapping the tool invocation and returns that to the client. The tool-created task is orphaned in the store. This is at odds with what is documented in the reference: "When a tool returns McpTask, the SDK bypasses automatic task wrapping and returns the task directly to the client."
This happens because ExecuteToolAsTaskAsync in McpServerImpl.cs unconditionally creates its own task and runs the tool in a background Task.Run — it never checks whether the tool's CallToolResult already contains a Task. Additionally, AIFunctionMcpServerTool.InvokeAsync has no case for McpTask in its result switch expression, so the returned McpTask falls into the default _ case and gets JSON-serialized as text content rather than being placed on CallToolResult.Task.
To Reproduce
- Register a tool that returns
Task<McpTask>after callingIMcpTaskStore.CreateTaskAsync(the exact pattern from the docs) - Call the tool with task metadata via
CallToolAsTaskAsync - Observe that the task ID returned to the client differs from the one the tool created
- List all tasks — there are 2 instead of 1
Self-contained repro: https://github.com/k8x10/mcp-double-task-issue
dotnet run
Expected behavior
When a tool returns McpTask, the SDK should bypass automatic task wrapping and return that task directly to the client, as stated in the docs:
"When a tool returns
McpTask, the SDK bypasses automatic task wrapping and returns the task directly to the client."
Only 1 task should exist in the store, and its ID should match what the client receives.
Logs
Calling tool with task metadata (task-augmented request)...
Tool created task: ID = 08de8ec1774cf5040000000000000002
Task returned to client: ID = 08de8ec1774c34650000000000000001
Status = Working
Total tasks in store: 2
Task ID = 08de8ec1774c34650000000000000001, Status = Completed
Task ID = 08de8ec1774cf5040000000000000002, Status = Working
BUG CONFIRMED: Double-task issue.
Expected 1 task (tool-created), found 2.
Client received task: 08de8ec1774c34650000000000000001 (SDK-created)
Tool actually created: 08de8ec1774cf5040000000000000002 (orphaned)
Additional context
There are two issues contributing to this:
-
AIFunctionMcpServerTool.InvokeAsync— The result switch expression has noMcpTaskcase. When a tool returnsMcpTask, it falls into the default_ =>arm and gets serialized as a JSON textContentBlockinstead of being placed onCallToolResult.Task. -
McpServerImpl.ExecuteToolAsTaskAsync— When the client sends a task-augmented request (request.Params?.Taskis set), this method unconditionally creates its own SDK-managed task, runs the tool inTask.Run, and stores the tool's result as the SDK task's payload. This should ideally be completely bypassed if the SDK detects that a tool has a return type of McpTask. Even if issue 1 is fixed, this code path would still double-wrap.
Tested with ModelContextProtocol.AspNetCore 1.2.0 on .NET 10.