Skip to content

fix(ai): pass durable MCP input answers to pending server tools - #1903

Open
TPSJASPER wants to merge 2 commits into
TanStack:mainfrom
TPSJASPER:fix/durable-mcp-input
Open

TPSJASPER wants to merge 2 commits into
TanStack:mainfrom
TPSJASPER:fix/durable-mcp-input

Conversation

@TPSJASPER

@TPSJASPER TPSJASPER commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Run the durable MCP cases in the persistence interrupt tests. A durable resume with an empty message list left ctx.inputResponse undefined on the pending server tool. The tool asked for MCP input again. This PR maps the stored mcp_input_<toolCallId> answer onto that tool before it runs again.

🎯 Changes

A pending server tool now receives a resolved or cancelled MCP input answer when chat resumes through persistence with messages: []. The mapping lives in applyResumeToolState, which both the ephemeral path and the durable path call. Docs and a patch changeset are included.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested code changes locally with pnpm run test:pr, or these tests do not apply to this pull request.
  • I fully understand the code in this pull request, including any code generated with AI assistance.
  • Docs: I updated docs/ for this change, or this change is not user-facing.
  • Changeset: I added a changeset (pnpm changeset), or this PR does not change a published package.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Root cause

Issue. A pending server tool that waits for MCP input gets no answer on a durable resume with an empty message list.

Cause. Persistence applies those answers in the DURABLE_RESUME callback. The old mapping lived only in applyEphemeralInterruptResume. applyResumeToolState copied genericInterrupts and did not map mcp_input_<toolCallId> onto resumeInputResponses.

Fix. applyResumeToolState maps each pending server tool resolution into resumeInputResponses. The durable callback calls that function after messages and tools exist. The call inside applyMiddlewareConfig stays at the start of that function.

Possible alternatives

  • Map in run(). Add the same loop after the durable callback. This duplicates the ephemeral rule.
  • New middleware field. Put inputResponses on ChatResumeToolState. This widens the middleware contract.
  • Reorder applyMiddlewareConfig. Call applyResumeToolState after the message assignment. That config object does not carry genericInterrupts.

Testing

Commands run.

  1. packages/ai tests/mcp-input-interrupt.test.ts: 14 passed.
  2. packages/ai-persistence tests/interrupts.test.ts: 35 passed.
  3. pnpm test:pr was not run.
  4. Playwright testing/e2e/tests/durable-mcp-input.spec.ts was not run.

Manual test.

  1. On main, pause a server tool on mcp_input_<toolCallId>. Resume through withPersistence with messages: [] and a resolved payload. The tool runs again with ctx.inputResponse undefined and pauses a second time.
  2. On this branch, run packages/ai-persistence/tests/interrupts.test.ts.
  3. Confirm the resumed tool receives { status: 'resolved', payload }.
  4. Repeat with a cancelled answer. Confirm { status: 'cancelled' }.

How this PR makes testing easy. packages/ai-persistence/tests/interrupts.test.ts covers resolved, cancelled, and an unknown tool-call id. testing/e2e/tests/durable-mcp-input.spec.ts covers the same two statuses over HTTP.

Linked issues

Fixes #1885

Risk / rollback

Risk is low. The ephemeral MCP input path still has its own tests. Revert this PR to restore the old behavior.

Public API change

The tool callback is unchanged. The value of ctx.inputResponse on a durable resume changes.

Before

tool.server(async (_input, ctx) => {
  return ctx.inputResponse
})

On a durable resume with messages: [], ctx.inputResponse was undefined.

After

tool.server(async (_input, ctx) => {
  return ctx.inputResponse
})

The same resume sets ctx.inputResponse to { status: 'resolved', payload } or { status: 'cancelled' }.

Summary by CodeRabbit

  • Bug Fixes
    • Resuming a persisted conversation now delivers resolved or cancelled MCP input responses to pending server tools, even when the incoming message history is empty.
    • Runs now complete and clear pending interruptions when a response matches a pending tool call. Responses for unknown tool calls return an error without invoking the tool or clearing the interruption.
  • Documentation
    • Updated persistence guidance to explain how resolved and cancelled MCP input responses are handled when resuming a conversation.

withPersistence hands generic interrupt resolutions to the chat engine
through resumeToolState. The engine copied them into
resumeGenericInterrupts, but only the ephemeral path mapped
mcp_input_<toolCallId> answers into resumeInputResponses. A durable
resume therefore ran the pending server tool with ctx.inputResponse
undefined, so the tool asked for input again.

Apply the mapping in applyResumeToolState, which both resume paths use,
and run it after the restored history and tools exist. The ephemeral
path keeps the same behaviour and the old duplicate mapping goes away.

Fixes TanStack#1885
@changeset-bot

changeset-bot Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 27858f9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@tanstack/ai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: TanStack/ai
  • Branch: main
  • File: .coderabbit.yaml
  • Commit: 98055ca
  • Layers: Repository YAML
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7f98f31d-9971-4e02-8257-d6cf3fa9abc9
Commits

Reviewing files that changed from the base of the PR and between c73f7fd and 27858f9.

