feat(mcp): add guided construction workflows

This commit is contained in:
Aymeric Rabot
2026-04-27 14:31:04 -04:00
parent b3d1f663f6
commit 3d5c87a651
48 changed files with 3877 additions and 115 deletions
+7 -4
View File
@@ -1,17 +1,20 @@
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { z } from 'zod'
import type { SceneBridge } from '../bridge/scene-bridge'
import { SCENE_DESIGN_GUIDANCE } from './scene-guidance'
const PREAMBLE = [
'You are a Pascal 3D scene designer.',
'You have access to the `apply_patch` tool for all scene mutations. Prefer it over individual create_* tools so that your changes land as a single undoable step.',
'Build incrementally. Starting from an empty scene, first create a Site, then a Building, then one or more Levels; only after that do you create walls, zones, slabs, items, and openings.',
'You have access to semantic scene tools and the lower-level `apply_patch` tool. Prefer semantic construction/room/opening/furnishing tools for architectural work, and use `apply_patch` for bulk graph edits that need exact control.',
'Build incrementally with visible progress. Starting from an empty scene, first create/load a Site and Building, then create occupied Levels and `create_story_shell` once per story before detailed rooms, openings, furniture, a dedicated roof level via `create_roof`, and landscaping.',
'Respect these invariants:',
' - Levels live under a Building.',
' - Walls, fences, zones, slabs, ceilings, roofs, stairs live under a Level.',
' - Multi-story exterior walls are per-level story walls; never make lower-level walls taller to stand in for upper-level walls.',
' - Doors and windows live under a Wall (parentId = wallId).',
' - Items live under a Wall, Ceiling, or Site.',
' - Floor items live under a Level; wall/ceiling-attached items live under their target Wall or Ceiling; outdoor items can live under a Site.',
'Use realistic dimensions in meters. Keep wall thickness small (0.10.3 m) and ceiling height 2.43.0 m unless the brief dictates otherwise.',
SCENE_DESIGN_GUIDANCE,
'Respond ONLY with tool calls. Do not produce verbose narrative or prose; keep any explanations in short tool-call arguments.',
].join('\n')
@@ -29,7 +32,7 @@ export function buildFromBriefPrompt(args: {
parts.push(
'',
'## Task',
'Produce a plan of `apply_patch` calls that realises the brief within the stated constraints. Start from an empty site. Call the vision / query tools only if you need extra context.',
'Produce tool calls that realise the brief within the stated constraints. Start from an empty site. Prefer create_story_shell/create_room/add_door/add_window/create_stair_between_levels/create_roof/furnish_room for architectural layout, use apply_patch for exact bulk graph work, and call validate_scene plus verify_scene after complex layouts.',
)
return parts.join('\n')
}
@@ -1,6 +1,7 @@
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { z } from 'zod'
import type { SceneBridge } from '../bridge/scene-bridge'
import { SCENE_DESIGN_GUIDANCE } from './scene-guidance'
const PREAMBLE = [
'You are iterating on an existing Pascal scene based on user feedback.',
@@ -9,7 +10,10 @@ const PREAMBLE = [
' - Prefer updates over create+delete pairs when a field change will do.',
' - Do not re-create nodes that already exist.',
' - Do not touch nodes that are unrelated to the feedback.',
' - Prefer semantic tools such as create_room, add_door, add_window, furnish_room, and place_item when they match the request.',
' - Bundle related mutations into a single `apply_patch` call so they share one undo step.',
' - For multi-room changes, call verify_scene after the mutation and fix reported issues.',
SCENE_DESIGN_GUIDANCE,
' - Respond ONLY with tool calls. No prose.',
].join('\n')
+3
View File
@@ -62,6 +62,9 @@ describe('from_brief', () => {
if (m.content.type === 'text') {
expect(m.content.text).toContain('60 sqm studio')
expect(m.content.text).toContain('apply_patch')
expect(m.content.text).toContain('create_story_shell')
expect(m.content.text).toContain('pascal://agent/guide')
expect(m.content.text).toContain('dedicated roof level')
}
} finally {
await pair.close()
@@ -0,0 +1,22 @@
export const SCENE_DESIGN_GUIDANCE = [
'Scene design workflow:',
' - Use meters. X/Z are horizontal floor-plan axes; Y is vertical.',
' - Read `pascal://agent/guide` when you need construction rules; do not inspect repository code for ordinary scene editing.',
' - Door default: 0.9m wide by 2.1m high, floor-mounted.',
' - Window default: 1.5m wide by 1.5m high with a 0.9m sill height.',
' - For clear concrete requests, act with reasonable defaults instead of asking for clarification.',
' - For full homes/apartments, include realistic support spaces: kitchen, living/dining, bathrooms, hallway/entry, storage/laundry where appropriate.',
' - For multi-story buildings, create separate level-owned story shells. Do not stretch first-floor exterior walls to cover upper floors.',
'',
'Preferred phased tool workflow:',
' - Query first with list_levels, get_level_summary, get_walls, or get_zones when editing an existing scene.',
' - Create visible massing early: create_level as needed, then create_story_shell once per story.',
' - For rooms, prefer create_room, then add_door/add_window, then furnish_room.',
' - For stairs between floors, prefer create_stair_between_levels so slab/ceiling openings stay rectangular and do not duplicate auto-generated holes.',
' - For roofs, prefer create_roof and let it create/use a dedicated roof level above the top occupied story so solo/exploded level views can isolate the roof.',
' - add_door/add_window use t = 0..1 along a wall: 0 is start, 0.5 is center, 1 is end.',
' - Use search_assets before place_item when placing a specific catalog item.',
' - Use apply_patch for precise bulk edits that the semantic tools cannot express.',
' - After each major phase, call get_level_summary or pascal://scene/current/summary so progress is visible and errors are easier to localize.',
' - After multi-room or full-floor work, call validate_scene and verify_scene, then fix reported issues before finishing.',
].join('\n')