@pascal-app/mcp 1.0.3 → 1.0.5

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 (305) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +181 -56
  3. package/dist/bin/pascal-mcp.js +9 -3
  4. package/dist/bridge/agent-session.d.ts +18 -0
  5. package/dist/bridge/agent-session.d.ts.map +1 -0
  6. package/dist/bridge/agent-session.js +15 -0
  7. package/dist/bridge/index.d.ts +1 -1
  8. package/dist/bridge/index.d.ts.map +1 -1
  9. package/dist/bridge/scene-bridge.d.ts +42 -6
  10. package/dist/bridge/scene-bridge.d.ts.map +1 -1
  11. package/dist/bridge/scene-bridge.js +232 -45
  12. package/dist/index.d.ts +3 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +1 -0
  15. package/dist/lib/safe-fetch.d.ts +22 -4
  16. package/dist/lib/safe-fetch.d.ts.map +1 -1
  17. package/dist/lib/safe-fetch.js +233 -134
  18. package/dist/operations/scene-operations.d.ts +41 -3
  19. package/dist/operations/scene-operations.d.ts.map +1 -1
  20. package/dist/operations/scene-operations.js +42 -1
  21. package/dist/prompts/from-brief.d.ts +1 -1
  22. package/dist/prompts/from-brief.d.ts.map +1 -1
  23. package/dist/prompts/from-brief.js +3 -3
  24. package/dist/prompts/index.d.ts +1 -1
  25. package/dist/prompts/index.d.ts.map +1 -1
  26. package/dist/prompts/iterate-on-feedback.d.ts +1 -1
  27. package/dist/prompts/iterate-on-feedback.d.ts.map +1 -1
  28. package/dist/prompts/iterate-on-feedback.js +3 -3
  29. package/dist/prompts/renovation-from-photos.d.ts +1 -1
  30. package/dist/prompts/renovation-from-photos.d.ts.map +1 -1
  31. package/dist/prompts/renovation-from-photos.js +2 -2
  32. package/dist/prompts/scene-guidance.d.ts.map +1 -1
  33. package/dist/prompts/scene-guidance.js +13 -6
  34. package/dist/resources/agent-guide.d.ts +1 -1
  35. package/dist/resources/agent-guide.d.ts.map +1 -1
  36. package/dist/resources/agent-guide.js +62 -33
  37. package/dist/resources/catalog-items.d.ts +5 -6
  38. package/dist/resources/catalog-items.d.ts.map +1 -1
  39. package/dist/resources/catalog-items.js +7 -9
  40. package/dist/resources/constraints.d.ts +1 -1
  41. package/dist/resources/constraints.d.ts.map +1 -1
  42. package/dist/resources/constraints.js +1 -1
  43. package/dist/resources/design-schema.d.ts +9 -0
  44. package/dist/resources/design-schema.d.ts.map +1 -0
  45. package/dist/resources/design-schema.js +21 -0
  46. package/dist/resources/index.d.ts +4 -2
  47. package/dist/resources/index.d.ts.map +1 -1
  48. package/dist/resources/index.js +5 -2
  49. package/dist/resources/scene-current.d.ts +1 -1
  50. package/dist/resources/scene-current.d.ts.map +1 -1
  51. package/dist/resources/scene-summary.d.ts +1 -1
  52. package/dist/resources/scene-summary.d.ts.map +1 -1
  53. package/dist/server.d.ts +25 -1
  54. package/dist/server.d.ts.map +1 -1
  55. package/dist/server.js +71 -25
  56. package/dist/storage/disk-artifact-store.d.ts +9 -0
  57. package/dist/storage/disk-artifact-store.d.ts.map +1 -0
  58. package/dist/storage/disk-artifact-store.js +46 -0
  59. package/dist/storage/index.d.ts +1 -0
  60. package/dist/storage/index.d.ts.map +1 -1
  61. package/dist/storage/index.js +1 -0
  62. package/dist/storage/sqlite-scene-store.d.ts +3 -3
  63. package/dist/storage/sqlite-scene-store.d.ts.map +1 -1
  64. package/dist/storage/sqlite-scene-store.js +4 -3
  65. package/dist/storage/types.d.ts +36 -7
  66. package/dist/storage/types.d.ts.map +1 -1
  67. package/dist/storage/types.js +12 -0
  68. package/dist/templates/empty-studio.d.ts +3 -3
  69. package/dist/templates/empty-studio.d.ts.map +1 -1
  70. package/dist/templates/garden-house.d.ts +3 -3
  71. package/dist/templates/garden-house.d.ts.map +1 -1
  72. package/dist/templates/index.d.ts.map +1 -1
  73. package/dist/templates/two-bedroom.d.ts +3 -3
  74. package/dist/templates/two-bedroom.d.ts.map +1 -1
  75. package/dist/tools/add-column.d.ts +5 -0
  76. package/dist/tools/add-column.d.ts.map +1 -0
  77. package/dist/tools/add-column.js +59 -0
  78. package/dist/tools/add-object.d.ts +76 -0
  79. package/dist/tools/add-object.d.ts.map +1 -0
  80. package/dist/tools/add-object.js +187 -0
  81. package/dist/tools/annotations.d.ts.map +1 -1
  82. package/dist/tools/apply-patch.d.ts +9 -2
  83. package/dist/tools/apply-patch.d.ts.map +1 -1
  84. package/dist/tools/apply-patch.js +36 -5
  85. package/dist/tools/asset-catalog.d.ts +9 -5
  86. package/dist/tools/asset-catalog.d.ts.map +1 -1
  87. package/dist/tools/asset-catalog.js +33 -12
  88. package/dist/tools/author-scene.d.ts +10 -0
  89. package/dist/tools/author-scene.d.ts.map +1 -0
  90. package/dist/tools/author-scene.js +140 -0
  91. package/dist/tools/check-collisions.d.ts +7 -7
  92. package/dist/tools/check-collisions.d.ts.map +1 -1
  93. package/dist/tools/check-collisions.js +7 -8
  94. package/dist/tools/clear-scene.d.ts +9 -0
  95. package/dist/tools/clear-scene.d.ts.map +1 -0
  96. package/dist/tools/clear-scene.js +59 -0
  97. package/dist/tools/collections.d.ts +4 -0
  98. package/dist/tools/collections.d.ts.map +1 -0
  99. package/dist/tools/collections.js +53 -0
  100. package/dist/tools/construction-tools.d.ts +12 -88
  101. package/dist/tools/construction-tools.d.ts.map +1 -1
  102. package/dist/tools/construction-tools.js +122 -381
  103. package/dist/tools/create-unit.d.ts +5 -3
  104. package/dist/tools/create-unit.d.ts.map +1 -1
  105. package/dist/tools/create-unit.js +2 -2
  106. package/dist/tools/errors.d.ts +15 -4
  107. package/dist/tools/errors.d.ts.map +1 -1
  108. package/dist/tools/errors.js +14 -4
  109. package/dist/tools/export-glb.d.ts +1 -1
  110. package/dist/tools/export-glb.d.ts.map +1 -1
  111. package/dist/tools/export-glb.js +2 -2
  112. package/dist/tools/export-json.d.ts +1 -1
  113. package/dist/tools/export-json.d.ts.map +1 -1
  114. package/dist/tools/export-json.js +2 -2
  115. package/dist/tools/geometry-script-worker.d.ts +2 -0
  116. package/dist/tools/geometry-script-worker.d.ts.map +1 -0
  117. package/dist/tools/geometry-script-worker.js +21 -0
  118. package/dist/tools/get-scene.d.ts +2 -1
  119. package/dist/tools/get-scene.d.ts.map +1 -1
  120. package/dist/tools/get-scene.js +5 -3
  121. package/dist/tools/honest-patch-guard.d.ts +2 -0
  122. package/dist/tools/honest-patch-guard.d.ts.map +1 -0
  123. package/dist/tools/honest-patch-guard.js +32 -0
  124. package/dist/tools/hosted-services.d.ts +4 -0
  125. package/dist/tools/hosted-services.d.ts.map +1 -0
  126. package/dist/tools/hosted-services.js +40 -0
  127. package/dist/tools/index.d.ts +12 -2
  128. package/dist/tools/index.d.ts.map +1 -1
  129. package/dist/tools/index.js +18 -32
  130. package/dist/tools/level-height-guard.d.ts +2 -0
  131. package/dist/tools/level-height-guard.d.ts.map +1 -0
  132. package/dist/tools/level-height-guard.js +11 -0
  133. package/dist/tools/list-units.d.ts +2 -2
  134. package/dist/tools/list-units.d.ts.map +1 -1
  135. package/dist/tools/list-units.js +2 -2
  136. package/dist/tools/live-sync.d.ts +25 -6
  137. package/dist/tools/live-sync.d.ts.map +1 -1
  138. package/dist/tools/live-sync.js +48 -27
  139. package/dist/tools/local-geometry-scripts.d.ts +8 -0
  140. package/dist/tools/local-geometry-scripts.d.ts.map +1 -0
  141. package/dist/tools/local-geometry-scripts.js +67 -0
  142. package/dist/tools/measure.d.ts +7 -1
  143. package/dist/tools/measure.d.ts.map +1 -1
  144. package/dist/tools/measure.js +38 -70
  145. package/dist/tools/measurement.d.ts +1 -36
  146. package/dist/tools/measurement.d.ts.map +1 -1
  147. package/dist/tools/measurement.js +2 -94
  148. package/dist/tools/node-world-point.d.ts +37 -0
  149. package/dist/tools/node-world-point.d.ts.map +1 -0
  150. package/dist/tools/node-world-point.js +457 -0
  151. package/dist/tools/normalize-schema-dialect.d.ts +1 -1
  152. package/dist/tools/normalize-schema-dialect.d.ts.map +1 -1
  153. package/dist/tools/normalize-schema-dialect.js +22 -3
  154. package/dist/tools/patch-guards.d.ts +60 -0
  155. package/dist/tools/patch-guards.d.ts.map +1 -0
  156. package/dist/tools/patch-guards.js +256 -0
  157. package/dist/tools/photo-to-scene/index.d.ts +1 -1
  158. package/dist/tools/photo-to-scene/index.d.ts.map +1 -1
  159. package/dist/tools/photo-to-scene/photo-to-scene.d.ts +3 -3
  160. package/dist/tools/photo-to-scene/photo-to-scene.d.ts.map +1 -1
  161. package/dist/tools/photo-to-scene/photo-to-scene.js +10 -9
  162. package/dist/tools/project-files.d.ts +4 -0
  163. package/dist/tools/project-files.d.ts.map +1 -0
  164. package/dist/tools/project-files.js +30 -0
  165. package/dist/tools/redo.d.ts +4 -2
  166. package/dist/tools/redo.d.ts.map +1 -1
  167. package/dist/tools/redo.js +4 -4
  168. package/dist/tools/retired-tools.d.ts +10 -0
  169. package/dist/tools/retired-tools.d.ts.map +1 -0
  170. package/dist/tools/retired-tools.js +22 -0
  171. package/dist/tools/roof-level-guard.d.ts +2 -0
  172. package/dist/tools/roof-level-guard.d.ts.map +1 -0
  173. package/dist/tools/roof-level-guard.js +22 -0
  174. package/dist/tools/room-tools.d.ts +36 -98
  175. package/dist/tools/room-tools.d.ts.map +1 -1
  176. package/dist/tools/room-tools.js +130 -565
  177. package/dist/tools/scene-lifecycle/create-project.d.ts +1 -1
  178. package/dist/tools/scene-lifecycle/create-project.d.ts.map +1 -1
  179. package/dist/tools/scene-lifecycle/create-project.js +31 -6
  180. package/dist/tools/scene-lifecycle/delete-scene.d.ts +3 -1
  181. package/dist/tools/scene-lifecycle/delete-scene.d.ts.map +1 -1
  182. package/dist/tools/scene-lifecycle/delete-scene.js +10 -9
  183. package/dist/tools/scene-lifecycle/get-project-status.d.ts +1 -1
  184. package/dist/tools/scene-lifecycle/get-project-status.d.ts.map +1 -1
  185. package/dist/tools/scene-lifecycle/get-project-status.js +6 -6
  186. package/dist/tools/scene-lifecycle/index.d.ts +1 -1
  187. package/dist/tools/scene-lifecycle/index.d.ts.map +1 -1
  188. package/dist/tools/scene-lifecycle/list-scenes.d.ts +1 -1
  189. package/dist/tools/scene-lifecycle/list-scenes.d.ts.map +1 -1
  190. package/dist/tools/scene-lifecycle/list-scenes.js +4 -4
  191. package/dist/tools/scene-lifecycle/load-scene.d.ts +2 -2
  192. package/dist/tools/scene-lifecycle/load-scene.d.ts.map +1 -1
  193. package/dist/tools/scene-lifecycle/load-scene.js +5 -5
  194. package/dist/tools/scene-lifecycle/metadata.d.ts +1 -1
  195. package/dist/tools/scene-lifecycle/rename-scene.d.ts +1 -1
  196. package/dist/tools/scene-lifecycle/rename-scene.d.ts.map +1 -1
  197. package/dist/tools/scene-lifecycle/rename-scene.js +6 -6
  198. package/dist/tools/scene-lifecycle/save-scene.d.ts +6 -3
  199. package/dist/tools/scene-lifecycle/save-scene.d.ts.map +1 -1
  200. package/dist/tools/scene-lifecycle/save-scene.js +124 -16
  201. package/dist/tools/scene-lifecycle/test-utils.d.ts +3 -3
  202. package/dist/tools/scene-lifecycle/test-utils.d.ts.map +1 -1
  203. package/dist/tools/scene-lifecycle/test-utils.js +2 -1
  204. package/dist/tools/set-unit-members.d.ts +4 -2
  205. package/dist/tools/set-unit-members.d.ts.map +1 -1
  206. package/dist/tools/set-unit-members.js +6 -6
  207. package/dist/tools/set-zone.d.ts +4 -2
  208. package/dist/tools/set-zone.d.ts.map +1 -1
  209. package/dist/tools/set-zone.js +30 -18
  210. package/dist/tools/shared-tools.d.ts +40 -0
  211. package/dist/tools/shared-tools.d.ts.map +1 -0
  212. package/dist/tools/shared-tools.js +574 -0
  213. package/dist/tools/structure-tools.d.ts +57 -0
  214. package/dist/tools/structure-tools.d.ts.map +1 -0
  215. package/dist/tools/structure-tools.js +57 -0
  216. package/dist/tools/templates/create-from-template.d.ts +2 -2
  217. package/dist/tools/templates/create-from-template.d.ts.map +1 -1
  218. package/dist/tools/templates/create-from-template.js +6 -6
  219. package/dist/tools/templates/create-house-from-brief.d.ts +1 -1
  220. package/dist/tools/templates/create-house-from-brief.d.ts.map +1 -1
  221. package/dist/tools/templates/create-house-from-brief.js +6 -6
  222. package/dist/tools/templates/index.d.ts +1 -1
  223. package/dist/tools/templates/index.d.ts.map +1 -1
  224. package/dist/tools/templates/list-templates.d.ts +1 -1
  225. package/dist/tools/templates/list-templates.d.ts.map +1 -1
  226. package/dist/tools/templates/list-templates.js +2 -2
  227. package/dist/tools/undo.d.ts +4 -2
  228. package/dist/tools/undo.d.ts.map +1 -1
  229. package/dist/tools/undo.js +4 -4
  230. package/dist/tools/unit-members.d.ts.map +1 -1
  231. package/dist/tools/unit-members.js +6 -6
  232. package/dist/tools/variants/generate-variants.d.ts +6 -6
  233. package/dist/tools/variants/generate-variants.d.ts.map +1 -1
  234. package/dist/tools/variants/generate-variants.js +6 -6
  235. package/dist/tools/variants/index.d.ts +1 -1
  236. package/dist/tools/variants/index.d.ts.map +1 -1
  237. package/dist/tools/variants/mutations.js +1 -1
  238. package/dist/tools/view-scene.d.ts +44 -0
  239. package/dist/tools/view-scene.d.ts.map +1 -0
  240. package/dist/tools/view-scene.js +75 -0
  241. package/dist/tools/vision/analyze-floorplan-image.d.ts +1 -1
  242. package/dist/tools/vision/analyze-floorplan-image.d.ts.map +1 -1
  243. package/dist/tools/vision/analyze-floorplan-image.js +7 -7
  244. package/dist/tools/vision/analyze-room-photo.d.ts +1 -1
  245. package/dist/tools/vision/analyze-room-photo.d.ts.map +1 -1
  246. package/dist/tools/vision/analyze-room-photo.js +7 -7
  247. package/dist/tools/vision/index.d.ts +1 -1
  248. package/dist/tools/vision/index.d.ts.map +1 -1
  249. package/dist/transports/http.d.ts +8 -2
  250. package/dist/transports/http.d.ts.map +1 -1
  251. package/dist/transports/http.js +9 -3
  252. package/dist/transports/stdio.d.ts +15 -9
  253. package/dist/transports/stdio.d.ts.map +1 -1
  254. package/dist/transports/stdio.js +18 -11
  255. package/dist/version.d.ts +5 -0
  256. package/dist/version.d.ts.map +1 -1
  257. package/dist/version.js +17 -3
  258. package/examples/coordinate-conventions-demo.md +12 -13
  259. package/examples/embed-in-agent.ts +6 -7
  260. package/examples/generate-apartment.md +13 -15
  261. package/examples/photo-to-scene.md +3 -4
  262. package/examples/renovate-from-photos.md +6 -9
  263. package/package.json +19 -9
  264. package/dist/tools/create-level.d.ts +0 -21
  265. package/dist/tools/create-level.d.ts.map +0 -1
  266. package/dist/tools/create-level.js +0 -63
  267. package/dist/tools/create-wall.d.ts +0 -22
  268. package/dist/tools/create-wall.d.ts.map +0 -1
  269. package/dist/tools/create-wall.js +0 -57
  270. package/dist/tools/cut-opening.d.ts +0 -25
  271. package/dist/tools/cut-opening.d.ts.map +0 -1
  272. package/dist/tools/cut-opening.js +0 -61
  273. package/dist/tools/delete-node.d.ts +0 -19
  274. package/dist/tools/delete-node.d.ts.map +0 -1
  275. package/dist/tools/delete-node.js +0 -40
  276. package/dist/tools/describe-node.d.ts +0 -17
  277. package/dist/tools/describe-node.d.ts.map +0 -1
  278. package/dist/tools/describe-node.js +0 -97
  279. package/dist/tools/door-clearance.d.ts +0 -135
  280. package/dist/tools/door-clearance.d.ts.map +0 -1
  281. package/dist/tools/door-clearance.js +0 -334
  282. package/dist/tools/duplicate-level.d.ts +0 -19
  283. package/dist/tools/duplicate-level.d.ts.map +0 -1
  284. package/dist/tools/duplicate-level.js +0 -60
  285. package/dist/tools/find-nodes.d.ts +0 -32
  286. package/dist/tools/find-nodes.d.ts.map +0 -1
  287. package/dist/tools/find-nodes.js +0 -107
  288. package/dist/tools/geometry.d.ts +0 -24
  289. package/dist/tools/geometry.d.ts.map +0 -1
  290. package/dist/tools/geometry.js +0 -94
  291. package/dist/tools/get-node.d.ts +0 -11
  292. package/dist/tools/get-node.d.ts.map +0 -1
  293. package/dist/tools/get-node.js +0 -29
  294. package/dist/tools/layout-clearance.d.ts +0 -105
  295. package/dist/tools/layout-clearance.d.ts.map +0 -1
  296. package/dist/tools/layout-clearance.js +0 -190
  297. package/dist/tools/place-item.d.ts +0 -22
  298. package/dist/tools/place-item.d.ts.map +0 -1
  299. package/dist/tools/place-item.js +0 -89
  300. package/dist/tools/scene-query.d.ts +0 -62
  301. package/dist/tools/scene-query.d.ts.map +0 -1
  302. package/dist/tools/scene-query.js +0 -670
  303. package/dist/tools/validate-scene.d.ts +0 -19
  304. package/dist/tools/validate-scene.d.ts.map +0 -1
  305. package/dist/tools/validate-scene.js +0 -28
