Skip to content

feat: persistence + resumable runs (provider-agnostic, optional, agent-mode)#785

Open
AlemTuzlak wants to merge 6 commits into
feat/sandboxesfrom
feat/persistence
Open

feat: persistence + resumable runs (provider-agnostic, optional, agent-mode)#785
AlemTuzlak wants to merge 6 commits into
feat/sandboxesfrom
feat/persistence

Conversation

@AlemTuzlak

Copy link
Copy Markdown
Contributor

Builds on #774 (sandboxes). Adds persistence + resumable runs as composable chat() middleware — fully optional, working for both plain model adapters and sandbox-backed harness adapters.

What this adds

withPersistence(...) makes a run durable: server-authoritative thread history, run records, an append-only AG-UI event log, usage, and (agent mode) approvals + artifacts. A run with no persistence middleware is byte-for-byte unchanged.

Resume. Each chunk carries an in-band opaque cursor (a monotonic per-run sequence). A reconnecting client sends runId + cursor; chat({ cursor }) replays the persisted tail and — for harness adapters that re-attach to the still-running in-sandbox process — continues live. The headless client tracks the cursor (resume() / getResumeState() / maybeAutoResume(), autoResume opt-out).

Event model. The persisted log is the AG-UI StreamChunk stream — no parallel event type. Agent activity rides on a typed catalog of well-known CUSTOM events.

Backends (shared SQL core + thin adapters). @tanstack/ai-persistence-sql (one SQL impl behind a minimal SqlDriver), with -sqlite, -postgres, -cloudflare (D1), and BYO -drizzle / -prisma. Raw drivers auto-migrate (opt-out); ORMs own their schema. memoryPersistence() ships in core.

Agent mode. @tanstack/ai-sandbox-persistence bridges a durable SQL SandboxStore + distributed lock into withSandbox. The shared locks capability moves to @tanstack/ai (one token); @tanstack/ai-sandbox re-exports it for back-compat.

Packages

  • New: @tanstack/ai-persistence, -sql, -sqlite, -postgres, -cloudflare, -drizzle, -prisma, @tanstack/ai-sandbox-persistence
  • Changed: @tanstack/ai (cursor/resume/locks/custom-events), @tanstack/ai-sandbox (locks token), @tanstack/ai-client (auto-resume), 4 harness adapters (supportsReattach)

Verification

~1530 tests green (core 1054, ai-sandbox 50, ai-client 384, new packages 42). sherif / knip / docs-links / publint all pass; every package typecheck + lint + build clean. SQLite/Drizzle/Prisma/Cloudflare-D1 are runtime-verified against real node:sqlite; Postgres via a fake-pool plumbing test.

Deferred (documented)

Notable, grounded design choices

  • events/resume contracts live in core — the chat() resume seam consumes them without core depending on the persistence package.
  • Message reconciliation is whole-transcript, server-authoritative: ModelMessage has no id (ids live on the client UIMessage).

🤖 Generated with Claude Code

…M-event catalog

- Add optional in-band `cursor` to StreamChunk + `cursor` input on chat().
- Add a *replayAndResume() seam: when a cursor is supplied and a ResumeSource
  capability is provided, replay the persisted event tail; if the run is still
  running and the adapter supports re-attach, continue live. No-op without a
  resume source (a non-persisted run is unchanged).
- Move the generic LockStore + LocksCapability ('locks') into core so it is a
  single shared token across the sandbox and persistence layers.
- Add ResumeSource capability/contract and a typed catalog of well-known CUSTOM
  event names (file.changed, process.*, approval.*, artifact.*, sandbox.*).
…ttach markers

- ai-sandbox now imports LocksCapability/LockStore/InMemoryLockStore from
  @tanstack/ai and re-exports them for back-compat (one global 'locks' token).
- Add a token-identity test asserting the sandbox and core LocksCapability are
  the same object.
- Mark the four harness adapters (claude-code, codex, gemini-cli, opencode) with
  `supportsReattach = true` so the engine continues a still-running in-sandbox
  run live after replaying the persisted tail.
…andbox bridge

- @tanstack/ai-persistence: store contracts, withPersistence middleware,
  memoryPersistence, cursor utilities, approval controller, resume-source
  adapter, history projection. Fully optional; works with and without sandbox.
- @tanstack/ai-persistence-sql: one SQL store impl behind a minimal SqlDriver
  (sqlite|postgres dialect) + versioned migrations + DDL.
