feat(mcp): implement 19 scene query and mutation tools

Scene querying: get_scene, get_node, describe_node, find_nodes, measure.
Scene mutation (undoable, atomic): apply_patch, create_level, create_wall,
place_item, cut_opening, set_zone, duplicate_level, delete_node.
Undo/redo: undo, redo.
Export: export_json, export_glb (not_implemented stub).
Validation: validate_scene, check_collisions.

Each tool has:
- Exported Zod input + output schemas
- register<Tool>(server, bridge) wiring function
- Bun test with happy-path + error-path coverage via InMemoryTransport

59 tool tests, all passing end-to-end through MCP protocol.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adrian Perez
2026-04-18 17:51:08 +02:00
co-authored by Claude Opus 4.7
parent 07ed429d58
commit 58ad89e80b
41 changed files with 2584 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
import { z } from 'zod'
/**
* Shared Zod schemas used by multiple MCP tools. Keep DRY — if a shape is
* referenced by more than one tool, define it here.
*/
/** A node identifier — non-empty string. The core uses `${prefix}_${nanoid}`. */
export const NodeIdSchema = z.string().min(1)
/** 2D point as [x, z] (floor plane). Matches core's tuple convention. */
export const Vec2Schema = z.tuple([z.number(), z.number()])
/** 3D point as [x, y, z]. */
export const Vec3Schema = z.tuple([z.number(), z.number(), z.number()])
/**
* A single patch operation. Union of create / update / delete.
*
* For `create`, the node object must include `type` so Zod can discriminate at
* the bridge layer — we accept a plain object here and let the bridge's Zod
* re-parse catch structural issues. For `update`, `data` is a partial merge.
*/
export const CreatePatchSchema = z.object({
op: z.literal('create'),
node: z.record(z.string(), z.unknown()),
parentId: NodeIdSchema.optional(),
})
export const UpdatePatchSchema = z.object({
op: z.literal('update'),
id: NodeIdSchema,
data: z.record(z.string(), z.unknown()),
})
export const DeletePatchSchema = z.object({
op: z.literal('delete'),
id: NodeIdSchema,
cascade: z.boolean().optional(),
})
export const PatchSchema = z.discriminatedUnion('op', [
CreatePatchSchema,
UpdatePatchSchema,
DeletePatchSchema,
])
export type Patch = z.infer<typeof PatchSchema>