Repository navigation
Conversation
A file the page handed back was written under the name the page gave it, unchanged. On Windows a name with a colon, such as one taken from a scan titled "Bracket v2: final", wrote the bytes to a hidden NTFS stream and left an empty file, while the answer reported success. Reserved characters and control characters in the name now become an underscore, trailing dots and spaces are dropped, and a device name such as CON is prefixed so it is written as a plain file.
The picture tools (view_render, section_render, flat_render, scan_heightmap) are annotated read-only, so a client may run them without asking, yet given the path of an existing file they replaced it with PNG bytes. A picture written to a file path is now created with the exclusive flag. When a file is already there the call is refused with invalid_input and the file is left untouched. Folders still number the new picture past existing ones, and other returned files keep their current behaviour.
The allowed origins were compared to the browser's Origin header as raw strings. A value given with a trailing slash, a path or an uppercase host, such as https://x.example/, never matched, so the page was refused with 403 and only one log line to explain it. Each allowed value is now reduced to its URL origin before comparing, and a value that is not a URL is refused at start-up instead of silently never matching.
Answers rounded every number to six significant digits. For a scan far from the origin, as a scanner's frame often places it, a coordinate such as 12345.678 came back as 12345.7 and 123456.789 as 123457, so a point the agent fed back was off by a tenth of a millimetre or more. Numbers of a hundred or more are now rounded to a fixed step of 1e-4 instead, so millimetre values keep four decimals at any distance. Smaller numbers keep six significant digits as before. The existing rounding test is updated to the new values.
The MCP SDK's low-level server does not validate tool arguments, so the schemas of the server's own tools were advisory only. A scanruler_wait with timeoutSeconds "abc" or -5 computed a NaN or negative delay, the timer fired at once, and the agent polled in a tight loop believing it had waited. Before one of the server's own tools runs, each top-level number, integer and boolean argument is now checked for its type, its enum and its minimum, exclusive minimum and maximum, and a mismatch is answered with invalid_input. Lists and objects are left to the tools, which already accept some of them given as a string of JSON.
The launcher treated any non-zero exit within three seconds of handing off to a newer copy as a copy that failed to start, and stored that version in update.json as failed. A bad --port or --tools made the new copy exit with code 1 at once, so a good version was blacklisted and automatic updates stopped. The launcher then ran itself in its place, possibly after the child had already read the client's initialize message, which could hang the client. The server now exits with a dedicated usage code, 2, when it refuses its arguments or settings. The launcher passes that code on without marking the version failed and without starting itself as a fallback. The CLI tests that check a refused --tools value expect the new code.
loadConfig checked for config.json, read it and wrote a new token as separate steps. Two servers started together on a first run could each make a token; the last write won, and the other copy held a token that was not on disk, so pages paired with it were refused. A config.json that could not be read or parsed was silently replaced, dropping its other keys and invalidating the token already pasted into ScanRuler. The first token is now written whole to a temporary file and linked into place. Whoever's link succeeds wins; a copy whose link fails because the file is already there reads the winner's token and never makes its own. Read errors and parse errors are handled separately: only a missing file leads to a new token, while a read refused for a moment (EBUSY or EPERM on Windows, as another copy links the file in) is retried. A file that still does not parse after about a second of retries is kept aside as config.json.bad rather than overwritten.
A relative --out was joined to the working folder by string concatenation after a hand-written absolute-path check. "--out ../reports" was reported as "cwd/../reports", unresolved and with mixed separators on Windows, in the status and in answers. The folder is now resolved with path.resolve against the working folder.
scanruler_read, scanruler_write and scanruler_files kept to the folders a scan or project came from by comparing paths as written. A symbolic link or junction in the scan folder that pointed elsewhere carried them out of it. The three tools also used slightly different checks, and the "starts with .." test refused a plain name such as "..notes.md". The three tools now share one check: the path must lie in a known folder both as written and with links followed (the real path of the deepest existing part, for a file not yet written). Only a ".." segment counts as climbing out, so a name that merely starts with two dots is accepted. Tools that take an absolute path by design, such as scan_open and $file, are unchanged.
58 of 65 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The scanruler-mcp tool and CLI items from #34, rebased on 0.9.3. The
scanruler_writeguard is left out because you shipped it. Each fix is its own commit, with a regression test that fails without it::wrote to an NTFS alternate data stream, reported success, and left the visible file empty. Characters Windows refuses are now replaced, and reserved names likeCONget a prefix.pathreplaced whatever was there. An existing file is now refused and left untouched. Folders still number past existing files.--allow-originwas a raw string match. A trailing slash, a path or an uppercase host never matched. It now compares URL origins, and a value that isn't a URL is refused at start.tools.test.jsmoved accordingly.timeoutSecondscould be NaN, negative or a string. Numbers, booleans, enums and ranges are now checked against each tool's schema.config.jsonwas silently replaced. The first token is linked into place and the others read it. A Windows read during that write (EBUSY/EPERM) is retried rather than taken as corrupt. A file that stays unparsable is set aside asconfig.json.bad.--outwas concatenated, not resolved.scanruler_readandscanruler_filesnow also check where the path really leads, as your 0.9.3 change does forscanruler_write, which I left as it is. The..check also no longer refuses names like..notes.md.Build:
npm run buildpasses.npm --prefix mcp test(139),npm test(3094), the plugin boundary and units checks, andnpm run e2e:ciall pass. The token race test passed 20/20 alone and the full MCP suite 8 times in a row.