Skip to content

Tool returning McpTask is double-wrapped when client sends task-augmented request #1480

@k8x10

Description

@k8x10

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

  1. Register a tool that returns Task<McpTask> after calling IMcpTaskStore.CreateTaskAsync (the exact pattern from the docs)
  2. Call the tool with task metadata via CallToolAsTaskAsync
  3. Observe that the task ID returned to the client differs from the one the tool created
  4. 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:

  1. AIFunctionMcpServerTool.InvokeAsync — The result switch expression has no McpTask case. When a tool returns McpTask, it falls into the default _ => arm and gets serialized as a JSON text ContentBlock instead of being placed on CallToolResult.Task.

  2. McpServerImpl.ExecuteToolAsTaskAsync — When the client sends a task-augmented request (request.Params?.Task is set), this method unconditionally creates its own SDK-managed task, runs the tool in Task.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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions