Skip to content

fix(mcp): report skipped persistence instead of silently dropping live sync - #736

Merged
Aymericr merged 1 commit into
mainfrom
fix/mcp-live-sync-unbound-warning
Aug 31, 2026
Merged

fix(mcp): report skipped persistence instead of silently dropping live sync#736
Aymericr merged 1 commit into
mainfrom
fix/mcp-live-sync-unbound-warning

Conversation

@Aymericr

@Aymericr Aymericr commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes #725. When a store was attached but no scene was bound (or the store couldn't append scene events), publishLiveSceneSnapshot returned silently: the mutation applied to the in-memory session, but nothing persisted, no live event reached subscribers, and nothing told the caller — the failure mode that sent #561's investigation into the wrong layer.

Design decision (the question #561 left open): neither throw nor lazily bind. A typed no_active_scene throw breaks the README --stdio quick start on the first create_wall; lazy draft binding creates persistent artifacts the user never asked for, on stores that may require project context. Instead the skip becomes visible:

  • publishLiveSceneSnapshot now returns 'published' | 'unbound' | 'events_unsupported' (built on the existing canAppendSceneEvents seam).
  • Every mutating tool (17 sites: apply_patch, create_wall, create_room, add_door, add_window, furnish_room, cut_opening, create_story_shell, create_roof, create_stair_between_levels, create_level, duplicate_level, place_item, delete_node, set_zone, undo, redo) spreads a persistence: { status, warning } field into its result when the change stayed in-memory — declared in each tool's outputSchema via a shared liveSyncOutput fragment so the SDK's structured-content validation keeps it. The warning tells the caller to bind a scene with save_scene or load_scene.
  • undo/redo/furnish_room keep their conditional publish: no mutation → no warning.
  • InMemorySceneStore (test-utils) now implements appendSceneEvent/listSceneEvents, which MCP live-sync silently drops mutations when no active scene is bound #725 called out as the missing test seam.

How to test

  1. bun test packages/mcp/src/tools/live-sync.test.ts — six tests: unbound / published / events-unsupported, each through a real InMemoryTransport client-server pair, plus the three statuses at the unit level.
  2. bun test packages/mcp/src/tools — full tool suite (190 tests) passes.
  3. Manually: pascal-mcp --stdio, call create_wall without opening a scene — the result now carries persistence.status: "unbound" with the guidance string instead of silently not persisting.

Screenshots / screen recording

N/A — MCP server change, no UI.

Checklist

  • I've tested this locally with bun dev
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch

Note

Medium Risk
Touches every mutating MCP tool response shape and persistence behavior; callers may rely on the new optional persistence field, but successful paths stay backward compatible.

Overview
MCP scene mutations no longer fail silently when live sync cannot persist. publishLiveSceneSnapshot returns 'published' | 'unbound' | 'events_unsupported' instead of no-oping without feedback when there is no bound scene or the store cannot append scene events.

All mutating tools wire this through shared liveSyncOutput (output schema) and persistencePayload: successful publishes add nothing; skipped sync adds optional persistence: { status, warning } with guidance to bind via save_scene / load_scene. undo, redo, and furnish_room only publish (and warn) when they actually change the graph.

Tests cover the three statuses via create_wall over in-memory transport and extend InMemorySceneStore with appendSceneEvent / listSceneEvents for the live-sync path.

Reviewed by Cursor Bugbot for commit 6bd49f3. Bugbot is set up for automated code reviews on this repo. Configure here.

…e sync

publishLiveSceneSnapshot returned void and silently skipped both the
draft save and the live event whenever no active scene was bound or the
store could not append scene events — mutations 'vanished' with no
error, no log, and no signal to the caller, sending people hunting in
the wrong layer (#725).

Neither of the two fixes #561 debated survives the constraints: a typed
throw breaks the plain --stdio quick start on the first create_wall, and
lazily binding a draft scene creates persistent artifacts the user never
asked for on stores that may require project context. Instead the skip
becomes visible at the layer the caller sees: publishLiveSceneSnapshot
returns 'published' | 'unbound' | 'events_unsupported', and every
mutating tool spreads a persistence warning into its result (declared in
the tool outputSchema via a shared fragment so the SDK's structured-
content validation keeps it). An AI caller can react by binding a scene
with save_scene/load_scene; a human reading the transcript sees why
nothing persisted.

InMemorySceneStore now implements appendSceneEvent/listSceneEvents so
the three paths are testable; live-sync.test.ts covers them through a
real client/server pair and at the unit level.

Fixes #725

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Aymericr
Aymericr merged commit 1a5d2b0 into main Aug 31, 2026
4 checks passed
@Aymericr
Aymericr deleted the fix/mcp-live-sync-unbound-warning branch August 31, 2026 04:22
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.

MCP live-sync silently drops mutations when no active scene is bound

1 participant