@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,50 @@
1
+ import { WallNode } from '@pascal-app/core/schema';
2
+ import { z } from 'zod';
3
+ import { ErrorCode, throwMcpError } from './errors';
4
+ import { publishLiveSceneSnapshot } from './live-sync';
5
+ import { NodeIdSchema, Vec2Schema } from './schemas';
6
+ export const createWallInput = {
7
+ levelId: NodeIdSchema,
8
+ start: Vec2Schema,
9
+ end: Vec2Schema,
10
+ thickness: z.number().positive().optional(),
11
+ height: z.number().positive().optional(),
12
+ };
13
+ export const createWallOutput = {
14
+ wallId: z.string(),
15
+ };
16
+ export function registerCreateWall(server, bridge) {
17
+ server.registerTool('create_wall', {
18
+ title: 'Create wall',
19
+ description: 'Create a new wall on the given level between two 2D points. Thickness and height default to the core library defaults when omitted.',
20
+ inputSchema: createWallInput,
21
+ outputSchema: createWallOutput,
22
+ }, async ({ levelId, start, end, thickness, height }) => {
23
+ const parent = bridge.getNode(levelId);
24
+ if (!parent) {
25
+ throwMcpError(ErrorCode.InvalidParams, `Level not found: ${levelId}`);
26
+ }
27
+ if (parent.type !== 'level') {
28
+ throwMcpError(ErrorCode.InvalidParams, `Node ${levelId} is a ${parent.type}, expected level`);
29
+ }
30
+ if (typeof parent.metadata === 'object' &&
31
+ parent.metadata !== null &&
32
+ 'role' in parent.metadata &&
33
+ parent.metadata.role === 'roof') {
34
+ throwMcpError(ErrorCode.InvalidParams, `Roof support level ${levelId} is not an occupied story; create walls on an occupied level instead`);
35
+ }
36
+ const wall = WallNode.parse({
37
+ start: start,
38
+ end: end,
39
+ ...(thickness !== undefined ? { thickness } : {}),
40
+ ...(height !== undefined ? { height } : {}),
41
+ });
42
+ const id = bridge.createNode(wall, levelId);
43
+ await publishLiveSceneSnapshot(bridge, 'create_wall');
44
+ const payload = { wallId: id };
45
+ return {
46
+ content: [{ type: 'text', text: JSON.stringify(payload) }],
47
+ structuredContent: payload,
48
+ };
49
+ });
50
+ }
@@ -0,0 +1,18 @@
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 cutOpeningInput: {
5
+ wallId: z.ZodString;
6
+ type: z.ZodEnum<{
7
+ window: "window";
8
+ door: "door";
9
+ }>;
10
+ position: z.ZodNumber;
11
+ width: z.ZodNumber;
12
+ height: z.ZodNumber;
13
+ };
14
+ export declare const cutOpeningOutput: {
15
+ openingId: z.ZodString;
16
+ };
17
+ export declare function registerCutOpening(server: McpServer, bridge: SceneOperations): void;
18
+ //# sourceMappingURL=cut-opening.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cut-opening.d.ts","sourceRoot":"","sources":["../../src/tools/cut-opening.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,eAAe;;;;;;;;;CAM3B,CAAA;AAED,eAAO,MAAM,gBAAgB;;CAE5B,CAAA;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAyDnF"}
@@ -0,0 +1,57 @@
1
+ import { DoorNode, WindowNode } from '@pascal-app/core/schema';
2
+ import { z } from 'zod';
3
+ import { ErrorCode, throwMcpError } from './errors';
4
+ import { wallLength, wallLocalXFromT } from './geometry';
5
+ import { publishLiveSceneSnapshot } from './live-sync';
6
+ import { NodeIdSchema } from './schemas';
7
+ export const cutOpeningInput = {
8
+ wallId: NodeIdSchema,
9
+ type: z.enum(['door', 'window']),
10
+ position: z.number().min(0).max(1),
11
+ width: z.number().positive(),
12
+ height: z.number().positive(),
13
+ };
14
+ export const cutOpeningOutput = {
15
+ openingId: z.string(),
16
+ };
17
+ export function registerCutOpening(server, bridge) {
18
+ server.registerTool('cut_opening', {
19
+ title: 'Cut opening',
20
+ description: 'Cut a door or window opening into an existing wall. position is a parametric 0..1 offset along the wall centreline.',
21
+ inputSchema: cutOpeningInput,
22
+ outputSchema: cutOpeningOutput,
23
+ }, async ({ wallId, type, position, width, height }) => {
24
+ const wall = bridge.getNode(wallId);
25
+ if (!wall) {
26
+ throwMcpError(ErrorCode.InvalidParams, `Wall not found: ${wallId}`);
27
+ }
28
+ if (wall.type !== 'wall') {
29
+ throwMcpError(ErrorCode.InvalidParams, `Node ${wallId} is a ${wall.type}, expected wall`);
30
+ }
31
+ const length = wallLength(wall);
32
+ if (length < width) {
33
+ throwMcpError(ErrorCode.InvalidParams, `Wall ${wallId} is ${length.toFixed(2)}m long, too short for a ${width.toFixed(2)}m opening`);
34
+ }
35
+ // `position` is public MCP ergonomics: 0..1 along the wall. Door/window
36
+ // nodes store wall-local meters in position[0], so convert before writing.
37
+ const base = {
38
+ wallId,
39
+ width,
40
+ height,
41
+ position: [wallLocalXFromT(wall, position, width), height / 2, 0],
42
+ };
43
+ const opening = type === 'door'
44
+ ? DoorNode.parse(base)
45
+ : WindowNode.parse({
46
+ ...base,
47
+ position: [base.position[0], 0.9 + height / 2, 0],
48
+ });
49
+ const id = bridge.createNode(opening, wallId);
50
+ await publishLiveSceneSnapshot(bridge, 'cut_opening');
51
+ const payload = { openingId: id };
52
+ return {
53
+ content: [{ type: 'text', text: JSON.stringify(payload) }],
54
+ structuredContent: payload,
55
+ };
56
+ });
57
+ }
@@ -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 deleteNodeInput: {
5
+ id: z.ZodString;
6
+ cascade: z.ZodOptional<z.ZodBoolean>;
7
+ };
8
+ export declare const deleteNodeOutput: {
9
+ deletedIds: z.ZodArray<z.ZodString>;
10
+ };
11
+ export declare function registerDeleteNode(server: McpServer, bridge: SceneOperations): void;
12
+ //# sourceMappingURL=delete-node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delete-node.d.ts","sourceRoot":"","sources":["../../src/tools/delete-node.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;AAKpD,eAAO,MAAM,eAAe;;;CAG3B,CAAA;AAED,eAAO,MAAM,gBAAgB;;CAE5B,CAAA;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CA6BnF"}
@@ -0,0 +1,37 @@
1
+ import { z } from 'zod';
2
+ import { ErrorCode, throwMcpError } from './errors';
3
+ import { publishLiveSceneSnapshot } from './live-sync';
4
+ import { NodeIdSchema } from './schemas';
5
+ export const deleteNodeInput = {
6
+ id: NodeIdSchema,
7
+ cascade: z.boolean().optional(),
8
+ };
9
+ export const deleteNodeOutput = {
10
+ deletedIds: z.array(z.string()),
11
+ };
12
+ export function registerDeleteNode(server, bridge) {
13
+ server.registerTool('delete_node', {
14
+ title: 'Delete node',
15
+ description: 'Delete a node. If it has children, pass `cascade: true` to delete descendants recursively.',
16
+ inputSchema: deleteNodeInput,
17
+ outputSchema: deleteNodeOutput,
18
+ }, async ({ id, cascade }) => {
19
+ const node = bridge.getNode(id);
20
+ if (!node) {
21
+ throwMcpError(ErrorCode.InvalidParams, `Node not found: ${id}`);
22
+ }
23
+ try {
24
+ const removed = bridge.deleteNode(id, cascade ?? false);
25
+ await publishLiveSceneSnapshot(bridge, 'delete_node');
26
+ const payload = { deletedIds: removed };
27
+ return {
28
+ content: [{ type: 'text', text: JSON.stringify(payload) }],
29
+ structuredContent: payload,
30
+ };
31
+ }
32
+ catch (err) {
33
+ const msg = err instanceof Error ? err.message : String(err);
34
+ throwMcpError(ErrorCode.InvalidRequest, msg);
35
+ }
36
+ });
37
+ }
@@ -0,0 +1,17 @@
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 describeNodeInput: {
5
+ id: z.ZodString;
6
+ };
7
+ export declare const describeNodeOutput: {
8
+ id: z.ZodString;
9
+ type: z.ZodString;
10
+ parentId: z.ZodNullable<z.ZodString>;
11
+ ancestryIds: z.ZodArray<z.ZodString>;
12
+ childrenIds: z.ZodArray<z.ZodString>;
13
+ properties: z.ZodRecord<z.ZodString, z.ZodUnknown>;
14
+ description: z.ZodString;
15
+ };
16
+ export declare function registerDescribeNode(server: McpServer, bridge: SceneOperations): void;
17
+ //# sourceMappingURL=describe-node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"describe-node.d.ts","sourceRoot":"","sources":["../../src/tools/describe-node.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;AAIpD,eAAO,MAAM,iBAAiB;;CAE7B,CAAA;AAED,eAAO,MAAM,kBAAkB;;;;;;;;CAQ9B,CAAA;AA0CD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAwCrF"}
@@ -0,0 +1,86 @@
1
+ import { z } from 'zod';
2
+ import { ErrorCode, throwMcpError } from './errors';
3
+ import { NodeIdSchema } from './schemas';
4
+ export const describeNodeInput = {
5
+ id: NodeIdSchema,
6
+ };
7
+ export const describeNodeOutput = {
8
+ id: z.string(),
9
+ type: z.string(),
10
+ parentId: z.string().nullable(),
11
+ ancestryIds: z.array(z.string()),
12
+ childrenIds: z.array(z.string()),
13
+ properties: z.record(z.string(), z.unknown()),
14
+ description: z.string(),
15
+ };
16
+ /**
17
+ * Build a short, human-readable one-liner describing the node.
18
+ * Covers the common shapes; falls back to a generic sentence otherwise.
19
+ */
20
+ function describe(node) {
21
+ switch (node.type) {
22
+ case 'wall': {
23
+ const [x1, z1] = node.start;
24
+ const [x2, z2] = node.end;
25
+ const t = node.thickness ?? 0.1;
26
+ const h = node.height ?? 2.5;
27
+ return `Wall from (${x1},${z1}) to (${x2},${z2}), thickness ${t.toFixed(2)}m, height ${h.toFixed(2)}m`;
28
+ }
29
+ case 'level':
30
+ return `Level ${node.level}`;
31
+ case 'building': {
32
+ const [x, y, z] = node.position;
33
+ return `Building at (${x},${y},${z})`;
34
+ }
35
+ case 'site':
36
+ return `Site with ${node.polygon?.points.length ?? 0}-sided property line`;
37
+ case 'zone':
38
+ return `Zone "${node.name}" with ${node.polygon.length} vertices`;
39
+ case 'slab':
40
+ return `Slab with ${node.polygon.length} vertices`;
41
+ case 'ceiling':
42
+ return `Ceiling with ${node.polygon.length} vertices, height ${node.height.toFixed(2)}m`;
43
+ case 'door':
44
+ return `Door (${node.width.toFixed(2)}m x ${node.height.toFixed(2)}m)`;
45
+ case 'window':
46
+ return `Window (${node.width.toFixed(2)}m x ${node.height.toFixed(2)}m)`;
47
+ case 'item': {
48
+ const [x, y, z] = node.position;
49
+ return `Item "${node.asset.name}" at (${x},${y},${z})`;
50
+ }
51
+ default:
52
+ return `${node.type} node`;
53
+ }
54
+ }
55
+ export function registerDescribeNode(server, bridge) {
56
+ server.registerTool('describe_node', {
57
+ title: 'Describe node',
58
+ description: 'Return a structured summary of a node including its ancestry, children IDs, key properties, and a short human description.',
59
+ inputSchema: describeNodeInput,
60
+ outputSchema: describeNodeOutput,
61
+ }, async ({ id }) => {
62
+ const node = bridge.getNode(id);
63
+ if (!node) {
64
+ throwMcpError(ErrorCode.InvalidParams, `Node not found: ${id}`);
65
+ }
66
+ // Ancestry minus self.
67
+ const ancestry = bridge.getAncestry(id);
68
+ const ancestryIds = ancestry.slice(1).map((n) => n.id);
69
+ const children = bridge.getChildren(id);
70
+ const childrenIds = children.map((n) => n.id);
71
+ const n = node;
72
+ const payload = {
73
+ id: n.id,
74
+ type: n.type,
75
+ parentId: (n.parentId ?? null),
76
+ ancestryIds,
77
+ childrenIds,
78
+ properties: n,
79
+ description: describe(n),
80
+ };
81
+ return {
82
+ content: [{ type: 'text', text: JSON.stringify(payload) }],
83
+ structuredContent: payload,
84
+ };
85
+ });
86
+ }
@@ -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 duplicateLevelInput: {
5
+ levelId: z.ZodString;
6
+ };
7
+ export declare const duplicateLevelOutput: {
8
+ newLevelId: z.ZodString;
9
+ newNodeIds: z.ZodArray<z.ZodString>;
10
+ };
11
+ export declare function registerDuplicateLevel(server: McpServer, bridge: SceneOperations): void;
12
+ //# sourceMappingURL=duplicate-level.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duplicate-level.d.ts","sourceRoot":"","sources":["../../src/tools/duplicate-level.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAKpD,eAAO,MAAM,mBAAmB;;CAE/B,CAAA;AAED,eAAO,MAAM,oBAAoB;;;CAGhC,CAAA;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAsDvF"}
@@ -0,0 +1,56 @@
1
+ import { cloneLevelSubtree } from '@pascal-app/core/clone-scene-graph';
2
+ import { z } from 'zod';
3
+ import { ErrorCode, throwMcpError } from './errors';
4
+ import { publishLiveSceneSnapshot } from './live-sync';
5
+ import { NodeIdSchema } from './schemas';
6
+ export const duplicateLevelInput = {
7
+ levelId: NodeIdSchema,
8
+ };
9
+ export const duplicateLevelOutput = {
10
+ newLevelId: z.string(),
11
+ newNodeIds: z.array(z.string()),
12
+ };
13
+ export function registerDuplicateLevel(server, bridge) {
14
+ server.registerTool('duplicate_level', {
15
+ title: 'Duplicate level',
16
+ description: 'Clone a level and all its descendants into a new subtree attached to the same building.',
17
+ inputSchema: duplicateLevelInput,
18
+ outputSchema: duplicateLevelOutput,
19
+ }, async ({ levelId }) => {
20
+ const node = bridge.getNode(levelId);
21
+ if (!node) {
22
+ throwMcpError(ErrorCode.InvalidParams, `Level not found: ${levelId}`);
23
+ }
24
+ if (node.type !== 'level') {
25
+ throwMcpError(ErrorCode.InvalidParams, `Node ${levelId} is a ${node.type}, expected level`);
26
+ }
27
+ // cloneLevelSubtree(nodes, levelId) — returns { clonedNodes, newLevelId, idMap }.
28
+ const { clonedNodes, newLevelId } = cloneLevelSubtree(bridge.getNodes(), levelId);
29
+ const buildingId = node.parentId ?? undefined;
30
+ // Flatten cloned subtree into create patches. The level node itself
31
+ // attaches to the original building; descendants attach to their
32
+ // already-remapped parent (encoded in `parentId`).
33
+ const patches = clonedNodes.map((n) => {
34
+ const isRoot = n.id === newLevelId;
35
+ const parentIdForBridge = isRoot
36
+ ? buildingId
37
+ : (n.parentId ?? undefined);
38
+ const createOp = {
39
+ op: 'create',
40
+ node: n,
41
+ ...(parentIdForBridge !== undefined ? { parentId: parentIdForBridge } : {}),
42
+ };
43
+ return createOp;
44
+ });
45
+ const result = bridge.applyPatch(patches);
46
+ await publishLiveSceneSnapshot(bridge, 'duplicate_level');
47
+ const payload = {
48
+ newLevelId: newLevelId,
49
+ newNodeIds: result.createdIds,
50
+ };
51
+ return {
52
+ content: [{ type: 'text', text: JSON.stringify(payload) }],
53
+ structuredContent: payload,
54
+ };
55
+ });
56
+ }
@@ -0,0 +1,20 @@
1
+ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
+ /**
3
+ * Throw a structured MCP error. The SDK translates `McpError` into a
4
+ * JSON-RPC error response automatically.
5
+ */
6
+ export declare function throwMcpError(code: ErrorCode, message: string, data?: unknown): never;
7
+ /**
8
+ * Return a non-throwing tool error payload — used for structured failures that
9
+ * we want the client to see inline in `content` rather than as a protocol
10
+ * error. Sets `isError: true` so SDK clients treat it as a failure.
11
+ */
12
+ export declare function toolError(message: string, data?: Record<string, unknown>): {
13
+ content: {
14
+ type: 'text';
15
+ text: string;
16
+ }[];
17
+ isError: true;
18
+ };
19
+ export { ErrorCode, McpError };
20
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/tools/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAA;AAExE;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,KAAK,CAErF;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B;IACD,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACzC,OAAO,EAAE,IAAI,CAAA;CACd,CAUA;AAED,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAA"}
@@ -0,0 +1,25 @@
1
+ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
+ /**
3
+ * Throw a structured MCP error. The SDK translates `McpError` into a
4
+ * JSON-RPC error response automatically.
5
+ */
6
+ export function throwMcpError(code, message, data) {
7
+ throw new McpError(code, message, data);
8
+ }
9
+ /**
10
+ * Return a non-throwing tool error payload — used for structured failures that
11
+ * we want the client to see inline in `content` rather than as a protocol
12
+ * error. Sets `isError: true` so SDK clients treat it as a failure.
13
+ */
14
+ export function toolError(message, data) {
15
+ return {
16
+ content: [
17
+ {
18
+ type: 'text',
19
+ text: JSON.stringify({ error: message, ...(data ?? {}) }),
20
+ },
21
+ ],
22
+ isError: true,
23
+ };
24
+ }
25
+ export { ErrorCode, McpError };
@@ -0,0 +1,10 @@
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 exportGlbInput: {};
5
+ export declare const exportGlbOutput: {
6
+ status: z.ZodLiteral<"not_implemented">;
7
+ reason: z.ZodString;
8
+ };
9
+ export declare function registerExportGlb(server: McpServer, _bridge: SceneOperations): void;
10
+ //# sourceMappingURL=export-glb.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-glb.d.ts","sourceRoot":"","sources":["../../src/tools/export-glb.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,eAAe,CAAA;AAEpD,eAAO,MAAM,cAAc,IAAK,CAAA;AAEhC,eAAO,MAAM,eAAe;;;CAG3B,CAAA;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI,CAsBnF"}
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod';
2
+ export const exportGlbInput = {};
3
+ export const exportGlbOutput = {
4
+ status: z.literal('not_implemented'),
5
+ reason: z.string(),
6
+ };
7
+ export function registerExportGlb(server, _bridge) {
8
+ server.registerTool('export_glb', {
9
+ title: 'Export GLB',
10
+ description: 'GLB export is not available in headless mode — it requires the Three.js renderer, which is browser-only. Returns a structured `not_implemented` response.',
11
+ inputSchema: exportGlbInput,
12
+ outputSchema: exportGlbOutput,
13
+ }, async () => {
14
+ const payload = {
15
+ status: 'not_implemented',
16
+ reason: 'GLB export requires the Three.js renderer, which is browser-only',
17
+ };
18
+ return {
19
+ content: [{ type: 'text', text: JSON.stringify(payload) }],
20
+ structuredContent: payload,
21
+ isError: false,
22
+ };
23
+ });
24
+ }
@@ -0,0 +1,11 @@
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 exportJsonInput: {
5
+ pretty: z.ZodOptional<z.ZodBoolean>;
6
+ };
7
+ export declare const exportJsonOutput: {
8
+ json: z.ZodString;
9
+ };
10
+ export declare function registerExportJson(server: McpServer, bridge: SceneOperations): void;
11
+ //# sourceMappingURL=export-json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-json.d.ts","sourceRoot":"","sources":["../../src/tools/export-json.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,eAAe,CAAA;AAEpD,eAAO,MAAM,eAAe;;CAE3B,CAAA;AAED,eAAO,MAAM,gBAAgB;;CAE5B,CAAA;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAoBnF"}
@@ -0,0 +1,23 @@
1
+ import { z } from 'zod';
2
+ export const exportJsonInput = {
3
+ pretty: z.boolean().optional(),
4
+ };
5
+ export const exportJsonOutput = {
6
+ json: z.string(),
7
+ };
8
+ export function registerExportJson(server, bridge) {
9
+ server.registerTool('export_json', {
10
+ title: 'Export JSON',
11
+ description: 'Return the scene as a serialized JSON string. Pass `pretty: true` to indent with 2 spaces.',
12
+ inputSchema: exportJsonInput,
13
+ outputSchema: exportJsonOutput,
14
+ }, async ({ pretty }) => {
15
+ const scene = bridge.exportJSON();
16
+ const json = JSON.stringify(scene, null, pretty ? 2 : 0);
17
+ const payload = { json };
18
+ return {
19
+ content: [{ type: 'text', text: JSON.stringify(payload) }],
20
+ structuredContent: payload,
21
+ };
22
+ });
23
+ }
@@ -0,0 +1,32 @@
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 findNodesInput: {
5
+ type: z.ZodOptional<z.ZodEnum<{
6
+ site: "site";
7
+ building: "building";
8
+ item: "item";
9
+ level: "level";
10
+ wall: "wall";
11
+ fence: "fence";
12
+ zone: "zone";
13
+ slab: "slab";
14
+ stair: "stair";
15
+ ceiling: "ceiling";
16
+ roof: "roof";
17
+ "roof-segment": "roof-segment";
18
+ "stair-segment": "stair-segment";
19
+ scan: "scan";
20
+ guide: "guide";
21
+ window: "window";
22
+ door: "door";
23
+ }>>;
24
+ parentId: z.ZodOptional<z.ZodString>;
25
+ levelId: z.ZodOptional<z.ZodString>;
26
+ zoneId: z.ZodOptional<z.ZodString>;
27
+ };
28
+ export declare const findNodesOutput: {
29
+ nodes: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
30
+ };
31
+ export declare function registerFindNodes(server: McpServer, bridge: SceneOperations): void;
32
+ //# sourceMappingURL=find-nodes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-nodes.d.ts","sourceRoot":"","sources":["../../src/tools/find-nodes.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;AAuBpD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;CAK1B,CAAA;AAED,eAAO,MAAM,eAAe;;CAE3B,CAAA;AAkCD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAuDlF"}
@@ -0,0 +1,105 @@
1
+ import { pointInPolygon } from '@pascal-app/core/spatial-grid';
2
+ import { z } from 'zod';
3
+ import { NodeIdSchema } from './schemas';
4
+ const ALL_NODE_TYPES = [
5
+ 'site',
6
+ 'building',
7
+ 'level',
8
+ 'wall',
9
+ 'fence',
10
+ 'zone',
11
+ 'slab',
12
+ 'ceiling',
13
+ 'roof',
14
+ 'roof-segment',
15
+ 'stair',
16
+ 'stair-segment',
17
+ 'item',
18
+ 'door',
19
+ 'window',
20
+ 'scan',
21
+ 'guide',
22
+ ];
23
+ export const findNodesInput = {
24
+ type: z.enum(ALL_NODE_TYPES).optional(),
25
+ parentId: NodeIdSchema.optional(),
26
+ levelId: NodeIdSchema.optional(),
27
+ zoneId: NodeIdSchema.optional(),
28
+ };
29
+ export const findNodesOutput = {
30
+ nodes: z.array(z.record(z.string(), z.unknown())),
31
+ };
32
+ /** Compute a representative 2D point (x, z) for zone-filtering. */
33
+ function getPointForZoneFilter(node) {
34
+ if (node.type === 'wall' || node.type === 'fence') {
35
+ const [x1, z1] = node.start;
36
+ const [x2, z2] = node.end;
37
+ return [(x1 + x2) / 2, (z1 + z2) / 2];
38
+ }
39
+ if (node.type === 'item' ||
40
+ node.type === 'door' ||
41
+ node.type === 'window' ||
42
+ node.type === 'building' ||
43
+ node.type === 'stair' ||
44
+ node.type === 'roof') {
45
+ const [x, , z] = node.position;
46
+ return [x, z];
47
+ }
48
+ if (node.type === 'slab' || node.type === 'ceiling' || node.type === 'zone') {
49
+ const poly = node.polygon;
50
+ if (!poly || poly.length === 0)
51
+ return null;
52
+ let cx = 0;
53
+ let cz = 0;
54
+ for (const [x, z] of poly) {
55
+ cx += x;
56
+ cz += z;
57
+ }
58
+ return [cx / poly.length, cz / poly.length];
59
+ }
60
+ return null;
61
+ }
62
+ export function registerFindNodes(server, bridge) {
63
+ server.registerTool('find_nodes', {
64
+ title: 'Find nodes',
65
+ description: 'Find nodes matching any combination of type, parentId, levelId, or zoneId filters.',
66
+ inputSchema: findNodesInput,
67
+ outputSchema: findNodesOutput,
68
+ }, async (args) => {
69
+ const { type, parentId, levelId, zoneId } = args;
70
+ // Delegate type/parent/level filtering to the bridge.
71
+ const baseFilter = {};
72
+ if (type !== undefined)
73
+ baseFilter.type = type;
74
+ if (parentId !== undefined)
75
+ baseFilter.parentId = parentId;
76
+ if (levelId !== undefined)
77
+ baseFilter.levelId = levelId;
78
+ let results = bridge.findNodes(baseFilter);
79
+ // Zone-polygon filter: point-in-polygon on a representative 2D point.
80
+ if (zoneId) {
81
+ const zone = bridge.getNode(zoneId);
82
+ if (!zone || zone.type !== 'zone') {
83
+ // Unknown zoneId → return empty list rather than throw; matches
84
+ // typical "filter" semantics.
85
+ results = [];
86
+ }
87
+ else {
88
+ const poly = zone.polygon;
89
+ results = results.filter((n) => {
90
+ const pt = getPointForZoneFilter(n);
91
+ if (!pt)
92
+ return false;
93
+ return pointInPolygon(pt[0], pt[1], poly);
94
+ });
95
+ }
96
+ }
97
+ const payload = {
98
+ nodes: results,
99
+ };
100
+ return {
101
+ content: [{ type: 'text', text: JSON.stringify(payload) }],
102
+ structuredContent: payload,
103
+ };
104
+ });
105
+ }