- Backends: -sqlite (node:sqlite/better-sqlite3), -postgres (pg), -cloudflare
  (D1, compile-verified), -drizzle and -prisma (BYO).
- @tanstack/ai-sandbox-persistence: durable SQL-backed SandboxStore +
  withPersistenceBridge wiring durable store/locks into withSandbox (agent mode).
- Track the latest in-band cursor per active run; expose getResumeState(),
  resume(), and maybeAutoResume() with an `autoResume` opt-out (default on).
- Pass `cursor` through the connection adapter's RunAgentInput payload so the
  server can replay a run. streamResponse reuses the original runId on resume.
…ip config

- New docs/persistence/overview.md (+ nav entry, addedAt) with server + client +
  agent-mode usage.
- New ai-core/persistence agent skill.
- Changeset covering the feature; knip ignore for the optional pg peer dep.
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0d1485d5-8d89-4020-b56e-ee6bc421ea69

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/persistence

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 and usage tips.

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

18 package(s) bumped directly, 30 bumped as dependents.

🟥 Major bumps

Package Version Reason
@tanstack/ai-claude-code 0.1.0 → 1.0.0 Changeset
@tanstack/ai-codex 0.1.0 → 1.0.0 Changeset
@tanstack/ai-gemini-cli 0.1.0 → 1.0.0 Changeset
@tanstack/ai-opencode 0.1.0 → 1.0.0 Changeset
@tanstack/ai-persistence 0.1.0 → 1.0.0 Changeset
@tanstack/ai-persistence-cloudflare 0.1.0 → 1.0.0 Changeset
@tanstack/ai-persistence-drizzle 0.1.0 → 1.0.0 Changeset
@tanstack/ai-persistence-postgres 0.1.0 → 1.0.0 Changeset
@tanstack/ai-persistence-prisma 0.1.0 → 1.0.0 Changeset
@tanstack/ai-persistence-sql 0.1.0 → 1.0.0 Changeset
@tanstack/ai-persistence-sqlite 0.1.0 → 1.0.0 Changeset
@tanstack/ai-sandbox 0.1.0 → 1.0.0 Changeset
@tanstack/ai-sandbox-cloudflare 0.1.0 → 1.0.0 Changeset
@tanstack/ai-sandbox-docker 0.1.0 → 1.0.0 Changeset
@tanstack/ai-sandbox-local-process 0.1.0 → 1.0.0 Changeset
@tanstack/ai-sandbox-persistence 0.1.0 → 1.0.0 Changeset
@tanstack/ai-angular 0.1.2 → 1.0.0 Dependent
@tanstack/ai-anthropic 0.15.3 → 1.0.0 Dependent
@tanstack/ai-code-mode 0.2.7 → 1.0.0 Dependent
@tanstack/ai-code-mode-skills 0.2.7 → 1.0.0 Dependent
@tanstack/ai-elevenlabs 0.2.22 → 1.0.0 Dependent
@tanstack/ai-event-client 0.6.1 → 1.0.0 Dependent
@tanstack/ai-fal 0.8.1 → 1.0.0 Dependent
@tanstack/ai-gemini 0.16.1 → 1.0.0 Dependent
@tanstack/ai-grok 0.11.4 → 1.0.0 Dependent
@tanstack/ai-groq 0.4.4 → 1.0.0 Dependent
@tanstack/ai-isolate-node 0.1.32 → 1.0.0 Dependent
@tanstack/ai-isolate-quickjs 0.1.32 → 1.0.0 Dependent
@tanstack/ai-ollama 0.8.3 → 1.0.0 Dependent
@tanstack/ai-openai 0.14.3 → 1.0.0 Dependent
@tanstack/ai-openrouter 0.13.3 → 1.0.0 Dependent
@tanstack/ai-preact 0.9.7 → 1.0.0 Dependent
@tanstack/ai-react 0.15.7 → 1.0.0 Dependent
@tanstack/ai-react-ui 0.8.8 → 1.0.0 Dependent
@tanstack/ai-solid 0.13.7 → 1.0.0 Dependent
@tanstack/ai-solid-ui 0.7.8 → 1.0.0 Dependent
@tanstack/ai-svelte 0.13.7 → 1.0.0 Dependent
@tanstack/ai-vue 0.13.7 → 1.0.0 Dependent
@tanstack/openai-base 0.8.3 → 1.0.0 Dependent

🟨 Minor bumps

Package Version Reason
@tanstack/ai 0.30.0 → 0.31.0 Changeset
@tanstack/ai-client 0.17.2 → 0.18.0 Changeset

