Skip to content

feat(ai-mcp): support task-based tool execution (execution.taskSupport) #704

@tombeckenham

Description

@tombeckenham

Context

#700 ships @tanstack/ai-mcp with plain callTool() execution. The MCP spec's experimental tasks feature lets servers mark tools with execution.taskSupport: 'required' | 'optional' | 'forbidden'. Task-required tools reject plain tools/call with -32600 ("requires task-based execution") — e.g. simulate-research-query on recent @modelcontextprotocol/server-everything.

Current v1 behavior (since the task-required-tool fix on #700):

  • tools() auto-discovery excludes task-required tools (they could never succeed; offering them gives the model a guaranteed-failure tool).
  • Explicitly binding one via tools([toolDefinition(...)]) throws MCPTaskRequiredToolError.

Proposal

Support task-based execution so these tools become usable instead of skipped. The SDK API is a clean fit for our execute proxy:

const stream = client.experimental.tasks.callToolStream(
  { name, arguments: args },
  undefined,
  { signal: ctx?.abortSignal },
)
for await (const message of stream) {
  // 'taskCreated' | 'taskStatus' | 'result' | 'error' — guaranteed terminal result/error
}

Minimal version: branch in makeMcpExecute — task-required tools drain callToolStream to the terminal message; the terminal result is the same CallToolResult shape we already normalize (structuredContent / mcpContentToTanstack). Stop excluding them from discovery.

Optional second step: surface taskStatus updates through ToolExecutionContext.emitCustomEvent so UIs can render long-running tool progress in the transcript.

Why it was deferred from #700

The tasks API lives under client.experimental.* ("may change without notice"). Wiring the core execution path of a brand-new package to unstable SDK surface means a routine SDK bump could break tool execution. The v1 exclusion only depends on stable spec types (Tool.execution.taskSupport).

Design questions to settle

  • Should taskSupport: 'optional' tools also route through tasks (the SDK server does auto-polling for optional), or stay on plain callTool?
  • On chat-run abort: drop the stream, or also call cancelTask?
  • Expose getTask / listTasks / cancelTask on the MCPClient surface?
  • Surface taskCreated/taskStatus as custom events in the chat stream (devtools/UI rendering)?
  • How to pin/guard against experimental API drift across SDK versions (peer-dep range, runtime feature detection)?

References

  • Exclusion + MCPTaskRequiredToolError introduced in feat: host-side MCP client (@tanstack/ai-mcp) #700 (packages/ai-mcp/src/tools.ts requiresTaskExecution(), packages/ai-mcp/src/client.ts defs path)
  • SDK: @modelcontextprotocol/sdk/dist/esm/experimental/tasks/client.d.ts (ExperimentalClientTasks.callToolStream)
  • Repro: npx -y @modelcontextprotocol/server-everythingsimulate-research-query

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions