@pascal-app/mcp 1.0.4 → 1.0.6
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/CHANGELOG.md +76 -0
- package/README.md +166 -62
- package/dist/bin/pascal-mcp.js +1 -1
- package/dist/bridge/agent-session.d.ts +18 -0
- package/dist/bridge/agent-session.d.ts.map +1 -0
- package/dist/bridge/agent-session.js +15 -0
- package/dist/bridge/index.d.ts +1 -1
- package/dist/bridge/index.d.ts.map +1 -1
- package/dist/bridge/scene-bridge.d.ts +3 -0
- package/dist/bridge/scene-bridge.d.ts.map +1 -1
- package/dist/bridge/scene-bridge.js +7 -0
- package/dist/lib/safe-fetch.d.ts +22 -4
- package/dist/lib/safe-fetch.d.ts.map +1 -1
- package/dist/lib/safe-fetch.js +233 -134
- package/dist/operations/scene-operations.d.ts +18 -0
- package/dist/operations/scene-operations.d.ts.map +1 -1
- package/dist/operations/scene-operations.js +11 -0
- package/dist/prompts/from-brief.d.ts +1 -1
- package/dist/prompts/from-brief.d.ts.map +1 -1
- package/dist/prompts/from-brief.js +3 -3
- package/dist/prompts/index.d.ts +1 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/iterate-on-feedback.d.ts +1 -1
- package/dist/prompts/iterate-on-feedback.d.ts.map +1 -1
- package/dist/prompts/iterate-on-feedback.js +2 -2
- package/dist/prompts/renovation-from-photos.d.ts +1 -1
- package/dist/prompts/renovation-from-photos.d.ts.map +1 -1
- package/dist/prompts/renovation-from-photos.js +2 -2
- package/dist/prompts/scene-guidance.d.ts.map +1 -1
- package/dist/prompts/scene-guidance.js +10 -3
- package/dist/resources/agent-guide.d.ts +1 -1
- package/dist/resources/agent-guide.d.ts.map +1 -1
- package/dist/resources/agent-guide.js +52 -44
- package/dist/resources/catalog-items.d.ts +1 -1
- package/dist/resources/catalog-items.d.ts.map +1 -1
- package/dist/resources/constraints.d.ts +1 -1
- package/dist/resources/constraints.d.ts.map +1 -1
- package/dist/resources/constraints.js +1 -1
- package/dist/resources/design-schema.d.ts +1 -1
- package/dist/resources/design-schema.d.ts.map +1 -1
- package/dist/resources/index.d.ts +1 -1
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/scene-current.d.ts +1 -1
- package/dist/resources/scene-current.d.ts.map +1 -1
- package/dist/resources/scene-summary.d.ts +1 -1
- package/dist/resources/scene-summary.d.ts.map +1 -1
- package/dist/server.d.ts +7 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +59 -21
- package/dist/storage/disk-artifact-store.d.ts.map +1 -1
- package/dist/storage/sqlite-scene-store.d.ts +1 -1
- package/dist/storage/sqlite-scene-store.d.ts.map +1 -1
- package/dist/storage/types.d.ts +4 -4
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/templates/empty-studio.d.ts +3 -3
- package/dist/templates/empty-studio.d.ts.map +1 -1
- package/dist/templates/garden-house.d.ts +3 -3
- package/dist/templates/garden-house.d.ts.map +1 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/two-bedroom.d.ts +3 -3
- package/dist/templates/two-bedroom.d.ts.map +1 -1
- package/dist/tools/add-column.d.ts +1 -1
- package/dist/tools/add-column.d.ts.map +1 -1
- package/dist/tools/add-column.js +3 -2
- package/dist/tools/add-object.d.ts +19 -3
- package/dist/tools/add-object.d.ts.map +1 -1
- package/dist/tools/add-object.js +66 -35
- package/dist/tools/annotations.d.ts.map +1 -1
- package/dist/tools/apply-patch.d.ts +6 -2
- package/dist/tools/apply-patch.d.ts.map +1 -1
- package/dist/tools/apply-patch.js +13 -8
- package/dist/tools/asset-catalog.d.ts.map +1 -1
- package/dist/tools/asset-catalog.js +23 -0
- package/dist/tools/author-scene.d.ts +10 -0
- package/dist/tools/author-scene.d.ts.map +1 -0
- package/dist/tools/author-scene.js +140 -0
- package/dist/tools/check-collisions.d.ts +6 -6
- package/dist/tools/check-collisions.d.ts.map +1 -1
- package/dist/tools/check-collisions.js +6 -6
- package/dist/tools/clear-scene.d.ts +1 -1
- package/dist/tools/clear-scene.d.ts.map +1 -1
- package/dist/tools/clear-scene.js +3 -3
- package/dist/tools/collections.d.ts +1 -1
- package/dist/tools/collections.d.ts.map +1 -1
- package/dist/tools/collections.js +3 -2
- package/dist/tools/construction-tools.d.ts +6 -49
- package/dist/tools/construction-tools.d.ts.map +1 -1
- package/dist/tools/construction-tools.js +9 -158
- package/dist/tools/create-unit.d.ts +3 -3
- package/dist/tools/create-unit.d.ts.map +1 -1
- package/dist/tools/create-unit.js +2 -2
- package/dist/tools/errors.d.ts +5 -5
- package/dist/tools/errors.d.ts.map +1 -1
- package/dist/tools/errors.js +4 -4
- package/dist/tools/export-glb.d.ts +1 -1
- package/dist/tools/export-glb.d.ts.map +1 -1
- package/dist/tools/export-glb.js +2 -2
- package/dist/tools/export-json.d.ts +1 -1
- package/dist/tools/export-json.d.ts.map +1 -1
- package/dist/tools/export-json.js +2 -2
- package/dist/tools/get-scene.d.ts +1 -1
- package/dist/tools/get-scene.d.ts.map +1 -1
- package/dist/tools/get-scene.js +2 -2
- package/dist/tools/hosted-services.d.ts +1 -1
- package/dist/tools/hosted-services.d.ts.map +1 -1
- package/dist/tools/hosted-services.js +13 -3
- package/dist/tools/index.d.ts +4 -3
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +8 -13
- package/dist/tools/level-height-guard.d.ts +2 -0
- package/dist/tools/level-height-guard.d.ts.map +1 -0
- package/dist/tools/level-height-guard.js +11 -0
- package/dist/tools/list-units.d.ts +2 -2
- package/dist/tools/list-units.d.ts.map +1 -1
- package/dist/tools/list-units.js +2 -2
- package/dist/tools/live-sync.d.ts +1 -1
- package/dist/tools/live-sync.d.ts.map +1 -1
- package/dist/tools/live-sync.js +4 -4
- package/dist/tools/measure.d.ts +1 -1
- package/dist/tools/measure.d.ts.map +1 -1
- package/dist/tools/measure.js +7 -7
- package/dist/tools/node-world-point.d.ts +1 -1
- package/dist/tools/node-world-point.d.ts.map +1 -1
- package/dist/tools/normalize-schema-dialect.d.ts +1 -1
- package/dist/tools/normalize-schema-dialect.d.ts.map +1 -1
- package/dist/tools/normalize-schema-dialect.js +2 -3
- package/dist/tools/patch-guards.d.ts +6 -4
- package/dist/tools/patch-guards.d.ts.map +1 -1
- package/dist/tools/patch-guards.js +5 -2
- package/dist/tools/photo-to-scene/index.d.ts +1 -1
- package/dist/tools/photo-to-scene/index.d.ts.map +1 -1
- package/dist/tools/photo-to-scene/photo-to-scene.d.ts +3 -3
- package/dist/tools/photo-to-scene/photo-to-scene.d.ts.map +1 -1
- package/dist/tools/photo-to-scene/photo-to-scene.js +7 -7
- package/dist/tools/project-files.d.ts +4 -0
- package/dist/tools/project-files.d.ts.map +1 -0
- package/dist/tools/project-files.js +30 -0
- package/dist/tools/redo.d.ts +2 -2
- package/dist/tools/redo.d.ts.map +1 -1
- package/dist/tools/redo.js +2 -2
- package/dist/tools/retired-tools.d.ts +10 -0
- package/dist/tools/retired-tools.d.ts.map +1 -0
- package/dist/tools/retired-tools.js +22 -0
- package/dist/tools/roof-level-guard.d.ts +2 -0
- package/dist/tools/roof-level-guard.d.ts.map +1 -0
- package/dist/tools/roof-level-guard.js +22 -0
- package/dist/tools/room-tools.d.ts +47 -46
- package/dist/tools/room-tools.d.ts.map +1 -1
- package/dist/tools/room-tools.js +6 -5
- package/dist/tools/scene-lifecycle/create-project.d.ts +1 -1
- package/dist/tools/scene-lifecycle/create-project.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/create-project.js +5 -5
- package/dist/tools/scene-lifecycle/delete-scene.d.ts +1 -1
- package/dist/tools/scene-lifecycle/delete-scene.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/delete-scene.js +6 -6
- package/dist/tools/scene-lifecycle/get-project-status.d.ts +1 -1
- package/dist/tools/scene-lifecycle/get-project-status.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/get-project-status.js +6 -6
- package/dist/tools/scene-lifecycle/index.d.ts +1 -1
- package/dist/tools/scene-lifecycle/index.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/list-scenes.d.ts +1 -1
- package/dist/tools/scene-lifecycle/list-scenes.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/list-scenes.js +4 -4
- package/dist/tools/scene-lifecycle/load-scene.d.ts +2 -2
- package/dist/tools/scene-lifecycle/load-scene.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/load-scene.js +5 -5
- package/dist/tools/scene-lifecycle/metadata.d.ts +1 -1
- package/dist/tools/scene-lifecycle/rename-scene.d.ts +1 -1
- package/dist/tools/scene-lifecycle/rename-scene.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/rename-scene.js +6 -6
- package/dist/tools/scene-lifecycle/save-scene.d.ts +5 -3
- package/dist/tools/scene-lifecycle/save-scene.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/save-scene.js +80 -12
- package/dist/tools/scene-lifecycle/test-utils.d.ts +1 -1
- package/dist/tools/scene-lifecycle/test-utils.d.ts.map +1 -1
- package/dist/tools/set-unit-members.d.ts +2 -2
- package/dist/tools/set-unit-members.d.ts.map +1 -1
- package/dist/tools/set-unit-members.js +6 -6
- package/dist/tools/set-zone.d.ts +2 -2
- package/dist/tools/set-zone.d.ts.map +1 -1
- package/dist/tools/set-zone.js +30 -18
- package/dist/tools/shared-tools.d.ts +15 -5
- package/dist/tools/shared-tools.d.ts.map +1 -1
- package/dist/tools/shared-tools.js +242 -29
- package/dist/tools/structure-tools.d.ts +3 -3
- package/dist/tools/templates/create-from-template.d.ts +2 -2
- package/dist/tools/templates/create-from-template.d.ts.map +1 -1
- package/dist/tools/templates/create-from-template.js +6 -6
- package/dist/tools/templates/create-house-from-brief.d.ts +1 -1
- package/dist/tools/templates/create-house-from-brief.d.ts.map +1 -1
- package/dist/tools/templates/create-house-from-brief.js +6 -6
- package/dist/tools/templates/index.d.ts +1 -1
- package/dist/tools/templates/index.d.ts.map +1 -1
- package/dist/tools/templates/list-templates.d.ts +1 -1
- package/dist/tools/templates/list-templates.d.ts.map +1 -1
- package/dist/tools/templates/list-templates.js +2 -2
- package/dist/tools/undo.d.ts +2 -2
- package/dist/tools/undo.d.ts.map +1 -1
- package/dist/tools/undo.js +2 -2
- package/dist/tools/unit-members.d.ts.map +1 -1
- package/dist/tools/unit-members.js +6 -6
- package/dist/tools/variants/generate-variants.d.ts +6 -6
- package/dist/tools/variants/generate-variants.d.ts.map +1 -1
- package/dist/tools/variants/generate-variants.js +6 -6
- package/dist/tools/variants/index.d.ts +1 -1
- package/dist/tools/variants/index.d.ts.map +1 -1
- package/dist/tools/view-scene.d.ts +8 -2
- package/dist/tools/view-scene.d.ts.map +1 -1
- package/dist/tools/view-scene.js +17 -5
- package/dist/tools/vision/analyze-floorplan-image.d.ts +1 -1
- package/dist/tools/vision/analyze-floorplan-image.d.ts.map +1 -1
- package/dist/tools/vision/analyze-floorplan-image.js +7 -7
- package/dist/tools/vision/analyze-room-photo.d.ts +1 -1
- package/dist/tools/vision/analyze-room-photo.d.ts.map +1 -1
- package/dist/tools/vision/analyze-room-photo.js +7 -7
- package/dist/tools/vision/index.d.ts +1 -1
- package/dist/tools/vision/index.d.ts.map +1 -1
- package/dist/transports/http.d.ts +8 -2
- package/dist/transports/http.d.ts.map +1 -1
- package/dist/transports/http.js +9 -3
- package/dist/transports/stdio.d.ts +15 -9
- package/dist/transports/stdio.d.ts.map +1 -1
- package/dist/transports/stdio.js +18 -11
- package/examples/coordinate-conventions-demo.md +12 -13
- package/examples/embed-in-agent.ts +5 -6
- package/examples/generate-apartment.md +4 -4
- package/examples/photo-to-scene.md +2 -3
- package/examples/renovate-from-photos.md +3 -5
- package/package.json +13 -9
- package/dist/tools/describe-node.d.ts +0 -17
- package/dist/tools/describe-node.d.ts.map +0 -1
- package/dist/tools/describe-node.js +0 -99
- package/dist/tools/find-nodes.d.ts +0 -16
- package/dist/tools/find-nodes.d.ts.map +0 -1
- package/dist/tools/find-nodes.js +0 -204
- package/dist/tools/place-design.d.ts +0 -34
- package/dist/tools/place-design.d.ts.map +0 -1
- package/dist/tools/place-design.js +0 -91
- package/dist/tools/validate-design.d.ts +0 -136
- package/dist/tools/validate-design.d.ts.map +0 -1
- package/dist/tools/validate-design.js +0 -28
- package/dist/tools/validate-scene.d.ts +0 -19
- package/dist/tools/validate-scene.d.ts.map +0 -1
- package/dist/tools/validate-scene.js +0 -28
package/dist/transports/stdio.js
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { serveStdio, } from '@modelcontextprotocol/server/stdio';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Serve Pascal over stdio from a server factory.
|
|
4
4
|
*
|
|
5
|
-
* The
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The SDK negotiates the protocol era per connection: a client that opens with
|
|
6
|
+
* the 2025 `initialize` handshake and one that opens with the 2026-07-28
|
|
7
|
+
* `server/discover` probe are both served. A server handed to
|
|
8
|
+
* `StdioServerTransport` directly would answer only the first. The factory can
|
|
9
|
+
* be called twice on one connection (a discarded probe instance, then the
|
|
10
|
+
* instance that serves it), so it must build a fresh, unconnected server each
|
|
11
|
+
* time.
|
|
8
12
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
13
|
+
* The connection owns stdin/stdout for JSON-RPC messaging. Callers must send
|
|
14
|
+
* any operator logging to stderr (not stdout) to avoid corrupting the protocol
|
|
15
|
+
* stream. The process stays alive while stdin is open; the connection tears
|
|
16
|
+
* itself down when stdin reaches end-of-file.
|
|
12
17
|
*/
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
export function connectStdio(createServer, options = {}) {
|
|
19
|
+
return serveStdio(createServer, {
|
|
20
|
+
onerror: (error) => console.error('[pascal-mcp] stdio transport error', error),
|
|
21
|
+
...options,
|
|
22
|
+
});
|
|
16
23
|
}
|
|
@@ -19,43 +19,42 @@ A flat ground level with four demos, plus a labelled compass at the
|
|
|
19
19
|
origin so the world axes are unambiguous regardless of which viewport
|
|
20
20
|
you're in.
|
|
21
21
|
|
|
22
|
-
| Section
|
|
23
|
-
|
|
24
|
-
| Reference compass at the origin
|
|
25
|
-
| **Demo A** at world `(18, 0)`
|
|
26
|
-
| **Demo B** at world `(18, 10)`
|
|
27
|
-
| **Demo C** at world `(0, 22)` and `(10, 22)`
|
|
28
|
-
| **Demo D** at world `(22, 22)` and `(32, 22)` | Same pairing for the L with its second coordinate reflected (`z → 5 − z`) before paste.
|
|
29
|
-
| Takeaway band on the south edge
|
|
22
|
+
| Section | What | Why |
|
|
23
|
+
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
24
|
+
| Reference compass at the origin | 12 m `+X` and `+Z` axis bars with arrowhead tips, short `-X` / `-Z` stubs, an origin marker, plus zone labels naming each component of `[x, z]`. | Establishes which way `+X` and `+Z` actually point in world space, independent of viewport rotation. |
|
|
25
|
+
| **Demo A** at world `(18, 0)` | Axis-aligned 6 m × 4 m rectangle. | Baseline that matches the only example currently in `examples/generate-apartment.md` style (`[0,0] → [10,0]`). |
|
|
26
|
+
| **Demo B** at world `(18, 10)` | The proposed README example, **verbatim**: `polygon: [[0,0],[5.196,3.0],[3.196,6.464],[-2.0,3.464]]` at this offset. | Programmatically verified to be a 6 m × 4 m rectangle whose first edge is heading 30° CCW from `+X` (side lengths 6/4/6/4 m to 4 dp, `AB · AD = 0`, heading = 30.0007°). Confirms the README's worked example produces the geometry it claims. |
|
|
27
|
+
| **Demo C** at world `(0, 22)` and `(10, 22)` | An **L** authored on a north-up page (page-+x right, page-+y up) drawn at half scale and faded; alongside it, the same L pasted into `[x, z]` **uncorrected**, drawn at full scale and vivid. | Makes the external-coordinate gotcha visual: the author's page-+y direction lands on world +Z, which does not correspond to "screen-up" in any of Pascal's viewports. |
|
|
28
|
+
| **Demo D** at world `(22, 22)` and `(32, 22)` | Same pairing for the L with its second coordinate reflected (`z → 5 − z`) before paste. | Makes plain that z-reflection only "corrects" a true mirror; Pascal's viewports apply a _rotation_, so the reflection produces a mirror-image of Demo C rather than a page-correct L. |
|
|
29
|
+
| Takeaway band on the south edge | Short labels summarising the convention. | Self-documenting; readable in any viewport without an external README. |
|
|
30
30
|
|
|
31
31
|
## What it confirms
|
|
32
32
|
|
|
33
33
|
Open the scene in Pascal (or render it from the JSON) and you can read
|
|
34
34
|
each claim directly off the geometry:
|
|
35
|
-
|
|
36
35
|
1. **`[x, z] → world (x, 0, z)` is exact, no sign flip.** Demo A and
|
|
37
36
|
Demo B both sit flat on the floor at Y = 0; their polygon vertices
|
|
38
37
|
round-trip through `save_scene` / `get_scene` byte-identical to what
|
|
39
|
-
was authored (graph hash matches, see `
|
|
38
|
+
was authored (graph hash matches, see `save_scene` output).
|
|
40
39
|
|
|
41
40
|
2. **Demo B is the rectangle the README says it is.** A simple analytic
|
|
42
41
|
check on its 4 vertices yields side lengths 6, 4, 6, 4 m (to 4 dp)
|
|
43
42
|
with the first edge at 30.0007° from +X and perpendicular adjacent
|
|
44
43
|
edges. No rendering required.
|
|
45
44
|
|
|
46
|
-
3. **External page coordinates
|
|
45
|
+
3. **External page coordinates _rotate_ (not mirror) when pasted as
|
|
47
46
|
`[x, z]` and viewed in Pascal.** Inspecting Demo C in the 2-D plan
|
|
48
47
|
panel: the page-up L lands rotated 90° clockwise, so the author's
|
|
49
48
|
"stem-up, foot-bottom-right" reads as "stem-on-right, foot-along-top"
|
|
50
49
|
on screen. Inspecting Demo D right next to it: the z-reflected
|
|
51
|
-
variant lands as the
|
|
50
|
+
variant lands as the _mirror_ of Demo C — neither matches a
|
|
52
51
|
page-correct L. That demonstrates why "reflect across the axis" is
|
|
53
52
|
the wrong corrective for an issue that is, in this viewport, a
|
|
54
53
|
rotation.
|
|
55
54
|
|
|
56
55
|
4. **The 3-D "top-down" snap is offset 45° from world axes by default.**
|
|
57
56
|
Viewing the scene in 3-D and snapping to top-down from the default
|
|
58
|
-
iso camera shows the rectangular site polygon as a
|
|
57
|
+
iso camera shows the rectangular site polygon as a _diamond_ (its
|
|
59
58
|
long edges are diagonals on screen, not axis-aligned). That's the
|
|
60
59
|
camera's "up" vector being inherited from the iso start; once you
|
|
61
60
|
orbit to a true axis-aligned top-down it goes away. The label
|
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
* bun run packages/mcp/examples/embed-in-agent.ts
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { Client } from '@modelcontextprotocol/
|
|
14
|
-
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'
|
|
13
|
+
import { Client, InMemoryTransport } from '@modelcontextprotocol/client'
|
|
15
14
|
import { createPascalMcpServer, SceneBridge } from '@pascal-app/mcp'
|
|
16
15
|
|
|
17
16
|
async function main(): Promise<void> {
|
|
@@ -40,10 +39,10 @@ async function main(): Promise<void> {
|
|
|
40
39
|
|
|
41
40
|
// 5. Find the default level, create a 5 m wall, and undo it.
|
|
42
41
|
const levels = await client.callTool({
|
|
43
|
-
name: '
|
|
42
|
+
name: 'find_by_type',
|
|
44
43
|
arguments: { type: 'level' },
|
|
45
44
|
})
|
|
46
|
-
const levelId = (levels.structuredContent as {
|
|
45
|
+
const levelId = (levels.structuredContent as { results: Array<{ id: string }> }).results[0]?.id
|
|
47
46
|
|
|
48
47
|
if (levelId) {
|
|
49
48
|
const created = await client.callTool({
|
|
@@ -63,8 +62,8 @@ async function main(): Promise<void> {
|
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
// 6. Validate and export.
|
|
66
|
-
const
|
|
67
|
-
console.log('
|
|
65
|
+
const verification = await client.callTool({ name: 'verify_scene', arguments: {} })
|
|
66
|
+
console.log('verification:', verification.structuredContent)
|
|
68
67
|
|
|
69
68
|
const exported = await client.callTool({
|
|
70
69
|
name: 'export_json',
|
|
@@ -193,14 +193,14 @@ more windows on the east façade.
|
|
|
193
193
|
### 6. Validate and report
|
|
194
194
|
|
|
195
195
|
```jsonc
|
|
196
|
-
// tool:
|
|
197
|
-
{ "name": "
|
|
196
|
+
// tool: verify_scene
|
|
197
|
+
{ "name": "verify_scene", "arguments": {} }
|
|
198
198
|
```
|
|
199
199
|
|
|
200
200
|
Response:
|
|
201
201
|
|
|
202
202
|
```jsonc
|
|
203
|
-
{ "valid": true, "
|
|
203
|
+
{ "valid": true, "issues": [], "hasIssues": false, … }
|
|
204
204
|
```
|
|
205
205
|
|
|
206
206
|
The agent then reads the scene summary for its response to the user:
|
|
@@ -235,6 +235,6 @@ Zustand store, the user can `undo` the change if they dislike it:
|
|
|
235
235
|
- Mutations batch inside a single `apply_patch` so that `undo` rolls back
|
|
236
236
|
the whole logical change.
|
|
237
237
|
- Zones are not walls — they're polygon annotations that make later queries
|
|
238
|
-
(`
|
|
238
|
+
(`find_by_type({ zoneId })`) and planning steps much easier for the agent.
|
|
239
239
|
- The agent never needs to speak to `@pascal-app/viewer`: everything the
|
|
240
240
|
host sees flows through tools + resources + prompts.
|
|
@@ -33,7 +33,6 @@ The agent reads the attachment as a data URI and issues a single tool call:
|
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
Optional knobs:
|
|
36
|
-
|
|
37
36
|
- `save` (default `true`) — if `false`, the response includes `graph`
|
|
38
37
|
inline instead of persisting to the `SceneStore`.
|
|
39
38
|
- `defaultWallThickness` (default `0.2` m) — used when the vision model
|
|
@@ -56,7 +55,7 @@ Optional knobs:
|
|
|
56
55
|
re-parsed with `AnyNode.safeParse`; invalid ones are dropped with a
|
|
57
56
|
warning appended to `notes`.
|
|
58
57
|
4. `bridge.setScene(...)` swaps the live scene so any follow-up MCP call
|
|
59
|
-
(`
|
|
58
|
+
(`find_by_type`, `measure`, `apply_patch`, ...) operates on the new
|
|
60
59
|
geometry.
|
|
61
60
|
5. If `save: true`, the graph is persisted via `SceneStore.save` and the
|
|
62
61
|
response carries `sceneId` + `url: /scene/<id>`.
|
|
@@ -109,7 +108,7 @@ operate on it without reloading:
|
|
|
109
108
|
|
|
110
109
|
> **User:** add a door on the south wall between Living and Kitchen.
|
|
111
110
|
|
|
112
|
-
The agent calls `
|
|
111
|
+
The agent calls `find_by_type({ type: "wall" })`, picks the appropriate
|
|
113
112
|
wall, and issues `add_door` — no extra wiring needed.
|
|
114
113
|
|
|
115
114
|
## Takeaways
|
|
@@ -12,7 +12,6 @@ tools to propose a renovation plan grounded in real photos.
|
|
|
12
12
|
## The brief
|
|
13
13
|
|
|
14
14
|
The user drops four photos into the chat:
|
|
15
|
-
|
|
16
15
|
1. A floorplan PDF page (exported as PNG).
|
|
17
16
|
2. A photo of the current living room.
|
|
18
17
|
3. A photo of the current kitchen.
|
|
@@ -148,7 +147,6 @@ against the corresponding perimeter wall:
|
|
|
148
147
|
|
|
149
148
|
Guided by the reference photo's analysis (bright neutrals, open plan,
|
|
150
149
|
minimal furnishing), the agent proposes a single logical patch:
|
|
151
|
-
|
|
152
150
|
- Remove the partition wall between Living and Kitchen.
|
|
153
151
|
- Relocate the kitchen island further west.
|
|
154
152
|
- Delete the bulky TV unit item; leave the sofa and coffee table.
|
|
@@ -177,9 +175,9 @@ The user can walk back with `undo`; `redo` returns them to the proposal.
|
|
|
177
175
|
### 6. Sanity-check
|
|
178
176
|
|
|
179
177
|
```jsonc
|
|
180
|
-
// tool:
|
|
181
|
-
{ "name": "
|
|
182
|
-
// → { "valid": true, "
|
|
178
|
+
// tool: verify_scene
|
|
179
|
+
{ "name": "verify_scene", "arguments": {} }
|
|
180
|
+
// → { "valid": true, "issues": [], "hasIssues": false, … }
|
|
183
181
|
|
|
184
182
|
// tool: check_collisions
|
|
185
183
|
{ "name": "check_collisions", "arguments": { "levelId": "level-1" } }
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Model Context Protocol server for Pascal 3D editor",
|
|
5
|
+
"mcpName": "app.pascal/editor",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"main": "./dist/index.js",
|
|
7
8
|
"types": "./dist/index.d.ts",
|
|
@@ -61,19 +62,22 @@
|
|
|
61
62
|
"prepublishOnly": "bun run build && bun test && bun run smoke:node"
|
|
62
63
|
},
|
|
63
64
|
"peerDependencies": {
|
|
64
|
-
"@pascal-app/core": "^1.0.
|
|
65
|
+
"@pascal-app/core": "^1.0.6"
|
|
65
66
|
},
|
|
66
67
|
"dependencies": {
|
|
67
|
-
"@
|
|
68
|
-
"@
|
|
69
|
-
"@
|
|
70
|
-
"
|
|
68
|
+
"@hono/node-server": "^1.19.15",
|
|
69
|
+
"@modelcontextprotocol/node": "^2.1.1",
|
|
70
|
+
"@modelcontextprotocol/server": "^2.3.1",
|
|
71
|
+
"@pascal-app/geometry-script": "^1.0.6",
|
|
72
|
+
"@pascal-app/lingo": "^0.5.0",
|
|
73
|
+
"zod": ">=4.6.7 <4.7"
|
|
71
74
|
},
|
|
72
75
|
"devDependencies": {
|
|
73
|
-
"@
|
|
76
|
+
"@modelcontextprotocol/client": "^2.3.1",
|
|
77
|
+
"@pascal-app/core": "^1.0.6",
|
|
74
78
|
"@pascal/typescript-config": "*",
|
|
75
|
-
"@types/node": "^
|
|
76
|
-
"typescript": "
|
|
79
|
+
"@types/node": "^26.6.5",
|
|
80
|
+
"typescript": "7.0.2"
|
|
77
81
|
},
|
|
78
82
|
"keywords": [
|
|
79
83
|
"mcp",
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import type { SceneOperations } from '../operations/index.js';
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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;AAIxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAKpD,eAAO,MAAM,iBAAiB;;CAE7B,CAAA;AAED,eAAO,MAAM,kBAAkB;;;;;;;;CAQ9B,CAAA;AA4CD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAiDrF"}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { floorFootprintName, getLevelDisplayName, resolveCeilingHeight } from '@pascal-app/core';
|
|
2
|
-
import { wallResolvedHeight } from '@pascal-app/core/agent-operations';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
import { READ_ONLY_TOOL_ANNOTATIONS } from './annotations.js';
|
|
5
|
-
import { ErrorCode, throwMcpError } from './errors.js';
|
|
6
|
-
import { NodeIdSchema } from './schemas.js';
|
|
7
|
-
export const describeNodeInput = {
|
|
8
|
-
id: NodeIdSchema,
|
|
9
|
-
};
|
|
10
|
-
export const describeNodeOutput = {
|
|
11
|
-
id: z.string(),
|
|
12
|
-
type: z.string(),
|
|
13
|
-
parentId: z.string().nullable(),
|
|
14
|
-
ancestryIds: z.array(z.string()),
|
|
15
|
-
childrenIds: z.array(z.string()),
|
|
16
|
-
properties: z.record(z.string(), z.unknown()),
|
|
17
|
-
description: z.string(),
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* Build a short, human-readable one-liner describing the node.
|
|
21
|
-
* Covers the common shapes; falls back to a generic sentence otherwise.
|
|
22
|
-
*/
|
|
23
|
-
function describe(node, bridge) {
|
|
24
|
-
switch (node.type) {
|
|
25
|
-
case 'wall': {
|
|
26
|
-
const [x1, z1] = node.start;
|
|
27
|
-
const [x2, z2] = node.end;
|
|
28
|
-
const t = node.thickness ?? 0.1;
|
|
29
|
-
const h = wallResolvedHeight(bridge.getNodes(), node);
|
|
30
|
-
return `Wall from (${x1},${z1}) to (${x2},${z2}), thickness ${t.toFixed(2)}m, height ${h.toFixed(2)}m`;
|
|
31
|
-
}
|
|
32
|
-
case 'level':
|
|
33
|
-
return getLevelDisplayName(node);
|
|
34
|
-
case 'building': {
|
|
35
|
-
const [x, y, z] = node.position;
|
|
36
|
-
return `Building at (${x},${y},${z})`;
|
|
37
|
-
}
|
|
38
|
-
case 'site':
|
|
39
|
-
return `Site with ${node.polygon?.points.length ?? 0}-sided property line`;
|
|
40
|
-
case 'zone':
|
|
41
|
-
return `Zone "${node.name}" with ${node.polygon.length} vertices`;
|
|
42
|
-
case 'slab':
|
|
43
|
-
return node.plateRole === 'base'
|
|
44
|
-
? `Floor footprint "${floorFootprintName(bridge.getNodes(), node)}" with ${node.polygon.length} vertices`
|
|
45
|
-
: `Slab with ${node.polygon.length} vertices`;
|
|
46
|
-
case 'ceiling':
|
|
47
|
-
return `Ceiling with ${node.polygon.length} vertices, height ${resolveCeilingHeight(node, bridge.getNodes()).toFixed(2)}m`;
|
|
48
|
-
case 'door':
|
|
49
|
-
return `Door (${node.width.toFixed(2)}m x ${node.height.toFixed(2)}m)`;
|
|
50
|
-
case 'window':
|
|
51
|
-
return `Window (${node.width.toFixed(2)}m x ${node.height.toFixed(2)}m)`;
|
|
52
|
-
case 'item': {
|
|
53
|
-
const [x, y, z] = node.position;
|
|
54
|
-
return `Item "${node.asset.name}" at (${x},${y},${z})`;
|
|
55
|
-
}
|
|
56
|
-
default:
|
|
57
|
-
return `${node.type} node`;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
export function registerDescribeNode(server, bridge) {
|
|
61
|
-
server.registerTool('describe_node', {
|
|
62
|
-
title: 'Describe node',
|
|
63
|
-
description: 'Return a structured summary of a node including its ancestry, children IDs, key properties, and a short human description.',
|
|
64
|
-
inputSchema: describeNodeInput,
|
|
65
|
-
outputSchema: describeNodeOutput,
|
|
66
|
-
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
67
|
-
}, async ({ id }) => {
|
|
68
|
-
const node = bridge.getNode(id);
|
|
69
|
-
if (!node) {
|
|
70
|
-
throwMcpError(ErrorCode.InvalidParams, `Node not found: ${id}`);
|
|
71
|
-
}
|
|
72
|
-
// Ancestry minus self.
|
|
73
|
-
const ancestry = bridge.getAncestry(id);
|
|
74
|
-
const ancestryIds = ancestry.slice(1).map((n) => n.id);
|
|
75
|
-
const children = bridge.getChildren(id);
|
|
76
|
-
const childrenIds = children.map((n) => n.id);
|
|
77
|
-
const n = node;
|
|
78
|
-
const properties = n.type === 'wall'
|
|
79
|
-
? {
|
|
80
|
-
...n,
|
|
81
|
-
resolvedHeight: wallResolvedHeight(bridge.getNodes(), n),
|
|
82
|
-
heightIsExplicit: n.height !== undefined,
|
|
83
|
-
}
|
|
84
|
-
: n;
|
|
85
|
-
const payload = {
|
|
86
|
-
id: n.id,
|
|
87
|
-
type: n.type,
|
|
88
|
-
parentId: (n.parentId ?? null),
|
|
89
|
-
ancestryIds,
|
|
90
|
-
childrenIds,
|
|
91
|
-
properties: properties,
|
|
92
|
-
description: describe(n, bridge),
|
|
93
|
-
};
|
|
94
|
-
return {
|
|
95
|
-
content: [{ type: 'text', text: JSON.stringify(payload) }],
|
|
96
|
-
structuredContent: payload,
|
|
97
|
-
};
|
|
98
|
-
});
|
|
99
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import type { SceneOperations } from '../operations/index.js';
|
|
4
|
-
export declare const findNodesInput: {
|
|
5
|
-
type: z.ZodOptional<z.ZodString>;
|
|
6
|
-
parentId: z.ZodOptional<z.ZodString>;
|
|
7
|
-
levelId: z.ZodOptional<z.ZodString>;
|
|
8
|
-
zoneId: z.ZodOptional<z.ZodString>;
|
|
9
|
-
sourceId: z.ZodOptional<z.ZodString>;
|
|
10
|
-
sourceIdPrefix: z.ZodOptional<z.ZodString>;
|
|
11
|
-
};
|
|
12
|
-
export declare const findNodesOutput: {
|
|
13
|
-
nodes: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
14
|
-
};
|
|
15
|
-
export declare function registerFindNodes(server: McpServer, bridge: SceneOperations): void;
|
|
16
|
-
//# sourceMappingURL=find-nodes.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
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;AAKxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAOpD,eAAO,MAAM,cAAc;;;;;;;CAyB1B,CAAA;AAED,eAAO,MAAM,eAAe;;CAE3B,CAAA;AAmHD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAuFlF"}
|
package/dist/tools/find-nodes.js
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import { measurementCentroid, nodeRegistry } from '@pascal-app/core';
|
|
2
|
-
import { nodeLevelFrame, transformPoint } from '@pascal-app/core/procedural-items';
|
|
3
|
-
import { AnyNode, nodeKindOf } from '@pascal-app/core/schema';
|
|
4
|
-
import { pointInPolygon } from '@pascal-app/core/spatial-grid';
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
import { READ_ONLY_TOOL_ANNOTATIONS } from './annotations.js';
|
|
7
|
-
import { ErrorCode, throwMcpError } from './errors.js';
|
|
8
|
-
import { NodeIdSchema } from './schemas.js';
|
|
9
|
-
const CORE_NODE_KINDS = AnyNode.options.map(nodeKindOf);
|
|
10
|
-
export const findNodesInput = {
|
|
11
|
-
type: z
|
|
12
|
-
.string()
|
|
13
|
-
.min(1)
|
|
14
|
-
.optional()
|
|
15
|
-
.describe(`Node kind: a core kind (${CORE_NODE_KINDS.join(', ')}) or a plugin kind such as "fixture:bench".`),
|
|
16
|
-
parentId: NodeIdSchema.optional(),
|
|
17
|
-
levelId: NodeIdSchema.optional(),
|
|
18
|
-
zoneId: NodeIdSchema.optional(),
|
|
19
|
-
sourceId: z
|
|
20
|
-
.string()
|
|
21
|
-
.min(1)
|
|
22
|
-
.optional()
|
|
23
|
-
.describe('Exact match on any import source id of the node: provenance.refs[].id or legacy metadata.sourceIds. Characters outside printable ASCII also match their percent-encoded form.'),
|
|
24
|
-
sourceIdPrefix: z
|
|
25
|
-
.string()
|
|
26
|
-
.min(1)
|
|
27
|
-
.optional()
|
|
28
|
-
.describe('Prefix match on any import source id of the node (provenance.refs[].id or legacy metadata.sourceIds).'),
|
|
29
|
-
};
|
|
30
|
-
export const findNodesOutput = {
|
|
31
|
-
nodes: z.array(z.record(z.string(), z.unknown())),
|
|
32
|
-
};
|
|
33
|
-
/**
|
|
34
|
-
* A source id as importers must write it for typed provenance: printable
|
|
35
|
-
* ASCII kept, every other character percent-encoded as UTF-8 bytes.
|
|
36
|
-
*/
|
|
37
|
-
function encodeSourceId(id) {
|
|
38
|
-
let out = '';
|
|
39
|
-
for (const char of id) {
|
|
40
|
-
if (/^[\x20-\x7E]$/.test(char)) {
|
|
41
|
-
out += char;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
for (const byte of new TextEncoder().encode(char)) {
|
|
45
|
-
out += `%${byte.toString(16).toUpperCase().padStart(2, '0')}`;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return out;
|
|
49
|
-
}
|
|
50
|
-
/** A node's import source ids: typed `provenance.refs[].id`, then legacy `metadata.sourceIds`. */
|
|
51
|
-
function nodeSourceIds(node) {
|
|
52
|
-
const typed = (node.provenance?.refs ?? []).map((ref) => ref.id);
|
|
53
|
-
const legacy = node.metadata?.sourceIds;
|
|
54
|
-
return Array.isArray(legacy)
|
|
55
|
-
? [...typed, ...legacy.filter((id) => typeof id === 'string')]
|
|
56
|
-
: typed;
|
|
57
|
-
}
|
|
58
|
-
function centre(points) {
|
|
59
|
-
if (points.length === 0)
|
|
60
|
-
return null;
|
|
61
|
-
const area = measurementCentroid(points.map(([x, z]) => [x, 0, z]));
|
|
62
|
-
if (area)
|
|
63
|
-
return [area[0], area[2]];
|
|
64
|
-
let cx = 0;
|
|
65
|
-
let cz = 0;
|
|
66
|
-
for (const [x, z] of points) {
|
|
67
|
-
cx += x;
|
|
68
|
-
cz += z;
|
|
69
|
-
}
|
|
70
|
-
return [cx / points.length, cz / points.length];
|
|
71
|
-
}
|
|
72
|
-
function boundsCentre3(points) {
|
|
73
|
-
if (points.length === 0)
|
|
74
|
-
return null;
|
|
75
|
-
const centre = [0, 0, 0];
|
|
76
|
-
for (let axis = 0; axis < 3; axis++) {
|
|
77
|
-
const values = points.map((p) => p[axis]);
|
|
78
|
-
centre[axis] = (Math.min(...values) + Math.max(...values)) / 2;
|
|
79
|
-
}
|
|
80
|
-
return centre;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* The node's plan point in its level's frame, for zone filtering: a polygon's
|
|
84
|
-
* area centroid, a segment's midpoint, a path's centre, or the origin of a
|
|
85
|
-
* positioned node resolved through its hosts by core's `nodeLevelFrame` (a
|
|
86
|
-
* window in its wall, a module in its cabinet); blocks and imported meshes
|
|
87
|
-
* use their vertex-bounds centre, transformed in 3D before projecting.
|
|
88
|
-
* Frames are plan-only (no slab support or floor lift, which only move
|
|
89
|
-
* heights) and `frames` is shared across one query, so each host resolves once.
|
|
90
|
-
*/
|
|
91
|
-
function levelPlanPoint(node, nodes, frames) {
|
|
92
|
-
const n = node;
|
|
93
|
-
if (Array.isArray(n.polygon))
|
|
94
|
-
return centre(n.polygon);
|
|
95
|
-
if (Array.isArray(n.start) && Array.isArray(n.end)) {
|
|
96
|
-
const [x1, z1] = n.start;
|
|
97
|
-
const [x2, z2] = n.end;
|
|
98
|
-
return [(x1 + x2) / 2, (z1 + z2) / 2];
|
|
99
|
-
}
|
|
100
|
-
if (Array.isArray(n.path)) {
|
|
101
|
-
const path = boundsCentre3(n.path);
|
|
102
|
-
return path ? [path[0], path[2]] : null;
|
|
103
|
-
}
|
|
104
|
-
if (!Array.isArray(n.position))
|
|
105
|
-
return null;
|
|
106
|
-
let frame;
|
|
107
|
-
try {
|
|
108
|
-
frame = nodeLevelFrame(node.id, nodes, undefined, { cache: frames, planOnly: true });
|
|
109
|
-
}
|
|
110
|
-
catch {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
const local = node.type === 'block'
|
|
114
|
-
? boundsCentre3(node.topology.vertices.map((v) => v.position))
|
|
115
|
-
: node.type === 'imported-mesh'
|
|
116
|
-
? boundsCentre3(node.primitives.flatMap((primitive) => {
|
|
117
|
-
const out = [];
|
|
118
|
-
for (let i = 0; i + 2 < primitive.positions.length; i += 3) {
|
|
119
|
-
out.push([
|
|
120
|
-
primitive.positions[i],
|
|
121
|
-
primitive.positions[i + 1],
|
|
122
|
-
primitive.positions[i + 2],
|
|
123
|
-
]);
|
|
124
|
-
}
|
|
125
|
-
return out;
|
|
126
|
-
}))
|
|
127
|
-
: null;
|
|
128
|
-
const [x, , z] = transformPoint(frame, local ?? [0, 0, 0]);
|
|
129
|
-
return [x, z];
|
|
130
|
-
}
|
|
131
|
-
/** Node kinds `type` accepts: core kinds, registered plugin kinds, and kinds in the scene. */
|
|
132
|
-
function knownNodeKinds(nodes) {
|
|
133
|
-
const kinds = new Set(CORE_NODE_KINDS);
|
|
134
|
-
for (const node of Object.values(nodes))
|
|
135
|
-
kinds.add(node.type);
|
|
136
|
-
return kinds;
|
|
137
|
-
}
|
|
138
|
-
export function registerFindNodes(server, bridge) {
|
|
139
|
-
server.registerTool('find_nodes', {
|
|
140
|
-
title: 'Find nodes',
|
|
141
|
-
description: 'Find nodes matching any combination of type, parentId, levelId, zoneId, sourceId or sourceIdPrefix filters. sourceId / sourceIdPrefix match the ids an importer recorded for the source elements: provenance.refs[].id, or legacy metadata.sourceIds.',
|
|
142
|
-
inputSchema: findNodesInput,
|
|
143
|
-
outputSchema: findNodesOutput,
|
|
144
|
-
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
145
|
-
}, async (args) => {
|
|
146
|
-
const { type, parentId, levelId, zoneId, sourceId, sourceIdPrefix } = args;
|
|
147
|
-
// Delegate type/parent/level filtering to the bridge.
|
|
148
|
-
const baseFilter = {};
|
|
149
|
-
if (type !== undefined) {
|
|
150
|
-
if (!(knownNodeKinds(bridge.getNodes()).has(type) || nodeRegistry.get(type))) {
|
|
151
|
-
throwMcpError(ErrorCode.InvalidParams, `unknown node type "${type}": not a core kind, a registered plugin kind or a kind in this scene`);
|
|
152
|
-
}
|
|
153
|
-
baseFilter.type = type;
|
|
154
|
-
}
|
|
155
|
-
if (parentId !== undefined)
|
|
156
|
-
baseFilter.parentId = parentId;
|
|
157
|
-
if (levelId !== undefined)
|
|
158
|
-
baseFilter.levelId = levelId;
|
|
159
|
-
let results = bridge.findNodes(baseFilter);
|
|
160
|
-
if (sourceId !== undefined || sourceIdPrefix !== undefined) {
|
|
161
|
-
// Legacy ids may be stored raw, typed-provenance ones percent-encoded.
|
|
162
|
-
const exact = sourceId === undefined ? [] : [sourceId, encodeSourceId(sourceId)];
|
|
163
|
-
const prefixes = sourceIdPrefix === undefined ? [] : [sourceIdPrefix, encodeSourceId(sourceIdPrefix)];
|
|
164
|
-
results = results.filter((n) => {
|
|
165
|
-
const ids = nodeSourceIds(n);
|
|
166
|
-
if (sourceId !== undefined && !ids.some((id) => exact.includes(id)))
|
|
167
|
-
return false;
|
|
168
|
-
if (sourceIdPrefix !== undefined &&
|
|
169
|
-
!ids.some((id) => prefixes.some((prefix) => id.startsWith(prefix)))) {
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
return true;
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
// Zone filter: nodes on the zone's level whose level-frame plan point
|
|
176
|
-
// falls inside the zone polygon.
|
|
177
|
-
if (zoneId) {
|
|
178
|
-
const zone = bridge.getNode(zoneId);
|
|
179
|
-
if (zone?.type !== 'zone') {
|
|
180
|
-
// Unknown zoneId → return empty list rather than throw; matches
|
|
181
|
-
// typical "filter" semantics.
|
|
182
|
-
results = [];
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
const nodes = bridge.getNodes();
|
|
186
|
-
const frames = new Map();
|
|
187
|
-
const zoneLevelId = bridge.resolveLevelId(zone.id);
|
|
188
|
-
results = results.filter((n) => {
|
|
189
|
-
if (bridge.resolveLevelId(n.id) !== zoneLevelId)
|
|
190
|
-
return false;
|
|
191
|
-
const pt = levelPlanPoint(n, nodes, frames);
|
|
192
|
-
return pt !== null && pointInPolygon(pt[0], pt[1], zone.polygon);
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
const payload = {
|
|
197
|
-
nodes: results,
|
|
198
|
-
};
|
|
199
|
-
return {
|
|
200
|
-
content: [{ type: 'text', text: JSON.stringify(payload) }],
|
|
201
|
-
structuredContent: payload,
|
|
202
|
-
};
|
|
203
|
-
});
|
|
204
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import type { SceneOperations } from '../operations/index.js';
|
|
4
|
-
export declare const placeDesignInput: {
|
|
5
|
-
design: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
6
|
-
hostId: z.ZodString;
|
|
7
|
-
position: z.ZodArray<z.ZodNumber>;
|
|
8
|
-
rotation: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
9
|
-
side: z.ZodOptional<z.ZodEnum<{
|
|
10
|
-
front: "front";
|
|
11
|
-
back: "back";
|
|
12
|
-
}>>;
|
|
13
|
-
surfaceId: z.ZodOptional<z.ZodString>;
|
|
14
|
-
parameters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
|
|
15
|
-
slots: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
16
|
-
name: z.ZodOptional<z.ZodString>;
|
|
17
|
-
id: z.ZodOptional<z.ZodString>;
|
|
18
|
-
};
|
|
19
|
-
export declare const placeDesignOutput: {
|
|
20
|
-
project: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
21
|
-
unsaved: z.ZodOptional<z.ZodString>;
|
|
22
|
-
persistence: z.ZodOptional<z.ZodObject<{
|
|
23
|
-
status: z.ZodEnum<{
|
|
24
|
-
unbound: "unbound";
|
|
25
|
-
events_unsupported: "events_unsupported";
|
|
26
|
-
}>;
|
|
27
|
-
warning: z.ZodString;
|
|
28
|
-
}, z.core.$strip>>;
|
|
29
|
-
designId: z.ZodString;
|
|
30
|
-
parentId: z.ZodString;
|
|
31
|
-
surfaceId: z.ZodNullable<z.ZodString>;
|
|
32
|
-
};
|
|
33
|
-
export declare function registerPlaceDesign(server: McpServer, bridge: SceneOperations): void;
|
|
34
|
-
//# sourceMappingURL=place-design.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"place-design.d.ts","sourceRoot":"","sources":["../../src/tools/place-design.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;AAOpD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;CA8B5B,CAAA;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;CAK7B,CAAA;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAyDpF"}
|