🟩 Patch bumps

Package Version Reason
@tanstack/ai-devtools-core 0.4.10 → 0.4.11 Dependent
@tanstack/ai-isolate-cloudflare 0.2.23 → 0.2.24 Dependent
@tanstack/ai-mcp 0.1.2 → 0.1.3 Dependent
@tanstack/ai-vue-ui 0.2.19 → 0.2.20 Dependent
@tanstack/preact-ai-devtools 0.1.53 → 0.1.54 Dependent
@tanstack/react-ai-devtools 0.2.53 → 0.2.54 Dependent
@tanstack/solid-ai-devtools 0.2.53 → 0.2.54 Dependent

@nx-cloud

nx-cloud Bot commented Jun 18, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 767e1c6

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

☁️ Nx Cloud last updated this comment at 2026-06-18 19:08:11 UTC

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​types/​pg@​8.20.01001007383100
Addedpg@​8.21.0991009989100

View full report

@pkg-pr-new

pkg-pr-new Bot commented Jun 18, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

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

@tanstack/ai-angular

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

@tanstack/ai-anthropic

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

@tanstack/ai-claude-code

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

@tanstack/ai-client

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

@tanstack/ai-code-mode

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

@tanstack/ai-code-mode-skills

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-code-mode-skills@785

@tanstack/ai-codex

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

@tanstack/ai-devtools-core

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

@tanstack/ai-elevenlabs

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

@tanstack/ai-event-client

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

@tanstack/ai-fal

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

@tanstack/ai-gemini

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

@tanstack/ai-gemini-cli

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-gemini-cli@785

@tanstack/ai-grok

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

@tanstack/ai-groq

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

@tanstack/ai-isolate-cloudflare

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

@tanstack/ai-isolate-node

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

@tanstack/ai-isolate-quickjs

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

@tanstack/ai-mcp

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

@tanstack/ai-ollama

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

@tanstack/ai-openai

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

@tanstack/ai-opencode

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

@tanstack/ai-openrouter

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

@tanstack/ai-persistence

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

@tanstack/ai-persistence-cloudflare

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-persistence-cloudflare@785

@tanstack/ai-persistence-drizzle

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-persistence-drizzle@785

@tanstack/ai-persistence-postgres

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-persistence-postgres@785

@tanstack/ai-persistence-prisma

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-persistence-prisma@785

@tanstack/ai-persistence-sql

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-persistence-sql@785

@tanstack/ai-persistence-sqlite

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-persistence-sqlite@785

@tanstack/ai-preact

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

@tanstack/ai-react

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

@tanstack/ai-react-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-react-ui@785

@tanstack/ai-sandbox

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

@tanstack/ai-sandbox-cloudflare

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

@tanstack/ai-sandbox-docker

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

@tanstack/ai-sandbox-local-process

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

@tanstack/ai-sandbox-persistence

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-persistence@785

@tanstack/ai-solid

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

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-solid-ui@785

@tanstack/ai-svelte

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

@tanstack/ai-utils

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

@tanstack/ai-vue

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

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vue-ui@785

@tanstack/openai-base

npm i https://pkg.pr.new/TanStack/ai/@tanstack/openai-base@785

@tanstack/preact-ai-devtools

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

@tanstack/react-ai-devtools

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

@tanstack/solid-ai-devtools

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

commit: 767e1c6

@willwillems

Copy link
Copy Markdown

Awesome to see that this is in progress, it was one of the final bits that prevented it from feeling completely batteries included!

AlemTuzlak added a commit that referenced this pull request Jun 26, 2026
* chore(deps): add @mcp-ui/client + ext-apps for MCP Apps support

* feat(ai): add UIResourcePart + UIResourceEvent types

* feat(ai-mcp): capture serverId + _meta.ui.resourceUri at tool discovery

* feat(ai): extend MCPToolSource with optional readResource

