scanruler-mcp 0.3.0 → 0.4.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 +107 -40
- package/bin/scanruler-mcp.js +15 -8
- package/package.json +1 -1
- package/src/commands.json +4611 -3584
- package/src/config.js +38 -6
- package/src/link.js +304 -293
- package/src/main.js +44 -11
- package/src/mcp.js +1155 -615
- package/src/relay.js +218 -218
- package/src/tools.js +192 -17
- package/src/update.js +380 -0
package/README.md
CHANGED
|
@@ -10,7 +10,8 @@ history, so you can take any of it back.
|
|
|
10
10
|
Nothing goes anywhere but your own computer: the scan travels from your disk
|
|
11
11
|
to your browser through this server, as it does when you open it yourself.
|
|
12
12
|
What the agent reads — the figures, the report — goes to the agent's model
|
|
13
|
-
provider, like everything else it reads.
|
|
13
|
+
provider, like everything else it reads. The server's one request of its own
|
|
14
|
+
asks npm, once a day, for its latest version (see *Staying current*).
|
|
14
15
|
|
|
15
16
|
## Setting up
|
|
16
17
|
|
|
@@ -22,7 +23,7 @@ Firefox.
|
|
|
22
23
|
**Claude Code**
|
|
23
24
|
|
|
24
25
|
```bash
|
|
25
|
-
claude mcp add --scope user scanruler -- npx -y scanruler-mcp
|
|
26
|
+
claude mcp add --scope user scanruler -- npx -y scanruler-mcp@latest
|
|
26
27
|
```
|
|
27
28
|
|
|
28
29
|
`--scope user` has it in every project; leave it out for this one only.
|
|
@@ -32,7 +33,7 @@ unless the agent names a path.
|
|
|
32
33
|
**Codex** — the CLI, the IDE extension and the app share one setting:
|
|
33
34
|
|
|
34
35
|
```bash
|
|
35
|
-
codex mcp add scanruler -- npx -y scanruler-mcp
|
|
36
|
+
codex mcp add scanruler -- npx -y scanruler-mcp@latest
|
|
36
37
|
```
|
|
37
38
|
|
|
38
39
|
or, in `~/.codex/config.toml`, with room for the first start (npx fetches the
|
|
@@ -42,7 +43,7 @@ call 60 unless told otherwise:
|
|
|
42
43
|
```toml
|
|
43
44
|
[mcp_servers.scanruler]
|
|
44
45
|
command = "npx"
|
|
45
|
-
args = ["-y", "scanruler-mcp"]
|
|
46
|
+
args = ["-y", "scanruler-mcp@latest"]
|
|
46
47
|
startup_timeout_sec = 60
|
|
47
48
|
tool_timeout_sec = 300
|
|
48
49
|
```
|
|
@@ -55,7 +56,7 @@ tool_timeout_sec = 300
|
|
|
55
56
|
"mcpServers": {
|
|
56
57
|
"scanruler": {
|
|
57
58
|
"command": "npx",
|
|
58
|
-
"args": ["-y", "scanruler-mcp", "--out", "/Users/you/Documents/ScanRuler"]
|
|
59
|
+
"args": ["-y", "scanruler-mcp@latest", "--out", "/Users/you/Documents/ScanRuler"]
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
}
|
|
@@ -76,7 +77,7 @@ tab connects to it, and the other sends its calls through that one — see
|
|
|
76
77
|
"mcpServers": {
|
|
77
78
|
"scanruler": {
|
|
78
79
|
"command": "npx",
|
|
79
|
-
"args": ["-y", "scanruler-mcp"]
|
|
80
|
+
"args": ["-y", "scanruler-mcp@latest"]
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
}
|
|
@@ -85,7 +86,7 @@ tab connects to it, and the other sends its calls through that one — see
|
|
|
85
86
|
**From a clone of the ScanRuler repository**, to try a change: run
|
|
86
87
|
`npm --prefix mcp ci` once, then use
|
|
87
88
|
`node /path/to/scanruler/mcp/bin/scanruler-mcp.js` where the examples say
|
|
88
|
-
`npx -y scanruler-mcp` — in Claude Code,
|
|
89
|
+
`npx -y scanruler-mcp@latest` — in Claude Code,
|
|
89
90
|
`claude mcp add scanruler -- node /path/to/scanruler/mcp/bin/scanruler-mcp.js`.
|
|
90
91
|
|
|
91
92
|
### 2. Let the agent open ScanRuler
|
|
@@ -102,7 +103,7 @@ When the default browser is not the one you use ScanRuler in, or the agent
|
|
|
102
103
|
runs somewhere it cannot open one, pair by hand:
|
|
103
104
|
|
|
104
105
|
```bash
|
|
105
|
-
npx -y scanruler-mcp --pair
|
|
106
|
+
npx -y scanruler-mcp@latest --pair
|
|
106
107
|
```
|
|
107
108
|
|
|
108
109
|
prints the port, the pairing token and a link. Open the link once in the
|
|
@@ -123,6 +124,32 @@ access* for the site in the site settings (the icon left of the address) and
|
|
|
123
124
|
reload. **Safari** does not let a page from the web reach the computer it
|
|
124
125
|
runs on at all; use `--serve` below.
|
|
125
126
|
|
|
127
|
+
### Staying current
|
|
128
|
+
|
|
129
|
+
With `@latest` in the setting, npx asks npm for the newest version at every
|
|
130
|
+
start and fetches it when there is one, so quitting the app and opening it
|
|
131
|
+
again updates the server. Offline, npx starts the copy it has; on a network
|
|
132
|
+
that turns connections away rather than having none, it tries for about a
|
|
133
|
+
minute first. (npm 9.7 and newer treat a bare `scanruler-mcp` the same way.)
|
|
134
|
+
|
|
135
|
+
The server also keeps itself current — for a setting without `@latest` on an
|
|
136
|
+
older npm, or a copy installed with `npm install -g`. Once a day, in the
|
|
137
|
+
background, it asks npm's registry (registry.npmjs.org, or the one npm is set
|
|
138
|
+
to) for its latest version: that one request, given 2 seconds. When there is
|
|
139
|
+
a newer one, npm fetches it into its cache, and the next start runs it in
|
|
140
|
+
place of the old copy, without waiting on the network. What it found is kept
|
|
141
|
+
in `update.json` in the config folder, and `scanruler_status` says it:
|
|
142
|
+
`server.version`, `server.latest`, and `server.handedOffFrom` when an older
|
|
143
|
+
copy started this one. A version pinned in the setting
|
|
144
|
+
(`scanruler-mcp@0.4.0`) is left as it is, and so is a copy run from a clone
|
|
145
|
+
of the repository. To turn it off: `--no-update`, or
|
|
146
|
+
`SCANRULER_MCP_NO_UPDATE=1`.
|
|
147
|
+
|
|
148
|
+
When ScanRuler shows **Your ScanRuler connector is out of date**, the server
|
|
149
|
+
your app starts is older than the page needs: change the setting to the
|
|
150
|
+
`@latest` line it shows, then quit the app completely — not just its window —
|
|
151
|
+
and open it again.
|
|
152
|
+
|
|
126
153
|
### Several agents
|
|
127
154
|
|
|
128
155
|
Any number of copies of the server can share the tab. The first to start
|
|
@@ -139,7 +166,7 @@ the tab, each one step of the same undo history.
|
|
|
139
166
|
### Serving the app yourself
|
|
140
167
|
|
|
141
168
|
```bash
|
|
142
|
-
npx scanruler-mcp --serve /path/to/scanruler/dist
|
|
169
|
+
npx -y scanruler-mcp@latest --serve /path/to/scanruler/dist
|
|
143
170
|
```
|
|
144
171
|
|
|
145
172
|
serves a built ScanRuler (`npm run build` in a clone) at
|
|
@@ -151,7 +178,7 @@ no browser permission, and `--pair` then gives a link to it.
|
|
|
151
178
|
|
|
152
179
|
```bash
|
|
153
180
|
npm run build:engine # in a clone of ScanRuler
|
|
154
|
-
npx scanruler-mcp --engine /path/to/scanruler/dist-engine/engine.mjs
|
|
181
|
+
npx -y scanruler-mcp@latest --engine /path/to/scanruler/dist-engine/engine.mjs
|
|
155
182
|
```
|
|
156
183
|
|
|
157
184
|
runs ScanRuler itself inside the server, with no browser and no tab: the
|
|
@@ -168,7 +195,7 @@ Files still go by `path`. The server's instructions say it runs headless,
|
|
|
168
195
|
`scanruler_open` has nothing to open. Each server is a session of its own —
|
|
169
196
|
an agent's subagents can each start one and work side by side, sharing
|
|
170
197
|
nothing — and what the app logs goes to stderr with the server's own lines.
|
|
171
|
-
`--tools compact` works headless too: the server's
|
|
198
|
+
`--tools compact` works headless too: the server's own tools are listed, and
|
|
172
199
|
the engine's are read through `scanruler_tools` and run through
|
|
173
200
|
`scanruler_batch` (see *Small local models*).
|
|
174
201
|
### Small local models
|
|
@@ -179,9 +206,10 @@ tools and 25k tokens of schema in the open-source build, over a hundred and
|
|
|
179
206
|
frontier model takes that in its stride. A local model with a 128k window
|
|
180
207
|
runs out of room within a few tool rounds, and picks less reliably among so
|
|
181
208
|
many. For such a model, start the server with `--tools compact` (or set
|
|
182
|
-
`SCANRULER_MCP_TOOLS=compact`). It then lists
|
|
183
|
-
the server's
|
|
184
|
-
picture — `view_render`
|
|
209
|
+
`SCANRULER_MCP_TOOLS=compact`). It then lists about a dozen tools, a few
|
|
210
|
+
thousand tokens: the server's six, `scanruler_tools`, and the tab's tools
|
|
211
|
+
that return a picture — `view_render`, `section_render`, `flat_render` and
|
|
212
|
+
`scan_heightmap`.
|
|
185
213
|
|
|
186
214
|
- `scanruler_tools` with no arguments lists the tab's tools, a name and a
|
|
187
215
|
line each, about 1.5k tokens for a hundred. With `names` it gives those
|
|
@@ -192,9 +220,10 @@ picture — `view_render` and `flat_render`.
|
|
|
192
220
|
wrong, so the model can try again with corrected arguments. A client that
|
|
193
221
|
lets the model call a tool it was not given can call them directly too.
|
|
194
222
|
- The tools that return a picture are listed so the model calls them
|
|
195
|
-
directly and sees the picture. In a batch a picture
|
|
196
|
-
|
|
197
|
-
|
|
223
|
+
directly and sees the picture. In a batch a picture is not shown: it is
|
|
224
|
+
written to a file, the call's `path` or a new file in the output folder
|
|
225
|
+
(`--out`), and the call's result names it. A tab that connects with
|
|
226
|
+
another picture tool has the client list the tools again.
|
|
198
227
|
|
|
199
228
|
**LM Studio**: add `--tools` and `compact` to the server's arguments as two
|
|
200
229
|
separate entries. In `mcp.json`:
|
|
@@ -204,7 +233,7 @@ separate entries. In `mcp.json`:
|
|
|
204
233
|
"mcpServers": {
|
|
205
234
|
"scanruler": {
|
|
206
235
|
"command": "npx",
|
|
207
|
-
"args": ["-y", "scanruler-mcp", "--tools", "compact"]
|
|
236
|
+
"args": ["-y", "scanruler-mcp@latest", "--tools", "compact"]
|
|
208
237
|
}
|
|
209
238
|
}
|
|
210
239
|
}
|
|
@@ -213,10 +242,10 @@ separate entries. In `mcp.json`:
|
|
|
213
242
|
**Codex** with a local model:
|
|
214
243
|
|
|
215
244
|
```bash
|
|
216
|
-
codex mcp add scanruler -- npx -y scanruler-mcp --tools compact
|
|
245
|
+
codex mcp add scanruler -- npx -y scanruler-mcp@latest --tools compact
|
|
217
246
|
```
|
|
218
247
|
|
|
219
|
-
or `args = ["-y", "scanruler-mcp", "--tools", "compact"]` in
|
|
248
|
+
or `args = ["-y", "scanruler-mcp@latest", "--tools", "compact"]` in
|
|
220
249
|
`~/.codex/config.toml`.
|
|
221
250
|
|
|
222
251
|
## Options
|
|
@@ -230,10 +259,13 @@ or `args = ["-y", "scanruler-mcp", "--tools", "compact"]` in
|
|
|
230
259
|
| `--app <url>` | The ScanRuler address the pairing link opens. |
|
|
231
260
|
| `--engine <file>` | Run ScanRuler headless in the server, from an engine built with `npm run build:engine` — no tab. Also `SCANRULER_ENGINE`. |
|
|
232
261
|
| `--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
|
|
262
|
+
| `--tools <mode>` | `full`, the default: every command the tab offers is a tool. `compact`: the server's own 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
263
|
| `--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
264
|
| `--trace-args` | With `--trace`, each call's arguments too; otherwise only their size. |
|
|
265
|
+
| `--answer-within <s>` | Hold a call at most this many seconds, 45 unless given: a change, a batch or a long read still running then answers `{ queued: true, job, after }` and runs on, and `scanruler_wait` waits no longer either. A client, or a bridge between it and this computer, may cut a call at a minute; a cut call says nothing. `0` holds every call to its end. Also `SCANRULER_MCP_ANSWER_WITHIN`. |
|
|
266
|
+
| `--no-update` | Do not keep the server current (see *Staying current*). Also `SCANRULER_MCP_NO_UPDATE=1`. |
|
|
236
267
|
| `--pair` | Print the port, the token and the pairing link, and exit. |
|
|
268
|
+
| `--version` | Print the server's version, and exit. |
|
|
237
269
|
| `--help` | The options. |
|
|
238
270
|
|
|
239
271
|
`SCANRULER_MCP_TOKEN` overrides the stored token; `SCANRULER_MCP_CONFIG_DIR`
|
|
@@ -249,15 +281,24 @@ with more commands lists more. Before a tab is there it is the list of the
|
|
|
249
281
|
last tab that connected, which the server keeps in its config folder
|
|
250
282
|
(`tools.json`), or, the first time, the app's own; a client that lists the
|
|
251
283
|
tools once, when it starts, sees them all that way, and one that listens is
|
|
252
|
-
told when the list changes.
|
|
253
|
-
(is a tab connected, which version, how to pair one
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
`
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
284
|
+
told when the list changes. Six more are the server's: `scanruler_status`
|
|
285
|
+
(is a tab connected, which version, how to pair one, and what the tab's
|
|
286
|
+
session holds — its scan, project, unsaved changes and work counted — and,
|
|
287
|
+
when the client's list is out of date, which tools were added, removed or
|
|
288
|
+
changed since and which page it came from),
|
|
289
|
+
`scanruler_open` (open ScanRuler in the default browser, paired, and wait
|
|
290
|
+
for it), `scanruler_wait` (until a tab is connected, or also idle),
|
|
291
|
+
`scanruler_batch` (several of the tab's tools in one call, in order — a run
|
|
292
|
+
of `scan_nearest` or `element_fit` calls — each answer in its place, stopped
|
|
293
|
+
at the first refused unless told to go on), `scanruler_write` (a text
|
|
294
|
+
file — `.md`, `.txt`, `.csv` or `.json`, a report say — written in the folder
|
|
295
|
+
the server last opened a scan or a project from, and nowhere else) and
|
|
296
|
+
`scanruler_files` (that folder's files — name, size, kind — newest first).
|
|
297
|
+
The folders are kept beside `tools.json` (`folders.json`), so a server
|
|
298
|
+
started afresh still writes and lists there, and `scanruler_status` lists
|
|
299
|
+
them. With `--tools compact` only the tab's tools that return a picture are
|
|
300
|
+
listed; the rest are read through a seventh of the server's own,
|
|
301
|
+
`scanruler_tools` — see *Small local models*.
|
|
261
302
|
|
|
262
303
|
- **Places on the scan** are `{ "point": [x, y, z] }` in millimetres — the
|
|
263
304
|
scan vertex nearest it is taken — or `{ "vertex": n }`. `scan_query` reads
|
|
@@ -269,6 +310,18 @@ server's own, `scanruler_tools` — see *Small local models*.
|
|
|
269
310
|
the server reads it and streams it to the tab. The exports and
|
|
270
311
|
`project_save` take an optional `path` — a file, or a folder for the name
|
|
271
312
|
ScanRuler gives it — and the server writes the file there and returns where.
|
|
313
|
+
`scan_open` and `project_load` take a folder too, and open the one mesh,
|
|
314
|
+
or the one `.scanruler`, in it; with none or several the answer lists what
|
|
315
|
+
is there.
|
|
316
|
+
A tool that replaces the session (`scan_open`, `project_load`,
|
|
317
|
+
`session_reset`) takes `saveTo`: the session is saved as a project file
|
|
318
|
+
there first, then replaced — one call to keep what a tab someone left
|
|
319
|
+
holds.
|
|
320
|
+
- **Arguments from a file.** `{ "$file": path }` anywhere in a tool's
|
|
321
|
+
arguments — an object of that one key — is replaced by the JSON the file
|
|
322
|
+
holds: a long list of stations a script wrote need not pass through the
|
|
323
|
+
conversation. A relative path is taken from the folder the server was
|
|
324
|
+
started in.
|
|
272
325
|
- **Pictures.** `view_render` comes back as an image the agent sees — the
|
|
273
326
|
3D view as the tab shows it, after `view_set` has turned it if asked — and
|
|
274
327
|
is written to a file only when the call gives `path`. `show` puts the
|
|
@@ -292,23 +345,37 @@ server's own, `scanruler_tools` — see *Small local models*.
|
|
|
292
345
|
an image and measures them; `flat_profile` reads an outline round a centre
|
|
293
346
|
or along an axis and counts its peaks — a knurl's teeth, say.
|
|
294
347
|
- **Batches stand or fall whole.** `scanruler_batch` stops at the first
|
|
295
|
-
refused call
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
`history_restore` do the same
|
|
348
|
+
refused call — the answer names `failed: { index, tool, error }`. When
|
|
349
|
+
that call is a change, `rollback` (the default) undoes what the calls
|
|
350
|
+
before it changed, once the tab is idle: `undone` lists the steps taken
|
|
351
|
+
back, newest first (`history_checkpoint` and `history_restore` do the same
|
|
352
|
+
by hand across several calls). A refused read — a `section_get` of a
|
|
353
|
+
section that is not there — undoes nothing, and `kept` says what came
|
|
354
|
+
before it stands. A call refused `busy` (the tab working out something of
|
|
355
|
+
its own) is run again once, when the tab is idle, and says
|
|
356
|
+
`retried: "busy"`. `timings:
|
|
299
357
|
true` gives each call its `ms`, the batch its `total_ms`, and a change's
|
|
300
358
|
build the steps it played with their milliseconds (`result.steps.played`;
|
|
301
359
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
360
|
+
- **Long changes.** Every tool that changes the session takes `wait`, and
|
|
361
|
+
so does a read the tab marks long (a comparison over whole bodies, say);
|
|
362
|
+
`wait: false` answers at once with `{ queued: true, job }` while the call
|
|
363
|
+
runs on, a batch too, and `scanruler_wait { for: "job", job }` gives its
|
|
364
|
+
answer when it is done. Still running at the answer limit
|
|
365
|
+
(`--answer-within`, 45 s), a call answers the same way with `after`, its
|
|
366
|
+
seconds; `scanruler_wait` waits no longer than that either and answers
|
|
367
|
+
`state: "running"` — call it again. The server keeps every change as a
|
|
368
|
+
job, so a call the client stopped waiting for keeps running and keeps its
|
|
307
369
|
answer: `scanruler_status` lists the latest jobs, running, done or failed.
|
|
370
|
+
The tab keeps a record of the commands run under each job too
|
|
371
|
+
(`session_recent`, the last hundred), so a server started afresh — whose
|
|
372
|
+
own jobs are gone — still answers `scanruler_wait` for a job of the copy
|
|
373
|
+
before it, from that record (`from` says so).
|
|
308
374
|
`scanruler_wait { for: "idle" }` waits for the changes still running too,
|
|
309
375
|
and answers `finished`: every change done or failed since it last said —
|
|
310
376
|
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
|
|
377
|
+
once, so a run of `wait: false` changes is read back in one call, and
|
|
378
|
+
`state`: `idle`, or `running` when the limit came first. A
|
|
312
379
|
change called on its own takes `timings: true` as well: its answer adds
|
|
313
380
|
the call's `ms` and the steps its build played. The tab answers what only
|
|
314
381
|
reads — `session_state` — while a change runs.
|
package/bin/scanruler-mcp.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
+
// The launcher: a newer copy of the server, fetched in the background by an
|
|
4
|
+
// earlier start, runs in this one's place (src/update.js); otherwise this
|
|
5
|
+
// one does. Nothing here waits on the network.
|
|
6
|
+
import { launch } from '../src/update.js'
|
|
7
|
+
|
|
8
|
+
const code = await launch(process.argv.slice(2))
|
|
9
|
+
if (code !== null) process.exit(code)
|
|
10
|
+
|
|
11
|
+
const { main } = await import('../src/main.js')
|
|
12
|
+
main().catch((error) => {
|
|
13
|
+
process.stderr.write(`[scanruler-mcp] ${error instanceof Error ? error.message : String(error)}\n`)
|
|
14
|
+
process.exit(1)
|
|
15
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scanruler-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
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
5
|
"type": "module",
|
|
6
6
|
"license": "AGPL-3.0-only",
|