/** * Casa del Sol — end-to-end build via MCP HTTP transport. * * Connects to http://localhost:3917/mcp (already running) and constructs * the scene described in ./DESIGN.md: * - 12x8 house footprint, 1 storey, 4 perimeter + 5 interior walls * - 7 interior zones (living, kitchen, bed2, hallway, bath1, bath2, master) * - 6 doors and 6 windows cut into specific walls * - 5x3 pool zone + pool basin slab at elevation -1.8 * - 5 privacy fence segments around the 20x15 lot (with south gap) * - 1 garden zone * * Emits: * - stdout [casa] step log lines * - ./scene.json (pretty-printed export after fences) * - ./BUILD_REPORT.md (per-step report) * * Run: * bun packages/mcp/test-reports/casa-sol/build.ts */ import { Client } from '@modelcontextprotocol/sdk/client/index.js' import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js' import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js' import { writeFileSync } from 'node:fs' import { dirname } from 'node:path' import { fileURLToPath } from 'node:url' const HERE = dirname(fileURLToPath(import.meta.url)) const SERVER_URL = 'http://localhost:3917/mcp' let TRANSPORT_NOTE = '' type StepRecord = { n: number name: string ok: boolean durationMs: number summary: string nodeIds?: string[] errors?: string[] } const steps: StepRecord[] = [] function log(line: string): void { // biome-ignore lint/suspicious/noConsole: build script console.log(line) } function pad2(n: number): string { return n < 10 ? `0${n}` : String(n) } async function step( n: number, name: string, fn: () => Promise<{ summary: string; nodeIds?: string[]; errors?: string[]; result: T }>, ): Promise { const start = Date.now() try { const { summary, nodeIds, errors, result } = await fn() const durationMs = Date.now() - start steps.push({ n, name, ok: true, durationMs, summary, nodeIds, errors }) log( `[casa] step ${pad2(n)} ${name.padEnd(22)} ok (${summary}, ${durationMs}ms)` + (nodeIds && nodeIds.length > 0 && nodeIds.length <= 10 ? ` ids: ${nodeIds.join(', ')}` : ''), ) return result } catch (err) { const durationMs = Date.now() - start const msg = err instanceof Error ? err.message : String(err) steps.push({ n, name, ok: false, durationMs, summary: 'FAILED', errors: [msg] }) log(`[casa] step ${pad2(n)} ${name.padEnd(22)} FAIL (${msg}, ${durationMs}ms)`) return null } } async function callTool>( client: Client, name: string, args: Record = {}, ): Promise { const res = await client.callTool({ name, arguments: args }) if (res.isError) { const text = Array.isArray(res.content) ? res.content .map((c) => typeof (c as { text?: unknown }).text === 'string' ? (c as { text: string }).text : '', ) .join('\n') : '' throw new Error(`tool ${name} error: ${text || 'unknown'}`) } return (res.structuredContent ?? {}) as T } /** * Try to call a tool that might fail with a structured error; caller can * continue on failure. Returns { ok, value | error }. */ async function tryCallTool>( client: Client, name: string, args: Record = {}, ): Promise<{ ok: true; value: T } | { ok: false; error: string }> { try { const v = await callTool(client, name, args) return { ok: true, value: v } } catch (err) { return { ok: false, error: err instanceof Error ? err.message : String(err) } } } // --- Geometry spec --------------------------------------------------------- type Vec2 = [number, number] type WallSpec = { key: string designId: number // 1..9 per DESIGN.md wall numbering start: Vec2 end: Vec2 } const PERIMETER_WALLS: WallSpec[] = [ { key: 'south-outer', designId: 1, start: [-8, 4], end: [4, 4] }, { key: 'north-outer', designId: 2, start: [-8, -4], end: [4, -4] }, { key: 'west-outer', designId: 3, start: [-8, -4], end: [-8, 4] }, { key: 'east-outer', designId: 4, start: [4, -4], end: [4, 4] }, ] const INTERIOR_WALLS: WallSpec[] = [ { key: 'living-kitchen-split', designId: 5, start: [-1, 0], end: [-1, 4] }, { key: 'north-bedrooms-split', designId: 6, start: [-1, -4], end: [-1, 0] }, { key: 'bedroom2-east', designId: 7, start: [-4, -4], end: [-4, 0] }, { key: 'hallway-north-edge', designId: 8, start: [-4, -1], end: [-1, -1] }, { key: 'hallway-south-edge', designId: 9, start: [-4, -2], end: [-1, -2] }, ] type ZoneSpec = { label: string polygon: Vec2[] properties?: Record } const INTERIOR_ZONES: ZoneSpec[] = [ { label: 'living-dining', polygon: [ [-8, 0], [-1, 0], [-1, 4], [-8, 4], ], }, { label: 'kitchen', polygon: [ [-1, 0], [4, 0], [4, 4], [-1, 4], ], }, { label: 'bedroom-2', polygon: [ [-8, -4], [-4, -4], [-4, 0], [-8, 0], ], }, { label: 'hallway', polygon: [ [-4, -2], [-1, -2], [-1, -1], [-4, -1], ], }, { label: 'bathroom-2', polygon: [ [-4, -4], [-1, -4], [-1, -2], [-4, -2], ], }, { label: 'bathroom-1', polygon: [ [-4, -1], [-1, -1], [-1, 0], [-4, 0], ], }, { label: 'master-bedroom', polygon: [ [-1, -4], [4, -4], [4, 0], [-1, 0], ], }, ] // Per DESIGN.md: (wall designId, kind, pos, width, height, label) type OpeningSpec = { wallDesignId: number kind: 'door' | 'window' position: number width: number height: number label: string } const OPENINGS: OpeningSpec[] = [ { wallDesignId: 1, kind: 'door', position: 0.2, width: 0.9, height: 2.1, label: 'front-door' }, { wallDesignId: 1, kind: 'door', position: 0.75, width: 2.2, height: 2.1, label: 'sliding-pool', }, { wallDesignId: 2, kind: 'door', position: 0.65, width: 0.9, height: 2.1, label: 'kitchen-back' }, { wallDesignId: 6, kind: 'door', position: 0.3, width: 0.8, height: 2.1, label: 'master-door' }, { wallDesignId: 7, kind: 'door', position: 0.5, width: 0.8, height: 2.1, label: 'bedroom-2-door' }, { wallDesignId: 9, kind: 'door', position: 0.5, width: 0.7, height: 2.0, label: 'bath2-door' }, { wallDesignId: 1, kind: 'window', position: 0.3, width: 2.0, height: 1.4, label: 'living-pic', }, { wallDesignId: 1, kind: 'window', position: 0.45, width: 1.4, height: 1.4, label: 'living-2' }, { wallDesignId: 3, kind: 'window', position: 0.25, width: 1.0, height: 1.1, label: 'kitchen-w' }, { wallDesignId: 4, kind: 'window', position: 0.25, width: 1.4, height: 1.4, label: 'master-w' }, { wallDesignId: 4, kind: 'window', position: 0.75, width: 1.4, height: 1.4, label: 'bedroom-2-w', }, { wallDesignId: 2, kind: 'window', position: 0.3, width: 0.8, height: 0.6, label: 'bath2-high', }, ] const POOL_POLY: Vec2[] = [ [5, -1.5], [10, -1.5], [10, 1.5], [5, 1.5], ] type FenceSpec = { label: string; start: Vec2; end: Vec2 } const FENCES: FenceSpec[] = [ { label: 'south-west', start: [-10, 7.5], end: [-1, 7.5] }, { label: 'south-east', start: [1, 7.5], end: [10, 7.5] }, { label: 'east', start: [10, 7.5], end: [10, -7.5] }, { label: 'north', start: [10, -7.5], end: [-10, -7.5] }, { label: 'west', start: [-10, -7.5], end: [-10, 7.5] }, ] const SITE_POLY: Vec2[] = [ [-10, -7.5], [10, -7.5], [10, 7.5], [-10, 7.5], ] // --- Validation helper ----------------------------------------------------- type ValidationResult = { valid: boolean errors: Array<{ nodeId: string; path: string; message: string }> } async function runValidate(client: Client, phase: string): Promise { const v = await callTool(client, 'validate_scene', {}) log(`[casa] validate after ${phase}: valid=${v.valid}, errors=${v.errors.length}`) if (!v.valid && v.errors.length > 0) { for (const e of v.errors.slice(0, 5)) { log(`[casa] - ${e.nodeId} @ ${e.path}: ${e.message}`) } if (v.errors.length > 5) log(`[casa] (+${v.errors.length - 5} more)`) } return v } // --- Main ----------------------------------------------------------------- async function main(): Promise { log(`[casa] connecting to ${SERVER_URL} via StreamableHTTPClientTransport`) const httpTransport = new StreamableHTTPClientTransport(new URL(SERVER_URL)) let client = new Client({ name: 'casa-sol-builder', version: '0.1.0' }) let closers: Array<() => Promise> = [] let usedTransport: 'http' | 'in-memory' = 'http' try { await client.connect(httpTransport) // Smoke probe await client.listTools() TRANSPORT_NOTE = 'shared HTTP server at :3917 via StreamableHTTPClientTransport' log(`[casa] HTTP transport connected`) closers = [async () => client.close()] } catch (err) { const msg = err instanceof Error ? err.message : String(err) log(`[casa] HTTP transport failed: ${msg}`) log(`[casa] falling back to in-memory MCP server (same tool surface)`) // Load the in-process MCP server to keep the build moving. This preserves // the tool contract; the only thing we lose is the HTTP wire test. const { SceneBridge } = await import('/Users/adrian/Desktop/editor/.worktrees/mcp-server/packages/mcp/src/bridge/scene-bridge.ts') const { createPascalMcpServer } = await import('/Users/adrian/Desktop/editor/.worktrees/mcp-server/packages/mcp/src/server.ts') const bridge = new SceneBridge() bridge.loadDefault() const server = createPascalMcpServer({ bridge }) const [srvT, cliT] = InMemoryTransport.createLinkedPair() const inMemClient = new Client({ name: 'casa-sol-builder-inmem', version: '0.1.0' }) await Promise.all([server.connect(srvT), inMemClient.connect(cliT)]) client = inMemClient usedTransport = 'in-memory' TRANSPORT_NOTE = `in-memory fallback — HTTP server returned: ${msg}` closers = [async () => client.close(), async () => server.close()] log(`[casa] in-memory MCP server connected`) } void usedTransport // ----- Step 01: Discover building & level ----- const discovered = await step(1, 'discover', async () => { const buildings = await callTool<{ nodes: Array<{ id: string; type: string; name?: string }> }>(client, 'find_nodes', { type: 'building' }) const levels = await callTool<{ nodes: Array<{ id: string; type: string; name?: string; parentId?: string }> }>(client, 'find_nodes', { type: 'level' }) if (!buildings.nodes.length) throw new Error('no building found in default scene') if (!levels.nodes.length) throw new Error('no level found in default scene') const building = buildings.nodes[0]! const level = levels.nodes.find((l) => l.parentId === building.id) ?? levels.nodes[0]! return { summary: `building=${building.id}, level=${level.id}`, nodeIds: [building.id, level.id], result: { buildingId: building.id, levelId: level.id }, } }) if (!discovered) { log('[casa] cannot continue without discovered ids; aborting') await client.close() return } const { levelId } = discovered // Initial scene snapshot for "before" count. const initialAll = await callTool<{ nodes: Array<{ type: string }> }>(client, 'find_nodes', {}) const initialCount = initialAll.nodes.length log(`[casa] initial node count: ${initialCount}`) // ----- Step 02: Perimeter walls (via create_wall) ----- // DESIGN.md wall IDs 1..4: south-outer, north-outer, west-outer, east-outer const perimeterIds: Record = {} const perimeter = await step(2, 'perimeter walls', async () => { const ids: string[] = [] for (const w of PERIMETER_WALLS) { const r = await callTool<{ wallId: string }>(client, 'create_wall', { levelId, start: w.start, end: w.end, thickness: 0.2, height: 2.7, }) perimeterIds[w.key] = r.wallId ids.push(r.wallId) } return { summary: `${ids.length} walls via create_wall`, nodeIds: ids, result: ids, } }) // ----- Step 03: Interior walls (via apply_patch) ----- const interiorIds: Record = {} const interior = await step(3, 'interior walls', async () => { const res = await callTool<{ appliedOps: number createdIds: string[] deletedIds: string[] }>(client, 'apply_patch', { patches: INTERIOR_WALLS.map((w) => ({ op: 'create', parentId: levelId, node: { type: 'wall', start: w.start, end: w.end, thickness: 0.2, height: 2.7, }, })), }) const createdIds = res.createdIds for (let i = 0; i < INTERIOR_WALLS.length; i++) { const w = INTERIOR_WALLS[i]! const id = createdIds[i] if (id) interiorIds[w.key] = id } return { summary: `${createdIds.length} walls via apply_patch`, nodeIds: createdIds, result: createdIds, } }) // Map designId -> wallId for opening lookup const wallByDesignId = new Map() for (const w of PERIMETER_WALLS) { const id = perimeterIds[w.key] if (id) wallByDesignId.set(w.designId, id) } for (const w of INTERIOR_WALLS) { const id = interiorIds[w.key] if (id) wallByDesignId.set(w.designId, id) } log(`[casa] wall ids (by designId): ${JSON.stringify(Object.fromEntries(wallByDesignId))}`) const validationAfterWalls = perimeter && interior ? await runValidate(client, 'walls') : null void validationAfterWalls // ----- Step 04: Zones ----- const interiorZoneIds: Record = {} const zones = await step(4, 'zones', async () => { const ids: string[] = [] for (const z of INTERIOR_ZONES) { const r = await callTool<{ zoneId: string }>(client, 'set_zone', { levelId, label: z.label, polygon: z.polygon, properties: z.properties ?? {}, }) interiorZoneIds[z.label] = r.zoneId ids.push(r.zoneId) } return { summary: `${ids.length} zones`, nodeIds: ids, result: ids, } }) void zones const validationAfterZones = await runValidate(client, 'zones') void validationAfterZones // ----- Step 05: Openings (doors + windows) ----- const openingResults: Array<{ label: string kind: string wallId: string ok: boolean openingId?: string error?: string }> = [] const openings = await step(5, 'openings', async () => { let doors = 0 let windows = 0 const failures: string[] = [] for (const o of OPENINGS) { const wallId = wallByDesignId.get(o.wallDesignId) if (!wallId) { const err = `skipped ${o.label}: wall designId=${o.wallDesignId} not found` log(`[casa] ${err}`) failures.push(err) openingResults.push({ label: o.label, kind: o.kind, wallId: `design-${o.wallDesignId}`, ok: false, error: 'wall not found', }) continue } const r = await tryCallTool<{ openingId: string }>(client, 'cut_opening', { wallId, type: o.kind, position: o.position, width: o.width, height: o.height, }) if (r.ok) { openingResults.push({ label: o.label, kind: o.kind, wallId, ok: true, openingId: r.value.openingId, }) if (o.kind === 'door') doors++ else windows++ } else { log(`[casa] cut_opening failed for ${o.label} (wall=${wallId}): ${r.error}`) failures.push(`${o.label}: ${r.error}`) openingResults.push({ label: o.label, kind: o.kind, wallId, ok: false, error: r.error, }) } } const ids = openingResults.filter((r) => r.ok && r.openingId).map((r) => r.openingId!) as string[] return { summary: `${doors} doors, ${windows} windows, ${failures.length} failures`, nodeIds: ids, errors: failures, result: { doors, windows, failures }, } }) void openings const validationAfterOpenings = await runValidate(client, 'openings') void validationAfterOpenings // ----- Step 06: Pool zone + pool basin slab ----- const poolZoneIdRef: { id?: string } = {} const poolSlabIdRef: { id?: string } = {} const poolResult = await step(6, 'pool zone+slab', async () => { const zoneRes = await callTool<{ zoneId: string }>(client, 'set_zone', { levelId, label: 'pool', polygon: POOL_POLY, properties: { kind: 'pool', depthM: 1.8, finish: 'tile' }, }) poolZoneIdRef.id = zoneRes.zoneId // Create pool basin slab at elevation -1.8 via apply_patch. const slabRes = await callTool<{ appliedOps: number createdIds: string[] deletedIds: string[] }>(client, 'apply_patch', { patches: [ { op: 'create', parentId: levelId, node: { type: 'slab', polygon: POOL_POLY, elevation: -1.8, }, }, ], }) const slabId = slabRes.createdIds[0] if (slabId) poolSlabIdRef.id = slabId return { summary: `zone=${zoneRes.zoneId}, basin slab=${slabId ?? 'NONE'}`, nodeIds: [zoneRes.zoneId, slabId ?? ''].filter(Boolean) as string[], result: { zoneId: zoneRes.zoneId, slabId }, } }) void poolResult const validationAfterPool = await runValidate(client, 'pool') void validationAfterPool // ----- Step 07: Fences ----- const fenceIds: string[] = [] const fencesStep = await step(7, 'privacy fences', async () => { const res = await callTool<{ appliedOps: number createdIds: string[] deletedIds: string[] }>(client, 'apply_patch', { patches: FENCES.map((f) => ({ op: 'create', parentId: levelId, node: { type: 'fence', start: f.start, end: f.end, height: 1.8, style: 'privacy', thickness: 0.08, }, })), }) fenceIds.push(...res.createdIds) return { summary: `${res.createdIds.length} fences via apply_patch`, nodeIds: res.createdIds, result: res.createdIds, } }) void fencesStep const validationAfterFences = await runValidate(client, 'fences') void validationAfterFences // ----- Step 08: Garden zone ----- const gardenZoneIdRef: { id?: string } = {} const gardenStep = await step(8, 'garden zone', async () => { const r = await callTool<{ zoneId: string }>(client, 'set_zone', { levelId, label: 'garden', polygon: SITE_POLY, properties: { kind: 'garden' }, }) gardenZoneIdRef.id = r.zoneId return { summary: `zone=${r.zoneId}`, nodeIds: [r.zoneId], result: r.zoneId, } }) void gardenStep // ----- Step 09: Measure — SW building corner wall -> NE lot fence ----- // SW building corner ~= start of first perimeter wall (south-outer: starts at (-8, 4)) // NE lot fence corner = east fence segment (start at (10, 7.5), end at (10, -7.5)) await step(9, 'measure cross-zone', async () => { const fromId = perimeterIds['south-outer'] const toId = fenceIds[2] // east fence (see FENCES order) if (!fromId) throw new Error('missing south-outer wall id') if (!toId) throw new Error('missing east fence id') const r = await callTool<{ distanceMeters: number; units: string }>(client, 'measure', { fromId, toId, }) return { summary: `distance=${r.distanceMeters.toFixed(3)}m`, nodeIds: [fromId, toId], result: r, } }) // ----- Step 10: Export JSON ----- const exportInfo: { bytes?: number } = {} await step(10, 'export json', async () => { const r = await callTool<{ json: string }>(client, 'export_json', { pretty: true }) const path = `${HERE}/scene.json` writeFileSync(path, r.json, 'utf-8') exportInfo.bytes = r.json.length return { summary: `wrote ${r.json.length} bytes -> scene.json`, result: r.json.length, } }) // ----- Step 11: Duplicate level ----- const preDupCount = (await callTool<{ nodes: unknown[] }>(client, 'find_nodes', {})).nodes.length const dupRef: { newLevelId?: string; cloned?: number } = {} await step(11, 'duplicate level', async () => { const r = await callTool<{ newLevelId: string; newNodeIds: string[] }>( client, 'duplicate_level', { levelId }, ) dupRef.newLevelId = r.newLevelId dupRef.cloned = r.newNodeIds.length return { summary: `newLevelId=${r.newLevelId}, cloned=${r.newNodeIds.length} nodes`, nodeIds: [r.newLevelId], result: r, } }) const postDupCount = (await callTool<{ nodes: unknown[] }>(client, 'find_nodes', {})).nodes.length // ----- Step 12: Final validate + summary counts ----- const finalValid = await runValidate(client, 'final') const allNodes = ( await callTool<{ nodes: Array<{ type: string }> }>(client, 'find_nodes', {}) ).nodes const tally: Record = {} for (const n of allNodes) { tally[n.type] = (tally[n.type] ?? 0) + 1 } log( `[casa] FINAL: totalNodes=${allNodes.length} walls=${tally.wall ?? 0} zones=${ tally.zone ?? 0 } doors=${tally.door ?? 0} windows=${tally.window ?? 0} fences=${ tally.fence ?? 0 } slabs=${tally.slab ?? 0} levels=${tally.level ?? 0}`, ) log(`[casa] pre-duplicate=${preDupCount}, post-duplicate=${postDupCount}`) log(`[casa] validation: valid=${finalValid.valid}, errors=${finalValid.errors.length}`) // ----- Write BUILD_REPORT.md ----- const lines: string[] = [] lines.push('# Casa del Sol — Build Report') lines.push('') lines.push(`Generated: ${new Date().toISOString()}`) lines.push(`Server: ${SERVER_URL}`) lines.push(`Transport used: **${usedTransport}** — ${TRANSPORT_NOTE}`) lines.push(`Initial node count: ${initialCount}`) lines.push('') lines.push('## Steps') lines.push('') lines.push('| # | Name | Status | Duration | Summary |') lines.push('|---|------|--------|----------|---------|') for (const s of steps) { lines.push( `| ${s.n} | ${s.name} | ${s.ok ? 'OK' : 'FAIL'} | ${s.durationMs}ms | ${s.summary.replace(/\|/g, '\\|')} |`, ) } lines.push('') lines.push('## Per-Step Details') lines.push('') for (const s of steps) { lines.push(`### Step ${s.n} — ${s.name}`) lines.push('') lines.push(`- Status: **${s.ok ? 'OK' : 'FAIL'}**`) lines.push(`- Duration: ${s.durationMs}ms`) lines.push(`- Summary: ${s.summary}`) if (s.nodeIds && s.nodeIds.length > 0) { const shown = s.nodeIds.slice(0, 15).join(', ') lines.push( `- Node IDs (${s.nodeIds.length}): \`${shown}${s.nodeIds.length > 15 ? ' ...' : ''}\``, ) } if (s.errors && s.errors.length > 0) { lines.push(`- Errors/warnings:`) for (const e of s.errors) lines.push(` - ${e}`) } lines.push('') } lines.push('## Opening attempts') lines.push('') lines.push('| Label | Kind | Wall | OK | Opening Id / Error |') lines.push('|-------|------|------|----|--------------------|') for (const r of openingResults) { lines.push( `| ${r.label} | ${r.kind} | \`${r.wallId}\` | ${r.ok ? 'yes' : 'no'} | ${ r.ok ? r.openingId : (r.error ?? 'n/a').replace(/\|/g, '\\|') } |`, ) } lines.push('') lines.push('## Final scene totals') lines.push('') lines.push('| Node type | Count |') lines.push('|-----------|-------|') const types = [ 'site', 'building', 'level', 'wall', 'fence', 'zone', 'slab', 'door', 'window', 'ceiling', 'roof', 'stair', 'item', 'guide', ] for (const t of types) { if ((tally[t] ?? 0) > 0) lines.push(`| ${t} | ${tally[t]} |`) } lines.push(`| **total** | **${allNodes.length}** |`) lines.push('') lines.push('## Validation') lines.push('') lines.push(`- Final \`validate_scene\`: valid=\`${finalValid.valid}\`, errors=${finalValid.errors.length}`) if (!finalValid.valid && finalValid.errors.length > 0) { lines.push('') lines.push('Errors (verbatim):') lines.push('') for (const e of finalValid.errors) { lines.push(`- \`${e.nodeId}\` @ \`${e.path}\`: ${e.message}`) } } lines.push('') lines.push('## Duplicate-level') lines.push('') lines.push(`- Pre-duplicate node count: **${preDupCount}**`) lines.push(`- Post-duplicate node count: **${postDupCount}**`) lines.push(`- New level id: \`${dupRef.newLevelId ?? 'N/A'}\``) lines.push(`- Nodes cloned: ${dupRef.cloned ?? 0}`) lines.push('') // Known discrepancies section const discrepancies: string[] = [] const openingFailures = openingResults.filter((r) => !r.ok) if (openingFailures.length > 0) { discrepancies.push( `${openingFailures.length} cut_opening call(s) failed — see "Opening attempts" table for details. Core may have rejected an opening due to overlap or width exceeding the wall length.`, ) } if (!finalValid.valid) { discrepancies.push( 'Final validate_scene returned valid=false — see Validation section for the verbatim error list.', ) } const gardenPolyNote = 'Garden zone polygon equals the full site polygon (20x15) — per design brief §Garden zone we set it to the site polygon and rely on the building zones overlapping visually, rather than subtracting the building footprint.' discrepancies.push(gardenPolyNote) if (usedTransport === 'in-memory') { discrepancies.push( `Build fell back to in-memory MCP transport. The HTTP server at ${SERVER_URL} rejected the SDK client's initialize with "Server already initialized" — the server uses the SDK's single-session StreamableHTTPServerTransport which only accepts one \`initialize\` POST per process lifetime. The tool surface exercised is identical; only the wire transport differs.`, ) } lines.push('## Known discrepancies with DESIGN.md') lines.push('') for (const d of discrepancies) lines.push(`- ${d}`) lines.push('') lines.push('## Artifacts') lines.push('') lines.push( `- \`scene.json\`: full pretty-printed JSON export (${exportInfo.bytes ?? 'n/a'} bytes)`, ) lines.push(`- \`build.log\`: stdout from this run`) lines.push(`- \`BUILD_REPORT.md\`: this file`) lines.push('') writeFileSync(`${HERE}/BUILD_REPORT.md`, lines.join('\n'), 'utf-8') log(`[casa] wrote BUILD_REPORT.md`) log(`[casa] transport: ${usedTransport} (${TRANSPORT_NOTE})`) log(`[casa] DONE`) for (const c of closers) await c() } main().catch((err) => { // biome-ignore lint/suspicious/noConsole: build script console.error('[casa] FATAL:', err instanceof Error ? err.stack : String(err)) process.exit(1) })