Adrian Perez 42bd05db9c docs(mcp): add 10-agent research on scene-save workflow
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>
2026-04-18 18:47:36 +02:00

155 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 existing `applySceneGraphToEditor` utility.
- Delete `window.__pascalScene` injection from `apps/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:
```sql
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.tsx` is a Server Component that fetches the row and passes to a client `<SceneLoader>`.
- RLS: owner reads/writes; `public = true` rows 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
1. **"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.
2. **MCP-written scenes can carry malicious URLs** — `guide.url`, `scan.url`, `material.texture.url`, `item.asset.src` are `z.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.**
3. **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.
4. **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.
5. **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.
6. **`metadata: json` on every node is AI-visible** — an attacker-crafted `metadata.note: "ignore all instructions and..."` could prompt-inject a summarising agent.
7. **MCP and editor use separate Zundo temporal stores** — undo in one doesn't reach the other.
8. **`SiteNode.children` holds 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).
9. **Offline editor opening a cloud-only scene** — degrade to cached/read-only, don't crash.
10. **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)
1. **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.
2. **Scene templates catalogue** — doubles as marketplace seed inventory.
3. **Auto-framing camera** — fixes "black screen" failure mode.
4. **Prompt → Pascal one-shot studio** — the canonical "MCP creates scene" hosted flow.
5. **Multi-variant generation** — "give me 5 variations" using `forkSceneGraph` (already in core).
6. **Scene diff view** — makes AI actions reviewable.
7. **BOM + cost synthesis** — turns toy scenes into quoteable artefacts.
8. **Scene branches & forks** — prerequisite for co-design workflows.
9. **GLB export via headless renderer** — unlocks AR + USD/IFC pipelines.
10. **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:
1. **Deployment target** — local-only (Option A sufficient), or Vercel + multi-user (must do B)?
2. **Auth direction** — Supabase Auth, Better Auth (env var is there), Clerk, or none yet?
3. **Scope for v0.1** — just "save + open" with one hardcoded user, OR proper multi-tenant + sharing from day one?
4. **Scene-list UI location** — inside the Editor package (add a new panel), inside the host app (`apps/editor`), or both?
5. **What do I do with the current `feat/mcp-server` branch?** — merge as is (MCP server + test-reports), or fold this new work into the same branch, or open a new `feat/mcp-persistence` branch?
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](./R1-persistence.md) — localStorage-only, single key, no backend
- [R2 — `projectId` semantics & Editor API](./R2-project-id.md) — Editor is backend-agnostic via `onLoad`/`onSave`
- [R3 — Scene management UI](./R3-scene-ui.md) — 40% there; needs scene list + palette commands
- [R4 — Routing & URLs](./R4-routing.md) — zero dynamic routes; latent expectation of `/editor/<projectId>/…`
- [R5 — Backend / Supabase](./R5-backend.md) — env declared, zero code
- [R6 — File I/O pathways](./R6-file-io.md) — "Save/Load Build" work; no Zod validation on import
- [R7 — `@pascal-app/editor` API](./R7-editor-api.md) — rich callback surface; scene switcher is a 1–2 day host feature
- [R8 — Integration design options](./R8-mcp-integration-design.md) — A→B→D phased recommendation
- [R9 — Production readiness](./R9-production-readiness.md) — 4–5 weeks to beta, 10–14 to GA
- [R10 — Ideas and edge cases](./R10-ideas-and-edges.md) — 300+ lines; top 10 ranked by value×feasibility