@pascal-app/mcp 1.0.7 → 1.1.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.
- package/README.md +1 -1
- package/dist/bridge/scene-bridge.d.ts +1 -4
- package/dist/bridge/scene-bridge.d.ts.map +1 -1
- package/dist/bridge/scene-bridge.js +3 -5
- package/dist/tools/apply-patch.js +1 -1
- package/dist/tools/patch-guards.d.ts.map +1 -1
- package/dist/tools/patch-guards.js +8 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -431,7 +431,7 @@ is a "pitch" fraction like `"1/4 pitch"`, which is rise over span.
|
|
|
431
431
|
| `furnish_room` | Furnish a room for its type from the host's item catalog: the main piece against the wall facing the door, the rest along a side wall. Nothing lands in a door's clear zone or on another item: a piece that does not fit is nudged, else skipped, and `skipped` says why. | `{ zoneId?, polygon?, levelId?, roomType, doorWallIndex? }` | `{ placed, itemIds, skipped, doorWallIndex, doorsDetected }`; refusals `room_required`, `roof_level`, `level_not_found`, `no_catalog` |
|
|
432
432
|
| `add_corner_window` | A window wrapping the corner where two walls meet: one window per wall, both running to the corner and joined, the glass fused (a fixed window) or at a thin post (the other window types, whose frames stay), at any angle from 10° to 170° (a square corner, a bay's 135°, an acute one). `corner` is the point the walls of `levelId` end at (default the floor in view: stacked storeys share their corners' points), `[x, z]` in the level's plan as `get_walls` gives their ends; `width` is how far the glass runs along each wall from the corner (default 1.2 m) and `widths` overrides it per wall id; `post` is `'none'` (the glass fused, a fixed window's default) or `'post'` (the other types' default and only choice); `height` defaults to 1.5 m and the window is centred on the wall unless `sillHeight` (its bottom edge above the floor) is given; `wallIds` (two) is needed only when more than two walls end at the corner; `windowType` and `style` are `add_window`'s (a style needs a Fixed window). By hand: select a window that ends at a corner and use the window panel's Corner section (Wrap the corner, This side and Other side, Fused glass or Corner post for a fixed window, Unwrap). | `{ corner: [x, z], levelId? (or level), wallIds?, width?, widths?, height?, sillHeight?, post?, windowType?, style? }` | `{ windowIds, wallIds, angle, post, achieved }`; refusals `no_corner` (no two walls end there), `corner_ambiguous` (more than two do: pass `wallIds`), `corner_angle` (under 10° or over 170°: use `add_window`), `corner_not_fused` (`post: 'none'` on a type other than fixed), `width_exceeds_wall`, `style_needs_fixed_window` |
|
|
433
433
|
| `merge_windows` | Make two windows that exist one window, as the editor's Merge windows button does; nothing is recreated. On two walls that meet at a corner, each window within 0.30 m of it, they become one corner window (the `add_corner_window` result): each is slid to the corner keeping its width, the glass fused (a fixed window's; other types meet at a post), and both take the first window's height, sill and type, so name first the window whose look you want; the corner is refused over 170° or under 10°. On one wall, two windows with at most 0.30 m between them and nothing between become one window spanning both (overlapping or one inside the other, their union), from the lower sill to the higher head, with the first window's type and style. Use it for two windows already built that open a corner or one wide opening (`verify_scene`'s `corner_unjoined` names a pair for the first); `add_corner_window` builds a new pair. By hand: select two windows and use Merge windows in the multi-selection panel, or place or drag a window near a corner where one waits and click or release (Alt places it plain). | `{ windowIds: [a, b] }` | `{ kind: 'corner' \| 'same_wall', windowIds, removedIds, achieved }`; refusals `window_not_found`, `not_a_window`, `same_window`, `window_not_on_wall`, `window_already_joined` (unwrap it first), `windows_already_joined`, `windows_not_adjacent`, `opening_between`, `walls_not_meeting`, `corner_angle`, `window_not_at_corner`, `height_exceeds_wall`, `opening_overlap` |
|
|
434
|
-
| `apply_patch` | Batched create/update/delete/move, validated and dry-run before commit. Batch-first is the default: send all create/update/delete ops for a build step in one atomic call (stable order, later ops may reference earlier created ids); do not loop one-op calls. A create with an id already in the scene is refused (`node_exists`); delete it earlier in the same patch to replace it. Updates cannot change `id` or `type` (`identity_change`), `object` or `children` (`immutable_field`), move a node under a missing or childless parent (`invalid_parent`), or add schema issues (`invalid_update`); default gutters and downspouts a delete regenerates are addressable only in a later call (`regenerated_default`). Refusals are tool errors with JSON text `{ code, patchIndex, id, message }`. A level created without a `height`, or an update that clears it, gets the height the editor's load derives from what the level holds (an empty level: 2.5 m), one line per level in `notes`. A roof the patch makes or moves onto the storey whose walls carry it is seated on the level above those walls, one line per roof in `notes`; a new storey at or above the roof level moves it up. Refused: storey content on a roof level (`roof_level`), declaring a level that holds storey content the roof level (`roof_level_holds_storey`), floor indexes that put the roof level under a storey (`roof_level_on_top`).
|
|
434
|
+
| `apply_patch` | Batched create/update/delete/move, validated and dry-run before commit. Batch-first is the default: send all create/update/delete ops for a build step in one atomic call (stable order, later ops may reference earlier created ids); do not loop one-op calls. A create with an id already in the scene is refused (`node_exists`); delete it earlier in the same patch to replace it. Updates cannot change `id` or `type` (`identity_change`), `object` or `children` (`immutable_field`), move a node under a missing or childless parent, itself or one of its descendants (`invalid_parent`), or add schema issues (`invalid_update`); default gutters and downspouts a delete regenerates are addressable only in a later call (`regenerated_default`). Refusals are tool errors with JSON text `{ code, patchIndex, id, message }`. A level created without a `height`, or an update that clears it, gets the height the editor's load derives from what the level holds (an empty level: 2.5 m), one line per level in `notes`. A roof the patch makes or moves onto the storey whose walls carry it is seated on the level above those walls, one line per roof in `notes`; a new storey at or above the roof level moves it up. Refused: storey content on a roof level (`roof_level`), declaring a level that holds storey content the roof level (`roof_level_holds_storey`), floor indexes that put the roof level under a storey (`roof_level_on_top`). | `{ patches: Patch[] }` | `{ applied: number, notes?: string[] }` |
|
|
435
435
|
| `add_level` | Add an empty level to a building, as the editor does: on top of its storeys, under its roof level (which moves up one), or below the lowest for a basement (every existing floor, the ground floor included, keeps its place). Omit `buildingId` for the scene's only building. | `{ buildingId?, position?, name?, height? }` | `{ levelId, buildingId, floorIndex, height, shiftedLevelIds? }`; refusals `building_required`, `building_not_found`, `no_building` |
|
|
436
436
|
| `set_ground_level` | Make a level the building's ground floor, as the level panel's "Set as ground floor": the building names it, is seated so that floor stands on grade (height only, the plan stays), and the floor indexes are renumbered so it is 0 and the levels under it are the basements. | `{ levelId }` | `{ levelId, buildingId, floorElevation, gradeElevation, movedBuildingBy, renumbered, unchanged? }`; refusals `level_not_found`, `not_a_level`, `no_building`, `not_a_storey` |
|
|
437
437
|
| `add_wall` | Add a wall to a level, straight or, with `curveOffset`, an arc. Omit `levelId` for the lowest storey. wallType railing builds a guard (default 0.9144 m high); railing sets style, posts, infill and cap; postSize sets thickness. | `{ start, end, levelId?, thickness?, height?, curveOffset?, type?, wallType?, railing? }` | `{ wallId, levelId, length }`; refusals `roof_level`, `wall_too_short`, `level_not_found`, `unknown_wall_type` |
|
|
@@ -174,10 +174,7 @@ export declare class SceneBridge {
|
|
|
174
174
|
undo(steps?: number): number;
|
|
175
175
|
/** Redo. Returns the number of steps actually redone. */
|
|
176
176
|
redo(steps?: number): number;
|
|
177
|
-
/**
|
|
178
|
-
* Zod-validate every node in the scene. Reports one error per failed node,
|
|
179
|
-
* concatenating Zod issue paths.
|
|
180
|
-
*/
|
|
177
|
+
/** Validate node schemas and global parent-chain acyclicity. */
|
|
181
178
|
validateScene(): ValidationResult;
|
|
182
179
|
/**
|
|
183
180
|
* Drain the dirtyNodes set. Returns the ids that were present. No-op for
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene-bridge.d.ts","sourceRoot":"","sources":["../../src/bridge/scene-bridge.ts"],"names":[],"mappings":"AACA,OAAO,cAAc,CAAA;AACrB,OAAO,
|
|
1
|
+
{"version":3,"file":"scene-bridge.d.ts","sourceRoot":"","sources":["../../src/bridge/scene-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Side-effect import MUST come first: installs RAF polyfill before core loads.
|
|
2
2
|
import './node-shims.js';
|
|
3
|
-
import { applyStructureReconciliation, assertDerivedNodeWrites, HIDDEN_SITE_NOTE, pauseSceneHistory, pauseSpaceDetection, planNodeDeletion, planWallDeletion, resumeSceneHistory, resumeSpaceDetection, structureChangeBatch, } from '@pascal-app/core';
|
|
3
|
+
import { applyStructureReconciliation, assertDerivedNodeWrites, HIDDEN_SITE_NOTE, hierarchyCycleErrors, pauseSceneHistory, pauseSpaceDetection, planNodeDeletion, planWallDeletion, resumeSceneHistory, resumeSpaceDetection, structureChangeBatch, } from '@pascal-app/core';
|
|
4
4
|
import { declareActionDependencies, liveSceneRuntime, materialize, runAsSettledSceneStep, } from '@pascal-app/core/runtime';
|
|
5
5
|
import { AnyNode as AnyNodeSchema, generateId, parseNode, SceneMaterial, } from '@pascal-app/core/schema';
|
|
6
6
|
import { InMemoryAgentSession } from './agent-session.js';
|
|
@@ -555,10 +555,7 @@ export class SceneBridge {
|
|
|
555
555
|
return Math.max(0, before - this.store.temporal.getState().futureStates.length);
|
|
556
556
|
});
|
|
557
557
|
}
|
|
558
|
-
/**
|
|
559
|
-
* Zod-validate every node in the scene. Reports one error per failed node,
|
|
560
|
-
* concatenating Zod issue paths.
|
|
561
|
-
*/
|
|
558
|
+
/** Validate node schemas and global parent-chain acyclicity. */
|
|
562
559
|
validateScene() {
|
|
563
560
|
const errors = [];
|
|
564
561
|
const warnings = [];
|
|
@@ -578,6 +575,7 @@ export class SceneBridge {
|
|
|
578
575
|
});
|
|
579
576
|
}
|
|
580
577
|
}
|
|
578
|
+
errors.push(...hierarchyCycleErrors(nodes));
|
|
581
579
|
return { valid: errors.length === 0, errors, warnings };
|
|
582
580
|
}
|
|
583
581
|
/**
|
|
@@ -22,7 +22,7 @@ export const applyPatchOutput = {
|
|
|
22
22
|
export function registerApplyPatch(server, bridge) {
|
|
23
23
|
server.registerTool('apply_patch', {
|
|
24
24
|
title: 'Apply patch',
|
|
25
|
-
description: "Apply a batch of create/update/delete operations atomically. All patches are validated before any are applied; the entire batch forms a single undo step. Batch-first is the default: prefer one apply_patch call containing all create/update/delete ops for a build step, in stable order so later ops can reference ids created by earlier ops. A single call is atomic (all or nothing) and pays the snapshot and save cost once; do not loop one-op calls. A create whose id already exists fails the whole patch with node_exists; to replace a node, delete it earlier in the same patch. An update cannot change id or type (identity_change) or object or children (immutable_field); restating the current value is fine. A parentId change must name an existing node that holds children (invalid_parent). An update cannot add schema issues to a node (invalid_update). After a delete or a roof update that regenerates a roof's default gutters, those gutters and their downspouts are addressable only in a later call (regenerated_default). A roof created or moved onto the storey whose walls carry it goes to the level above them (the roof level over a top storey), said in notes; storey content (walls, zones, slabs, ceilings, items, stairs) on a roof level (roof_level), a walled level declared the roof level (roof_level_holds_storey) and floor indexes putting a roof level under a storey (roof_level_on_top) are refused. Refusals come back as a tool error whose text is JSON {code, patchIndex, id, message}.",
|
|
25
|
+
description: "Apply a batch of create/update/delete operations atomically. All patches are validated before any are applied; the entire batch forms a single undo step. Batch-first is the default: prefer one apply_patch call containing all create/update/delete ops for a build step, in stable order so later ops can reference ids created by earlier ops. A single call is atomic (all or nothing) and pays the snapshot and save cost once; do not loop one-op calls. A create whose id already exists fails the whole patch with node_exists; to replace a node, delete it earlier in the same patch. An update cannot change id or type (identity_change) or object or children (immutable_field); restating the current value is fine. A parentId change must name an existing node that holds children and cannot point to the node or one of its descendants (invalid_parent). An update cannot add schema issues to a node (invalid_update). After a delete or a roof update that regenerates a roof's default gutters, those gutters and their downspouts are addressable only in a later call (regenerated_default). A roof created or moved onto the storey whose walls carry it goes to the level above them (the roof level over a top storey), said in notes; storey content (walls, zones, slabs, ceilings, items, stairs) on a roof level (roof_level), a walled level declared the roof level (roof_level_holds_storey) and floor indexes putting a roof level under a storey (roof_level_on_top) are refused. Refusals come back as a tool error whose text is JSON {code, patchIndex, id, message}.",
|
|
26
26
|
inputSchema: z.object(applyPatchInput),
|
|
27
27
|
outputSchema: z.object(applyPatchOutput),
|
|
28
28
|
annotations: DESTRUCTIVE_TOOL_ANNOTATIONS,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"patch-guards.d.ts","sourceRoot":"","sources":["../../src/tools/patch-guards.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,
|
|
1
|
+
{"version":3,"file":"patch-guards.d.ts","sourceRoot":"","sources":["../../src/tools/patch-guards.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EAMvB,MAAM,kBAAkB,CAAA;AAKzB,OAAO,EAAE,OAAO,EAAE,KAAK,SAAS,EAAyB,MAAM,yBAAyB,CAAA;AACxF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AAEnD,MAAM,MAAM,gBAAgB,GACxB,aAAa,GACb,iBAAiB,GACjB,iBAAiB,GACjB,gBAAgB,GAChB,gBAAgB,GAChB,qBAAqB,GACrB,gBAAgB,GAEhB,eAAe,GACf,kBAAkB,GAClB,gBAAgB,GAChB,gBAAgB,GAEhB,YAAY,GACZ,yBAAyB,GACzB,mBAAmB,CAAA;AAEvB;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,EAAE,GAAG,IAAI,CAAA;CACjF,CAAA;AAID,4FAA4F;AAC5F,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,QAKnD;AAED,qFAAqF;AACrF,wBAAgB,cAAc,CAC5B,OAAO,EAAE,KAAK,EAAE,EAChB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACvC,MAAM,EAAE,CAKV;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAExC,QAAQ,CAAC,IAAI,EAAE,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM;IAHzB,YACW,IAAI,EAAE,gBAAgB,EACtB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,EAIf;CACF;AAkDD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,SAAS,KAAK,EAAE,EACzB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,WAAW,EAAE,SAAS,SAAS,EAAE,EACjC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,gBAAgB,GAC9E,IAAI,CAwMN"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { nodeRegistry, planNodeDeletion, previewDefaultGutterRefresh, validateNodeRelations, } from '@pascal-app/core';
|
|
1
|
+
import { nodeRegistry, planNodeDeletion, previewDefaultGutterRefresh, validateNodeRelations, wouldCreateHierarchyCycle, } from '@pascal-app/core';
|
|
2
2
|
import { scriptedFieldRefusal, unknownMaterialPresetRefusal, } from '@pascal-app/core/agent-operations';
|
|
3
3
|
import { AnyNode, nodeKindOf, parseNode } from '@pascal-app/core/schema';
|
|
4
4
|
const guards = [];
|
|
@@ -185,6 +185,9 @@ export function assertPatchKeepsIdentity(patches, nodes, rootNodeIds, planDeleti
|
|
|
185
185
|
if (patch.parentId !== undefined && !at(patch.parentId)) {
|
|
186
186
|
throw new Error(`invalid patch: patches[${index}] create parentId "${patch.parentId}" not found`);
|
|
187
187
|
}
|
|
188
|
+
if (wouldCreateHierarchyCycle(node.id, parentId, at)) {
|
|
189
|
+
throw new PatchRefusedError('invalid_parent', index, node.id, `create cannot place "${node.id}" under ${JSON.stringify(parentId)}: it would create a hierarchy cycle.`);
|
|
190
|
+
}
|
|
188
191
|
put({ ...node, parentId });
|
|
189
192
|
const parent = parentId ? at(parentId) : undefined;
|
|
190
193
|
if (parent)
|
|
@@ -227,6 +230,10 @@ export function assertPatchKeepsIdentity(patches, nodes, rootNodeIds, planDeleti
|
|
|
227
230
|
}
|
|
228
231
|
}
|
|
229
232
|
if ('parentId' in data && data.parentId !== current.parentId) {
|
|
233
|
+
const newParentId = typeof data.parentId === 'string' ? data.parentId : null;
|
|
234
|
+
if (wouldCreateHierarchyCycle(patch.id, newParentId, at)) {
|
|
235
|
+
throw new PatchRefusedError('invalid_parent', index, patch.id, `update cannot move "${patch.id}" under ${JSON.stringify(data.parentId)}: it would create a hierarchy cycle.`);
|
|
236
|
+
}
|
|
230
237
|
const newParent = typeof data.parentId === 'string' ? at(data.parentId) : undefined;
|
|
231
238
|
if (!(newParent && Array.isArray(newParent.children))) {
|
|
232
239
|
throw new PatchRefusedError('invalid_parent', index, patch.id, `update cannot move "${patch.id}" under ${JSON.stringify(data.parentId)}: ${newParent ? `a ${newParent.type} holds no children` : 'no such node'}.`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/mcp",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Model Context Protocol server for Pascal 3D editor",
|
|
5
5
|
"mcpName": "app.pascal/editor",
|
|
6
6
|
"type": "module",
|
|
@@ -62,19 +62,19 @@
|
|
|
62
62
|
"prepublishOnly": "bun run build && bun test && bun run smoke:node"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"@pascal-app/core": "^1.0
|
|
65
|
+
"@pascal-app/core": "^1.1.0"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@hono/node-server": "^2.1.6",
|
|
69
69
|
"@modelcontextprotocol/node": "^2.1.2",
|
|
70
70
|
"@modelcontextprotocol/server": "^2.4.0",
|
|
71
|
-
"@pascal-app/geometry-script": "^1.0
|
|
71
|
+
"@pascal-app/geometry-script": "^1.1.0",
|
|
72
72
|
"@pascal-app/lingo": "^0.5.0",
|
|
73
73
|
"zod": ">=4.6.7 <4.7"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@modelcontextprotocol/client": "^2.4.0",
|
|
77
|
-
"@pascal-app/core": "^1.0
|
|
77
|
+
"@pascal-app/core": "^1.1.0",
|
|
78
78
|
"@pascal/typescript-config": "*",
|
|
79
79
|
"@types/node": "^26.6.6",
|
|
80
80
|
"typescript": "7.0.2"
|