@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
package/CHANGELOG.md ADDED
@@ -0,0 +1,39 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@pascal-app/mcp` will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-04-18
9
+
10
+ ### Added
11
+
12
+ - Initial release.
13
+ - `SceneBridge` headless adapter for `@pascal-app/core` with RAF polyfill so
14
+ the Zustand store and Zundo temporal middleware run cleanly in Node.
15
+ - 19 MCP tools covering scene querying (`get_scene`, `get_node`,
16
+ `describe_node`, `find_nodes`, `measure`), mutation (`apply_patch`,
17
+ `create_level`, `create_wall`, `place_item`, `cut_opening`, `set_zone`,
18
+ `duplicate_level`, `delete_node`), undo/redo (`undo`, `redo`), export
19
+ (`export_json`, `export_glb`), validation (`validate_scene`,
20
+ `check_collisions`), plus 2 vision tools (`analyze_floorplan_image`,
21
+ `analyze_room_photo`) backed by MCP sampling.
22
+ - 4 MCP resources: `pascal://scene/current`,
23
+ `pascal://scene/current/summary`, `pascal://catalog/items`, and
24
+ `pascal://constraints/{levelId}`.
25
+ - 3 MCP prompts: `from_brief`, `iterate_on_feedback`, and
26
+ `renovation_from_photos`.
27
+ - stdio and Streamable HTTP transports.
28
+ - `pascal-mcp` CLI binary with `--stdio`, `--http --port`, and `--scene`
29
+ flags.
30
+ - Local `SqliteSceneStore` backed by built-in SQLite drivers (`bun:sqlite` in
31
+ the MCP CLI, `node:sqlite` in the Next.js editor server), with WAL mode,
32
+ transaction-scoped optimistic locking, revision rows, and shared
33
+ `PASCAL_DATA_DIR` / `PASCAL_DB_PATH` configuration for MCP and the editor.
34
+
35
+ ### Removed
36
+
37
+ - Supabase storage adapter, SQL migrations, and the `@supabase/supabase-js`
38
+ runtime dependency.
39
+ - Committed MCP `test-reports/` development artifacts.
package/README.md ADDED
@@ -0,0 +1,330 @@
1
+ # @pascal-app/mcp
2
+
3
+ Model Context Protocol server for the Pascal 3D editor. Drives the
4
+ `@pascal-app/core` scene graph from any MCP-compatible AI host.
5
+
6
+ The server runs headlessly in Bun with no browser, WebGPU, React, or external
7
+ database service. It exposes the same scene mutations used by the editor UI
8
+ (create walls, place items, cut openings, undo, etc.) as MCP tools, resources,
9
+ and prompts.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ bun add @pascal-app/mcp
15
+ ```
16
+
17
+ `@pascal-app/core` is a peer dependency; Bun workspaces resolve it automatically.
18
+ The MCP CLI is intended to run with Bun. When the storage package is consumed by
19
+ the Next.js editor server, it opens the same local database through Node's
20
+ built-in SQLite driver.
21
+
22
+ ## Quick start
23
+
24
+ Launch the server over stdio in one line:
25
+
26
+ ```bash
27
+ bunx pascal-mcp
28
+ ```
29
+
30
+ Load an initial scene from disk:
31
+
32
+ ```bash
33
+ pascal-mcp --stdio --scene ./my-scene.json
34
+ ```
35
+
36
+ Expose it over loopback HTTP:
37
+
38
+ ```bash
39
+ pascal-mcp --http --port 8787
40
+ ```
41
+
42
+ Binding a non-loopback host requires a bearer token:
43
+
44
+ ```bash
45
+ PASCAL_MCP_HTTP_TOKEN="$(openssl rand -hex 32)" \
46
+ pascal-mcp --http --host 0.0.0.0 --port 8787 --cors-origin https://editor.example
47
+ ```
48
+
49
+ ## Local scene storage
50
+
51
+ Scenes saved through MCP are stored in a local SQLite database:
52
+
53
+ ```text
54
+ ~/.pascal/data/pascal.db
55
+ ```
56
+
57
+ Set `PASCAL_DATA_DIR` when you want the MCP server and the running editor to
58
+ share a different directory, or `PASCAL_DB_PATH` when you need an exact database
59
+ file path. The store uses WAL mode and transactional version checks so separate
60
+ local processes can save and open the same scene database.
61
+
62
+ During workspace development, run both sides with the same data directory:
63
+
64
+ ```bash
65
+ # Terminal 1: run the editor
66
+ PASCAL_DATA_DIR="$HOME/.pascal/data" bun run dev
67
+
68
+ # Terminal 2 or an MCP host: run the server
69
+ PASCAL_DATA_DIR="$HOME/.pascal/data" bun packages/mcp/dist/bin/pascal-mcp.js
70
+ ```
71
+
72
+ ## Live editor updates
73
+
74
+ When the editor and MCP server share the same `PASCAL_DATA_DIR`, MCP mutations
75
+ against a loaded saved scene are persisted to SQLite and recorded in a local
76
+ `scene_events` stream. The editor page subscribes to that stream at
77
+ `/api/scenes/:id/events` with server-sent events, so an open browser tab can
78
+ apply scene graph snapshots as the agent edits the scene.
79
+
80
+ The flow is intentionally local and lightweight:
81
+
82
+ 1. Open or create a scene in the editor so it is saved in the local database.
83
+ 2. Load that scene through MCP with `load_scene`.
84
+ 3. Run MCP mutation tools such as `create_room`, `add_door`, `furnish_room`,
85
+ `create_wall`, `place_item`, or `set_zone`.
86
+
87
+ Each mutation version-checks the saved scene before writing. If the browser or
88
+ another MCP process saved a newer version first, the MCP tool returns
89
+ `live_sync_version_conflict`; reload the scene with `load_scene` before
90
+ continuing.
91
+
92
+ ## Claude Desktop config
93
+
94
+ Edit `~/Library/Application Support/Claude/claude_desktop_config.json`
95
+ (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
96
+
97
+ ```json
98
+ {
99
+ "mcpServers": {
100
+ "pascal": {
101
+ "command": "bunx",
102
+ "args": ["pascal-mcp"],
103
+ "env": {
104
+ "PASCAL_DATA_DIR": "/Users/you/.pascal/data"
105
+ }
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ If `bunx` is not on your PATH, point `command` at the absolute path to `bun`
112
+ and pass the built `dist/bin/pascal-mcp.js` file as the first arg.
113
+
114
+ ## Claude Code config
115
+
116
+ Via the CLI:
117
+
118
+ ```bash
119
+ claude mcp add pascal bunx pascal-mcp
120
+ ```
121
+
122
+ Or add to `.mcp.json` at the repo root:
123
+
124
+ ```json
125
+ {
126
+ "mcpServers": {
127
+ "pascal": {
128
+ "command": "bunx",
129
+ "args": ["pascal-mcp"],
130
+ "env": {
131
+ "PASCAL_DATA_DIR": "/Users/you/.pascal/data"
132
+ }
133
+ }
134
+ }
135
+ }
136
+ ```
137
+
138
+ For local workspace testing before publish, build first and point Claude Code at
139
+ the built binary:
140
+
141
+ ```json
142
+ {
143
+ "mcpServers": {
144
+ "pascal": {
145
+ "command": "bun",
146
+ "args": ["/absolute/path/to/editor/packages/mcp/dist/bin/pascal-mcp.js"],
147
+ "env": {
148
+ "PASCAL_DATA_DIR": "/Users/you/.pascal/data"
149
+ }
150
+ }
151
+ }
152
+ }
153
+ ```
154
+
155
+ ## Codex CLI config
156
+
157
+ Via the CLI:
158
+
159
+ ```bash
160
+ codex mcp add pascal --env PASCAL_DATA_DIR="$HOME/.pascal/data" -- bunx pascal-mcp
161
+ ```
162
+
163
+ For local workspace testing before publish:
164
+
165
+ ```bash
166
+ bun run --cwd packages/mcp build
167
+ codex mcp add pascal-dev \
168
+ --env PASCAL_DATA_DIR="$HOME/.pascal/data" \
169
+ -- bun "$PWD/packages/mcp/dist/bin/pascal-mcp.js"
170
+ ```
171
+
172
+ This writes an entry like this to `~/.codex/config.toml`:
173
+
174
+ ```toml
175
+ [mcp_servers.pascal-dev]
176
+ command = "bun"
177
+ args = ["/absolute/path/to/editor/packages/mcp/dist/bin/pascal-mcp.js"]
178
+
179
+ [mcp_servers.pascal-dev.env]
180
+ PASCAL_DATA_DIR = "/Users/you/.pascal/data"
181
+ ```
182
+
183
+ ## Cursor config
184
+
185
+ In Cursor settings (`settings.json`):
186
+
187
+ ```json
188
+ {
189
+ "mcp.servers": {
190
+ "pascal": {
191
+ "command": "bunx",
192
+ "args": ["pascal-mcp"],
193
+ "env": {
194
+ "PASCAL_DATA_DIR": "/Users/you/.pascal/data"
195
+ }
196
+ }
197
+ }
198
+ }
199
+ ```
200
+
201
+ ## Programmatic use
202
+
203
+ Embed the server in your own Bun process using the in-memory transport. The
204
+ example below runs a full client/server pair inside a single script — useful
205
+ for agent frameworks and tests.
206
+
207
+ ```ts
208
+ import { createPascalMcpServer, SceneBridge } from '@pascal-app/mcp'
209
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js'
210
+ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'
211
+
212
+ const bridge = new SceneBridge()
213
+ bridge.loadDefault()
214
+ const server = createPascalMcpServer({ bridge })
215
+
216
+ const [srvT, cliT] = InMemoryTransport.createLinkedPair()
217
+ const client = new Client({ name: 'my-agent', version: '0.1.0' })
218
+ await Promise.all([server.connect(srvT), client.connect(cliT)])
219
+
220
+ const tools = await client.listTools()
221
+ console.log('available tools:', tools.tools.map((t) => t.name))
222
+
223
+ const scene = await client.callTool({ name: 'get_scene', arguments: {} })
224
+ console.log(scene)
225
+ ```
226
+
227
+ See [`examples/embed-in-agent.ts`](./examples/embed-in-agent.ts) for a
228
+ compilable version.
229
+
230
+ ## Tools
231
+
232
+ All tools validate their inputs and outputs with Zod. Mutation tools are
233
+ captured by Zundo's temporal middleware as a single undoable step.
234
+
235
+ | Name | Purpose | Key input | Output |
236
+ | --- | --- | --- | --- |
237
+ | `get_scene` | Return the full scene graph. | — | `{ nodes, rootNodeIds, collections }` |
238
+ | `get_node` | Fetch a node by id. | `{ id }` | the node, or `InvalidParams` if not found |
239
+ | `describe_node` | Node summary with ancestry, children count and properties. | `{ id }` | `{ id, type, parentId, ancestry[], childrenCount, properties, description }` |
240
+ | `find_nodes` | Filter nodes by type / parent / zone / level. | `{ type?, parentId?, zoneId?, levelId? }` | `{ nodes: AnyNode[] }` |
241
+ | `list_levels` | List levels with ids, floor indices, parent ids and child counts. | — | `{ activeSceneId, levels[] }` |
242
+ | `get_level_summary` | Compact summary of one level with counts, wall/opening lists, zones, slabs, ceilings and items. | `{ levelId? }` | `{ levelId, counts, walls, zones, items, slabs, ceilings }` |
243
+ | `get_walls` | Walls on a level with length and child doors/windows. | `{ levelId? }` | `{ levelId, walls[] }` |
244
+ | `get_zones` | Room/zone polygons with approximate areas and bounds. | `{ levelId? }` | `{ levelId, zones[] }` |
245
+ | `measure` | Distance between two nodes; area when applicable. | `{ fromId, toId }` | `{ distanceMeters, areaSqMeters?, units: 'meters' }` |
246
+ | `search_assets` | Search the built-in MCP item catalog. | `{ query, category? }` | `{ results, total }` |
247
+ | `create_story_shell` | Create one level-owned story shell from a footprint: perimeter walls plus optional slab and ceiling. Use once per story. | `{ levelId, footprint, wallHeight?, wallThickness?, createSlab?, createCeiling? }` | `{ wallIds, slabId, ceilingId, createdIds }` |
248
+ | `create_stair_between_levels` | Create a straight stair and one rectangular manual opening in the destination slab/source ceiling, with auto-opening disabled. | `{ fromLevelId, toLevelId, position, width?, runLength?, totalRise? }` | `{ stairId, stairSegmentId, openingPolygon }` |
249
+ | `create_roof` | Create a roof container and one roof segment. By default creates a dedicated roof level above the reference occupied level for solo/exploded views. | `{ levelId, width, depth, roofType?, roofHeight?, roofLevelId?, useDedicatedRoofLevel? }` | `{ roofLevelId, createdRoofLevelId, roofId, roofSegmentId }` |
250
+ | `create_room` | Create a zone, slab, ceiling, and walls from a polygon. | `{ levelId, name, polygon, color?, wallHeight?, wallThickness? }` | `{ zoneId, slabId, ceilingId, wallIds, areaSqMeters }` |
251
+ | `add_door` | Add a door to a wall using parametric placement. | `{ wallId, t, width?, height?, hingesSide?, swingDirection? }` | `{ doorId, localX }` |
252
+ | `add_window` | Add a window to a wall using parametric placement and sill height. | `{ wallId, t, width?, height?, sillHeight? }` | `{ windowId, localX, sillHeight }` |
253
+ | `furnish_room` | Place realistic furniture for a room type inside a polygon. | `{ levelId, roomType, polygon, doorWallIndex? }` | `{ placed, itemIds, skipped }` |
254
+ | `apply_patch` | Batched create/update/delete/move, validated and dry-run before commit. | `{ patches: Patch[] }` | `{ applied: number }` |
255
+ | `create_level` | Add a new level to a building. | `{ buildingId, elevation, height, label? }` | `{ levelId }` |
256
+ | `create_wall` | Add a wall to a level. | `{ levelId, start, end, thickness?, height? }` | `{ wallId }` |
257
+ | `place_item` | Place a catalog item on a level/slab/zone, ceiling, wall, or site. Slab/zone targets resolve to the parent level so floor items render and validate. | `{ catalogItemId, targetNodeId, position, rotation? }` | `{ itemId, status }` |
258
+ | `cut_opening` | Cut a door or window opening into a wall. `position` is 0..1 along the wall and is stored as wall-local meters. | `{ wallId, type: 'door' \| 'window', position, width, height }` | `{ openingId }` |
259
+ | `set_zone` | Create a zone/room polygon on a level. | `{ levelId, polygon, label, properties? }` | `{ zoneId }` |
260
+ | `duplicate_level` | Clone a level and all of its descendants. | `{ levelId }` | `{ newLevelId, newNodeIds[] }` |
261
+ | `delete_node` | Delete a node; cascades when `cascade: true`. | `{ id, cascade? }` | `{ deletedIds: [] }` |
262
+ | `undo` | Step back through temporal history. | `{ steps? }` | `{ undone: number }` |
263
+ | `redo` | Step forward through temporal history. | `{ steps? }` | `{ redone: number }` |
264
+ | `export_json` | Serialize the scene graph as JSON. | `{ pretty? }` | `{ json: string }` |
265
+ | `export_glb` | Stubbed: GLB export requires the browser renderer. | — | throws `not_implemented` |
266
+ | `validate_scene` | Zod-validate every node and parent-child integrity. | — | `{ valid, errors: { nodeId, path, message }[] }` |
267
+ | `verify_scene` | High-level layout check with validation status, per-level counts, empty levels and practical issues. | — | `{ valid, levels[], issues, hasIssues }` |
268
+ | `check_collisions` | Find overlapping items and out-of-bounds placements. | `{ levelId? }` | `{ collisions: { aId, bId, kind }[] }` |
269
+ | `analyze_floorplan_image` | Vision tool: extract walls, rooms, and approximate dimensions from a floorplan image. | `{ image, scaleHint? }` | `{ walls, rooms, approximateDimensions, confidence }` |
270
+ | `analyze_room_photo` | Vision tool: extract approximate dimensions and fixtures from a room photo. | `{ image }` | `{ approximateDimensions, identifiedFixtures, identifiedWindows }` |
271
+
272
+ The vision tools require the MCP host to support the sampling capability
273
+ (`createMessage`). Hosts that don't will see a structured
274
+ `sampling_unavailable` error.
275
+
276
+ ## Resources
277
+
278
+ | URI | MIME | Purpose |
279
+ | --- | --- | --- |
280
+ | `pascal://scene/current` | `application/json` | Full `{ nodes, rootNodeIds, collections }` snapshot. |
281
+ | `pascal://scene/current/summary` | `text/markdown` | Human-readable summary with node counts, bounding box, and level areas. |
282
+ | `pascal://agent/guide` | `text/markdown` | MCP-first construction workflow, scene invariants, and tool preferences for agents. |
283
+ | `pascal://catalog/items` | `application/json` | Dependency-free built-in catalog subset for common residential furniture and fixtures. |
284
+ | `pascal://constraints/{levelId}` | `application/json` | Slab footprints and wall polygons for the given level — useful as planner context. |
285
+
286
+ ## Prompts
287
+
288
+ | Name | Args | Purpose |
289
+ | --- | --- | --- |
290
+ | `from_brief` | `{ brief: string, constraints?: string }` | Guided workflow for turning a prose brief (e.g. "2-bed apartment in 80 m²") into an incremental sequence of `apply_patch` calls starting from an empty site. |
291
+ | `iterate_on_feedback` | `{ feedback: string }` | Minimal-diff instructions: examine the current scene, then propose the smallest patch set that satisfies the feedback. |
292
+ | `renovation_from_photos` | `{ currentPhotos: string[], referencePhotos: string[], goals: string }` | Chains the vision tools with the scene mutation tools to produce a renovation plan grounded in photos. |
293
+
294
+ ## Limitations
295
+
296
+ - `export_glb` returns `not_implemented`. GLB export depends on the Three.js
297
+ renderer and isn't reachable headlessly without a large additional effort.
298
+ - Vision tools require MCP host sampling support. Claude Desktop supports
299
+ this; some MCP clients don't.
300
+ - The built-in MCP catalog is intentionally small. Host applications can expose
301
+ their own richer catalog through additional tools/resources without requiring
302
+ the MCP package to depend on the editor UI bundle.
303
+ - Systems (wall mitering, slab triangulation, CSG cutouts, roof / stair
304
+ generation) run inside React hooks in the editor. Headless mode doesn't
305
+ regenerate derived geometry — but all node data remains fully manipulable.
306
+ Consumers that need rendered geometry run `@pascal-app/viewer` in a browser
307
+ host.
308
+ - Core's `loadAssetUrl` / `saveAsset` are browser-only; items that reference
309
+ `asset://<id>` URLs aren't resolvable in Node. Supply absolute URLs or
310
+ `data:` URLs for item assets if you need them usable outside the browser.
311
+ - `dirtyNodes` accumulates in headless mode because no renderer consumes it.
312
+ Call `bridge.flushDirty()` if observability matters to your consumer.
313
+
314
+ ## Development
315
+
316
+ ```bash
317
+ bun install
318
+ bun run --cwd packages/mcp build
319
+ bun test
320
+ ```
321
+
322
+ Smoke-test the stdio binary end-to-end:
323
+
324
+ ```bash
325
+ bun run --cwd packages/mcp smoke
326
+ ```
327
+
328
+ ## License
329
+
330
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bun
2
+ import '../bridge/node-shims';
3
+ //# sourceMappingURL=pascal-mcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pascal-mcp.d.ts","sourceRoot":"","sources":["../../src/bin/pascal-mcp.ts"],"names":[],"mappings":";AAEA,OAAO,sBAAsB,CAAA"}
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env bun
2
+ // Load shims FIRST so any subsequent core import sees the RAF polyfill.
3
+ import '../bridge/node-shims';
4
+ import { readFileSync } from 'node:fs';
5
+ import { parseArgs } from 'node:util';
6
+ import { SceneBridge } from '../bridge/scene-bridge';
7
+ import { version } from '../index';
8
+ import { createPascalMcpServer } from '../server';
9
+ import { createSceneStore } from '../storage';
10
+ import { connectHttp } from '../transports/http';
11
+ import { connectStdio } from '../transports/stdio';
12
+ const HELP = `pascal-mcp — MCP server for the Pascal editor
13
+
14
+ USAGE:
15
+ pascal-mcp [--stdio | --http --port <n>] [--scene <path>]
16
+
17
+ OPTIONS:
18
+ --stdio Use stdio transport (default)
19
+ --http Use Streamable HTTP transport
20
+ --port <n> HTTP port (default 3917)
21
+ --host <host> HTTP bind host (default 127.0.0.1)
22
+ --auth-token <t> Bearer token required for HTTP calls
23
+ --cors-origin <o> Repeatable allowed HTTP CORS origin
24
+ --scene <path> Initial scene JSON to load
25
+ --version Print version
26
+ --help Print this help
27
+ `;
28
+ async function main() {
29
+ const { values } = parseArgs({
30
+ options: {
31
+ stdio: { type: 'boolean', default: false },
32
+ http: { type: 'boolean', default: false },
33
+ port: { type: 'string', default: '3917' },
34
+ host: { type: 'string', default: '127.0.0.1' },
35
+ 'auth-token': { type: 'string' },
36
+ 'cors-origin': { type: 'string', multiple: true, default: [] },
37
+ scene: { type: 'string' },
38
+ help: { type: 'boolean', default: false },
39
+ version: { type: 'boolean', default: false },
40
+ },
41
+ });
42
+ if (values.help) {
43
+ console.log(HELP);
44
+ process.exit(0);
45
+ }
46
+ if (values.version) {
47
+ console.log(version);
48
+ process.exit(0);
49
+ }
50
+ const bridge = new SceneBridge();
51
+ if (values.scene) {
52
+ const raw = readFileSync(values.scene, 'utf8');
53
+ bridge.loadJSON(raw);
54
+ }
55
+ else {
56
+ bridge.loadDefault();
57
+ }
58
+ const store = await createSceneStore();
59
+ const server = createPascalMcpServer({ bridge, store });
60
+ if (values.http) {
61
+ const portNum = Number.parseInt(values.port ?? '3917', 10);
62
+ if (!Number.isFinite(portNum) || portNum < 0 || portNum > 65_535) {
63
+ throw new Error(`invalid --port value: ${values.port}`);
64
+ }
65
+ const handle = await connectHttp(server, portNum, {
66
+ host: values.host,
67
+ authToken: values['auth-token'],
68
+ allowedOrigins: values['cors-origin'],
69
+ });
70
+ console.error(`[pascal-mcp] HTTP server listening on ${handle.host}:${handle.port}`);
71
+ const shutdown = async () => {
72
+ try {
73
+ await handle.close();
74
+ }
75
+ finally {
76
+ process.exit(0);
77
+ }
78
+ };
79
+ process.on('SIGINT', shutdown);
80
+ process.on('SIGTERM', shutdown);
81
+ }
82
+ else {
83
+ // --stdio is the default when no transport flag is passed.
84
+ await connectStdio(server);
85
+ console.error('[pascal-mcp] stdio server running');
86
+ }
87
+ }
88
+ main().catch((err) => {
89
+ console.error('[pascal-mcp] fatal:', err instanceof Error ? (err.stack ?? err.message) : err);
90
+ process.exit(1);
91
+ });
@@ -0,0 +1,3 @@
1
+ export type { ActiveSceneMeta, CreatePatch, DeletePatch, Patch, UpdatePatch, ValidationError, ValidationResult, } from './scene-bridge';
2
+ export { SceneBridge } from './scene-bridge';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bridge/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,WAAW,EACX,WAAW,EACX,KAAK,EACL,WAAW,EACX,eAAe,EACf,gBAAgB,GACjB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1 @@
1
+ export { SceneBridge } from './scene-bridge';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Node-compatibility shims for `@pascal-app/core`.
3
+ *
4
+ * The core store uses `requestAnimationFrame` inside `updateNodesAction` (to batch
5
+ * dirty-marking) and inside the temporal undo/redo subscribe callback. Both are
6
+ * load-reachable — the subscribe callback registers at module import time.
7
+ *
8
+ * This file installs a no-op-if-already-defined polyfill that works both in
9
+ * Node and in the browser. It MUST be imported FIRST from any module that
10
+ * transitively loads `@pascal-app/core/store`, otherwise the core module will
11
+ * throw at import time.
12
+ *
13
+ * Side-effectful on import: there is no exported API — just import this file.
14
+ */
15
+ export {};
16
+ //# sourceMappingURL=node-shims.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-shims.d.ts","sourceRoot":"","sources":["../../src/bridge/node-shims.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Node-compatibility shims for `@pascal-app/core`.
3
+ *
4
+ * The core store uses `requestAnimationFrame` inside `updateNodesAction` (to batch
5
+ * dirty-marking) and inside the temporal undo/redo subscribe callback. Both are
6
+ * load-reachable — the subscribe callback registers at module import time.
7
+ *
8
+ * This file installs a no-op-if-already-defined polyfill that works both in
9
+ * Node and in the browser. It MUST be imported FIRST from any module that
10
+ * transitively loads `@pascal-app/core/store`, otherwise the core module will
11
+ * throw at import time.
12
+ *
13
+ * Side-effectful on import: there is no exported API — just import this file.
14
+ */
15
+ const g = globalThis;
16
+ if (typeof g.requestAnimationFrame === 'undefined') {
17
+ g.requestAnimationFrame = (cb) => {
18
+ const now = typeof performance !== 'undefined' ? performance.now() : Date.now();
19
+ return setTimeout(() => cb(now), 0);
20
+ };
21
+ g.cancelAnimationFrame = (id) => {
22
+ clearTimeout(id);
23
+ };
24
+ }
25
+ export {};