@pascal-app/mcp 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +330 -0
  3. package/dist/bin/pascal-mcp.d.ts +3 -0
  4. package/dist/bin/pascal-mcp.d.ts.map +1 -0
  5. package/dist/bin/pascal-mcp.js +91 -0
  6. package/dist/bridge/index.d.ts +3 -0
  7. package/dist/bridge/index.d.ts.map +1 -0
  8. package/dist/bridge/index.js +1 -0
  9. package/dist/bridge/node-shims.d.ts +16 -0
  10. package/dist/bridge/node-shims.d.ts.map +1 -0
  11. package/dist/bridge/node-shims.js +25 -0
  12. package/dist/bridge/scene-bridge.d.ts +158 -0
  13. package/dist/bridge/scene-bridge.d.ts.map +1 -0
  14. package/dist/bridge/scene-bridge.js +482 -0
  15. package/dist/index.d.ts +5 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +4 -0
  18. package/dist/lib/rehydrate-site-children.d.ts +12 -0
  19. package/dist/lib/rehydrate-site-children.d.ts.map +1 -0
  20. package/dist/lib/rehydrate-site-children.js +40 -0
  21. package/dist/lib/safe-fetch.d.ts +19 -0
  22. package/dist/lib/safe-fetch.d.ts.map +1 -0
  23. package/dist/lib/safe-fetch.js +221 -0
  24. package/dist/operations/index.d.ts +2 -0
  25. package/dist/operations/index.d.ts.map +1 -0
  26. package/dist/operations/index.js +1 -0
  27. package/dist/operations/scene-operations.d.ts +67 -0
  28. package/dist/operations/scene-operations.d.ts.map +1 -0
  29. package/dist/operations/scene-operations.js +192 -0
  30. package/dist/prompts/from-brief.d.ts +11 -0
  31. package/dist/prompts/from-brief.d.ts.map +1 -0
  32. package/dist/prompts/from-brief.js +48 -0
  33. package/dist/prompts/index.d.ts +10 -0
  34. package/dist/prompts/index.d.ts.map +1 -0
  35. package/dist/prompts/index.js +14 -0
  36. package/dist/prompts/iterate-on-feedback.d.ts +11 -0
  37. package/dist/prompts/iterate-on-feedback.d.ts.map +1 -0
  38. package/dist/prompts/iterate-on-feedback.js +41 -0
  39. package/dist/prompts/renovation-from-photos.d.ts +24 -0
  40. package/dist/prompts/renovation-from-photos.d.ts.map +1 -0
  41. package/dist/prompts/renovation-from-photos.js +135 -0
  42. package/dist/prompts/scene-guidance.d.ts +2 -0
  43. package/dist/prompts/scene-guidance.d.ts.map +1 -0
  44. package/dist/prompts/scene-guidance.js +24 -0
  45. package/dist/resources/agent-guide.d.ts +5 -0
  46. package/dist/resources/agent-guide.d.ts.map +1 -0
  47. package/dist/resources/agent-guide.js +85 -0
  48. package/dist/resources/catalog-items.d.ts +10 -0
  49. package/dist/resources/catalog-items.d.ts.map +1 -0
  50. package/dist/resources/catalog-items.js +29 -0
  51. package/dist/resources/constraints.d.ts +9 -0
  52. package/dist/resources/constraints.d.ts.map +1 -0
  53. package/dist/resources/constraints.js +69 -0
  54. package/dist/resources/index.d.ts +15 -0
  55. package/dist/resources/index.d.ts.map +1 -0
  56. package/dist/resources/index.js +23 -0
  57. package/dist/resources/scene-current.d.ts +9 -0
  58. package/dist/resources/scene-current.d.ts.map +1 -0
  59. package/dist/resources/scene-current.js +20 -0
  60. package/dist/resources/scene-summary.d.ts +10 -0
  61. package/dist/resources/scene-summary.d.ts.map +1 -0
  62. package/dist/resources/scene-summary.js +196 -0
  63. package/dist/server.d.ts +14 -0
  64. package/dist/server.d.ts.map +1 -0
  65. package/dist/server.js +18 -0
  66. package/dist/storage/index.d.ts +13 -0
  67. package/dist/storage/index.d.ts.map +1 -0
  68. package/dist/storage/index.js +14 -0
  69. package/dist/storage/slug.d.ts +21 -0
  70. package/dist/storage/slug.d.ts.map +1 -0
  71. package/dist/storage/slug.js +62 -0
  72. package/dist/storage/sqlite-driver.d.ts +18 -0
  73. package/dist/storage/sqlite-driver.d.ts.map +1 -0
  74. package/dist/storage/sqlite-driver.js +32 -0
  75. package/dist/storage/sqlite-scene-store.d.ts +51 -0
  76. package/dist/storage/sqlite-scene-store.d.ts.map +1 -0
  77. package/dist/storage/sqlite-scene-store.js +526 -0
  78. package/dist/storage/types.d.ts +136 -0
  79. package/dist/storage/types.d.ts.map +1 -0
  80. package/dist/storage/types.js +28 -0
  81. package/dist/templates/empty-studio.d.ts +8 -0
  82. package/dist/templates/empty-studio.d.ts.map +1 -0
  83. package/dist/templates/empty-studio.js +230 -0
  84. package/dist/templates/garden-house.d.ts +8 -0
  85. package/dist/templates/garden-house.d.ts.map +1 -0
  86. package/dist/templates/garden-house.js +242 -0
  87. package/dist/templates/index.d.ts +23 -0
  88. package/dist/templates/index.d.ts.map +1 -0
  89. package/dist/templates/index.js +20 -0
  90. package/dist/templates/two-bedroom.d.ts +8 -0
  91. package/dist/templates/two-bedroom.d.ts.map +1 -0
  92. package/dist/templates/two-bedroom.js +270 -0
  93. package/dist/tools/apply-patch.d.ts +25 -0
  94. package/dist/tools/apply-patch.d.ts.map +1 -0
  95. package/dist/tools/apply-patch.js +59 -0
  96. package/dist/tools/asset-catalog.d.ts +16 -0
  97. package/dist/tools/asset-catalog.d.ts.map +1 -0
  98. package/dist/tools/asset-catalog.js +308 -0
  99. package/dist/tools/check-collisions.d.ts +15 -0
  100. package/dist/tools/check-collisions.d.ts.map +1 -0
  101. package/dist/tools/check-collisions.js +61 -0
  102. package/dist/tools/construction-tools.d.ts +93 -0
  103. package/dist/tools/construction-tools.d.ts.map +1 -0
  104. package/dist/tools/construction-tools.js +369 -0
  105. package/dist/tools/create-level.d.ts +14 -0
  106. package/dist/tools/create-level.d.ts.map +1 -0
  107. package/dist/tools/create-level.js +48 -0
  108. package/dist/tools/create-wall.d.ts +15 -0
  109. package/dist/tools/create-wall.d.ts.map +1 -0
  110. package/dist/tools/create-wall.js +50 -0
  111. package/dist/tools/cut-opening.d.ts +18 -0
  112. package/dist/tools/cut-opening.d.ts.map +1 -0
  113. package/dist/tools/cut-opening.js +57 -0
  114. package/dist/tools/delete-node.d.ts +12 -0
  115. package/dist/tools/delete-node.d.ts.map +1 -0
  116. package/dist/tools/delete-node.js +37 -0
  117. package/dist/tools/describe-node.d.ts +17 -0
  118. package/dist/tools/describe-node.d.ts.map +1 -0
  119. package/dist/tools/describe-node.js +86 -0
  120. package/dist/tools/duplicate-level.d.ts +12 -0
  121. package/dist/tools/duplicate-level.d.ts.map +1 -0
  122. package/dist/tools/duplicate-level.js +56 -0
  123. package/dist/tools/errors.d.ts +20 -0
  124. package/dist/tools/errors.d.ts.map +1 -0
  125. package/dist/tools/errors.js +25 -0
  126. package/dist/tools/export-glb.d.ts +10 -0
  127. package/dist/tools/export-glb.d.ts.map +1 -0
  128. package/dist/tools/export-glb.js +24 -0
  129. package/dist/tools/export-json.d.ts +11 -0
  130. package/dist/tools/export-json.d.ts.map +1 -0
  131. package/dist/tools/export-json.js +23 -0
  132. package/dist/tools/find-nodes.d.ts +32 -0
  133. package/dist/tools/find-nodes.d.ts.map +1 -0
  134. package/dist/tools/find-nodes.js +105 -0
  135. package/dist/tools/geometry.d.ts +24 -0
  136. package/dist/tools/geometry.d.ts.map +1 -0
  137. package/dist/tools/geometry.js +94 -0
  138. package/dist/tools/get-node.d.ts +11 -0
  139. package/dist/tools/get-node.d.ts.map +1 -0
  140. package/dist/tools/get-node.js +27 -0
  141. package/dist/tools/get-scene.d.ts +11 -0
  142. package/dist/tools/get-scene.d.ts.map +1 -0
  143. package/dist/tools/get-scene.js +26 -0
  144. package/dist/tools/index.d.ts +12 -0
  145. package/dist/tools/index.d.ts.map +1 -0
  146. package/dist/tools/index.js +64 -0
  147. package/dist/tools/live-sync.d.ts +11 -0
  148. package/dist/tools/live-sync.d.ts.map +1 -0
  149. package/dist/tools/live-sync.js +62 -0
  150. package/dist/tools/measure.d.ts +14 -0
  151. package/dist/tools/measure.d.ts.map +1 -0
  152. package/dist/tools/measure.js +137 -0
  153. package/dist/tools/photo-to-scene/index.d.ts +11 -0
  154. package/dist/tools/photo-to-scene/index.d.ts.map +1 -0
  155. package/dist/tools/photo-to-scene/index.js +11 -0
  156. package/dist/tools/photo-to-scene/photo-to-scene.d.ts +26 -0
  157. package/dist/tools/photo-to-scene/photo-to-scene.d.ts.map +1 -0
  158. package/dist/tools/photo-to-scene/photo-to-scene.js +323 -0
  159. package/dist/tools/place-item.d.ts +15 -0
  160. package/dist/tools/place-item.d.ts.map +1 -0
  161. package/dist/tools/place-item.js +84 -0
  162. package/dist/tools/redo.d.ts +11 -0
  163. package/dist/tools/redo.d.ts.map +1 -0
  164. package/dist/tools/redo.js +25 -0
  165. package/dist/tools/room-tools.d.ts +97 -0
  166. package/dist/tools/room-tools.d.ts.map +1 -0
  167. package/dist/tools/room-tools.js +510 -0
  168. package/dist/tools/scene-lifecycle/create-project.d.ts +33 -0
  169. package/dist/tools/scene-lifecycle/create-project.d.ts.map +1 -0
  170. package/dist/tools/scene-lifecycle/create-project.js +70 -0
  171. package/dist/tools/scene-lifecycle/delete-scene.d.ts +12 -0
  172. package/dist/tools/scene-lifecycle/delete-scene.d.ts.map +1 -0
  173. package/dist/tools/scene-lifecycle/delete-scene.js +42 -0
  174. package/dist/tools/scene-lifecycle/get-project-status.d.ts +31 -0
  175. package/dist/tools/scene-lifecycle/get-project-status.d.ts.map +1 -0
  176. package/dist/tools/scene-lifecycle/get-project-status.js +69 -0
  177. package/dist/tools/scene-lifecycle/index.d.ts +17 -0
  178. package/dist/tools/scene-lifecycle/index.d.ts.map +1 -0
  179. package/dist/tools/scene-lifecycle/index.js +29 -0
  180. package/dist/tools/scene-lifecycle/list-scenes.d.ts +27 -0
  181. package/dist/tools/scene-lifecycle/list-scenes.d.ts.map +1 -0
  182. package/dist/tools/scene-lifecycle/list-scenes.js +49 -0
  183. package/dist/tools/scene-lifecycle/load-scene.d.ts +31 -0
  184. package/dist/tools/scene-lifecycle/load-scene.d.ts.map +1 -0
  185. package/dist/tools/scene-lifecycle/load-scene.js +55 -0
  186. package/dist/tools/scene-lifecycle/metadata.d.ts +49 -0
  187. package/dist/tools/scene-lifecycle/metadata.d.ts.map +1 -0
  188. package/dist/tools/scene-lifecycle/metadata.js +68 -0
  189. package/dist/tools/scene-lifecycle/rename-scene.d.ts +22 -0
  190. package/dist/tools/scene-lifecycle/rename-scene.d.ts.map +1 -0
  191. package/dist/tools/scene-lifecycle/rename-scene.js +63 -0
  192. package/dist/tools/scene-lifecycle/save-scene.d.ts +42 -0
  193. package/dist/tools/scene-lifecycle/save-scene.d.ts.map +1 -0
  194. package/dist/tools/scene-lifecycle/save-scene.js +130 -0
  195. package/dist/tools/scene-lifecycle/test-utils.d.ts +39 -0
  196. package/dist/tools/scene-lifecycle/test-utils.d.ts.map +1 -0
  197. package/dist/tools/scene-lifecycle/test-utils.js +211 -0
  198. package/dist/tools/scene-query.d.ts +57 -0
  199. package/dist/tools/scene-query.d.ts.map +1 -0
  200. package/dist/tools/scene-query.js +645 -0
  201. package/dist/tools/schemas.d.ts +52 -0
  202. package/dist/tools/schemas.d.ts.map +1 -0
  203. package/dist/tools/schemas.js +42 -0
  204. package/dist/tools/set-zone.d.ts +14 -0
  205. package/dist/tools/set-zone.d.ts.map +1 -0
  206. package/dist/tools/set-zone.js +48 -0
  207. package/dist/tools/templates/create-from-template.d.ts +54 -0
  208. package/dist/tools/templates/create-from-template.d.ts.map +1 -0
  209. package/dist/tools/templates/create-from-template.js +145 -0
  210. package/dist/tools/templates/create-house-from-brief.d.ts +35 -0
  211. package/dist/tools/templates/create-house-from-brief.d.ts.map +1 -0
  212. package/dist/tools/templates/create-house-from-brief.js +170 -0
  213. package/dist/tools/templates/index.d.ts +14 -0
  214. package/dist/tools/templates/index.d.ts.map +1 -0
  215. package/dist/tools/templates/index.js +18 -0
  216. package/dist/tools/templates/list-templates.d.ts +18 -0
  217. package/dist/tools/templates/list-templates.d.ts.map +1 -0
  218. package/dist/tools/templates/list-templates.js +36 -0
  219. package/dist/tools/undo.d.ts +11 -0
  220. package/dist/tools/undo.d.ts.map +1 -0
  221. package/dist/tools/undo.js +25 -0
  222. package/dist/tools/validate-scene.d.ts +14 -0
  223. package/dist/tools/validate-scene.d.ts.map +1 -0
  224. package/dist/tools/validate-scene.js +24 -0
  225. package/dist/tools/variants/generate-variants.d.ts +30 -0
  226. package/dist/tools/variants/generate-variants.d.ts.map +1 -0
  227. package/dist/tools/variants/generate-variants.js +160 -0
  228. package/dist/tools/variants/index.d.ts +9 -0
  229. package/dist/tools/variants/index.d.ts.map +1 -0
  230. package/dist/tools/variants/index.js +9 -0
  231. package/dist/tools/variants/mutations.d.ts +18 -0
  232. package/dist/tools/variants/mutations.d.ts.map +1 -0
  233. package/dist/tools/variants/mutations.js +324 -0
  234. package/dist/tools/vision/analyze-floorplan-image.d.ts +33 -0
  235. package/dist/tools/vision/analyze-floorplan-image.d.ts.map +1 -0
  236. package/dist/tools/vision/analyze-floorplan-image.js +146 -0
  237. package/dist/tools/vision/analyze-room-photo.d.ts +29 -0
  238. package/dist/tools/vision/analyze-room-photo.d.ts.map +1 -0
  239. package/dist/tools/vision/analyze-room-photo.js +130 -0
  240. package/dist/tools/vision/index.d.ts +12 -0
  241. package/dist/tools/vision/index.d.ts.map +1 -0
  242. package/dist/tools/vision/index.js +14 -0
  243. package/dist/transports/http.d.ts +36 -0
  244. package/dist/transports/http.d.ts.map +1 -0
  245. package/dist/transports/http.js +203 -0
  246. package/dist/transports/stdio.d.ts +14 -0
  247. package/dist/transports/stdio.d.ts.map +1 -0
  248. package/dist/transports/stdio.js +16 -0
  249. package/package.json +86 -0