@@ -1,8 +1,11 @@
1
+ import { applySceneWrites, seatWrittenRoofs, } from '@pascal-app/core/agent-operations';
2
+ import { isAgentRefusal } from '@pascal-app/core/agent-tools';
3
+ import { migrateLegacyWallAssemblies } from '@pascal-app/core/scene-migrations';
1
4
  import { AnyNode } from '@pascal-app/core/schema';
2
5
  import { z } from 'zod';
3
6
  import { SceneVersionConflictError } from '../../storage/types.js';
4
7
  import { DESTRUCTIVE_TOOL_ANNOTATIONS } from '../annotations.js';
5
- import { ErrorCode, throwMcpError } from '../errors.js';
8
+ import { ProtocolErrorCode, throwMcpError, toolError } from '../errors.js';
6
9
  import { appendLiveSceneEvent } from '../live-sync.js';
7
10
  import { currentLevelContext, sceneMetaPayload } from './metadata.js';
8
11
  export const saveSceneInput = {
@@ -10,14 +13,18 @@ export const saveSceneInput = {
10
13
  name: z.string().min(1).max(200),
11
14
  projectId: z.string().optional(),
12
15
  expectedVersion: z.number().int().positive().optional(),
16
+ replace: z
17
+ .boolean()
18
+ .optional()
19
+ .describe("Write this session's scene over a project or scene it was not loaded from, replacing what that one holds."),
13
20
  saveMode: z
14
21
  .enum(['draft', 'checkpoint'])
15
22
  .default('draft')
16
- .describe('`draft` updates the browser-visible working model without polluting version history. `checkpoint` creates a meaningful saved version.'),
23
+ .describe('`draft` updates the browser-visible working model without polluting version history. `checkpoint` creates a meaningful saved version; it does not publish.'),
17
24
  publish: z
18
25
  .boolean()
19
26
  .optional()
20
- .describe('For checkpoint saves, publish the checkpoint as the browser-visible version.'),
27
+ .describe("Hosted Pascal: also publish this checkpoint, making it the version the project's viewers see. Only when the user asks to publish."),
21
28
  thumbnail: z.string().url().optional(),
22
29
  includeCurrentScene: z
23
30
  .boolean()
@@ -26,7 +33,7 @@ export const saveSceneInput = {
26
33
  graph: z
27
34
  .record(z.string(), z.unknown())
28
35
  .optional()
29
- .describe('Full SceneGraph { nodes, rootNodeIds, collections? } to save instead of the bridge state.'),
36
+ .describe("Full SceneGraph { nodes, rootNodeIds, collections? } to save instead of the bridge state. Judged against the scene it is saved over (the session's scene when the save goes to it, else the stored scene `id` names) as apply_patch is: a roof it puts on the walls of the storey holding it goes to the level above them, said in notes, and storey content on a roof level is refused. A graph with no scene under it is saved as it is."),
30
37
  };
31
38
  export const saveSceneOutput = {
32
39
  id: z.string(),
@@ -47,36 +54,117 @@ export const saveSceneOutput = {
47
54
  graphHash: z.string().optional(),
48
55
  levelIds: z.array(z.string()),
49
56
  defaultLevelId: z.string().nullable(),
57
+ /** What the server changed in a provided graph before saving it, one line each. */
58
+ notes: z.array(z.string()).optional(),
50
59
  };
60
+ const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
61
+ /** A graph as writes over the scene in hand, so a rule on writes judges only what it changes. */
62
+ function writesOver(scene, nodes) {
63
+ const writes = [];
64
+ for (const node of Object.values(nodes)) {
65
+ const prior = scene[node.id];
66
+ if (!prior) {
67
+ writes.push({ op: 'create', node });
68
+ continue;
69
+ }
70
+ const next = node;
71
+ const data = Object.fromEntries([...new Set([...Object.keys(prior), ...Object.keys(next)])]
72
+ .filter((key) => !same(prior[key], next[key]))
73
+ .map((key) => [key, next[key]]));
74
+ if (Object.keys(data).length)
75
+ writes.push({ op: 'update', id: node.id, data });
76
+ }
77
+ for (const id of Object.keys(scene))
78
+ if (!nodes[id])
79
+ writes.push({ op: 'delete', id });
80
+ return writes;
81
+ }
82
+ /**
83
+ * The scene a provided graph is saved over, as it stands: the session's when the save goes to the
84
+ * scene in hand, else the stored one. Null for a new scene: a graph with nothing under it is saved
85
+ * as it is, its roofs never judged against an unrelated scene (a legacy project saved from a fresh
86
+ * session would otherwise read as all new writes, and be migrated or refused).
87
+ */
88
+ async function savedOver(bridge, target) {
89
+ const active = bridge.getActiveScene();
90
+ const inHand = active &&
91
+ (target.id !== undefined
92
+ ? active.id === target.id
93
+ : target.projectId !== undefined && active.projectId === target.projectId);
94
+ if (inHand)
95
+ return bridge.getNodes();
96
+ if (target.id === undefined)
97
+ return null;
98
+ const stored = await bridge.loadStoredScene(target.id).catch(() => null);
99
+ return stored?.graph.nodes ?? null;
100
+ }
101
+ /** A saved scene that is more than the default site, building and level. */
102
+ const HOLDS_CONTENT = 3;
103
+ /**
104
+ * The session's scene goes over a project or scene only when it was loaded from it (or created
105
+ * for it). After a server reload a session starts over on a blank scene, and saving it by id wrote
106
+ * it over the project's draft (2026-10-03: 8 levels and 10 imported plans lost).
107
+ */
108
+ async function requireSceneLoadedFrom(bridge, target) {
109
+ if (target.id === undefined && target.projectId === undefined)
110
+ return;
111
+ const active = bridge.getActiveScene();
112
+ if (target.id !== undefined ? active?.id === target.id : active?.projectId === target.projectId)
113
+ return;
114
+ const nodeCount = target.projectId !== undefined && bridge.canGetProjectStatus
115
+ ? ((await bridge.getProjectStatus(target.projectId))?.nodeCount ?? 0)
116
+ : target.id !== undefined
117
+ ? Object.keys((await bridge.loadStoredScene(target.id))?.graph.nodes ?? {}).length
118
+ : 0;
119
+ if (nodeCount <= HOLDS_CONTENT)
120
+ return;
121
+ throwMcpError(ProtocolErrorCode.InvalidRequest, `scene_not_loaded: this session's scene was not loaded from ${target.projectId ?? target.id}, which holds ${nodeCount} nodes — the server may have reloaded and this session started over. Call load_scene with it first, or pass replace: true to write this scene over it.`, { ...target, nodeCount });
122
+ }
51
123
  export function registerSaveScene(server, bridge) {
52
124
  server.registerTool('save_scene', {
53
125
  title: 'Save scene',
54
- description: 'Save the current scene (or a provided graph) to the SceneStore. Defaults to a browser-visible draft save so agents can iterate without creating many project versions. Use saveMode: "checkpoint" for meaningful version history.',
55
- inputSchema: saveSceneInput,
56
- outputSchema: saveSceneOutput,
126
+ description: 'Save the current scene (or a provided graph) to the SceneStore. Without id or projectId it saves to the project this session is bound to (create_project or load_scene). Defaults to a browser-visible draft save so agents can iterate without creating many project versions. Use saveMode: "checkpoint" for meaningful version history.',
127
+ inputSchema: z.object(saveSceneInput),
128
+ outputSchema: z.object(saveSceneOutput),
57
129
  annotations: DESTRUCTIVE_TOOL_ANNOTATIONS,
58
- }, async ({ id, name, projectId, expectedVersion, saveMode, publish, thumbnail, includeCurrentScene, graph, }) => {
130
+ }, async ({ id, name, projectId, expectedVersion, saveMode, publish, thumbnail, includeCurrentScene, graph, replace, }) => {
131
+ // Without a target, the session's scene goes where it came from (create_project, load_scene,
132
+ // an earlier save). An agent was refused "Call create_project first" on its first save, right
133
+ // after create_project bound the session to its project.
134
+ const bound = includeCurrentScene && id === undefined && projectId === undefined
135
+ ? bridge.getActiveScene()
136
+ : null;
137
+ if (bound) {
138
+ id = bound.id;
139
+ projectId = bound.projectId ?? undefined;
140
+ }
59
141
  let sceneGraph;
142
+ let notes = [];
143
+ if (includeCurrentScene && !replace)
144
+ await requireSceneLoadedFrom(bridge, { id, projectId });
60
145
  if (includeCurrentScene) {
61
146
  const validation = bridge.validateScene();
62
147
  if (!validation.valid) {
63
- throwMcpError(ErrorCode.InvalidRequest, 'scene_invalid', { errors: validation.errors });
148
+ throwMcpError(ProtocolErrorCode.InvalidRequest, 'scene_invalid', {
149
+ errors: validation.errors,
150
+ });
64
151
  }
65
152
  sceneGraph = bridge.exportSceneGraph();
66
153
  }
67
154
  else {
68
155
  if (!graph) {
69
- throwMcpError(ErrorCode.InvalidParams, 'graph_required: pass `graph` when includeCurrentScene is false');
156
+ throwMcpError(ProtocolErrorCode.InvalidParams, 'graph_required: pass `graph` when includeCurrentScene is false');
70
157
  }
71
158
  // Security: revalidate every node with AnyNode schema (including the
72
159
  // AssetUrl allowlist) BEFORE persisting. Without this, the save_scene
73
160
  // graph arg is a bypass for the URL hardening in A7. See P4 report.
74
161
  const rawNodes = graph.nodes;
75
162
  if (!rawNodes || typeof rawNodes !== 'object') {
76
- throwMcpError(ErrorCode.InvalidParams, 'graph.nodes must be an object');
163
+ throwMcpError(ProtocolErrorCode.InvalidParams, 'graph.nodes must be an object');
77
164
  }
165
+ const migration = migrateLegacyWallAssemblies(rawNodes);
78
166
  const errors = [];
79
- for (const [nodeId, node] of Object.entries(rawNodes)) {
167
+ for (const [nodeId, node] of Object.entries(migration.nodes)) {
80
168
  const res = AnyNode.safeParse(node);
81
169
  if (!res.success) {
82
170
  for (const issue of res.error.issues) {
@@ -89,9 +177,28 @@ export function registerSaveScene(server, bridge) {
89
177
  }
90
178
  }
91
179
  if (errors.length > 0) {
92
- throwMcpError(ErrorCode.InvalidParams, 'graph_invalid', { errors });
180
+ throwMcpError(ProtocolErrorCode.InvalidParams, 'graph_invalid', { errors });
181
+ }
182
+ // The roof-level rule, as apply_patch holds it: what the graph changes in the scene it
183
+ // replaces.
184
+ const scene = await savedOver(bridge, { id, projectId });
185
+ let nodes = migration.nodes;
186
+ if (scene) {
187
+ const writes = writesOver(scene, nodes);
188
+ const written = writes.length;
189
+ try {
190
+ notes = seatWrittenRoofs(writes, scene);
191
+ }
192
+ catch (error) {
193
+ if (!isAgentRefusal(error))
194
+ throw error;
195
+ // An index into the graph's writes means nothing to the caller; the ids do.
196
+ const { index: _index, ...details } = error.details;
197
+ return toolError(error.message, { code: error.code, ...details });
198
+ }
199
+ nodes = applySceneWrites(nodes, writes.slice(written));
93
200
  }
94
- sceneGraph = graph;
201
+ sceneGraph = { ...graph, nodes };
95
202
  }
96
203
  try {
97
204
  const meta = await bridge.saveScene({
@@ -112,6 +219,7 @@ export function registerSaveScene(server, bridge) {
112
219
  const payload = {
113
220
  ...sceneMetaPayload(meta, sceneGraph),
114
221
  ...currentLevelContext(bridge),
222
+ ...(notes.length ? { notes } : {}),
115
223
  };
116
224
  return {
117
225
  content: [{ type: 'text', text: JSON.stringify(payload) }],
@@ -120,13 +228,13 @@ export function registerSaveScene(server, bridge) {
120
228
  }
121
229
  catch (err) {
122
230
  if (err instanceof SceneVersionConflictError) {
123
- throwMcpError(ErrorCode.InvalidRequest, 'version_conflict', {
231
+ throwMcpError(ProtocolErrorCode.InvalidRequest, 'version_conflict', {
124
232
  expectedVersion,
125
233
  id,
126
234
  });
127
235
  }
128
236
  const msg = err instanceof Error ? err.message : String(err);
129
- throwMcpError(ErrorCode.InvalidRequest, msg);
237
+ throwMcpError(ProtocolErrorCode.InvalidRequest, msg);
130
238
  }
131
239
  });
132
240
  }
@@ -1,6 +1,6 @@
1
1
  import { SceneBridge } from '../../bridge/scene-bridge.js';
2
2
  import { type SceneOperations } from '../../operations/index.js';
3
- import { type ProjectCreateOptions, type ProjectStatus, type SceneEvent, type SceneEventAppendOptions, type SceneEventListOptions, type SceneListOptions, type SceneMeta, type SceneMutateOptions, type SceneSaveOptions, type SceneStore, type SceneWithGraph } from '../../storage/types.js';
3
+ import { type ProjectCreateOptions, type ProjectStatus, type SceneDeleteResult, type SceneEvent, type SceneEventAppendOptions, type SceneEventListOptions, type SceneListOptions, type SceneMeta, type SceneMutateOptions, type SceneSaveOptions, type SceneStore, type SceneWithGraph } from '../../storage/types.js';
4
4
  export type StoredTextContent = {
5
5
  type: string;
6
6
  text: string;
@@ -20,7 +20,7 @@ export declare function createTestSceneOperations(options?: {
20
20
  * `expectedVersion`.
21
21
  */
22
22
  export declare class InMemorySceneStore implements SceneStore {
23
- readonly backend: "sqlite";
23
+ readonly backend: 'sqlite';
24
24
  private readonly data;
25
25
  private readonly projects;
26
26
  private readonly events;
@@ -32,7 +32,7 @@ export declare class InMemorySceneStore implements SceneStore {
32
32
  save(opts: SceneSaveOptions): Promise<SceneMeta>;
33
33
  load(id: string): Promise<SceneWithGraph | null>;
34
34
  list(opts?: SceneListOptions): Promise<SceneMeta[]>;
35
- delete(id: string, opts?: SceneMutateOptions): Promise<boolean>;
35
+ delete(id: string, opts?: SceneMutateOptions): Promise<SceneDeleteResult>;
36
36
  rename(id: string, newName: string, opts?: SceneMutateOptions): Promise<SceneMeta>;
37
37
  appendSceneEvent(opts: SceneEventAppendOptions): Promise<SceneEvent>;
38
38
  listSceneEvents(sceneId: string, opts?: SceneEventListOptions): Promise<SceneEvent[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../../../src/tools/scene-lifecycle/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AACvD,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAC9E,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EAEf,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAA;AAG5B,MAAM,MAAM,iBAAiB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAE9D,wBAAgB,aAAa,CAAC,OAAO,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEnF;AAED,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE;IAClD,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,KAAK,CAAC,EAAE,kBAAkB,CAAA;CAC3B,GAAG;IACF,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,kBAAkB,CAAA;IACzB,UAAU,EAAE,eAAe,CAAA;CAC5B,CAKA;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,YAAW,UAAU;IACnD,QAAQ,CAAC,OAAO,EAAG,QAAQ,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAWtB;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,YAAY,CAAI;IAElB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC;IAejE,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAK3D,IAAI,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC;IA+DhD,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAShD,IAAI,CAAC,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAanD,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAW/D,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,SAAS,CAAC;IAmBlF,gBAAgB,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC,UAAU,CAAC;IAapE,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAU3F,OAAO,CAAC,YAAY;IAiBpB,OAAO,CAAC,MAAM;IAoBd,OAAO,CAAC,eAAe;CA0BxB"}
1
+ {"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../../../src/tools/scene-lifecycle/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AACvD,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAC9E,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,UAAU,EACf,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EAEf,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAA;AAG5B,MAAM,MAAM,iBAAiB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAE9D,wBAAgB,aAAa,CAAC,OAAO,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEnF;AAED,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE;IAClD,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,KAAK,CAAC,EAAE,kBAAkB,CAAA;CAC3B,GAAG;IACF,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,kBAAkB,CAAA;IACzB,UAAU,EAAE,eAAe,CAAA;CAC5B,CAKA;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,YAAW,UAAU;IACnD,QAAQ,CAAC,OAAO,EAAG,QAAQ,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAWtB;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,YAAY,CAAI;IAElB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC,CAatE;IAEK,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAGhE;IAEK,IAAI,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC,CA6DrD;IAEK,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAOrD;IAEK,IAAI,CAAC,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAWxD;IAEK,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAU9E;IAEK,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,SAAS,CAAC,CAiBvF;IAEK,gBAAgB,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC,UAAU,CAAC,CAWzE;IAEK,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAQ1F;IAED,OAAO,CAAC,YAAY;IAiBpB,OAAO,CAAC,MAAM;IAoBd,OAAO,CAAC,eAAe;CA0BxB"}
@@ -130,7 +130,8 @@ export class InMemorySceneStore {
130
130
  if (opts?.expectedVersion !== undefined && rec.version !== opts.expectedVersion) {
131
131
  throw new SceneVersionConflictError(`Expected version ${opts.expectedVersion}, have ${rec.version}`);
132
132
  }
133
- return this.data.delete(id);
133
+ this.data.delete(id);
134
+ return { deleted: true, hidden: false };
134
135
  }
135
136
  async rename(id, newName, opts) {
136
137
  const rec = this.data.get(id);
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { z } from 'zod';
3
3
  import type { SceneOperations } from '../operations/index.js';
4
4
  export declare const setUnitMembersInput: {
@@ -6,10 +6,12 @@ export declare const setUnitMembersInput: {
6
6
  memberZoneIds: z.ZodArray<z.ZodString>;
7
7
  };
8
8
  export declare const setUnitMembersOutput: {
9
+ project: z.ZodOptional<z.ZodNullable<z.ZodString>>;
10
+ unsaved: z.ZodOptional<z.ZodString>;
9
11
  persistence: z.ZodOptional<z.ZodObject<{
10
12
  status: z.ZodEnum<{
11
- unbound: "unbound";
12
13
  events_unsupported: "events_unsupported";
14
+ unbound: "unbound";
13
15
  }>;
14
16
  warning: z.ZodString;
15
17
  }, z.core.$strip>>;
@@ -1 +1 @@
1
- {"version":3,"file":"set-unit-members.d.ts","sourceRoot":"","sources":["../../src/tools/set-unit-members.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAExE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAOpD,eAAO,MAAM,mBAAmB;;;CAG/B,CAAA;AAED,eAAO,MAAM,oBAAoB;;;;;;;;;;CAIhC,CAAA;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAgCvF"}
1
+ {"version":3,"file":"set-unit-members.d.ts","sourceRoot":"","sources":["../../src/tools/set-unit-members.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAE7D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAOpD,eAAO,MAAM,mBAAmB;IAC9B,MAAM;IACN,aAAa;CACd,CAAA;AAED,eAAO,MAAM,oBAAoB;;;;;;;;;;;;CAIhC,CAAA;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAmCvF"}
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { DESTRUCTIVE_TOOL_ANNOTATIONS } from './annotations.js';
3
- import { ErrorCode, throwMcpError } from './errors.js';
3
+ import { ProtocolErrorCode, throwMcpError } from './errors.js';
4
4
  import { liveSyncOutput, persistencePayload, publishLiveSceneSnapshot } from './live-sync.js';
5
5
  import { NodeIdSchema } from './schemas.js';
6
6
  import { validateUnitMembers } from './unit-members.js';
@@ -17,19 +17,19 @@ export function registerSetUnitMembers(server, bridge) {
17
17
  server.registerTool('set_unit_members', {
18
18
  title: 'Set unit members',
19
19
  description: 'Replace the zones referenced by a unit. Every zone must belong to a level in the same building.',
20
- inputSchema: setUnitMembersInput,
21
- outputSchema: setUnitMembersOutput,
20
+ inputSchema: z.object(setUnitMembersInput),
21
+ outputSchema: z.object(setUnitMembersOutput),
22
22
  annotations: { ...DESTRUCTIVE_TOOL_ANNOTATIONS, idempotentHint: true },
23
23
  }, async ({ unitId, memberZoneIds }) => {
24
24
  const unit = bridge.getNode(unitId);
25
25
  if (!unit) {
26
- throwMcpError(ErrorCode.InvalidParams, `Unit not found: ${unitId}`);
26
+ throwMcpError(ProtocolErrorCode.InvalidParams, `Unit not found: ${unitId}`);
27
27
  }
28
28
  if (unit.type !== 'unit') {
29
- throwMcpError(ErrorCode.InvalidParams, `Node ${unitId} is a ${unit.type}, expected unit`);
29
+ throwMcpError(ProtocolErrorCode.InvalidParams, `Node ${unitId} is a ${unit.type}, expected unit`);
30
30
  }
31
31
  if (!unit.parentId) {
32
- throwMcpError(ErrorCode.InvalidParams, `Unit ${unitId} must belong to a building`);
32
+ throwMcpError(ProtocolErrorCode.InvalidParams, `Unit ${unitId} must belong to a building`);
33
33
  }
34
34
  const members = validateUnitMembers(bridge, unit.parentId, memberZoneIds);
35
35
  bridge.updateNode(unit.id, { members });
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { z } from 'zod';
3
3
  import type { SceneOperations } from '../operations/index.js';
4
4
  export declare const setZoneInput: {
@@ -8,10 +8,12 @@ export declare const setZoneInput: {
8
8
  properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
9
9
  };
10
10
  export declare const setZoneOutput: {
11
+ project: z.ZodOptional<z.ZodNullable<z.ZodString>>;
12
+ unsaved: z.ZodOptional<z.ZodString>;
11
13
  persistence: z.ZodOptional<z.ZodObject<{
12
14
  status: z.ZodEnum<{
13
- unbound: "unbound";
14
15
  events_unsupported: "events_unsupported";
16
+ unbound: "unbound";
15
17
  }>;
16
18
  warning: z.ZodString;
17
19
  }, z.core.$strip>>;
@@ -1 +1 @@
1
- {"version":3,"file":"set-zone.d.ts","sourceRoot":"","sources":["../../src/tools/set-zone.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAMpD,eAAO,MAAM,YAAY;;;;;CAKxB,CAAA;AAED,eAAO,MAAM,aAAa;;;;;;;;;CAGzB,CAAA;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAiDhF"}
1
+ {"version":3,"file":"set-zone.d.ts","sourceRoot":"","sources":["../../src/tools/set-zone.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAI7D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAMpD,eAAO,MAAM,YAAY;IACvB,OAAO;IACP,OAAO;IACP,KAAK;IACL,UAAU;CACX,CAAA;AAED,eAAO,MAAM,aAAa;;;;;;;;;;;CAGzB,CAAA;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAwDhF"}
@@ -1,7 +1,8 @@
1
- import { ZoneNode } from '@pascal-app/core/schema';
1
+ import { isAllocatedRoomName, newZone, walledRoomAt } from '@pascal-app/core';
2
+ import { roofLevelRefusal } from '@pascal-app/core/agent-operations';
2
3
  import { z } from 'zod';
3
4
  import { ADDITIVE_TOOL_ANNOTATIONS } from './annotations.js';
4
- import { ErrorCode, throwMcpError } from './errors.js';
5
+ import { ProtocolErrorCode, refusalResult, throwMcpError } from './errors.js';
5
6
  import { liveSyncOutput, persistencePayload, publishLiveSceneSnapshot } from './live-sync.js';
6
7
  import { NodeIdSchema, Vec2Schema } from './schemas.js';
7
8
  export const setZoneInput = {
@@ -17,30 +18,41 @@ export const setZoneOutput = {
17
18
  export function registerSetZone(server, bridge) {
18
19
  server.registerTool('set_zone', {
19
20
  title: 'Set zone',
20
- description: 'Create a polygonal zone on the given level. label is stored as the zone name and properties are merged into metadata.',
21
- inputSchema: setZoneInput,
22
- outputSchema: setZoneOutput,
21
+ description: 'Create a polygonal zone on the given level. label is stored as the zone name and properties are merged into metadata. A polygon along walls that already enclose a room is that room, never a second zone: a room still named "Room N", or already named label, takes the label and properties; a room a person named otherwise is refused (rename it with update_node only when the person asked).',
22
+ inputSchema: z.object(setZoneInput),
23
+ outputSchema: z.object(setZoneOutput),
23
24
  annotations: ADDITIVE_TOOL_ANNOTATIONS,
24
25
  }, async ({ levelId, polygon, label, properties }) => {
25
26
  const parent = bridge.getNode(levelId);
26
27
  if (!parent) {
27
- throwMcpError(ErrorCode.InvalidParams, `Level not found: ${levelId}`);
28
+ throwMcpError(ProtocolErrorCode.InvalidParams, `Level not found: ${levelId}`);
28
29
  }
29
30
  if (parent.type !== 'level') {
30
- throwMcpError(ErrorCode.InvalidParams, `Node ${levelId} is a ${parent.type}, expected level`);
31
+ throwMcpError(ProtocolErrorCode.InvalidParams, `Node ${levelId} is a ${parent.type}, expected level`);
31
32
  }
32
- if (typeof parent.metadata === 'object' &&
33
- parent.metadata !== null &&
34
- 'role' in parent.metadata &&
35
- parent.metadata.role === 'roof') {
36
- throwMcpError(ErrorCode.InvalidParams, `Roof support level ${levelId} is not an occupied story; create zones on an occupied level instead`);
33
+ const onRoofLevel = roofLevelRefusal(bridge.getNodes(), levelId, 'a zone');
34
+ if (onRoofLevel)
35
+ return refusalResult(onRoofLevel);
36
+ const walled = walledRoomAt(bridge.getNodes(), levelId, polygon);
37
+ if (walled && !isAllocatedRoomName(walled.name) && walled.name !== label) {
38
+ throwMcpError(ProtocolErrorCode.InvalidParams, `The walls here already enclose the room "${walled.name}" (${walled.id}), which a person named; a zone "${label}" would stack a second room on it. Rename it with update_node only if the person asked; otherwise leave it.`);
39
+ }
40
+ let id;
41
+ if (walled) {
42
+ id = walled.id;
43
+ bridge.updateNode(id, {
44
+ name: label,
45
+ metadata: { ...walled.metadata, ...properties },
46
+ });
47
+ }
48
+ else {
49
+ const zone = newZone({
50
+ name: label,
51
+ polygon: polygon,
52
+ metadata: properties ?? {},
53
+ });
54
+ id = bridge.createNode(zone, levelId);
37
55
  }
38
- const zone = ZoneNode.parse({
39
- name: label,
40
- polygon: polygon,
41
- metadata: properties ?? {},
42
- });
43
- const id = bridge.createNode(zone, levelId);
44
56
  const persistence = await publishLiveSceneSnapshot(bridge, 'set_zone');
45
57
  const payload = { zoneId: id, ...persistencePayload(persistence) };
46
58
  return {
@@ -0,0 +1,40 @@
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
+ import { type AddObjectInput, type AgentHostRuntime, type AgentOperation, type AssetViewLoader, type SceneChanges } from '@pascal-app/core/agent-operations';
3
+ import { type AgentToolAnnotations, type AgentToolContract, type DesignBrief } from '@pascal-app/core/agent-tools';
4
+ import { type CompiledGeometryScript } from '@pascal-app/core/schema';
5
+ import { z } from 'zod';
6
+ import type { Patch } from '../bridge/scene-bridge.js';
7
+ import type { SceneOperations } from '../operations/index.js';
8
+ import { type AssetCatalog } from './asset-catalog.js';
9
+ /**
10
+ * A tool the server runs as a core-style operation: parse with the contract, plan with the
11
+ * operation, apply its changes through the bridge in one history step, answer with what the scene
12
+ * then holds. `annotations` are the host's to choose from its own review of the operation; a
13
+ * plugin contract's `annotations` are only its hints.
14
+ */
15
+ export type OperationTool = {
16
+ contract: AgentToolContract;
17
+ operation: AgentOperation;
18
+ annotations: AgentToolAnnotations;
19
+ outputSchema?: Record<string, z.ZodType>;
20
+ envelope?: (bridge: SceneOperations) => Record<string, unknown>;
21
+ /** Reads the host's item library: only these calls wait for it (the hosted one is a query). */
22
+ catalog?: true;
23
+ };
24
+ export type PlanReadingHosts = {
25
+ /** Reads catalog pictures and surfaces before view_assets and paint. */
26
+ assetViews?: AssetViewLoader;
27
+ /** Compiles one add_object module for a batch before the synchronous operations run. */
28
+ buildObject?: (input: Omit<AddObjectInput, 'compiled'>) => Promise<CompiledGeometryScript>;
29
+ };
30
+ export declare function toPatches(changes: SceneChanges): Patch[];
31
+ /** How an outcome reaches this bridge: its patches, its derived construction, its session record. */
32
+ export declare function agentRuntime(bridge: SceneOperations): AgentHostRuntime;
33
+ export declare function briefInForce(bridge: SceneOperations): DesignBrief | null | undefined;
34
+ export declare function registerSharedTools(server: McpServer, bridge: SceneOperations, catalog?: AssetCatalog, hosts?: PlanReadingHosts): void;
35
+ /**
36
+ * Registers one operation tool, as the shared tools are: a host adds a plugin's `'operation'`
37
+ * tools with it, from the plugin's `./agent-tools` contract and `./agent-operations` operation.
38
+ */
39
+ export declare function registerOperationTool(server: McpServer, bridge: SceneOperations, tool: OperationTool, catalog?: AssetCatalog, hosts?: PlanReadingHosts): void;
40
+ //# sourceMappingURL=shared-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-tools.d.ts","sourceRoot":"","sources":["../../src/tools/shared-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EACL,KAAK,cAAc,EAGnB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,eAAe,EAUpB,KAAK,YAAY,EAGlB,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EAStB,KAAK,WAAW,EA+BjB,MAAM,8BAA8B,CAAA;AAErC,OAAO,EAGL,KAAK,sBAAsB,EAG5B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAMpD,OAAO,EAAE,KAAK,YAAY,EAAkB,MAAM,iBAAiB,CAAA;AASnE;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,SAAS,EAAE,cAAc,CAAA;IACzB,WAAW,EAAE,oBAAoB,CAAA;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;IACxC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/D,+FAA+F;IAC/F,OAAO,CAAC,EAAE,IAAI,CAAA;CACf,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,wEAAwE;IACxE,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,wFAAwF;IACxF,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAA;CAC3F,CAAA;AAmaD,wBAAgB,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,KAAK,EAAE,CAmBxD;AAED,qGAAqG;AACrG,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,gBAAgB,CAsBtE;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,WAAW,GAAG,IAAI,GAAG,SAAS,CAIpF;AAUD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,eAAe,EACvB,OAAO,GAAE,YAA6B,EACtC,KAAK,GAAE,gBAAqB,GAC3B,IAAI,CAGN;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,aAAa,EACnB,OAAO,GAAE,YAA6B,EACtC,KAAK,GAAE,gBAAqB,GAC3B,IAAI,CAuHN"}