Skip to content

fix(mcp): keep store receiver bound in scene event operations#489

Open
konevenkatesh wants to merge 1 commit into
pascalorg:mainfrom
konevenkatesh:fix/mcp-unbound-scene-event-store-methods
Open

fix(mcp): keep store receiver bound in scene event operations#489
konevenkatesh wants to merge 1 commit into
pascalorg:mainfrom
konevenkatesh:fix/mcp-unbound-scene-event-store-methods

Conversation

@konevenkatesh

@konevenkatesh konevenkatesh commented Jul 11, 2026

Copy link
Copy Markdown

Summary

SceneOperationsFacade.appendSceneEvent and listSceneEvents detached the store methods from the store instance before invoking them:

const append = this.requireStore().appendSceneEvent
if (!append) return null
return append(options) // `this` is undefined inside the store method

For store implementations that rely on thisSqliteSceneStore calls this.withWriteTransaction(...) inside appendSceneEvent — the detached call crashes with:

TypeError: undefined is not an object (evaluating 'this.withWriteTransaction')

Because the underlying SQLite write happens inside the transaction helper that never runs (append) or runs after the row insert (depending on path), every save_scene, create_from_template, and live-sync mutation routed through the MCP facade reported this error to the client even when the scene row itself had been persisted, making the MCP server unusable for headless scene building.

Reproduction

Run any scene save through the pascal-mcp CLI under Bun (e.g. connect an MCP client and call save_scene, or any template/live-sync tool that appends scene events). The tool call returns undefined is not an object (evaluating 'this.withWriteTransaction').

Fix

Invoke the optional methods on the store instance so the receiver stays bound:

const store = this.requireStore()
if (!store.appendSceneEvent) return null
return store.appendSceneEvent(options)

Same change for listSceneEvents.

Tests

Added packages/mcp/src/operations/scene-operations.test.ts:

  • a facade-level regression test that appends/lists scene events through createSceneOperations backed by a real SqliteSceneStore — this fails with the exact TypeError above before the fix and passes after;
  • a test covering the fallback behavior when the store does not implement scene events (appendSceneEventnull, listSceneEventsscene_events_unavailable).

bun test in packages/mcp: 295 pass, 0 fail. bun check reports no findings in the changed files.

🤖 Generated with Claude Code


Note

Low Risk
Small, localized fix mirroring other facade store calls; tests cover the failure mode and optional-store fallbacks.

Overview
Fixes a this binding bug in SceneOperationsFacade that broke MCP scene event APIs when backed by SqliteSceneStore.

appendSceneEvent and listSceneEvents no longer copy store methods into locals before calling them. They now invoke store.appendSceneEvent(...) and store.listSceneEvents(...) on the store instance so methods that use this (e.g. withWriteTransaction during append) run correctly. That removes the undefined is not an object (evaluating 'this.withWriteTransaction') failures on headless saves and related tools.

Adds scene-operations.test.ts: a regression test through createSceneOperations + SqliteSceneStore, plus coverage when the store omits scene events (null / scene_events_unavailable).

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

appendSceneEvent and listSceneEvents detached the store methods before
invoking them (const append = this.requireStore().appendSceneEvent;
append(options)), so `this` was undefined inside store implementations
that rely on it — SqliteSceneStore.withWriteTransaction in particular.
Every save_scene, create_from_template, and live-sync mutation routed
through the facade failed with "undefined is not an object (evaluating
'this.withWriteTransaction')" even though the SQLite write succeeded.

Invoke the methods on the store instance instead, and add facade-level
regression tests covering both the bound-receiver path and the
stores-without-scene-events fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ef80fa3. Configure here.


afterEach(async () => {
await fs.rm(rootDir, { recursive: true, force: true })
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Test leaves SQLite handle open

Low Severity

The afterEach hook removes the temporary database directory without closing the SqliteSceneStore instance. This can leave SQLite database files locked, causing fs.rm to fail (e.g., EBUSY on Windows) and potentially leaking file handles. Other tests correctly close the store before cleanup.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ef80fa3. Configure here.

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.

1 participant