Skip to content

feat(ai-mcp): support task-based tool execution#940

Open
season179 wants to merge 8 commits into
TanStack:mainfrom
season179:feat/ai-mcp-task-execution
Open

feat(ai-mcp): support task-based tool execution#940
season179 wants to merge 8 commits into
TanStack:mainfrom
season179:feat/ai-mcp-task-execution

Conversation

@season179

@season179 season179 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • execute MCP tools with execution.taskSupport: 'required' through the SDK's experimental callToolStream flow
  • make task-required tools available through auto-discovery, explicit definitions, direct MCPClient.callTool(), and MCP Apps calls
  • keep task-optional tools on ordinary callTool, preserve the deprecated task-required error export, and document abort semantics
  • add real in-memory and Streamable HTTP task servers with unit and E2E coverage

On abort, the client stops waiting for the task stream but does not cancel a remote task that has already been created. Task progress events and public task-management methods remain out of scope for this initial implementation.

Closes #704

Test plan

  • pnpm test:pr
  • pnpm --filter @tanstack/ai-e2e test:e2e (349 passed, 1 gated skip)

Summary by CodeRabbit

  • New Features
    • Added support for MCP tools requiring task-based execution with automatic discovery and execution via the experimental task stream.
    • Extended the MCP “tasks” demo with new server/chat endpoints to showcase polling and task results.
  • Bug Fixes
    • Improved abort/cancellation behavior: local execution stops waiting, while already-created remote tasks continue; best-effort task cancellation is sent for in-flight tasks.
  • Documentation
    • Updated MCP docs and package/feature READMEs to describe task execution mode, capability gating, and revised cancellation/error guidance.
  • Tests
    • Expanded unit and end-to-end coverage for task streaming, discovery, schema handling, cancellation, and tool execution outcomes.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

MCP task-required tools are discovered and executed through the SDK’s experimental task stream. Optional-task tools continue using standard calls, with capability gating, abort handling, refreshed discovery, end-to-end coverage, a task execution example, and updated documentation.

Changes

MCP task execution

