Date: 2026-11-15
OS: Windows Server 2022 (x64). I used Git Bash for most steps and PowerShell for pktmon, links, the VHD and the desktop apps.
Source (GitLab, Apache-2.0): https://gitlab.com/jakubrojicek11/sentinel-ir
npm: https://www.npmjs.com/package/sentinel-ir
Playground (runs in your browser): https://sentinel-ir-playground-dc30c8.gitlab.io/
Node: 20.20.2 (npm 10.8.2), 22.23.5 (npm 10.9.9), 24.22.2 (npm 11.19.1)
Other tools: MCP client @modelcontextprotocol/sdk 1.32.2 (Client + StdioClientTransport), TypeScript 5.9.3 and 7.0.2, VS Code (portable zip, latest), Cursor (latest installer), Claude Desktop 0.14.10.
Source: only the published npm packages, installed or run through npx. No repo checkout, no code changes, no MR.
Not tested: macOS and Linux. This session ran only on the Windows machine.
Summary
There are no blockers. All 8 regression checks for 0.5.9 pass on Windows on Node 20, 22 and 24. Check 8 (README) has small gaps: the aliases are missing from one paragraph and not-a-folder is missing from the audit-log reasons. The MCP results were identical on all three Node versions. A diff of the normalized output for every call showed no differences.
Not verified: whether a desktop client actually starts the server from its config.
- VS Code accepted the entry. Starting it from MCP: List Servers turned the status to
Errorwith an empty server log, and nosentinel-ir-mcpprocess appeared. VS Code was unauthenticated and still in onboarding, so I can't blame the package (see U1). - Cursor and Claude Desktop stop at their login screens.
- The same command line started fine through the real SDK client.
Issues found
| # | Severity | Package | Title |
|---|---|---|---|
| 1 | bug (docs/behaviour mismatch) | sentinel-ir | An explicit language: "ruby" returns language: "ruby" and reason unsupported language: ruby, but the README says "unknown" / unsupported file type: .rb |
| 2 | papercut | sentinel-ir-mcp | analyze_code rejects language: "JS" (-32602); the library accepts any case |
| 3 | papercut | sentinel-ir-mcp | doctor prints [ok] analyze_directory works ... (0 file(s) checked) |
| 4 | papercut | sentinel-ir-mcp | doctor stale-cache warning names the copy sentinel-ir-mcp@^0.5.8 under npm 10 but sentinel-ir-mcp@0.5 under npm 11 |
| 5 | docs | sentinel-ir-mcp | The README upgrade text ("npx keeps starting the copy it downloaded first for a range such as @0.5") is true for npm 10 but not for npm 11.19.1 |
| 6 | papercut | sentinel-ir | examples/basic.js (the README's CLI route) prints a raw Node stack trace for a missing file |
| 7 | docs | sentinel-ir | The "Fewer tokens" table is stale (raw sizes are 2-4x smaller than the shipped files) |
| 8 | docs | sentinel-ir-mcp | The "Supported" paragraph lists language values without the aliases, and the audit-log refusal reasons don't include not-a-folder |
| 9 | papercut | sentinel-ir | Rust: use std::process; process::exit(0) in a broken file is critical with child_process; a valid file with std::process::exit is low but forbidden: ["process.exit"] |
| 10 | papercut (transient) | sentinel-ir-mcp | doctor once printed [skip] No usable answer from the npm registry ... while the network was busy |
Issues in detail
1. bug: explicit unsupported language doesn't match the README (sentinel-ir)
- Node: 20/22/24 (all identical)
- Repro:
mkdir lib && cd lib && npm init -y && npm install sentinel-ir@0.5.9 node -e 'const s=require("sentinel-ir");(async()=>{await s.initializeWasm(); for (const o of [{language:"ruby"},{target:"x.rb"}]) { const r=s.compress(s.analyze("puts 1",o)); console.log(JSON.stringify(o), r.analysisStatus, r.language, r.facts.reason) }})()' - Expected (README, "Supported languages"): "An explicit unsupported
language(e.g."ruby") or, since 0.5.8, atargetwith any other extension ... is reported asanalysisStatus: "incomplete"withlanguage: "unknown"andfacts.reason: "unsupported file type: .rb (…)"." - Actual:
{"language":"ruby"} incomplete ruby unsupported language: ruby (supported: javascript, typescript, tsx, python, rust) {"target":"x.rb"} incomplete unknown unsupported file type: .rb (supported: .js .mjs .cjs .jsx .ts .mts .cts .tsx .py .pyi .rs) The status is correctly incomplete, but language echoes the caller's string. Code that switches on language === "unknown" misses this case. Either always return "unknown" or change the README sentence.
2. papercut: MCP analyze_code language is case-sensitive, the library isn't
- Node: 20/22/24, MCP client SDK 1.32.2, server
npx -y sentinel-ir-mcp@stable-0.5 <folder>(reports 0.5.9) - Repro: call
analyze_codewith{ "code": "eval(x)", "language": "JS" }. - Expected: the same result as
js, because the library acceptsJSandPython. The 0.5.9 changelog says "analyze_codetakes thejs,ts,pyandrsaliases like sentinel-ir". - Actual:
MCP error -32602: Input validation error: Invalid option: expected one of "javascript"|"typescript"|"tsx"|"python"|"rust"|"js"|"ts"|"py"|"rs" at language - Note: the error is clear and lists the valid values, so this is minor. The lowercase aliases
js/ts/py/rsall work.
3. papercut: doctor says [ok] when 0 files were checked
- Node: 24.22.2
- Repro: use a project folder where the first file the walk reaches is skipped. My fixture had a 1.6 MB
big/huge.js, symlinks and a hard link beforesrc/; a folder where exclusions leave nothing analyzable also works. Then:
npx -y sentinel-ir-mcp@stable-0.5 setup --client cursor --yes --exclude nomatch-folder --exclude "*.rb" --audit-log npx -y sentinel-ir-mcp@stable-0.5 doctor --client cursor - Expected: a
[warn]or[skip], for example "nothing analyzable found in the first N files", or the check keeps walking until it analyzes one file. - Actual:
[ok] analyze_directory works on C:\t59\Projekt Žluťoučký kůň\myapp (0 file(s) checked) The same folder without the Cursor config gives (1 file(s) checked). "Works" next to "0 files checked" reads as a contradiction.
4. papercut: the stale-npx-cache warning labels the copy differently on npm 10 and npm 11
- Repro (npm 10, Node 20.20.2,
npm_config_cache=C:\t59\c20):
npx -y sentinel-ir-mcp@0.5.8 --version # puts 0.5.8 in the cache (key 46f9bf218df4163a) npx -y sentinel-ir-mcp@0.5 doctor # runs 0.5.8 (README behaviour) npx -y sentinel-ir-mcp@stable-0.5 doctor # runs 0.5.9 - Actual (npm 10): the warning names the cached copy
sentinel-ir-mcp@^0.5.8. On npm 11.19.1 the same situation reads:
[warn] The npx cache still holds sentinel-ir-mcp@0.5 (sentinel-ir-mcp 0.5.8, sentinel-ir 0.5.8) in C:\Users\Administrator\AppData\Local\npm-cache\_npx\46f9bf218df4163a; a client entry with that version or range keeps starting it, and sentinel-ir older than 0.5.9 misses security fixes -> Close the client, delete that folder (npm 11: npm cache npx rm 46f9bf218df4163a), then run: npx -y sentinel-ir-mcp@stable-0.5 setup - Cause (from the cache files): npm 10's
_npx/<hash>/package.jsonis only{"dependencies":{"sentinel-ir-mcp":"^0.5.8"}}. npm 11 adds"_npx":{"packages":["sentinel-ir-mcp@0.5"]}.doctorfalls back to the dependency range, which the user never typed. - Expected: a label the user recognises on both npm versions. When
_npx.packagesis missing, "a cached copy of sentinel-ir-mcp 0.5.8" would be enough. Apart from the label, both warnings are correct and actionable, and the folder hash is right.
5. docs: the README upgrade claim depends on the npm version
- Repro (Node 24.22.2 / npm 11.19.1): put 0.5.8 in the cache under both
@0.5and@stable-0.5, then runnpx -y sentinel-ir-mcp@stable-0.5 doctorand thennpx -y sentinel-ir-mcp@0.5 doctor. - Actual: the second command also ran 0.5.9. Its cache folder
_npx/46f9bf218df4163a/package.jsonnow reads{"dependencies":{"sentinel-ir-mcp":"^0.5.9"},"_npx":{"packages":["sentinel-ir-mcp@0.5"]}}. On npm 10.8.2 the@0.5copy stayed on 0.5.8, as the README says. - Caveat: I made the 0.5.8 cache entry with
--before. A cache from a real earlier download may behave differently. - Suggestion: say "npm 10 keeps starting ...; npm 11 may re-resolve". The advice to use
@stable-0.5is right either way.
6. papercut: examples/basic.js prints a raw stack trace for a missing file
- Node: 24.22.2 (same on 20/22)
- Repro:
node node_modules/sentinel-ir/examples/basic.js does/not/exist.js. The README gives this as the command-line route, and the package has nobin. - Actual (exit 1):
node:fs:483 return binding.readFileUtf8(path, stringToFlags(options.flag)); ^ Error: ENOENT: no such file or directory, open 'C:\t59\Projekt Žluťoučký kůň\lib20\does\not\exist.js' at Object.readFileSync (node:fs:483:20) at Object.<anonymous> (C:\t59\Projekt Žluťoučký kůň\lib20\node_modules\sentinel-ir\examples\basic.js:7:17) at Module._compile (node:internal/modules/cjs/loader:1929:14) at Object..js (node:internal/modules/cjs/loader:2060:10) at Module.load (node:internal/modules/cjs/loader:1651:32) at Module._load (node:internal/modules/cjs/loader:1443:12) at wrapModuleLoad (node:internal/modules/cjs/loader:261:19) at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5) at node:internal/main/run_main_module:33:47 { errno: -4058, code: 'ENOENT', syscall: 'open', path: 'C:\\t59\\Projekt Žluťoučký kůň\\lib20\\does\\not\\exist.js' } - Expected: a one-line message such as
basic.js: cannot read does/not/exist.js (ENOENT). Optionally, ship a smallbin, because newcomers look fornpx sentinel-ir <file>.
7. docs: the "Fewer tokens" table is stale (sentinel-ir README)
| file | README raw chars | actual 0.5.9 raw chars |
|---|---|---|
src/ast-facts.js | 21,823 | 96,827 |
src/sentinel-ir.js | 9,123 | 20,424 |
src/tree-parser.js | 4,393 | 13,181 |
Measuring examples/basic.js on itself gives an IR of 552 characters; the README says 461. The claim still holds, and the real savings are larger, but the numbers are from an older release.
8. docs: gaps in the sentinel-ir-mcp README
- The "Supported" paragraph says "an explicit
language(javascript|typescript|tsx|python|rust)" without the aliases. The Tools table and the changelog do listjs/ts/py/rs. - The audit-log section lists the refusal reasons
excluded, outside, not-found, too-large, hardlink, symlink, unsupported, not-a-file, url, no-folders.analyze_directoryon a file logsnot a folder, sonot-a-foldershould be added. - The minimum
sentinel-irversion is documented ("Needssentinel-ir0.5.9"), anddoctorchecks it. OK.
9. papercut: Rust std::process false positives (documented behaviour)
-
use std::process; fn main() { process::exit(0)(missing}) givescritical,forbidden: ["child_process","process_spawn"]. The README describes the module-onlyuse std::processheuristic, but this is a very common idiom. - A valid
fn main(){ std::process::exit(1); }gives risklow, butforbidden: ["process.exit"]. A reader who takesforbiddento mean "dangerous capability" may be confused.
10. papercut (transient): doctor update check
While a large download was running, one doctor run printed [skip] No usable answer from the npm registry about a newer sentinel-ir-mcp. The next run printed [ok] sentinel-ir-mcp 0.5.9 is the newest stable-0.5 on npm. The fallback is correct (skip, not fail). I'm noting it only for completeness.
Unverified / blocked
U1. Desktop clients starting the server from their config: not verified.
| client | config written by | config accepted? | server process started? |
|---|---|---|---|
| VS Code (portable zip, latest) | code --add-mcp '<json printed by setup --client vscode>' -> User\mcp.json, non-ASCII path intact | yes, listed in MCP: List Servers | no: Start Server sets the status to Error; mcpServer.mcp.config.usrlocal.sentinel-ir.log is empty; no sentinel-ir-mcp process. VS Code was unauthenticated and in onboarding; its renderer log shows Could not list installed MCP servers. (refused: policy-context-unavailable) |
| Cursor (latest) | setup --client cursor --yes -> <project>\.cursor\mcp.json (cmd /c npx -y sentinel-ir-mcp@stable-0.5 ... ${workspaceFolder}) | file is valid; the app won't go past the login modal | not observed |
| Claude Desktop 0.14.10 | setup --client claude-desktop --yes -> %APPDATA%\Claude\claude_desktop_config.json | file is valid; the app stops at "Get started" | not observed |
- I couldn't tell whether the VS Code
Errorcomes from the package or from the client state. - The same command line, run as
cmd /c npx -y sentinel-ir-mcp@stable-0.5 "C:\t59\Projekt Žluťoučký kůň\myapp", starts and answers the handshake through the real SDK client.doctoralso performs its own handshake. - Recommendation: repeat with a signed-in VS Code or Cursor.
U2. There is no second physical disk. I used a 512 MB NTFS VHD mounted as V:.
U3. macOS and Linux were not tested in this session (Windows machine only).
The 8 regression checks for 0.5.9
| # | Claim | Result |
|---|---|---|
| 1 | UNC \\server\share\x.js, //server/share/x.js, file://server/share/x.js refused before any file system or network access | PASS on Node 20, 22 and 24. pktmon filter: TCP 445/139, UDP 137/138, 5355, IP 10.255.255.1. During every UNC and remote-URL call: 0 packets on each Node version. Control Test-Path \\10.255.255.1\share\x.js: 28 packets (SYN to :445) after 22 s. Replies: Access denied ... outside the allowed folders (UNC) and path must be a file path, not a URL (remote file://) |
| 2 | file:src/x.js and http(s):// refused as URLs; local file:/// inside the folder still works | PASS. path must be a file path, not a URL: file:src/server.js; file:///C:/t59/Projekt%20Žluťoučký%20kůň/myapp/src/server.js (encoded and raw) gives complete javascript; file:/// outside gives Access denied |
| 3 | Broken Python/Rust is critical only when forbidden names the capability | PASS (library and MCP). subprocess, os.system, std::process::Command, runpy, spec_from_file_location(...).loader.exec_module, from ctypes import * give critical with forbidden. std::process::exit, std::process::id, importlib.reload, from ctypes import c_int are not critical. See issue 9 for the use std::process; idiom |
| 4 | analyze_directory summary lists forbidden for broken files with a dangerous capability | PASS (broken Python, Rust and JS fixtures) |
| 5 | analyze_code aliases js, ts, py, rs | PASS for lowercase. Uppercase is rejected, see issue 2 |
| 6 | log wording | PASS. pasted code (12 bytes, unknown): low (incomplete: unsupported language); REFUSED file http://example.com/x.js: not a local file path; ... outside the allowed folders; REFUSED file links/hard-out.js: file has several hard links; folder line 1 not read (too large or unreadable), 1 not read (several hard links); REFUSED folder src/server.js: not a folder; Not a file: src |
| 7 | doctor warns on unmatched exclusions; stale-cache warning reads right for a range and for a tag | PASS with papercuts. setup and doctor both warn "matches nothing there yet (typo?)". The stale-cache warning is correct and actionable, but the label differs between npm 10 and npm 11 (issue 4) |
| 8 | README documents the language option and aliases, every refusal reason, and the minimum sentinel-ir version | Mostly PASS. Aliases are in the Tools table and changelog but not in the "Supported" paragraph; not-a-folder is missing from the audit-log reasons (issue 8) |
0.5.8 fixes rechecked
| Check | Result |
|---|---|
Same Access denied for existing and missing outside paths, .., \\?\C:\... | PASS |
| Symlink outside refused (incl. dangling symlink and junction); symlink inside works | PASS |
Unknown extension (.rb, .go) gives incomplete / unknown | PASS (but see issue 1 for an explicit language) |
doctor per client (--client cursor; Claude Desktop and Windsurf paths) | PASS |
| `log \ | head -1` exits 0 without EPIPE |
TypeScript declarations under strict (tsc 5.9.3 and 7.0.2); the union on facts.ast narrows | PASS |
Rust and TSX parse-error positions (missing / unclosed with line and column) | PASS |
.jsx accepted | PASS |
Verified working
- sentinel-ir install:
npm install sentinel-irin a fresh project on Node 20, 22 and 24 takes 2-3 s. npm 11 prints the documented allow-scripts notice. - README API example: output is byte-identical to the README.
initializeWasm()works. Outputs are identical across Node versions. - Languages: JS, TS, TSX, Python and Rust files analysed. Broken files in each language give
incompletewith parse-error positions. Aliases and case-insensitive names work in the library. - Module formats: ESM default import and CJS
requireboth work. - sentinel-ir-mcp CLI entry points:
guide-
setupfor every client (dry run,--yes,--audit-log,--exclude); re-running keeps existing settings - whole-disk refusal:
Refusing to give the agent access to a whole disk (V:\). Pass a project folder with --project. -
doctor,doctor --client cursor -
log,log --all, and guidance when the audit log is off
- MCP over stdio with a real client (identical on Node 20, 22, 24):
- handshake
{"name":"sentinel-ir-mcp","version":"0.5.9"} - tools
analyze_code,analyze_file,analyze_directory - refusals: outside root,
.., symlink, hard link (has 2 hard links; files with more than one are refused), oversized file (File too large: big/huge.js is 1600000 bytes, limit is 1048576), unsupported extension,Not a file,Not a folder - directory summary with
skipped.tooLarge,skipped.symlinks,skipped.hardlinksandriskCounts
- handshake
- Audit log: JSONL with a
startrecord (version, folders, exclusions) and one line per call. - Paths:
C:\t59\Projekt Žluťoučký kůň\myapp(spaces and diacritics) andV:\Další projekton a second volume.setupanddoctorare fine on both, and generated configs keep the non-ASCII characters intact. - Upgrade path: I read the actual
_npx/<hash>/node_modules/sentinel-ir-mcp/package.jsonin each case.- npm 10:
@0.5keeps running 0.5.8, and 0.5.8's owndoctorwarnsThis is sentinel-ir-mcp 0.5.8, npm has 0.5.9 (stable-0.5). -
@stable-0.5runs 0.5.9, and itsdoctornames the stale folder with the delete command. - npm 11: see issue 5.
- npm 10:
Independent Engineering Projects
Built as independent engineering experiments across AI, software analysis, security, and autonomous systems.
Evaluated by reproducible evidence, not by the number of layers.
Explore the projects:
Sentinel Ecosystem: AutoDoc-Sentinel, Sentinel-IR, Sentinel-IR-MCP and related tools.
Agentix Lite: Independent security and system-observation project.
Find the projects: GitLab repositories | NPM packages
Support the work: ⭐ Star the repositories on GitLab.
Connect: Follow me here on DEV Community or explore the Playground.
Each project is distributed under its own license. Check the corresponding repository or NPM package for licensing terms.
Copyright © 2026 jackymenCZ. All rights reserved. Written for the community.
