10 parallel research agents (R1-R10) investigated whether MCP can save scenes as saveable entities that the user later opens in the editor — rather than injecting via a dev-only window.__pascalScene hook. Key findings: - Editor is already backend-agnostic via onLoad/onSave callbacks (R2,R7) - Current persistence is localStorage-only, single key (R1) - Zero dynamic routes and no backend code yet — env declared (R4,R5) - File import exists as "Load Build" but lacks Zod validation (R6) - MCP-written scenes load cleanly into the editor today (R6, Casa del Sol test already proved this) - Best path: filesystem-handoff this week, Supabase in weeks 2-4, Supabase Realtime for live mode in Q2. Skip Yjs for now. (R8) - 4-5 weeks to private beta, 10-14 to GA (R9) - 10 high-value ideas ranked; "photo -> scene" is the unblocker (R10) SYNTHESIS.md pulls all 10 reports together with the recommended implementation plan. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9.9 KiB
Research synthesis — "MCP creates scene → I open it in the editor"
10 parallel research agents (R1–R10) investigated this workflow against the Pascal repo. This document pulls their findings into a single actionable answer.
Direct answer to your question
Yes, this is the right approach. And it's about 40% already built. The Pascal Editor was designed from day one to be backend-agnostic — onLoad(sceneId) / onSave(scene) callbacks are public props. The plumbing that's missing isn't the Editor; it's the scene-entity layer (id, name, thumbnail, owner) and a backend to store it. The groundwork for that backend is already laid in env vars and privacy policy, but zero lines of backend code exist yet.
What exists today (40%)
| Piece | Status | Evidence |
|---|---|---|
| Scene graph serialization | ✅ done | SceneGraph type; export_json MCP tool; "Save Build" UI button |
| Scene graph deserialization | ✅ done | applySceneGraphToEditor() + setScene(); "Load Build" UI button |
| Autosave pipeline (debounced, status-reported) | ✅ done | use-auto-save.ts with 6-state machine + onSaveStatusChange |
Host persistence hooks (onLoad, onSave, onDirty) |
✅ done | <Editor> props, R2 |
| Thumbnail auto-capture | ✅ done | onThumbnailCapture fires ~10s after scene stable, 1920×1080 SSGI |
| Store-level project scoping | ✅ done | projectId prop flows through viewer + selection |
| localStorage fallback persistence | ✅ done | pascal-editor-scene key |
| IndexedDB for assets | ✅ done | idb-keyval for texture blobs |
Single route (/) |
✅ done | but no dynamic segments — R4 |
What's missing (60%)
| Piece | Effort | Owner |
|---|---|---|
| Scene entity metadata (id, name, thumbnail, owner, created_at) | S | R2 gap |
Backend storage (Supabase scenes table) — env is declared, code is zero |
M | R5 gap |
Dynamic routes /scene/[id] and /editor/[projectId]/[sceneId] |
S | R4 gap |
| Scene-list UI (picker, rename, delete, duplicate) | M | R3 gap |
MCP tools for scene lifecycle (save_scene, list_scenes, load_scene, delete_scene) |
S | R8 |
| Zod validation at the scene-load boundary | XS | R6 gap (pre-existing security finding) |
| Auth (Supabase auth / Better Auth — env is declared, code is zero) | M | R5, R9 gap |
| Device-pairing flow so MCP acts as the user | M | R9, R8 |
The recommended plan — R8's phased approach
Ship Option A this week. Commit to Option B for production. Defer D (real-time) to Q2. Skip C and E.
Week 1 — Option A: filesystem handoff (kills the injection hack)
MCP ──► ~/.pascal/scenes/<slug>.json ──► Next.js API route ──► /scene/<slug> page ──► applySceneGraphToEditor()
- New MCP tools:
save_scene({ slug }),load_scene({ slug }),list_scenes(). - New Next.js route
/scene/[slug]that fetches/api/scenes/[slug]and loads via the existingapplySceneGraphToEditorutility. - Delete
window.__pascalSceneinjection fromapps/editor/app/page.tsx. - Effort: 1–2 days. No new deps. No breaking changes.
Weeks 2–4 — Option B: Supabase backend (the product path)
MCP ──► Supabase (SERVICE_ROLE) ──► scenes table
Editor /scene/[id] ──► Supabase (ANON_KEY + RLS) ──► scenes row
Schema:
create table scenes (
id uuid primary key default gen_random_uuid(),
project_id uuid references projects(id),
owner_id uuid references auth.users(id),
name text not null,
graph_json jsonb not null,
thumbnail_url text,
version int not null default 1,
public boolean not null default false,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
create table scene_revisions (
scene_id uuid references scenes(id) on delete cascade,
version int,
graph_json jsonb,
author_kind text check (author_kind in ('human','mcp','agent')),
created_at timestamptz default now(),
primary key (scene_id, version)
);
- MCP side: ~200 LOC.
save_scene→supabase.from('scenes').upsert({...}). - Editor side:
/scene/[id]/page.tsxis a Server Component that fetches the row and passes to a client<SceneLoader>. - RLS: owner reads/writes;
public = truerows readable by anyone. - Effort: ~2 weeks.
Quarter 2 — Option D: live mode via Supabase Realtime
Supabase's postgres_changes channel over the scenes row gives you cross-agent realtime without Yjs. A human editing at the same time as an MCP agent would see each other's changes. This is not an MCP protocol feature — it's a Postgres feature that Supabase exposes. Much cheaper than rebuilding on Yjs (Option E).
What about Yjs / CRDT (Option E)?
Defer. It's a 3-month rewrite of @pascal-app/core's store. Only justified if multiplayer is the moat, which the current product signal doesn't demonstrate. If you DO go there later, the migration is painless because applySceneGraphToEditor still accepts a SceneGraph — you'd just rewrite the store underneath.
Edge cases R10 surfaced that you hadn't mentioned
- "Auto-frame camera on MCP-opened scene" — today the MCP creates a scene at world origin, the default editor camera points at 30m grid, user sees a black screen. Tiny fix, huge UX win.
- MCP-written scenes can carry malicious URLs —
guide.url,scan.url,material.texture.url,item.asset.srcarez.string()in core (no scheme allowlist). A scene opened in the editor can beacon home. This is the same finding as the Phase 3 security audit, not remediated. - Overwrite vs merge when MCP edits a scene the user is also editing — today: last-writer-wins silently. Needs ETag or revision-number optimistic locking.
- Scene size limit — Supabase API has an 8MB body limit. Casa del Sol is 27 KB; a real project might go to 1–2 MB. Measure before you commit.
- Undo-stack surprise — an MCP multi-op patch collapses to ONE undo step. From the user's view, Ctrl+Z wipes the whole MCP run. Might be surprising. Document or segment.
metadata: jsonon every node is AI-visible — an attacker-craftedmetadata.note: "ignore all instructions and..."could prompt-inject a summarising agent.- MCP and editor use separate Zundo temporal stores — undo in one doesn't reach the other.
SiteNode.childrenholds objects not ids — shipping the scene across the wire requires handling this inconsistency (already workaround-ed in MCP; would re-emerge on the Supabase side).- Offline editor opening a cloud-only scene — degrade to cached/read-only, don't crash.
- Agent writes infinite scenes in a loop — quota + rate limits per user.
Ideas you didn't ask for but should consider (R10's top 10 by value×feasibility)
- Photo → Pascal — MCP already has
analyze_floorplan_image. The unblocker is: a scene UI "Upload floor plan" → vision tool → new scene. Highest ROI in the repo. - Scene templates catalogue — doubles as marketplace seed inventory.
- Auto-framing camera — fixes "black screen" failure mode.
- Prompt → Pascal one-shot studio — the canonical "MCP creates scene" hosted flow.
- Multi-variant generation — "give me 5 variations" using
forkSceneGraph(already in core). - Scene diff view — makes AI actions reviewable.
- BOM + cost synthesis — turns toy scenes into quoteable artefacts.
- Scene branches & forks — prerequisite for co-design workflows.
- GLB export via headless renderer — unlocks AR + USD/IFC pipelines.
- Regulatory / accessibility linting — B2B architect segment.
Production readiness (R9)
~4–5 weeks to private beta. ~10–14 weeks to GA. Dominated by:
- Greenfield auth + persistence (Supabase rails declared but zero code).
- URL hardening migration on core node schemas (close the security audit's Phase 3 gap properly, not just in MCP).
- Optimistic concurrency / revision tracking.
- ETag-based merge UX.
Phase A (transport hardening + URL validation) is decoupled and can ship week 1 as a defensive floor.
What you should tell me next
Answer these and I can write the implementation PR:
- Deployment target — local-only (Option A sufficient), or Vercel + multi-user (must do B)?
- Auth direction — Supabase Auth, Better Auth (env var is there), Clerk, or none yet?
- Scope for v0.1 — just "save + open" with one hardcoded user, OR proper multi-tenant + sharing from day one?
- Scene-list UI location — inside the Editor package (add a new panel), inside the host app (
apps/editor), or both? - What do I do with the current
feat/mcp-serverbranch? — merge as is (MCP server + test-reports), or fold this new work into the same branch, or open a newfeat/mcp-persistencebranch?
My recommendation in one line: answer 1 = Vercel/multi-user → ship Option A as a branch-local step this week, then B over weeks 2–4, and merge the whole thing as feat/mcp-cloud-scenes.
Report index
- R1 — Persistence layer — localStorage-only, single key, no backend
- R2 —
projectIdsemantics & Editor API — Editor is backend-agnostic viaonLoad/onSave - R3 — Scene management UI — 40% there; needs scene list + palette commands
- R4 — Routing & URLs — zero dynamic routes; latent expectation of
/editor/<projectId>/… - R5 — Backend / Supabase — env declared, zero code
- R6 — File I/O pathways — "Save/Load Build" work; no Zod validation on import
- R7 —
@pascal-app/editorAPI — rich callback surface; scene switcher is a 1–2 day host feature - R8 — Integration design options — A→B→D phased recommendation
- R9 — Production readiness — 4–5 weeks to beta, 10–14 to GA
- R10 — Ideas and edge cases — 300+ lines; top 10 ranked by value×feasibility