@@ -0,0 +1,510 @@
1
+ import { CeilingNode, DoorNode, ItemNode, SlabNode, WallNode, WindowNode, ZoneNode, } from '@pascal-app/core/schema';
2
+ import { z } from 'zod';
3
+ import { findCatalogItem, searchCatalogItems } from './asset-catalog';
4
+ import { ErrorCode, throwMcpError } from './errors';
5
+ import { pointInBoundsWithPadding, polygonArea, polygonBounds, wallLength, wallLocalXFromT, } from './geometry';
6
+ import { publishLiveSceneSnapshot } from './live-sync';
7
+ import { NodeIdSchema, Vec2Schema } from './schemas';
8
+ const ROOM_TYPES = [
9
+ 'bedroom',
10
+ 'kitchen',
11
+ 'bathroom',
12
+ 'living',
13
+ 'dining',
14
+ 'hallway',
15
+ 'entry',
16
+ 'laundry',
17
+ 'storage',
18
+ ];
19
+ export const searchAssetsInput = {
20
+ query: z.string().min(1),
21
+ category: z.string().optional(),
22
+ };
23
+ export const searchAssetsOutput = {
24
+ results: z.array(z.record(z.string(), z.unknown())),
25
+ total: z.number(),
26
+ };
27
+ export const createRoomInput = {
28
+ levelId: NodeIdSchema,
29
+ name: z.string().min(1),
30
+ polygon: z.array(Vec2Schema).min(3),
31
+ color: z.string().optional(),
32
+ wallHeight: z.number().positive().optional(),
33
+ wallThickness: z.number().positive().optional(),
34
+ };
35
+ export const createRoomOutput = {
36
+ zoneId: z.string(),
37
+ slabId: z.string(),
38
+ ceilingId: z.string(),
39
+ wallIds: z.array(z.string()),
40
+ areaSqMeters: z.number(),
41
+ };
42
+ export const addDoorInput = {
43
+ wallId: NodeIdSchema,
44
+ t: z.number().min(0).max(1).optional(),
45
+ position: z.number().min(0).max(1).optional(),
46
+ width: z.number().positive().optional(),
47
+ height: z.number().positive().optional(),
48
+ hingesSide: z.enum(['left', 'right']).optional(),
49
+ swingDirection: z.enum(['inward', 'outward']).optional(),
50
+ };
51
+ export const addDoorOutput = {
52
+ doorId: z.string(),
53
+ localX: z.number(),
54
+ t: z.number(),
55
+ position: z.number(),
56
+ wallLength: z.number(),
57
+ clamped: z.boolean(),
58
+ coordinateSystem: z.literal('wall-local-meters'),
59
+ };
60
+ export const addWindowInput = {
61
+ wallId: NodeIdSchema,
62
+ t: z.number().min(0).max(1).optional(),
63
+ position: z.number().min(0).max(1).optional(),
64
+ width: z.number().positive().optional(),
65
+ height: z.number().positive().optional(),
66
+ sillHeight: z.number().min(0).optional(),
67
+ };
68
+ export const addWindowOutput = {
69
+ windowId: z.string(),
70
+ localX: z.number(),
71
+ t: z.number(),
72
+ position: z.number(),
73
+ wallLength: z.number(),
74
+ clamped: z.boolean(),
75
+ coordinateSystem: z.literal('wall-local-meters'),
76
+ sillHeight: z.number(),
77
+ };
78
+ export const furnishRoomInput = {
79
+ levelId: NodeIdSchema.optional(),
80
+ zoneId: NodeIdSchema.optional(),
81
+ roomType: z.enum(ROOM_TYPES),
82
+ polygon: z.array(Vec2Schema).min(3).optional(),
83
+ doorWallIndex: z.number().int().min(0).optional(),
84
+ };
85
+ export const furnishRoomOutput = {
86
+ placed: z.number(),
87
+ itemIds: z.array(z.string()),
88
+ skipped: z.array(z.string()),
89
+ };
90
+ function textResult(payload) {
91
+ return {
92
+ content: [{ type: 'text', text: JSON.stringify(payload) }],
93
+ structuredContent: payload,
94
+ };
95
+ }
96
+ function assertLevel(bridge, levelId) {
97
+ const level = bridge.getNode(levelId);
98
+ if (!level)
99
+ throwMcpError(ErrorCode.InvalidParams, `Level not found: ${levelId}`);
100
+ if (level.type !== 'level') {
101
+ throwMcpError(ErrorCode.InvalidParams, `Node ${levelId} is a ${level.type}, expected level`);
102
+ }
103
+ if (typeof level.metadata === 'object' &&
104
+ level.metadata !== null &&
105
+ 'role' in level.metadata &&
106
+ level.metadata.role === 'roof') {
107
+ throwMcpError(ErrorCode.InvalidParams, `Roof support level ${levelId} is not an occupied story; create rooms or furnishings on an occupied level instead`);
108
+ }
109
+ return level;
110
+ }
111
+ function assertWall(bridge, wallId) {
112
+ const wall = bridge.getNode(wallId);
113
+ if (!wall)
114
+ throwMcpError(ErrorCode.InvalidParams, `Wall not found: ${wallId}`);
115
+ if (wall.type !== 'wall') {
116
+ throwMcpError(ErrorCode.InvalidParams, `Node ${wallId} is a ${wall.type}, expected wall`);
117
+ }
118
+ return wall;
119
+ }
120
+ function inferRoomGeometry(bridge, levelId, polygon, zoneId) {
121
+ if (levelId && polygon)
122
+ return { levelId, polygon };
123
+ if (!zoneId) {
124
+ throwMcpError(ErrorCode.InvalidParams, 'Provide either levelId + polygon or zoneId so the room can be furnished');
125
+ }
126
+ const zone = bridge.getNode(zoneId);
127
+ if (!zone)
128
+ throwMcpError(ErrorCode.InvalidParams, `Zone not found: ${zoneId}`);
129
+ if (zone.type !== 'zone') {
130
+ throwMcpError(ErrorCode.InvalidParams, `Node ${zoneId} is a ${zone.type}, expected zone`);
131
+ }
132
+ const inferredLevelId = levelId ?? zone.parentId ?? undefined;
133
+ if (!inferredLevelId) {
134
+ throwMcpError(ErrorCode.InvalidParams, `Zone ${zoneId} is missing a parent level`);
135
+ }
136
+ return {
137
+ levelId: inferredLevelId,
138
+ polygon: polygon ?? zone.polygon,
139
+ };
140
+ }
141
+ function resolveWallT(toolName, t, position) {
142
+ const resolved = t ?? position;
143
+ if (resolved === undefined) {
144
+ throwMcpError(ErrorCode.InvalidParams, `${toolName} requires t or position in the 0..1 range`);
145
+ }
146
+ return resolved;
147
+ }
148
+ function makeItemAsset(asset) {
149
+ return {
150
+ id: asset.id,
151
+ name: asset.name,
152
+ category: asset.category,
153
+ thumbnail: asset.thumbnail ?? '',
154
+ src: asset.src,
155
+ dimensions: asset.dimensions ?? [1, 1, 1],
156
+ offset: asset.offset ?? [0, 0, 0],
157
+ rotation: asset.rotation ?? [0, 0, 0],
158
+ scale: asset.scale ?? [1, 1, 1],
159
+ ...(asset.attachTo ? { attachTo: asset.attachTo } : {}),
160
+ ...(asset.tags ? { tags: asset.tags } : {}),
161
+ ...(asset.surface ? { surface: asset.surface } : {}),
162
+ ...(asset.interactive ? { interactive: asset.interactive } : {}),
163
+ };
164
+ }
165
+ function itemFootprint(asset, x, z, rotationDeg = 0) {
166
+ const [w = 1, , d = 1] = asset.dimensions ?? [1, 1, 1];
167
+ const rot = (rotationDeg * Math.PI) / 180;
168
+ const cos = Math.abs(Math.cos(rot));
169
+ const sin = Math.abs(Math.sin(rot));
170
+ const halfW = (w * cos + d * sin) / 2;
171
+ const halfD = (w * sin + d * cos) / 2;
172
+ return { minX: x - halfW, maxX: x + halfW, minZ: z - halfD, maxZ: z + halfD };
173
+ }
174
+ function footprintsOverlap(a, b) {
175
+ const gap = 0.08;
176
+ return (a.maxX - gap > b.minX && a.minX + gap < b.maxX && a.maxZ - gap > b.minZ && a.minZ + gap < b.maxZ);
177
+ }
178
+ function buildRoomPlacements(roomType, polygon, doorWallIndex = 0) {
179
+ const bounds = polygonBounds(polygon);
180
+ const n = polygon.length;
181
+ const backIdx = (doorWallIndex + Math.floor(n / 2)) % n;
182
+ const backStart = polygon[backIdx];
183
+ const backEnd = polygon[(backIdx + 1) % n];
184
+ const backMidX = (backStart[0] + backEnd[0]) / 2;
185
+ const backMidZ = (backStart[1] + backEnd[1]) / 2;
186
+ const inwardX = bounds.centerX - backMidX;
187
+ const inwardZ = bounds.centerZ - backMidZ;
188
+ const inwardLen = Math.sqrt(inwardX * inwardX + inwardZ * inwardZ) || 1;
189
+ const inX = inwardX / inwardLen;
190
+ const inZ = inwardZ / inwardLen;
191
+ const facingRot = (Math.atan2(inX, inZ) * 180) / Math.PI;
192
+ const alongX = backEnd[0] - backStart[0];
193
+ const alongZ = backEnd[1] - backStart[1];
194
+ const alongLen = Math.sqrt(alongX * alongX + alongZ * alongZ) || 1;
195
+ const ax = alongX / alongLen;
196
+ const az = alongZ / alongLen;
197
+ const backPos = (inset, lateral = 0) => [
198
+ backMidX + inX * inset + ax * lateral,
199
+ backMidZ + inZ * inset + az * lateral,
200
+ ];
201
+ const sideIdx = (doorWallIndex + 1) % n;
202
+ const sideStart = polygon[sideIdx];
203
+ const sideEnd = polygon[(sideIdx + 1) % n];
204
+ const sideMidX = (sideStart[0] + sideEnd[0]) / 2;
205
+ const sideMidZ = (sideStart[1] + sideEnd[1]) / 2;
206
+ const sideInX = bounds.centerX - sideMidX;
207
+ const sideInZ = bounds.centerZ - sideMidZ;
208
+ const sideInLen = Math.sqrt(sideInX * sideInX + sideInZ * sideInZ) || 1;
209
+ const snX = sideInX / sideInLen;
210
+ const snZ = sideInZ / sideInLen;
211
+ const sideRot = (Math.atan2(snX, snZ) * 180) / Math.PI;
212
+ const sideAlongX = sideEnd[0] - sideStart[0];
213
+ const sideAlongZ = sideEnd[1] - sideStart[1];
214
+ const sideAlongLen = Math.sqrt(sideAlongX * sideAlongX + sideAlongZ * sideAlongZ) || 1;
215
+ const sax = sideAlongX / sideAlongLen;
216
+ const saz = sideAlongZ / sideAlongLen;
217
+ const sidePos = (inset, lateral = 0) => [
218
+ sideMidX + snX * inset + sax * lateral,
219
+ sideMidZ + snZ * inset + saz * lateral,
220
+ ];
221
+ const placements = [];
222
+ const area = polygonArea(polygon);
223
+ const addBack = (assetId, inset, lateral = 0, rotationDeg = facingRot) => {
224
+ const [x, z] = backPos(inset, lateral);
225
+ placements.push({ assetId, x, z, rotationDeg });
226
+ };
227
+ const addSide = (assetId, inset, lateral = 0, rotationDeg = sideRot) => {
228
+ const [x, z] = sidePos(inset, lateral);
229
+ placements.push({ assetId, x, z, rotationDeg });
230
+ };
231
+ switch (roomType) {
232
+ case 'bedroom': {
233
+ const bedId = Math.max(bounds.width, bounds.depth) >= 3.2 ? 'double-bed' : 'single-bed';
234
+ const bed = findCatalogItem(bedId);
235
+ const [bedW = 2, , bedD = 2.5] = bed?.dimensions ?? [];
236
+ addBack(bedId, bedD / 2 + 0.1);
237
+ if (alongLen > bedW + 1.1) {
238
+ addBack('bedside-table', 0.35, -(bedW / 2 + 0.35));
239
+ addBack('bedside-table', 0.35, bedW / 2 + 0.35);
240
+ }
241
+ if (area >= 10)
242
+ addSide('dresser', 0.55, sideAlongLen * 0.22);
243
+ if (area >= 13)
244
+ addSide('closet', 0.6, -sideAlongLen * 0.22);
245
+ break;
246
+ }
247
+ case 'kitchen':
248
+ addBack(alongLen >= 2.6 ? 'kitchen' : 'kitchen-counter', 0.55);
249
+ if (alongLen >= 3.5)
250
+ addBack('stove', 0.55, alongLen / 2 - 0.65);
251
+ addSide('fridge', 0.6, sideAlongLen * 0.25);
252
+ break;
253
+ case 'bathroom':
254
+ addBack('toilet', 0.55, alongLen * 0.25);
255
+ addBack('bathroom-sink', 0.8, -alongLen * 0.2);
256
+ if (area >= 6.5)
257
+ addSide('bathtub', 0.85);
258
+ else
259
+ placements.push({ assetId: 'shower-square', x: bounds.centerX, z: bounds.centerZ });
260
+ break;
261
+ case 'living': {
262
+ addBack('sofa', 0.9);
263
+ addBack('coffee-table', 2.1);
264
+ addSide('livingroom-chair', 0.85, -sideAlongLen * 0.18);
265
+ const doorIdx = doorWallIndex % n;
266
+ const doorStart = polygon[doorIdx];
267
+ const doorEnd = polygon[(doorIdx + 1) % n];
268
+ placements.push({
269
+ assetId: 'tv-stand',
270
+ x: (doorStart[0] + doorEnd[0]) / 2 - inX * 0.35,
271
+ z: (doorStart[1] + doorEnd[1]) / 2 - inZ * 0.35,
272
+ rotationDeg: facingRot + 180,
273
+ });
274
+ break;
275
+ }
276
+ case 'dining':
277
+ placements.push({ assetId: 'dining-table', x: bounds.centerX, z: bounds.centerZ });
278
+ placements.push({ assetId: 'dining-chair', x: bounds.centerX, z: bounds.centerZ - 0.85 });
279
+ placements.push({
280
+ assetId: 'dining-chair',
281
+ x: bounds.centerX,
282
+ z: bounds.centerZ + 0.85,
283
+ rotationDeg: 180,
284
+ });
285
+ if (Math.min(bounds.width, bounds.depth) >= 3) {
286
+ placements.push({
287
+ assetId: 'dining-chair',
288
+ x: bounds.centerX - 0.85,
289
+ z: bounds.centerZ,
290
+ rotationDeg: 270,
291
+ });
292
+ placements.push({
293
+ assetId: 'dining-chair',
294
+ x: bounds.centerX + 0.85,
295
+ z: bounds.centerZ,
296
+ rotationDeg: 90,
297
+ });
298
+ }
299
+ break;
300
+ case 'laundry':
301
+ addBack('washing-machine', 0.6, -0.55);
302
+ addBack('drying-rack', 0.65, 0.65);
303
+ break;
304
+ case 'entry':
305
+ case 'hallway':
306
+ if (Math.min(bounds.width, bounds.depth) >= 1.4)
307
+ addSide('coat-rack', 0.35);
308
+ break;
309
+ case 'storage':
310
+ addBack('closet', 0.6);
311
+ break;
312
+ }
313
+ return { placements, bounds };
314
+ }
315
+ export function registerSearchAssets(server) {
316
+ server.registerTool('search_assets', {
317
+ title: 'Search assets',
318
+ description: 'Search the built-in MCP item catalog by keyword. Call before place_item when you need a valid catalogItemId.',
319
+ inputSchema: searchAssetsInput,
320
+ outputSchema: searchAssetsOutput,
321
+ }, async ({ query, category }) => {
322
+ const results = searchCatalogItems({ query, category }).map((item) => ({
323
+ id: item.id,
324
+ name: item.name,
325
+ category: item.category,
326
+ tags: item.tags ?? [],
327
+ dimensions: item.dimensions,
328
+ attachTo: item.attachTo ?? null,
329
+ }));
330
+ return textResult({ results, total: results.length });
331
+ });
332
+ }
333
+ export function registerCreateRoom(server, bridge) {
334
+ server.registerTool('create_room', {
335
+ title: 'Create room',
336
+ description: 'Create a room on a level: zone, slab, ceiling, and one wall per polygon edge. Returns wallIds in polygon edge order.',
337
+ inputSchema: createRoomInput,
338
+ outputSchema: createRoomOutput,
339
+ }, async ({ levelId, name, polygon, color, wallHeight, wallThickness }) => {
340
+ assertLevel(bridge, levelId);
341
+ const points = polygon;
342
+ const zone = ZoneNode.parse({
343
+ name,
344
+ polygon: points,
345
+ color: color ?? '#60a5fa',
346
+ metadata: { mcpTool: 'create_room' },
347
+ });
348
+ const slab = SlabNode.parse({ polygon: points, metadata: { mcpTool: 'create_room' } });
349
+ const ceiling = CeilingNode.parse({ polygon: points, metadata: { mcpTool: 'create_room' } });
350
+ const walls = points.map((start, index) => WallNode.parse({
351
+ name: `${name} wall ${index + 1}`,
352
+ start,
353
+ end: points[(index + 1) % points.length],
354
+ ...(wallHeight !== undefined ? { height: wallHeight } : {}),
355
+ ...(wallThickness !== undefined ? { thickness: wallThickness } : {}),
356
+ metadata: { mcpTool: 'create_room', roomName: name, edgeIndex: index },
357
+ }));
358
+ bridge.applyPatch([
359
+ { op: 'create', node: zone, parentId: levelId },
360
+ { op: 'create', node: slab, parentId: levelId },
361
+ { op: 'create', node: ceiling, parentId: levelId },
362
+ ...walls.map((wall) => ({
363
+ op: 'create',
364
+ node: wall,
365
+ parentId: levelId,
366
+ })),
367
+ ]);
368
+ await publishLiveSceneSnapshot(bridge, 'create_room');
369
+ return textResult({
370
+ zoneId: zone.id,
371
+ slabId: slab.id,
372
+ ceilingId: ceiling.id,
373
+ wallIds: walls.map((wall) => wall.id),
374
+ areaSqMeters: Math.round(polygonArea(points) * 100) / 100,
375
+ });
376
+ });
377
+ }
378
+ export function registerAddDoor(server, bridge) {
379
+ server.registerTool('add_door', {
380
+ title: 'Add door',
381
+ description: 'Add a door to an existing wall. t/position is 0..1 along the wall: 0 = start, 0.5 = center, 1 = end.',
382
+ inputSchema: addDoorInput,
383
+ outputSchema: addDoorOutput,
384
+ }, async ({ wallId, t, position, width = 0.9, height = 2.1, hingesSide, swingDirection }) => {
385
+ const wall = assertWall(bridge, wallId);
386
+ const length = wallLength(wall);
387
+ if (length < width) {
388
+ throwMcpError(ErrorCode.InvalidParams, `Wall ${wallId} is ${length.toFixed(2)}m long, too short for a ${width.toFixed(2)}m door`);
389
+ }
390
+ const wallT = resolveWallT('add_door', t, position);
391
+ const localX = wallLocalXFromT(wall, wallT, width);
392
+ const door = DoorNode.parse({
393
+ wallId,
394
+ parentId: wallId,
395
+ position: [localX, height / 2, 0],
396
+ width,
397
+ height,
398
+ ...(hingesSide ? { hingesSide } : {}),
399
+ ...(swingDirection ? { swingDirection } : {}),
400
+ });
401
+ const id = bridge.createNode(door, wallId);
402
+ await publishLiveSceneSnapshot(bridge, 'add_door');
403
+ return textResult({
404
+ doorId: id,
405
+ localX,
406
+ t: wallT,
407
+ position: wallT,
408
+ wallLength: length,
409
+ clamped: Math.abs(localX - wallT * length) > 1e-9,
410
+ coordinateSystem: 'wall-local-meters',
411
+ });
412
+ });
413
+ }
414
+ export function registerAddWindow(server, bridge) {
415
+ server.registerTool('add_window', {
416
+ title: 'Add window',
417
+ description: 'Add a window to an existing wall. t/position is 0..1 along the wall; sillHeight is the height from floor to window bottom.',
418
+ inputSchema: addWindowInput,
419
+ outputSchema: addWindowOutput,
420
+ }, async ({ wallId, t, position, width = 1.5, height = 1.5, sillHeight = 0.9 }) => {
421
+ const wall = assertWall(bridge, wallId);
422
+ const length = wallLength(wall);
423
+ if (length < width) {
424
+ throwMcpError(ErrorCode.InvalidParams, `Wall ${wallId} is ${length.toFixed(2)}m long, too short for a ${width.toFixed(2)}m window`);
425
+ }
426
+ const wallT = resolveWallT('add_window', t, position);
427
+ const localX = wallLocalXFromT(wall, wallT, width);
428
+ const windowNode = WindowNode.parse({
429
+ wallId,
430
+ parentId: wallId,
431
+ position: [localX, sillHeight + height / 2, 0],
432
+ width,
433
+ height,
434
+ });
435
+ const id = bridge.createNode(windowNode, wallId);
436
+ await publishLiveSceneSnapshot(bridge, 'add_window');
437
+ return textResult({
438
+ windowId: id,
439
+ localX,
440
+ t: wallT,
441
+ position: wallT,
442
+ wallLength: length,
443
+ clamped: Math.abs(localX - wallT * length) > 1e-9,
444
+ coordinateSystem: 'wall-local-meters',
445
+ sillHeight,
446
+ });
447
+ });
448
+ }
449
+ export function registerFurnishRoom(server, bridge) {
450
+ server.registerTool('furnish_room', {
451
+ title: 'Furnish room',
452
+ description: 'Place realistic furniture for a room type using levelId + polygon, or infer both from zoneId. Parent floor items to the level so they render and validate.',
453
+ inputSchema: furnishRoomInput,
454
+ outputSchema: furnishRoomOutput,
455
+ }, async ({ levelId, zoneId, roomType, polygon, doorWallIndex }) => {
456
+ const room = inferRoomGeometry(bridge, levelId, polygon, zoneId);
457
+ assertLevel(bridge, room.levelId);
458
+ const points = room.polygon;
459
+ const { placements, bounds } = buildRoomPlacements(roomType, points, doorWallIndex ?? 0);
460
+ const footprints = [];
461
+ const skipped = [];
462
+ const items = [];
463
+ for (const placement of placements) {
464
+ const asset = findCatalogItem(placement.assetId);
465
+ if (!asset) {
466
+ skipped.push(`${placement.assetId}: asset not found`);
467
+ continue;
468
+ }
469
+ const fp = itemFootprint(asset, placement.x, placement.z, placement.rotationDeg ?? 0);
470
+ const padding = 0.05;
471
+ if (!(pointInBoundsWithPadding(fp.minX, fp.minZ, bounds, -padding) &&
472
+ pointInBoundsWithPadding(fp.maxX, fp.maxZ, bounds, -padding))) {
473
+ skipped.push(`${asset.id}: outside room bounds`);
474
+ continue;
475
+ }
476
+ if (footprints.some((existing) => footprintsOverlap(fp, existing))) {
477
+ skipped.push(`${asset.id}: overlaps another item`);
478
+ continue;
479
+ }
480
+ footprints.push(fp);
481
+ items.push(ItemNode.parse({
482
+ name: asset.name,
483
+ position: [placement.x, 0, placement.z],
484
+ rotation: [0, ((placement.rotationDeg ?? 0) * Math.PI) / 180, 0],
485
+ asset: makeItemAsset(asset),
486
+ metadata: { mcpTool: 'furnish_room', roomType },
487
+ }));
488
+ }
489
+ if (items.length > 0) {
490
+ bridge.applyPatch(items.map((item) => ({
491
+ op: 'create',
492
+ node: item,
493
+ parentId: room.levelId,
494
+ })));
495
+ await publishLiveSceneSnapshot(bridge, 'furnish_room');
496
+ }
497
+ return textResult({
498
+ placed: items.length,
499
+ itemIds: items.map((item) => item.id),
500
+ skipped,
501
+ });
502
+ });
503
+ }
504
+ export function registerRoomTools(server, bridge) {
505
+ registerSearchAssets(server);
506
+ registerCreateRoom(server, bridge);
507
+ registerAddDoor(server, bridge);
508
+ registerAddWindow(server, bridge);
509
+ registerFurnishRoom(server, bridge);
510
+ }
@@ -0,0 +1,33 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import type { SceneOperations } from '../../operations';
4
+ export declare const createProjectInput: {
5
+ name: z.ZodString;
6
+ id: z.ZodOptional<z.ZodString>;
7
+ isPrivate: z.ZodDefault<z.ZodBoolean>;
8
+ };
9
+ export declare const createProjectOutput: {
10
+ id: z.ZodString;
11
+ projectId: z.ZodString;
12
+ name: z.ZodString;
13
+ editorUrl: z.ZodString;
14
+ url: z.ZodString;
15
+ ownerId: z.ZodNullable<z.ZodString>;
16
+ thumbnailUrl: z.ZodNullable<z.ZodString>;
17
+ publishedVersion: z.ZodNullable<z.ZodNumber>;
18
+ latestVersion: z.ZodNullable<z.ZodNumber>;
19
+ draftVersion: z.ZodNullable<z.ZodNumber>;
20
+ browserVisibleVersion: z.ZodNullable<z.ZodNumber>;
21
+ version: z.ZodNumber;
22
+ isEmpty: z.ZodBoolean;
23
+ sizeBytes: z.ZodNumber;
24
+ nodeCount: z.ZodNumber;
25
+ graphHash: z.ZodNullable<z.ZodString>;
26
+ createdAt: z.ZodString;
27
+ updatedAt: z.ZodString;
28
+ levelIds: z.ZodArray<z.ZodString>;
29
+ defaultLevelId: z.ZodNullable<z.ZodString>;
30
+ nextStep: z.ZodString;
31
+ };
32
+ export declare function registerCreateProject(server: McpServer, operations: SceneOperations): void;
33
+ //# sourceMappingURL=create-project.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-project.d.ts","sourceRoot":"","sources":["../../../src/tools/scene-lifecycle/create-project.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAIvD,eAAO,MAAM,kBAAkB;;;;CAI9B,CAAA;AAED,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;CAsB/B,CAAA;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,GAAG,IAAI,CAgD1F"}
@@ -0,0 +1,70 @@
1
+ import { z } from 'zod';
2
+ import { ErrorCode, throwMcpError } from '../errors';
3
+ import { currentLevelContext, projectStatusPayload } from './metadata';
4
+ export const createProjectInput = {
5
+ name: z.string().min(1).max(200),
6
+ id: z.string().min(1).max(64).optional(),
7
+ isPrivate: z.boolean().default(true),
8
+ };
9
+ export const createProjectOutput = {
10
+ id: z.string(),
11
+ projectId: z.string(),
12
+ name: z.string(),
13
+ editorUrl: z.string(),
14
+ url: z.string(),
15
+ ownerId: z.string().nullable(),
16
+ thumbnailUrl: z.string().nullable(),
17
+ publishedVersion: z.number().nullable(),
18
+ latestVersion: z.number().nullable(),
19
+ draftVersion: z.number().nullable(),
20
+ browserVisibleVersion: z.number().nullable(),
21
+ version: z.number(),
22
+ isEmpty: z.boolean(),
23
+ sizeBytes: z.number(),
24
+ nodeCount: z.number(),
25
+ graphHash: z.string().nullable(),
26
+ createdAt: z.string(),
27
+ updatedAt: z.string(),
28
+ levelIds: z.array(z.string()),
29
+ defaultLevelId: z.string().nullable(),
30
+ nextStep: z.string(),
31
+ };
32
+ export function registerCreateProject(server, operations) {
33
+ server.registerTool('create_project', {
34
+ title: 'Create project',
35
+ description: 'Create a browser-visible Pascal project for the authenticated user. Use this before save_scene when the user asks for a new project.',
36
+ inputSchema: createProjectInput,
37
+ outputSchema: createProjectOutput,
38
+ }, async ({ name, id, isPrivate }) => {
39
+ if (!operations.canCreateProject) {
40
+ throwMcpError(ErrorCode.InvalidRequest, 'create_project_unavailable: this MCP store cannot create hosted projects');
41
+ }
42
+ try {
43
+ const status = await operations.createProject({
44
+ name,
45
+ ...(id !== undefined ? { id } : {}),
46
+ isPrivate,
47
+ });
48
+ operations.setActiveScene({
49
+ id: status.id,
50
+ name: status.name,
51
+ projectId: status.projectId,
52
+ ownerId: status.ownerId,
53
+ thumbnailUrl: status.thumbnailUrl,
54
+ version: status.version,
55
+ });
56
+ const payload = {
57
+ ...projectStatusPayload(status, 'The project is now bound to this MCP session. Open editorUrl now; semantic tools will update the browser-visible draft. Call save_scene with saveMode: "checkpoint" only when you want a meaningful version.'),
58
+ ...currentLevelContext(operations),
59
+ };
60
+ return {
61
+ content: [{ type: 'text', text: JSON.stringify(payload) }],
62
+ structuredContent: payload,
63
+ };
64
+ }
65
+ catch (err) {
66
+ const msg = err instanceof Error ? err.message : String(err);
67
+ throwMcpError(ErrorCode.InvalidRequest, msg);
68
+ }
69
+ });
70
+ }
@@ -0,0 +1,12 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import type { SceneOperations } from '../../operations';
4
+ export declare const deleteSceneInput: {
5
+ id: z.ZodString;
6
+ expectedVersion: z.ZodOptional<z.ZodNumber>;
7
+ };
8
+ export declare const deleteSceneOutput: {
9
+ deleted: z.ZodBoolean;
10
+ };
11
+ export declare function registerDeleteScene(server: McpServer, operations: SceneOperations): void;
12
+ //# sourceMappingURL=delete-scene.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delete-scene.d.ts","sourceRoot":"","sources":["../../../src/tools/scene-lifecycle/delete-scene.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAIvD,eAAO,MAAM,gBAAgB;;;CAG5B,CAAA;AAED,eAAO,MAAM,iBAAiB;;CAE7B,CAAA;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,GAAG,IAAI,CAmCxF"}
@@ -0,0 +1,42 @@
1
+ import { z } from 'zod';
2
+ import { SceneNotFoundError, SceneVersionConflictError } from '../../storage/types';
3
+ import { ErrorCode, throwMcpError } from '../errors';
4
+ export const deleteSceneInput = {
5
+ id: z.string().min(1).max(64),
6
+ expectedVersion: z.number().int().positive().optional(),
7
+ };
8
+ export const deleteSceneOutput = {
9
+ deleted: z.boolean(),
10
+ };
11
+ export function registerDeleteScene(server, operations) {
12
+ server.registerTool('delete_scene', {
13
+ title: 'Delete scene',
14
+ description: 'Delete a scene from the SceneStore by id. Optionally pass `expectedVersion` for optimistic concurrency.',
15
+ inputSchema: deleteSceneInput,
16
+ outputSchema: deleteSceneOutput,
17
+ }, async ({ id, expectedVersion }) => {
18
+ try {
19
+ const deleted = await operations.deleteStoredScene(id, {
20
+ ...(expectedVersion !== undefined ? { expectedVersion } : {}),
21
+ });
22
+ const payload = { deleted };
23
+ return {
24
+ content: [{ type: 'text', text: JSON.stringify(payload) }],
25
+ structuredContent: payload,
26
+ };
27
+ }
28
+ catch (err) {
29
+ if (err instanceof SceneNotFoundError) {
30
+ throwMcpError(ErrorCode.InvalidParams, 'scene_not_found', { id });
31
+ }
32
+ if (err instanceof SceneVersionConflictError) {
33
+ throwMcpError(ErrorCode.InvalidRequest, 'version_conflict', {
34
+ id,
35
+ expectedVersion,
36
+ });
37
+ }
38
+ const msg = err instanceof Error ? err.message : String(err);
39
+ throwMcpError(ErrorCode.InternalError, msg);
40
+ }
41
+ });
42
+ }