Layer / File(s) Summary
Task-aware execution routing
packages/ai-mcp/src/client.ts, packages/ai-mcp/src/tools.ts, packages/ai-mcp/src/errors.ts
Task-required tools use task streams, terminal results and errors are handled, abort signals are forwarded, discovery is cached and paginated, and capability checks remain enforced.
Client discovery and unit coverage
packages/ai-mcp/tests/helpers/*, packages/ai-mcp/tests/*.test.ts
Tests cover task execution, pagination, caching, fallback calls, capability gating, cancellation, and tools/list_changed refreshes.
Task-capable end-to-end flows
testing/e2e/src/routes/*, testing/e2e/tests/*
Mock MCP servers persist task state, and MCP status and MCP Apps tests verify task-required discovery and results.
Task execution example flow
examples/ts-react-chat/src/lib/*, examples/ts-react-chat/src/routes/*, examples/ts-react-chat/src/routeTree.gen.ts
The example adds task server and chat routes, server-side adapter resolution, route registration, cleanup handling, and a tasks demo mode.
Documentation and release metadata
.changeset/*, docs/tools/*, docs/config.json, packages/ai-mcp/README.md, packages/ai-mcp/skills/ai-mcp/*
Documentation describes task execution, abort behavior, capability requirements, and the compatibility error.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ChatClient
  participant TasksChatRoute
  participant MCPClient
  participant TasksMCPServer
  ChatClient->>TasksChatRoute: POST chat request
  TasksChatRoute->>MCPClient: create task-aware MCP client
  MCPClient->>TasksMCPServer: listTools()
  MCPClient->>TasksMCPServer: tasks.callToolStream()
  TasksMCPServer-->>MCPClient: task status and terminal result
  MCPClient-->>TasksChatRoute: tool execution output
  TasksChatRoute-->>ChatClient: server-sent events
Loading

Possibly related PRs

  • TanStack/ai#700: Introduced the MCP abort-signal plumbing extended here for task-aware execution.

Suggested reviewers: alemtuzlak, tombeckenham

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The Gemini model update in the ts-react-chat example is unrelated to task-based MCP execution and looks outside the issue scope. Remove unrelated provider/model changes or explain why they are needed for the tasks demo, and keep the PR centered on task-based MCP execution.
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding task-based tool execution to ai-mcp.
Description check ✅ Passed The description is detailed and covers the main change and test plan, but it omits the template's Checklist and Release Impact sections.
Linked Issues check ✅ Passed The changes implement task-required tool execution, discovery, abort handling, and preserve optional-tool behavior as requested in #704.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/ai-mcp/tests/helpers/in-memory-server.ts (1)

72-99: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Duplicated registerToolTask boilerplate; verify SDK actually invokes getTask/getTaskResult. Both sites register a task-required tool with an identical createTask (synchronously stores a 'completed' result)/getTask/getTaskResult triple. Because createTask completes the task synchronously, these tests can pass even if the SDK never calls getTask/getTaskResult — which is exactly the bug fixed by @modelcontextprotocol/sdk PR #1335 for the v1.x line ("registerToolTask's getTask and getTaskResult handlers not being invoked").

  • packages/ai-mcp/tests/helpers/in-memory-server.ts#L72-L99: confirm the installed @modelcontextprotocol/sdk version actually calls these handlers (e.g., add a spy/counter assertion), and consider extracting a shared registerSyncTaskTool(server, name, config, resultBuilder) helper to de-duplicate this pattern.
  • testing/e2e/src/routes/api.mcp-server.ts#L63-L93: same verification and de-duplication applies here; this block is otherwise identical to the one in in-memory-server.ts.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-mcp/tests/helpers/in-memory-server.ts` around lines 72 - 99, In
packages/ai-mcp/tests/helpers/in-memory-server.ts#L72-L99 and
testing/e2e/src/routes/api.mcp-server.ts#L63-L93, extract the duplicated
registerToolTask setup into a shared registerSyncTaskTool helper, then add spy
or counter assertions proving the installed SDK invokes getTask and
getTaskResult; preserve the synchronous completed-result behavior and apply the
shared helper at both sites.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ai-mcp/tests/tools.test.ts`:
- Line 246: Replace the expect.anything() matcher in the callMcpTool non-task
branch assertion with an exact options object asserting { signal: undefined },
preserving verification that the forwarded signal shape is constructed
correctly.

---

Nitpick comments:
In `@packages/ai-mcp/tests/helpers/in-memory-server.ts`:
- Around line 72-99: In
packages/ai-mcp/tests/helpers/in-memory-server.ts#L72-L99 and
testing/e2e/src/routes/api.mcp-server.ts#L63-L93, extract the duplicated
registerToolTask setup into a shared registerSyncTaskTool helper, then add spy
or counter assertions proving the installed SDK invokes getTask and
getTaskResult; preserve the synchronous completed-result behavior and apply the
shared helper at both sites.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 185d35bd-5add-4a50-8afb-cfdd8f1d5c7c

📥 Commits

Reviewing files that changed from the base of the PR and between 5fcaf90 and e6a82f5.

📒 Files selected for processing (17)
  • .changeset/mcp-task-execution.md
  • docs/config.json
  • docs/tools/mcp-manual.md
  • docs/tools/mcp.md
  • packages/ai-mcp/README.md
  • packages/ai-mcp/skills/ai-mcp/SKILL.md
  • packages/ai-mcp/src/client.ts
  • packages/ai-mcp/src/errors.ts
  • packages/ai-mcp/src/tools.ts
  • packages/ai-mcp/tests/client.test.ts
  • packages/ai-mcp/tests/helpers/in-memory-server.ts
  • packages/ai-mcp/tests/tools.test.ts
  • testing/e2e/src/routes/api.mcp-apps-server.ts
  • testing/e2e/src/routes/api.mcp-server.ts
  • testing/e2e/src/routes/api.mcp-status-test.ts
  • testing/e2e/tests/mcp-apps.spec.ts
  • testing/e2e/tests/mcp-status.spec.ts

Comment thread packages/ai-mcp/tests/tools.test.ts Outdated
@season179

Copy link
Copy Markdown
Contributor Author

Review follow-up on the task-fixture nitpick: I verified the suggested callback counters against the installed @modelcontextprotocol/sdk@1.29.0. Its tasks/get and tasks/getResult protocol handlers read taskStore directly, so the registerToolTask getTask/getTaskResult callbacks are not invoked; a counter assertion against those callbacks fails even though the terminal result completes correctly. The handler-routing change shown in SDK PR #1335 is not present in 1.29.0.

I therefore did not add impossible callback assertions or extract a shared helper across the packages/ai-mcp test boundary and the E2E runtime app. The existing unit result assertion and Streamable HTTP E2E 4200 assertion both require the SDK task status/result polling path to complete successfully.

@AlemTuzlak

Copy link
Copy Markdown
Contributor

can you create an example in ts-react-chat so i cna manually test this out?

mcp-providers.ts exported both the provider metadata (used by the
/mcp-demo and /mcp-apps pages) and resolveTextAdapter, so the pages
dragged every provider SDK into the browser bundle. @anthropic-ai/sdk
sits in vite's optimizeDeps.exclude, which serves its CJS dependency
standardwebhooks un-prebundled and crashes both pages in dev ("does not
provide an export named 'Webhook'").

Split adapter resolution into a server-only mcp-provider-adapters.ts and
keep mcp-providers.ts as client-safe metadata. Also bump the Gemini
example model to gemini-3.5-flash — gemini-2.5-flash now 404s for new
users.
Manual test surface for task-based tool execution. An in-process
Streamable HTTP MCP server (/api/mcp-tasks-server) exposes a single
appraise_guitar_collection tool with execution.taskSupport: 'required',
and /api/mcp-tasks-chat runs it through chat({ mcp }) so the task flow
(create task → poll → fetch result) drives the call instead of plain
tools/call.

The appraisal takes ~4s, so the tool call visibly sits pending in the
UI while the client polls every 500ms, then the result lands and the
model summarizes it. Try: "Appraise my guitar collection: strat, tele,
jazzmaster".
@season179

Copy link
Copy Markdown
Contributor Author

Done — added a Tasks mode to the ts-react-chat MCP demo.

To test:

  1. cd examples/ts-react-chat && pnpm dev — needs an LLM key for whichever provider you pick in the UI (OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY, GROQ_API_KEY, or OPENROUTER_API_KEY)
  2. Open http://localhost:3000/mcp-demo and switch the mode to Tasks
  3. Ask: "Appraise my guitar collection: strat, tele, jazzmaster"

You should see the tool call sit pending for ~4s while the client polls, then the result lands and the model summarizes it ($1400/guitar → $4200 for that prompt).

How it works: the demo hosts an in-process Streamable HTTP MCP server at /api/mcp-tasks-server whose only tool declares execution.taskSupport: 'required', so @tanstack/ai-mcp routes the call through the task flow (create task → poll every 500ms → fetch result) instead of plain tools/call. The chat route is a standard chat({ mcp }) setup — nothing task-specific needed on that side.

Heads up: the first of the two new commits fixes a pre-existing dev crash on /mcp-demo (standardwebhooks CJS interop, pulled into the client bundle via @anthropic-ai/sdk which sits in vite's optimizeDeps.exclude) — the page didn't load in dev at all, so manual testing needed it. It splits adapter resolution out of mcp-providers.ts into a server-only module.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/ts-react-chat/src/routes/api.mcp-tasks-chat.ts`:
- Around line 45-86: Prevent MCP resource leaks when stream setup fails: in
examples/ts-react-chat/src/routes/api.mcp-tasks-chat.ts lines 45-86, keep the
client accessible outside the try block and close it in catch; in
examples/ts-react-chat/src/routes/api.mcp-chat.ts lines 46-113, close all
connected clients with Promise.allSettled in catch; in
examples/ts-react-chat/src/routes/api.mcp-pool.ts lines 52-96, keep the pool
accessible outside the try block and close it in catch. Ensure cleanup occurs
when chat or response initialization throws, without disrupting existing abort
and error responses.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f58b9551-3a75-4f0a-afa0-1de28f600cb0

📥 Commits

Reviewing files that changed from the base of the PR and between b38fcfd and 22a7097.

📒 Files selected for processing (10)
  • examples/ts-react-chat/src/lib/mcp-provider-adapters.ts
  • examples/ts-react-chat/src/lib/mcp-providers.ts
  • examples/ts-react-chat/src/routeTree.gen.ts
  • examples/ts-react-chat/src/routes/api.mcp-apps-chat.ts
  • examples/ts-react-chat/src/routes/api.mcp-chat.ts
  • examples/ts-react-chat/src/routes/api.mcp-manual.ts
  • examples/ts-react-chat/src/routes/api.mcp-pool.ts
  • examples/ts-react-chat/src/routes/api.mcp-tasks-chat.ts
  • examples/ts-react-chat/src/routes/api.mcp-tasks-server.ts
  • examples/ts-react-chat/src/routes/mcp-demo.tsx

Comment thread examples/ts-react-chat/src/routes/api.mcp-tasks-chat.ts
…nnect

chat() only takes ownership of the clients once the SSE stream is
consumed, so a synchronous throw between connect and returning the
response (adapter resolution, capability validation) leaked stdio
subprocesses / HTTP sessions. Hoist the client/pool handle out of the
try and close it defensively in catch — close() is idempotent, and the
abort/error responses are unchanged.
- Gate task execution on the server declaring the tasks capability for
  tools/call: auto-discovery skips task-required tools the server cannot
  execute, and binding one explicitly throws MCPTaskRequiredToolError
  (revived from its deprecated state).
- Follow tools/list pagination when discovering tool definitions.
- Pass the task param to callToolStream explicitly instead of relying on
  the SDK's internal metadata cache.
- callTool: lazy discovery fetches tools/list raw (doesn't arm the SDK's
  output-schema validators, keeping direct calls validation-free as
  before), tolerates a failing tools/list by falling back to a plain
  call, and no longer re-lists on every call for unlisted tool names.
- callTool accepts an optional { signal }; aborting a task-required call
  now sends a best-effort tasks/cancel for the in-flight remote task.
- Invalidate the cached definitions on tools/list_changed so execution
  modes are re-discovered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ai-mcp/src/client.ts`:
- Around line 291-300: Update the direct tool-call flow in the client method
containing taskRequired so it throws MCPTaskRequiredToolError when the resolved
definition requires task execution but serverSupportsTaskCalls(this.#client) is
false, rather than falling back to callMcpTool. Preserve ordinary calls for
non-task-required tools and add a regression test using
makeServerWithUnsupportedTaskTool.

In `@packages/ai-mcp/src/tools.ts`:
- Around line 83-93: Remove the abort-triggered tasks.cancelTask call and the
related taskId tracking from the stream handling in tools.ts; local abort must
only stop waiting without cancelling server-side work. Update the corresponding
client behavior in the client.ts task flow and revise the affected client tests
to assert no remote cancellation occurs on abort.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 24e38f8f-f489-42c4-a4a9-3a27f49f08ab

📥 Commits

Reviewing files that changed from the base of the PR and between 173344a and 2f557cb.

📒 Files selected for processing (10)
  • .changeset/mcp-task-execution.md
  • docs/config.json
  • docs/tools/mcp-manual.md
  • docs/tools/mcp.md
  • packages/ai-mcp/src/client.ts
  • packages/ai-mcp/src/errors.ts
  • packages/ai-mcp/src/tools.ts
  • packages/ai-mcp/tests/client.test.ts
  • packages/ai-mcp/tests/helpers/in-memory-server.ts
  • packages/ai-mcp/tests/tools.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • docs/config.json
  • packages/ai-mcp/tests/tools.test.ts
  • docs/tools/mcp-manual.md

Comment thread packages/ai-mcp/src/client.ts
Comment thread packages/ai-mcp/src/tools.ts
… server cannot execute

Throw MCPTaskRequiredToolError (mirroring the tools([...defs]) binding
guard) instead of sending a plain tools/call the server rejects with an
opaque -32600.
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.

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

2 participants