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>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
bdb47a6e5d
commit
42bd05db9c
@@ -0,0 +1,154 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user