* feat(ai-mcp): in-memory McpSessionStore (#785-shaped seam)

* feat(ai-mcp): surface embedded ui:// resources separately from model text

* feat(ai): reconcile ui-resource CUSTOM events into UIResourcePart

* feat(ai-mcp): add public callTool method on MCPClient

* feat(ai): emit ui-resource events from MCP tool results (eager read, fail-soft)

* feat(ai-mcp): createMcpAppCallHandler + ./apps subpath (reconnect default, allowlist)

* fix(ai-mcp): type callTool return as SDK union to unblock declaration build

* feat(ai-client): createMcpAppBridge (framework-agnostic tool/prompt/link routing)

* feat(ai-react): MCPAppResource wrapper for @mcp-ui/client AppRenderer

* feat(ai-preact): MCPAppResource wrapper for @mcp-ui/client AppRenderer

* docs(skills): document MCP Apps support in ai-mcp + tool-calling skills

* docs: MCP Apps guide (server + client, React/Preact)

* fix(ai-client): handle UIResourcePart variant in MessagePart union

* fix(ai-react,ai-preact): narrow onCallTool structuredContent to satisfy CallToolResult

* test(e2e): MCP Apps static render + interactive call + allowlist rejection

* chore: add changeset and format MCP Apps files

* fix: satisfy eslint in MCP Apps source (require-await, arrow signatures)

* chore: align MCP Apps deps + knip ignore for optional @mcp-ui/client peer

* docs: make MCP Apps snippets type-check under kiira (no as-casts)

* fix(ai-client): bridge HTTP-status guard, drop dead onNotify/onIntent, warn on dropped link

* feat(ai): add toolName to UIResourcePart (AppRenderer requires the MCP tool name)

* fix(ai-mcp): pool readResource + call-handler prefix/serverId routing; drop dead extractUiResources

* fix(ai-react,ai-preact): use part.toolName, run .tsx tests, coalesce result, empty-prompt guard

* docs: correct MCP Apps skill/docs to shipped API; gpt-4o -> gpt-5.5 in tool-calling skill

* fix(ai): emit ui-resource only on uri match; reconcile to tool-call owner message

* fix(ai-mcp): drop harmful prefix-strip, pool readResource uri-ownership, sliding session TTL

* fix: openLink throw-safety, meta doc honesty, prop JSDoc parity, stronger test assertions

* docs: correct allowlist/prefix descriptions to match strip-free handler; pin ui:// exclusion test

* fix(ai-mcp): type error detail as string|undefined to satisfy no-unnecessary-condition

* ci: apply automated fixes

* feat(ai-mcp): createMcpAppCallHandler takes clients; MCPClient.getInfo + MCPClients.getServers

* test: adopt clients-based call handler + test-hygiene cleanup (remove casts, extract helpers)

* docs: clients-based createMcpAppCallHandler API; clarify sandbox proxy URL

* fix(ai-mcp): key app registry by serverId + collision throw, store->clients fallback, close e2e client, error causes

* test(ai-mcp): cover registry keying/collision, store fallback, readResource error causes

* fix(ai-mcp): getInfo/getServers retain only serializable TransportConfig (drop single-use Transport instances)

* test(ai-mcp): instance-built clients expose no reconnectable transport descriptor

* docs: multi-server interactive routing requires a per-server prefix

* ci: apply automated fixes

* fix(mcp-apps): reject malformed call args + validate openLink URL scheme

Address PR review feedback:
- createMcpAppCallHandler: reject a non-object args payload (array/primitive/
  null) instead of silently coercing it to {}; absent args still defaults to {}.
- createMcpAppBridge.openLink: only forward http(s)/mailto URLs to the host
  onLink handler; reject javascript:/data:/file:/etc. from untrusted widgets.
- docs(SKILL): point Preact readers at the @tanstack/ai-preact/mcp-apps subpath.

* fix(mcp-apps): address review — observability, fail-soft scope, type dedup

Applies the PR-review findings on the MCP Apps surface:

- processor: warn (not silently drop) a ui-resource event that resolves to
  no target message — a vanished widget is otherwise undebuggable client-side.
- call-handler: add optional onError(err, { phase, req }) so the otherwise
  opaque server handler can report 'call' and 'close' failures; library stays
  console-free.
- tool-calls: move emitCustomEvent out of the read try so an emit-path error
  can't be mislabeled as a read failure.
- pool.readResource: attach ALL per-client errors via AggregateError instead
  of last-error-wins, so the owning server's failure isn't buried.
- session-store: opportunistic expiry sweep on set() to bound growth for
  set-but-never-read threads.
- types: extract shared McpResourceReadResult (kills the hand-copied shape);
  type the processor event as UIResourceEvent['value'] and drop the as-cast;
  narrow isToolCallResponse without a cast; fix orphaned/inaccurate JSDoc and
  add a per-run mutation note on bindReadResource.
- docs: drop redundant updatedAt on the new page; document that unsafe link
  schemes are rejected even with an onLink handler.

Tests: pin the "widget never enters model input" invariant; onLink-throws
fail-soft; tool-result-still-flows on read failure; session-store sweep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: apply automated fixes

* fix(mcp-apps): onError sync-throw safety + test-quality fixes from re-review

Round-2 review of the prior fixes commit:

- call-handler: extract reportError() so onError is invoked inside the promise
  chain — a SYNCHRONOUSLY-throwing hook no longer escapes during argument
  evaluation and can't break the handler's fail-soft result (the previous
  `Promise.resolve(onError(...)).catch()` only absorbed async rejections).
- tests: cover the onError hook (phase 'call', phase 'close', and both sync-
  throw and async-reject safety) — previously untested.
- tests: drop a tautological `not.toContain('ui-resource')` assertion and
  reword the messages.ts invariant comment to claim only the load-bearing
  uri/HTML checks; reword the session-store sweep test to state honestly that
  it guards set() correctness across the sweep, not the (unobservable) memory
  reclamation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: apply automated fixes

* docs(example): add MCP Apps demo (static + interactive) to ts-react-chat

Adds a `/mcp-apps` route demonstrating both kinds of MCP Apps end to end:
- STATIC: an in-process MCP server (`api.mcp-apps-weather-server`) exposing a
  display-only `show_weather_card` tool whose `ui://weather/card` resource
  renders as a self-contained forecast card.
- INTERACTIVE: the official Three.js MCP server (@modelcontextprotocol/
  server-threejs) run on :3001, whose widget calls tools back through the
  bridge.

Wiring:
- `api.mcp-apps-chat` connects both servers via createMCPClient and streams
  `ui-resource` parts (tolerates :3001 being down).
- `api.mcp-apps-call` mounts createMcpAppCallHandler over both for the
  interactive plane.
- The page renders `ui-resource` parts with `MCPAppResource` + a
  `createMcpAppBridge`, seeds `toolInput` from the sibling tool-call part, and
  withholds the bridge for the static widget (display-only). Suggestion pills
  trigger each app.
- Vendors the official sandbox-proxy page and serves it cross-origin on :8765
  (a hard requirement of @mcp-ui/client AppRenderer); `dev` now runs the proxy,
  the Three.js server, and Vite via concurrently.

Verified: page renders with no console errors, the static MCP server route and
the Three.js server both respond, the proxy serves, and the example
type-checks. The live model->tool->widget render requires a provider API key.

* ci: apply automated fixes

* feat(example): add interactive storefront MCP App + solar-system scene

Address feedback on the /mcp-apps demo:
- Add an INTERACTIVE storefront widget (api.mcp-apps-shop-server) that
  demonstrates the full bridge round-trip: clicking "Buy now" in the sandbox
  sends a tools/call over a hand-rolled MCP Apps app-bridge -> AppRenderer ->
  createMcpAppBridge -> POST /api/mcp-apps-call -> createMcpAppCallHandler ->
  buy_product() on the server -> order confirmation rendered back in the widget.
  The widget speaks the app-bridge protocol in plain JS (no build step).
- Wire the shop server into the chat + call routes; gate the bridge on
  non-static widgets (the weather card stays display-only).
- Change the Three.js suggestion to render a solar system instead of a cube.
- Fix the tool-call note: show a check when done instead of a perpetual spinner.
- Make the weather pill name a city so the tool fires deterministically.

Verified live in the browser: static card, interactive buy round-trip
(correct server order ids, no auto-fire), and the 3D solar system all render.

* ci: apply automated fixes

* feat(ai-react,ai-preact): add useMcpAppBridge hook

A React/Preact wrapper over createMcpAppBridge that returns a stable bridge for
a given threadId/callEndpoint while always invoking the latest chat.sendMessage
and onLink (kept in refs) — removing the hand-written useMemo + exhaustive-deps
disable the example previously needed.

- Exported from the main entry of @tanstack/ai-react and @tanstack/ai-preact
  (no @mcp-ui/client needed — it only wraps the ai-client bridge); also
  re-export createMcpAppBridge / McpAppBridge / CreateMcpAppBridgeOptions.
- Unit tests cover stable identity, recreation on threadId change, latest-
  callback invocation (no stale closure), and display-only openLink.
- Use the hook in the ts-react-chat /mcp-apps example.
- Update docs/mcp/apps.md (interactive example + API reference) and the ai-mcp
  SKILL to use the hook; bump the docs updatedAt and the changeset.

* style: format useMcpAppBridge signatures

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Tom Beckenham <34339192+tombeckenham@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

2 participants