scanruler-mcp 0.1.0 → 0.3.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 +144 -8
- package/package.json +30 -30
- package/src/commands.json +1353 -68
- package/src/config.js +1 -1
- package/src/engine.js +69 -0
- package/src/link.js +10 -2
- package/src/main.js +74 -7
- package/src/mcp.js +434 -30
- package/src/relay.js +2 -1
- package/src/serve.js +1 -1
- package/src/tools.js +41 -1
- package/src/trace.js +55 -0
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ provider, like everything else it reads.
|
|
|
14
14
|
|
|
15
15
|
## Setting up
|
|
16
16
|
|
|
17
|
-
You need [Node.js](https://nodejs.org/)
|
|
17
|
+
You need [Node.js](https://nodejs.org/) 22 or newer, and Chrome, Edge or
|
|
18
18
|
Firefox.
|
|
19
19
|
|
|
20
20
|
### 1. Add the server to your agent
|
|
@@ -111,7 +111,8 @@ switch on **Local agent connection**, and enter the port and the token. The
|
|
|
111
111
|
token is made on the first run and kept in your config folder
|
|
112
112
|
(`%APPDATA%\scanruler-mcp` on Windows,
|
|
113
113
|
`~/Library/Application Support/scanruler-mcp` on macOS,
|
|
114
|
-
|
|
114
|
+
`$XDG_CONFIG_HOME/scanruler-mcp` elsewhere, `~/.config/scanruler-mcp`
|
|
115
|
+
without it). `scanruler_status` hands out the same
|
|
115
116
|
link.
|
|
116
117
|
|
|
117
118
|
A chip in ScanRuler's top bar says where the connection stands: **listening**
|
|
@@ -146,6 +147,78 @@ serves a built ScanRuler (`npm run build` in a clone) at
|
|
|
146
147
|
for working offline, and for the open-source build. A page from there needs
|
|
147
148
|
no browser permission, and `--pair` then gives a link to it.
|
|
148
149
|
|
|
150
|
+
### Without a browser (headless)
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
npm run build:engine # in a clone of ScanRuler
|
|
154
|
+
npx scanruler-mcp --engine /path/to/scanruler/dist-engine/engine.mjs
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
runs ScanRuler itself inside the server, with no browser and no tab: the
|
|
158
|
+
engine is a build of the app for Node, `dist-engine/engine.mjs`, which the
|
|
159
|
+
server loads from where it is and names nothing in. `--headless` takes the
|
|
160
|
+
engine built in the checkout the server is part of; `SCANRULER_ENGINE` gives
|
|
161
|
+
a path as `--engine` does. There is no scan, no viewport and nobody
|
|
162
|
+
watching, so only the commands that run without a page are tools —
|
|
163
|
+
`session_state` and `history_undo`/`history_redo` in the app itself, and
|
|
164
|
+
whatever a build of ScanRuler marks to run so; a tool that needs the page
|
|
165
|
+
(`view_render`, `scan_open` …) answers `unavailable`, naming the page mode.
|
|
166
|
+
Files still go by `path`. The server's instructions say it runs headless,
|
|
167
|
+
`scanruler_status` says `backend: "headless"` and where the engine is, and
|
|
168
|
+
`scanruler_open` has nothing to open. Each server is a session of its own —
|
|
169
|
+
an agent's subagents can each start one and work side by side, sharing
|
|
170
|
+
nothing — and what the app logs goes to stderr with the server's own lines.
|
|
171
|
+
`--tools compact` works headless too: the server's five tools are listed, and
|
|
172
|
+
the engine's are read through `scanruler_tools` and run through
|
|
173
|
+
`scanruler_batch` (see *Small local models*).
|
|
174
|
+
### Small local models
|
|
175
|
+
|
|
176
|
+
Listed in full, every command the tab offers is a tool of its own: some sixty
|
|
177
|
+
tools and 25k tokens of schema in the open-source build, over a hundred and
|
|
178
|
+
50k tokens at scanruler.com, all read before the conversation starts. A
|
|
179
|
+
frontier model takes that in its stride. A local model with a 128k window
|
|
180
|
+
runs out of room within a few tool rounds, and picks less reliably among so
|
|
181
|
+
many. For such a model, start the server with `--tools compact` (or set
|
|
182
|
+
`SCANRULER_MCP_TOOLS=compact`). It then lists seven tools, about 3k tokens:
|
|
183
|
+
the server's four, `scanruler_tools`, and the tab's tools that return a
|
|
184
|
+
picture — `view_render` and `flat_render`.
|
|
185
|
+
|
|
186
|
+
- `scanruler_tools` with no arguments lists the tab's tools, a name and a
|
|
187
|
+
line each, about 1.5k tokens for a hundred. With `names` it gives those
|
|
188
|
+
tools' full definitions, as the full list has them, up to 20 at a time.
|
|
189
|
+
Names it does not know come back in `unknown`.
|
|
190
|
+
- The model runs the tools through `scanruler_batch`, one call as a batch of
|
|
191
|
+
one. A refused call comes back with the tab's error code and what was
|
|
192
|
+
wrong, so the model can try again with corrected arguments. A client that
|
|
193
|
+
lets the model call a tool it was not given can call them directly too.
|
|
194
|
+
- The tools that return a picture are listed so the model calls them
|
|
195
|
+
directly and sees the picture. In a batch a picture comes back as text
|
|
196
|
+
only, written to a file when the call gives `path`. A tab that connects
|
|
197
|
+
with another picture tool has the client list the tools again.
|
|
198
|
+
|
|
199
|
+
**LM Studio**: add `--tools` and `compact` to the server's arguments as two
|
|
200
|
+
separate entries. In `mcp.json`:
|
|
201
|
+
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"mcpServers": {
|
|
205
|
+
"scanruler": {
|
|
206
|
+
"command": "npx",
|
|
207
|
+
"args": ["-y", "scanruler-mcp", "--tools", "compact"]
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Codex** with a local model:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
codex mcp add scanruler -- npx -y scanruler-mcp --tools compact
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
or `args = ["-y", "scanruler-mcp", "--tools", "compact"]` in
|
|
220
|
+
`~/.codex/config.toml`.
|
|
221
|
+
|
|
149
222
|
## Options
|
|
150
223
|
|
|
151
224
|
| Option | |
|
|
@@ -155,6 +228,11 @@ no browser permission, and `--pair` then gives a link to it.
|
|
|
155
228
|
| `--serve <dir>` | Also serve a built ScanRuler from `<dir>`. |
|
|
156
229
|
| `--allow-origin <url>` | Let a ScanRuler tab from another address connect — a self-hosted copy. Repeatable. scanruler.com and any page on this computer are always allowed. |
|
|
157
230
|
| `--app <url>` | The ScanRuler address the pairing link opens. |
|
|
231
|
+
| `--engine <file>` | Run ScanRuler headless in the server, from an engine built with `npm run build:engine` — no tab. Also `SCANRULER_ENGINE`. |
|
|
232
|
+
| `--headless` | The same, with the engine built in the checkout the server is part of. |
|
|
233
|
+
| `--tools <mode>` | `full`, the default: every command the tab offers is a tool. `compact`: the server's five and the tab's tools that return a picture; the rest are read through `scanruler_tools` and run through `scanruler_batch` — see *Small local models*. Also `SCANRULER_MCP_TOOLS`. |
|
|
234
|
+
| `--trace <dir>` | Write a line per tool call — tool, ms, bytes each way, error, whether the tab was hidden — into a new file in `<dir>`, for measuring a run; off unless given. `scripts/trace-summary.mjs` in the ScanRuler repository sums a run up. Also `SCANRULER_MCP_TRACE`. |
|
|
235
|
+
| `--trace-args` | With `--trace`, each call's arguments too; otherwise only their size. |
|
|
158
236
|
| `--pair` | Print the port, the token and the pairing link, and exit. |
|
|
159
237
|
| `--help` | The options. |
|
|
160
238
|
|
|
@@ -171,13 +249,21 @@ with more commands lists more. Before a tab is there it is the list of the
|
|
|
171
249
|
last tab that connected, which the server keeps in its config folder
|
|
172
250
|
(`tools.json`), or, the first time, the app's own; a client that lists the
|
|
173
251
|
tools once, when it starts, sees them all that way, and one that listens is
|
|
174
|
-
told when the list changes.
|
|
252
|
+
told when the list changes. Four more are the server's: `scanruler_status`
|
|
175
253
|
(is a tab connected, which version, how to pair one), `scanruler_open` (open
|
|
176
|
-
ScanRuler in the default browser, paired, and wait for it)
|
|
177
|
-
(until a tab is connected, or also idle)
|
|
254
|
+
ScanRuler in the default browser, paired, and wait for it), `scanruler_wait`
|
|
255
|
+
(until a tab is connected, or also idle) and `scanruler_batch` (several of
|
|
256
|
+
the tab's tools in one call, in order — a run of `scan_nearest` or
|
|
257
|
+
`element_fit` calls — each answer in its place, stopped at the first refused
|
|
258
|
+
unless told to go on). With `--tools compact` only the tab's tools that
|
|
259
|
+
return a picture are listed; the rest are read through a fifth of the
|
|
260
|
+
server's own, `scanruler_tools` — see *Small local models*.
|
|
178
261
|
|
|
179
262
|
- **Places on the scan** are `{ "point": [x, y, z] }` in millimetres — the
|
|
180
|
-
scan vertex nearest it is taken — or `{ "vertex": n }`.
|
|
263
|
+
scan vertex nearest it is taken — or `{ "vertex": n }`. `scan_query` reads
|
|
264
|
+
the scan's own points: the vertices in a box or about a point, with their
|
|
265
|
+
normals, facing a way if asked; `section_get` a section's cut as
|
|
266
|
+
polylines, on the sheet and in the world.
|
|
181
267
|
- **Files are paths.** `scan_open`, `deviation_open_reference`,
|
|
182
268
|
`flat_open_image` and `project_load` take `path`, a file on this computer;
|
|
183
269
|
the server reads it and streams it to the tab. The exports and
|
|
@@ -185,9 +271,59 @@ ScanRuler in the default browser, paired, and wait for it) and `scanruler_wait`
|
|
|
185
271
|
ScanRuler gives it — and the server writes the file there and returns where.
|
|
186
272
|
- **Pictures.** `view_render` comes back as an image the agent sees — the
|
|
187
273
|
3D view as the tab shows it, after `view_set` has turned it if asked — and
|
|
188
|
-
is written to a file only when the call gives `path`.
|
|
274
|
+
is written to a file only when the call gives `path`. `show` puts the
|
|
275
|
+
scan, the reference, the sections, the elements or the labels away for
|
|
276
|
+
the picture (`{ "scan": false }` shows what was built on the scan on its
|
|
277
|
+
own; a workspace adds keys of its own, which `view_set` lists), and
|
|
278
|
+
`frame` looks at a box or a place closely. `flat_render` is the 2D sheet's:
|
|
279
|
+
drawn as the sheet lies, with a grid ruled in millimetres and its values
|
|
280
|
+
at the margins, and a `map` from picture pixels to sheet places, so a
|
|
281
|
+
place read off the picture goes straight back as a place to click.
|
|
282
|
+
`section_render` pictures a section — or any plane, cut for the picture
|
|
283
|
+
alone — on its own sheet in millimetres: the scan's cut as points, what
|
|
284
|
+
the workspaces draw over it (a model's cut, a sketch's pieces with their
|
|
285
|
+
ids), and every point further than the tolerance from the model in red,
|
|
286
|
+
counted in the answer with the box round them.
|
|
287
|
+
- **The 2D sheet** takes places as `[x, y]` in its own millimetres (pixels
|
|
288
|
+
before any scale), origin at the image's bottom-left corner, y up. An edge
|
|
289
|
+
is named by its chain number — `flat_edges` lists them, `flat_render`
|
|
290
|
+
writes them beside the edges, `flat_fit` takes them as `chains` — and a
|
|
291
|
+
pick answers where it snapped. `flat_objects` finds the separate parts on
|
|
292
|
+
an image and measures them; `flat_profile` reads an outline round a centre
|
|
293
|
+
or along an axis and counts its peaks — a knurl's teeth, say.
|
|
294
|
+
- **Batches stand or fall whole.** `scanruler_batch` stops at the first
|
|
295
|
+
refused call, and with `rollback` (the default) undoes what the calls
|
|
296
|
+
before it changed — the answer names `failed: { index, tool, error }` and
|
|
297
|
+
`undone`, the steps taken back, newest first (`history_checkpoint` and
|
|
298
|
+
`history_restore` do the same by hand across several calls). `timings:
|
|
299
|
+
true` gives each call its `ms`, the batch its `total_ms`, and a change's
|
|
300
|
+
build the steps it played with their milliseconds (`result.steps.played`;
|
|
301
|
+
otherwise only how many it played and took up from a state held).
|
|
302
|
+
- **Long changes.** Every tool that changes the session takes `wait`;
|
|
303
|
+
`wait: false` answers at once with `{ queued: true, job }` while the
|
|
304
|
+
change runs on, a batch too, and `scanruler_wait { for: "job", job }`
|
|
305
|
+
gives its answer when it is done. The server keeps every change as a job,
|
|
306
|
+
so a call the client stopped waiting for keeps running and keeps its
|
|
307
|
+
answer: `scanruler_status` lists the latest jobs, running, done or failed.
|
|
308
|
+
`scanruler_wait { for: "idle" }` waits for the changes still running too,
|
|
309
|
+
and answers `finished`: every change done or failed since it last said —
|
|
310
|
+
a failed one with its error, a batch with the call it stopped at — each
|
|
311
|
+
once, so a run of `wait: false` changes is read back in one call. A
|
|
312
|
+
change called on its own takes `timings: true` as well: its answer adds
|
|
313
|
+
the call's `ms` and the steps its build played. The tab answers what only
|
|
314
|
+
reads — `session_state` — while a change runs.
|
|
315
|
+
- **A hidden tab.** A tab in the background, a covered or minimised window
|
|
316
|
+
or a locked screen is hidden to the browser, which then draws no frames
|
|
317
|
+
and slows timers to once a minute. Changes still run, and the waits a
|
|
318
|
+
command makes — for the session to settle, for the screen to follow —
|
|
319
|
+
look again on every change the session's stores see and draw their own
|
|
320
|
+
frames, so pictures still come. `scanruler_status` says `page.hidden`,
|
|
321
|
+
with a note: a browser that unloads background tabs (Chrome's Memory
|
|
322
|
+
Saver) may close it — keep the site among those always kept active.
|
|
189
323
|
- **Results** are the session as the tab holds it afterwards, as JSON, in
|
|
190
|
-
millimetres and degrees
|
|
324
|
+
millimetres and degrees, every number that is not whole kept to six
|
|
325
|
+
significant digits — except in an answer whose numbers are handed back
|
|
326
|
+
as inputs (a plane's frame, say), which keeps every digit. A refusal comes back as an error with a code —
|
|
191
327
|
`no_scan`, `busy`, `invalid_input`, `invalid_state` (something open in a
|
|
192
328
|
panel is in the way), `not_found`, `failed`, `unavailable`.
|
|
193
329
|
|
package/package.json
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "scanruler-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Drive ScanRuler, the 3D-scan measuring tool, from an AI agent: a local MCP server that reaches the ScanRuler tab open in your browser.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "AGPL-3.0-only",
|
|
7
|
-
"author": "Stefan Hermann (CNC Kitchen) <stefan@cnckitchen.com>",
|
|
8
|
-
"homepage": "https://scanruler.com/",
|
|
9
|
-
"repository": {
|
|
10
|
-
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/CNCKitchen/scanruler.git",
|
|
12
|
-
"directory": "mcp"
|
|
13
|
-
},
|
|
14
|
-
"keywords": ["mcp", "model-context-protocol", "3d-scan", "metrology", "scanruler"],
|
|
15
|
-
"bin": {
|
|
16
|
-
"scanruler-mcp": "bin/scanruler-mcp.js"
|
|
17
|
-
},
|
|
18
|
-
"files": ["bin", "src", "README.md", "LICENSE"],
|
|
19
|
-
"engines": {
|
|
20
|
-
"node": ">=
|
|
21
|
-
},
|
|
22
|
-
"scripts": {
|
|
23
|
-
"start": "node bin/scanruler-mcp.js",
|
|
24
|
-
"test": "node --test"
|
|
25
|
-
},
|
|
26
|
-
"dependencies": {
|
|
27
|
-
"@modelcontextprotocol/sdk": "1.32.1",
|
|
28
|
-
"ws": "8.22.0"
|
|
29
|
-
}
|
|
30
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "scanruler-mcp",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Drive ScanRuler, the 3D-scan measuring tool, from an AI agent: a local MCP server that reaches the ScanRuler tab open in your browser.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "AGPL-3.0-only",
|
|
7
|
+
"author": "Stefan Hermann (CNC Kitchen) <stefan@cnckitchen.com>",
|
|
8
|
+
"homepage": "https://scanruler.com/",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/CNCKitchen/scanruler.git",
|
|
12
|
+
"directory": "mcp"
|
|
13
|
+
},
|
|
14
|
+
"keywords": ["mcp", "model-context-protocol", "3d-scan", "metrology", "scanruler"],
|
|
15
|
+
"bin": {
|
|
16
|
+
"scanruler-mcp": "bin/scanruler-mcp.js"
|
|
17
|
+
},
|
|
18
|
+
"files": ["bin", "src", "README.md", "LICENSE"],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=22"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"start": "node bin/scanruler-mcp.js",
|
|
24
|
+
"test": "node --test"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@modelcontextprotocol/sdk": "1.32.1",
|
|
28
|
+
"ws": "8.22.0"
|
|
29
|
+
}
|
|
30
|
+
}
|