Files selected for processing: 1
  • packages/ai/src/activities/chat/index.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 4 remain after this review.

Walkthrough

Durable persistence resumes now pass resolved or cancelled MCP input responses to matching pending server tools. The change updates resume-state mapping, adds persistence and end-to-end tests, and documents support for resumes with empty incoming message history.

Changes: Durable MCP input resume

Layer / File(s) Summary
Match durable responses to pending tools
packages/ai/src/activities/chat/index.ts, docs/persistence/chat-persistence-advanced.md, .changeset/durable-mcp-input-response.md
Matching MCP input resolutions are stored by tool-call ID for pending executable server tools. The docs and changeset describe this response behavior during persistence resume.
Test persistence resume cases
packages/ai-persistence/tests/interrupts.test.ts
Tests cover resolved and cancelled responses for one or multiple pending calls. They also check that an unknown tool-call response does not invoke the adapter or clear the pending interrupt.
Exercise the resume flow through the E2E API
testing/e2e/src/routes/api.durable-mcp-input.ts, testing/e2e/src/routeTree.gen.ts, testing/e2e/tests/durable-mcp-input.spec.ts
A new route accepts resolved or cancelled responses and returns run events and persistence state. The E2E test checks that both response types complete the run and clear pending interrupts.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: alemtuzlak

Merge Risk: Minimal · up to 27858

The durable MCP resume behavior has no established merge-blocking issue.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: passing durable MCP input answers to pending server tools.
Description check Passed The description is complete and directly aligned with the changes. It explains the root cause, fix, alternatives, testing, linked issue, risk, rollback, and release impact. It also records that the fu…
Linked Issues check Passed Issue #1885 requires durable withPersistence resume with messages: [] to pass the matching MCP input answer to the pending server tool. applyResumeToolState now maps mcp_input_<toolCallId> res…
Out of Scope Changes check Passed The changes stay within issue #1885. The source change fixes resume-state mapping. The persistence and E2E tests verify the regression. The documentation and changeset describe the published behavior.…
Docstring Coverage Passed Docstring coverage is inconclusive: files from earlier reviews still need to be rechecked within the check's analysis limits. Not blocking.
Full details: Description check

Explanation

The description is complete and directly aligned with the changes. It explains the root cause, fix, alternatives, testing, linked issue, risk, rollback, and release impact. It also records that the full test suite and some E2E tests were not run.

Full details: Linked Issues check

Explanation

Issue #1885 requires durable withPersistence resume with messages: [] to pass the matching MCP input answer to the pending server tool. applyResumeToolState now maps mcp_input_&lt;toolCallId&gt; resolutions to resumeInputResponses after middleware restores messages and tools. Persistence tests cover resolved, cancelled, multiple pending tools, and unknown tool-call IDs. The E2E test covers the durable HTTP flow.

Full details: Out of Scope Changes check

Explanation

The changes stay within issue #1885. The source change fixes resume-state mapping. The persistence and E2E tests verify the regression. The documentation and changeset describe the published behavior. The generated route changes support the new E2E route. No unrelated product change is demonstrated.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@github-actions

Copy link
Copy Markdown
Contributor

Thanks for the PR, @TPSJASPER! 🙌 @tombeckenham will take a look.

Automated pre-review checks

  • ✅ CI passing
  • ✅ No merge conflicts
  • ✅ Changeset present
  • ✅ E2E test changes included

Automated triage — a human review follows.

@github-actions github-actions Bot added the waiting-on: maintainer The ball is in the maintainers’ court label Oct 29, 2026
Durable MCP answers arrive later from DURABLE_RESUME, after messages
and tools exist. Moving this call did not deliver them.
@nx-cloud

nx-cloud Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 27858f9

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 2m 40s View ↗

☁️ Nx Cloud last updated this comment at 2026-10-30 05:16:34 UTC

@pkg-pr-new

pkg-pr-new Bot commented Oct 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@1903

@tanstack/ai-acp

npm i https://pkg.pr.new/@tanstack/ai-acp@1903

@tanstack/ai-angular

npm i https://pkg.pr.new/@tanstack/ai-angular@1903

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@1903

@tanstack/ai-bedrock

npm i https://pkg.pr.new/@tanstack/ai-bedrock@1903

@tanstack/ai-byteplus

npm i https://pkg.pr.new/@tanstack/ai-byteplus@1903

@tanstack/ai-claude-code

npm i https://pkg.pr.new/@tanstack/ai-claude-code@1903

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@1903

@tanstack/ai-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-cloudflare@1903

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@1903

@tanstack/ai-code-mode-snippets

npm i https://pkg.pr.new/@tanstack/ai-code-mode-snippets@1903

@tanstack/ai-codex

npm i https://pkg.pr.new/@tanstack/ai-codex@1903

@tanstack/ai-cohere

npm i https://pkg.pr.new/@tanstack/ai-cohere@1903

@tanstack/ai-compaction

npm i https://pkg.pr.new/@tanstack/ai-compaction@1903

@tanstack/ai-computer-local

npm i https://pkg.pr.new/@tanstack/ai-computer-local@1903

@tanstack/ai-computer-playwright

npm i https://pkg.pr.new/@tanstack/ai-computer-playwright@1903

@tanstack/ai-computer-sandbox

npm i https://pkg.pr.new/@tanstack/ai-computer-sandbox@1903

@tanstack/ai-computer-use

npm i https://pkg.pr.new/@tanstack/ai-computer-use@1903

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@1903

@tanstack/ai-durable-stream

npm i https://pkg.pr.new/@tanstack/ai-durable-stream@1903

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@1903

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@1903

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@1903

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@1903

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@1903

@tanstack/ai-grok-build

npm i https://pkg.pr.new/@tanstack/ai-grok-build@1903

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@1903

@tanstack/ai-harness

npm i https://pkg.pr.new/@tanstack/ai-harness@1903

@tanstack/ai-harness-cli

npm i https://pkg.pr.new/@tanstack/ai-harness-cli@1903

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@1903

@tanstack/ai-isolate-daytona

npm i https://pkg.pr.new/@tanstack/ai-isolate-daytona@1903

@tanstack/ai-isolate-e2b

npm i https://pkg.pr.new/@tanstack/ai-isolate-e2b@1903

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@1903

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@1903

@tanstack/ai-isolate-quickjs-bun

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs-bun@1903

@tanstack/ai-llmgateway

npm i https://pkg.pr.new/@tanstack/ai-llmgateway@1903

@tanstack/ai-lovable

npm i https://pkg.pr.new/@tanstack/ai-lovable@1903

@tanstack/ai-mcp

npm i https://pkg.pr.new/@tanstack/ai-mcp@1903

@tanstack/ai-memory

npm i https://pkg.pr.new/@tanstack/ai-memory@1903

@tanstack/ai-mistral

npm i https://pkg.pr.new/@tanstack/ai-mistral@1903

@tanstack/ai-neon

npm i https://pkg.pr.new/@tanstack/ai-neon@1903

@tanstack/ai-octane

npm i https://pkg.pr.new/@tanstack/ai-octane@1903

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@1903

@tanstack/ai-ollaya

npm i https://pkg.pr.new/@tanstack/ai-ollaya@1903

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@1903

@tanstack/ai-opencode

npm i https://pkg.pr.new/@tanstack/ai-opencode@1903

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@1903

@tanstack/ai-perplexity

npm i https://pkg.pr.new/@tanstack/ai-perplexity@1903

@tanstack/ai-persistence

npm i https://pkg.pr.new/@tanstack/ai-persistence@1903

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@1903

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@1903

@tanstack/ai-reactor

npm i https://pkg.pr.new/@tanstack/ai-reactor@1903

@tanstack/ai-remix

npm i https://pkg.pr.new/@tanstack/ai-remix@1903

@tanstack/ai-sandbox

npm i https://pkg.pr.new/@tanstack/ai-sandbox@1903

@tanstack/ai-sandbox-blaxel

npm i https://pkg.pr.new/@tanstack/ai-sandbox-blaxel@1903

@tanstack/ai-sandbox-boxd

npm i https://pkg.pr.new/@tanstack/ai-sandbox-boxd@1903

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-sandbox-cloudflare@1903

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/@tanstack/ai-sandbox-daytona@1903

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/@tanstack/ai-sandbox-docker@1903

@tanstack/ai-sandbox-e2b

npm i https://pkg.pr.new/@tanstack/ai-sandbox-e2b@1903

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/@tanstack/ai-sandbox-local-process@1903

@tanstack/ai-sandbox-railway

npm i https://pkg.pr.new/@tanstack/ai-sandbox-railway@1903

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/@tanstack/ai-sandbox-sprites@1903

@tanstack/ai-sandbox-upstash-box

npm i https://pkg.pr.new/@tanstack/ai-sandbox-upstash-box@1903

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/@tanstack/ai-sandbox-vercel@1903

@tanstack/ai-skills

npm i https://pkg.pr.new/@tanstack/ai-skills@1903

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@1903

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@1903

@tanstack/ai-typesafe

npm i https://pkg.pr.new/@tanstack/ai-typesafe@1903

@tanstack/ai-utils

npm i https://pkg.pr.new/@tanstack/ai-utils@1903

@tanstack/ai-vercel-gateway

npm i https://pkg.pr.new/@tanstack/ai-vercel-gateway@1903

@tanstack/ai-vertex

npm i https://pkg.pr.new/@tanstack/ai-vertex@1903

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@1903

@tanstack/ai-worldlabs

npm i https://pkg.pr.new/@tanstack/ai-worldlabs@1903

@tanstack/openai-base

npm i https://pkg.pr.new/@tanstack/openai-base@1903

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@1903

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@1903

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@1903

@tanstack/svelte-ai-devtools

npm i https://pkg.pr.new/@tanstack/svelte-ai-devtools@1903

commit: 27858f9

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on: maintainer The ball is in the maintainers’ court

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Durable resume through withPersistence never fills ctx.inputResponse for mcp_input interrupts

2 participants