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 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 five tools are listed, and
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 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`.
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 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.
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 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`. |
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. Four more are the server's: `scanruler_status`
253
- (is a tab connected, which version, how to pair one), `scanruler_open` (open
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*.
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, 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:
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
- `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
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. A
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.
@@ -1,8 +1,15 @@
1
- #!/usr/bin/env node
2
- // SPDX-License-Identifier: AGPL-3.0-only
3
- import { main } from '../src/main.js'
4
-
5
- main().catch((error) => {
6
- process.stderr.write(`[scanruler-mcp] ${error instanceof Error ? error.message : String(error)}\n`)
7
- process.exit(1)
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.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",