sentinel-ir-mcp 0.4.5 → 0.5.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
@@ -3,11 +3,13 @@
3
3
  MCP server for [`sentinel-ir`](https://www.npmjs.com/package/sentinel-ir):
4
4
  it lets AI agents in Claude Desktop, Cursor, Windsurf or any other
5
5
  [Model Context Protocol](https://modelcontextprotocol.io) client read a
6
- compact **structural IR** of JavaScript, TypeScript and Python files instead of the raw source.
6
+ compact **structural IR** of JavaScript, TypeScript, Python and Rust files instead of the raw source.
7
7
 
8
8
  - npm: <https://www.npmjs.com/package/sentinel-ir-mcp>
9
- - GitLab: <https://gitlab.com/jakubrojicek11/sentinel-ir-mcp>
10
- - Library behind it: <https://gitlab.com/jakubrojicek11/sentinel-ir>
9
+ - GitLab (main repository): <https://gitlab.com/jakubrojicek11/sentinel-ir-mcp>
10
+ - GitHub (mirror): <https://github.com/jakcmencz/sentinel-ir-mcp>
11
+ - Library behind it: [`sentinel-ir`](https://github.com/jakcmencz/sentinel-ir)
12
+ - See what the agent sees, in your browser: <https://sentinel-ir-playground-dc30c8.gitlab.io/>
11
13
 
12
14
  The IR tells the agent what a file *does* (risk level, dangerous
13
15
  capabilities such as `eval` or `child_process`, routes, exports, imports,
@@ -16,15 +18,33 @@ hundred characters. The agent can triage a folder first and then read the raw
16
18
  source only of the files that matter.
17
19
 
18
20
  **Supported:** JavaScript (`.js`, `.mjs`, `.cjs`), TypeScript (`.ts`, `.mts`,
19
- `.cts`, `.tsx`; since 0.2.0) and Python (`.py`, `.pyi`; since 0.3.0). The
21
+ `.cts`, `.tsx`; since 0.2.0), Python (`.py`, `.pyi`; since 0.3.0) and Rust
22
+ (`.rs`; since 0.5.0). The
20
23
  language is picked from the file extension; `analyze_code` also takes `target`
21
- or an explicit `language` (`javascript` | `typescript` | `tsx` | `python`). Every result carries `language` and
24
+ or an explicit `language` (`javascript` | `typescript` | `tsx` | `python` | `rust`). Every result carries `language` and
22
25
  `analysisStatus`; a file that does not parse cleanly (including JSX in a `.ts`
23
26
  file) is `"incomplete"`, and a low risk on an incomplete result is not evidence
24
27
  of safety. Other languages are not supported yet. What is detected in Python
25
- (and what is not) is listed in the
28
+ and Rust (and what is not) is listed in the
26
29
  [sentinel-ir README](https://gitlab.com/jakubrojicek11/sentinel-ir#python).
27
30
 
31
+ **0.5.0** reads Rust (`.rs`) with `sentinel-ir` 0.5.2: `unsafe` and FFI,
32
+ `std::process::Command`, file deletes and writes, HTTP clients and routes
33
+ (`axum`, `actix-web`, ...). A `build.rs` or procedural macro that touches
34
+ files, the network or secret environment variables is at least `high`,
35
+ because it runs on your machine during `cargo build`.
36
+
37
+ - `analyze_directory` skips Cargo's build folder: a `target` folder with
38
+ Cargo's `CACHEDIR.TAG` file, listed in `skipped.cargoTarget`. A plain folder
39
+ named `target` (e.g. in a JS project) is still scanned.
40
+ - `setup` writes `sentinel-ir-mcp@0.5`; `doctor` asks entries pinned to 0.4
41
+ or older to run `setup` again.
42
+
43
+ **0.4.6** is listed in the official
44
+ [MCP Registry](https://registry.modelcontextprotocol.io) as
45
+ `io.github.jakcmencz/sentinel-ir-mcp`, so clients that browse the registry
46
+ can find and install it. Nothing else changes.
47
+
28
48
  **0.4.5** adds to that log the SHA-256 of every file the server read, so you
29
49
  can check later which version of a file the agent was shown (see
30
50
  [Audit log](#audit-log)).
@@ -34,7 +54,7 @@ can check later which version of a file the agent was shown (see
34
54
  - **One question in `setup`:** *"Keep a log of what the agent asks for (file
35
55
  names and results, never your code)? [y/N]"*. Or `setup --audit-log`;
36
56
  `--no-audit-log` turns it off. Running `setup` again keeps the setting.
37
- - **One command to read it:** `npx -y sentinel-ir-mcp@0.4 log` prints the last
57
+ - **One command to read it:** `npx -y sentinel-ir-mcp@0.5 log` prints the last
38
58
  50 entries as plain lines, `--all` everything, `--json` for scripts:
39
59
 
40
60
  ```text
@@ -163,9 +183,9 @@ nothing to compile (the parser is WebAssembly).
163
183
  Run it in your project folder:
164
184
 
165
185
  ```bash
166
- npx -y sentinel-ir-mcp@0.4 setup --client cursor # or windsurf, claude-desktop, vscode, claude-code
167
- npx -y sentinel-ir-mcp@0.4 doctor --client cursor # after restarting the client
168
- npx -y sentinel-ir-mcp@0.4 guide # optional: text for AGENTS.md / client rules
186
+ npx -y sentinel-ir-mcp@0.5 setup --client cursor # or windsurf, claude-desktop, vscode, claude-code
187
+ npx -y sentinel-ir-mcp@0.5 doctor --client cursor # after restarting the client
188
+ npx -y sentinel-ir-mcp@0.5 guide # optional: text for AGENTS.md / client rules
169
189
  ```
170
190
 
171
191
  `setup` (without `--client` it asks which client):
@@ -179,7 +199,7 @@ npx -y sentinel-ir-mcp@0.4 guide # optional: text for AGENTS
179
199
  touched;
180
200
  - gives the agent one folder (`--project`, default: the current folder) and
181
201
  refuses your home folder or a whole disk;
182
- - writes `sentinel-ir-mcp@0.4`, so clients get 0.4.x fixes but no unexpected
202
+ - writes `sentinel-ir-mcp@0.5`, so clients get 0.5.x fixes but no unexpected
183
203
  new features;
184
204
  - asks which files or folders the agent must not read (`--exclude <name>`,
185
205
  repeatable, skips the question; see [Excluding files](#excluding-files));
@@ -194,7 +214,7 @@ npx -y sentinel-ir-mcp@0.4 guide # optional: text for AGENTS
194
214
  | `vscode`, `claude-code` | nothing; prints the command to run |
195
215
 
196
216
  On Windows the entry is `"command": "cmd", "args": ["/c", "npx", "-y",
197
- "sentinel-ir-mcp@0.4", "<folder>"]`: there `npx` is `npx.cmd`, and starting it
217
+ "sentinel-ir-mcp@0.5", "<folder>"]`: there `npx` is `npx.cmd`, and starting it
198
218
  without a shell failed with `ENOENT` on a real Windows runner. For VS Code and
199
219
  Claude Code, `setup` prints a PowerShell and a Command Prompt version of the
200
220
  command. What was measured on which system: [COMPATIBILITY.md](COMPATIBILITY.md).
@@ -274,7 +294,7 @@ Settings → Developer → Edit Config opens `claude_desktop_config.json`
274
294
  "mcpServers": {
275
295
  "sentinel-ir": {
276
296
  "command": "npx",
277
- "args": ["-y", "sentinel-ir-mcp@0.4", "/Users/you/projects/my-app"]
297
+ "args": ["-y", "sentinel-ir-mcp@0.5", "/Users/you/projects/my-app"]
278
298
  }
279
299
  }
280
300
  }
@@ -293,7 +313,7 @@ project entry has to be switched on once in Cursor Settings → Customize → MC
293
313
  "mcpServers": {
294
314
  "sentinel-ir": {
295
315
  "command": "npx",
296
- "args": ["-y", "sentinel-ir-mcp@0.4", "${workspaceFolder}"]
316
+ "args": ["-y", "sentinel-ir-mcp@0.5", "${workspaceFolder}"]
297
317
  }
298
318
  }
299
319
  }
@@ -309,8 +329,8 @@ with your project's absolute path.
309
329
  #### Other clients
310
330
 
311
331
  Any MCP client that starts stdio servers works with
312
- `command: "npx"`, `args: ["-y", "sentinel-ir-mcp@0.4", "<folder>"]`
313
- (on Windows: `command: "cmd"`, `args: ["/c", "npx", "-y", "sentinel-ir-mcp@0.4", "<folder>"]`).
332
+ `command: "npx"`, `args: ["-y", "sentinel-ir-mcp@0.5", "<folder>"]`
333
+ (on Windows: `command: "cmd"`, `args: ["/c", "npx", "-y", "sentinel-ir-mcp@0.5", "<folder>"]`).
314
334
  Without folder arguments the server uses the client's
315
335
  [MCP roots](https://modelcontextprotocol.io/specification/latest/client/roots)
316
336
  if the client offers them; if it offers none, only `analyze_code` works.
@@ -318,7 +338,7 @@ if the client offers them; if it offers none, only `analyze_code` works.
318
338
  ### Excluding files
319
339
 
320
340
  ```bash
321
- npx -y sentinel-ir-mcp@0.4 setup --client cursor --exclude secrets --exclude "*.min.js"
341
+ npx -y sentinel-ir-mcp@0.5 setup --client cursor --exclude secrets --exclude "*.min.js"
322
342
  ```
323
343
 
324
344
  | you write | excludes |
@@ -343,8 +363,8 @@ other tools of its client is told about them, but this server cannot stop it.
343
363
  ### Audit log
344
364
 
345
365
  ```bash
346
- npx -y sentinel-ir-mcp@0.4 setup --client cursor --audit-log # or answer "y" in setup
347
- npx -y sentinel-ir-mcp@0.4 log # what the agent asked for
366
+ npx -y sentinel-ir-mcp@0.5 setup --client cursor --audit-log # or answer "y" in setup
367
+ npx -y sentinel-ir-mcp@0.5 log # what the agent asked for
348
368
  ```
349
369
 
350
370
  | system | folder |
@@ -501,10 +521,25 @@ A second CI job runs on a real Windows runner (Node 20/22/24): the full test
501
521
  suite, the packed package with `setup`/`doctor` against the real `%APPDATA%`,
502
522
  starting the published package through `npx`, and the printed VS Code/Claude
503
523
  Code commands pasted into PowerShell 5.1, PowerShell 7 and cmd.exe. See
504
- [COMPATIBILITY.md](COMPATIBILITY.md).
505
-
506
- Release: bump `version` in `package.json`, push a protected tag `vX.Y.Z`;
507
- GitLab CI tests and publishes to npm with provenance.
524
+ [COMPATIBILITY.md](COMPATIBILITY.md). On every push it runs on GitHub Actions
525
+ in the mirror (`.github/workflows/windows.yml`); on GitLab it runs on release
526
+ tags before `publish`, and by hand on other branches. The informational
527
+ large-file size probe runs on release tags only.
528
+
529
+ Release: bump `version` in `package.json` and both `version` fields in
530
+ `server.json` (a test checks they match), push a protected tag `vX.Y.Z`;
531
+ GitLab CI tests and publishes to npm with provenance. Then publish the same
532
+ `server.json` to the MCP Registry: `mcp-publisher login github` and
533
+ `mcp-publisher publish`.
534
+
535
+ ## Contributing
536
+
537
+ Development, CI and npm releases run on
538
+ [GitLab](https://gitlab.com/jakubrojicek11/sentinel-ir-mcp). The
539
+ [GitHub repository](https://github.com/jakcmencz/sentinel-ir-mcp) is a mirror
540
+ that GitLab updates on every push to `main` and every tag. Issues are welcome
541
+ on either; a pull request opened on GitHub is applied on GitLab, and the mirror
542
+ then brings it back.
508
543
 
509
544
  ## License
510
545
 
@@ -12,7 +12,7 @@ const { normalizePattern, ExcludeError, MAX_PATTERNS } = require("../src/exclude
12
12
  const { AuditLogError, defaultAuditDir, normalizeAuditDir, assertOutsideProjects } = require("../src/auditLog");
13
13
  const { version } = require("../package.json");
14
14
 
15
- const USAGE = `sentinel-ir-mcp ${version} — MCP server for sentinel-ir (JavaScript, TypeScript and Python)
15
+ const USAGE = `sentinel-ir-mcp ${version} — MCP server for sentinel-ir (JavaScript, TypeScript, Python and Rust)
16
16
 
17
17
  Usage: sentinel-ir-mcp [options] [folder ...]
18
18
  sentinel-ir-mcp setup | doctor | guide | log [--help]
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "sentinel-ir-mcp",
3
- "version": "0.4.5",
4
- "description": "MCP server for sentinel-ir: lets AI agents (Claude Desktop, Cursor, Windsurf, ...) read a compact structural IR of JavaScript, TypeScript and Python files instead of the raw source. Read-only, sandboxed to allowed folders.",
3
+ "version": "0.5.0",
4
+ "mcpName": "io.github.jakcmencz/sentinel-ir-mcp",
5
+ "description": "MCP server for sentinel-ir: lets AI agents (Claude Desktop, Cursor, Windsurf, ...) read a compact structural IR of JavaScript, TypeScript, Python and Rust files instead of the raw source. Read-only, sandboxed to allowed folders.",
5
6
  "main": "src/server.js",
6
7
  "bin": {
7
8
  "sentinel-ir-mcp": "bin/sentinel-ir-mcp.js"
@@ -19,11 +20,12 @@
19
20
  },
20
21
  "dependencies": {
21
22
  "@modelcontextprotocol/sdk": "^1.32.1",
22
- "sentinel-ir": "^0.4.5",
23
+ "sentinel-ir": "^0.5.2",
23
24
  "zod": "^4.1.0"
24
25
  },
25
26
  "keywords": [
26
27
  "python",
28
+ "rust",
27
29
  "mcp",
28
30
  "model-context-protocol",
29
31
  "mcp-server",
package/src/analyze.js CHANGED
@@ -87,6 +87,25 @@ function realNative(p) {
87
87
  }
88
88
 
89
89
  const SKIP_DIRS = new Set(["node_modules", ".git", "__pycache__", "venv", "site-packages"]);
90
+ // Cargo marks its build folder with a CACHEDIR.TAG (cache directory tagging
91
+ // spec); a plain folder named "target" in a JS project is still scanned.
92
+ const CACHEDIR_SIGNATURE = "Signature: 8a477f597d28d172789f06886806bc55";
93
+ async function isCargoTarget(dir) {
94
+ let handle;
95
+ try {
96
+ const tag = path.join(dir, "CACHEDIR.TAG");
97
+ if (!(await fs.lstat(tag)).isFile()) return false;
98
+ handle = await fs.open(tag, OPEN_FLAGS);
99
+ const buffer = Buffer.alloc(CACHEDIR_SIGNATURE.length);
100
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
101
+ return buffer.toString("latin1", 0, bytesRead) === CACHEDIR_SIGNATURE;
102
+ } catch {
103
+ return false;
104
+ } finally {
105
+ await handle?.close();
106
+ }
107
+ }
108
+
90
109
  const RISK_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
91
110
 
92
111
  async function analyzeCode(code, target, language) {
@@ -102,7 +121,7 @@ async function analyzeFile(requested, roots, { maxFileBytes, excluder = NONE, ha
102
121
  const stat = await fs.stat(real);
103
122
  if (!stat.isFile()) throw new AccessError(`Not a file: ${requested}`);
104
123
  if (!isSourceFile(real)) {
105
- throw new AccessError(`Unsupported file type: ${requested} (only .js, .mjs, .cjs, .ts, .mts, .cts, .tsx, .py and .pyi)`);
124
+ throw new AccessError(`Unsupported file type: ${requested} (only .js, .mjs, .cjs, .ts, .mts, .cts, .tsx, .py, .pyi and .rs)`);
106
125
  }
107
126
  if (stat.size > maxFileBytes) {
108
127
  throw new AccessError(`File too large: ${requested} is ${stat.size} bytes, limit is ${maxFileBytes}`);
@@ -112,11 +131,12 @@ async function analyzeFile(requested, roots, { maxFileBytes, excluder = NONE, ha
112
131
  }
113
132
 
114
133
  // Breadth-first, sorted walk. Symlinks are never followed; node_modules, .git,
115
- // __pycache__, venv, site-packages and hidden folders are skipped. Excluded
134
+ // __pycache__, venv, site-packages, hidden folders and Cargo build folders
135
+ // (target/ with a CACHEDIR.TAG, listed in skipped.cargoTarget) are skipped. Excluded
116
136
  // files and folders are only counted: their names can be private too.
117
137
  async function collectFiles(dir, root, { maxFiles, maxFileBytes, recursive, excluder = NONE }) {
118
138
  const files = [];
119
- const skipped = { excluded: 0, tooLarge: [], symlinks: 0, unreadable: [], hardlinks: [] };
139
+ const skipped = { excluded: 0, tooLarge: [], symlinks: 0, unreadable: [], hardlinks: [], cargoTarget: [] };
120
140
  // Exclusions match the real names: on Windows dir may be spelled with
121
141
  // short names (SRC~1) that the patterns do not know.
122
142
  const prefix = excluder.active ? path.relative(realNative(root), realNative(dir)) : "";
@@ -142,7 +162,9 @@ async function collectFiles(dir, root, { maxFiles, maxFileBytes, recursive, excl
142
162
  } else if (entry.isSymbolicLink()) {
143
163
  skipped.symlinks++;
144
164
  } else if (entry.isDirectory()) {
145
- if (recursive && !SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) queue.push(full);
165
+ if (!recursive || SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
166
+ if (entry.name === "target" && await isCargoTarget(full)) skipped.cargoTarget.push(rel);
167
+ else queue.push(full);
146
168
  } else if (entry.isFile() && isSourceFile(entry.name)) {
147
169
  let size;
148
170
  try {
package/src/clients.js CHANGED
@@ -4,7 +4,7 @@ const os = require("os");
4
4
  const path = require("path");
5
5
 
6
6
  const SERVER_NAME = "sentinel-ir";
7
- const PACKAGE_SPEC = "sentinel-ir-mcp@0.4";
7
+ const PACKAGE_SPEC = "sentinel-ir-mcp@0.5";
8
8
  const WORKSPACE_VAR = "${workspaceFolder}";
9
9
 
10
10
  function homeOf(env) {
package/src/doctor.js CHANGED
@@ -84,8 +84,8 @@ function checkConfig(client, file, explicit, project, add, platform, env) {
84
84
  const runs = `${label}: "${SERVER_NAME}" runs ${entry.command} ${args.join(" ")}`;
85
85
  if (entry.command === "npx" && platform === "win32") {
86
86
  add("warn", `${client}.entry`, runs, `On Windows a client may not find "npx" without a shell. Run: npx -y ${PACKAGE_SPEC} setup --client ${client}`);
87
- } else if (args.some(a => /^sentinel-ir-mcp@0\.[0-3](\.|$)/.test(a))) {
88
- add("warn", `${client}.entry`, runs, `This entry stays on an older version without native-code detection, whose parser stops working in long sessions. Run: npx -y ${PACKAGE_SPEC} setup --client ${client}`);
87
+ } else if (args.some(a => /^sentinel-ir-mcp@0\.[0-4](\.|$)/.test(a))) {
88
+ add("warn", `${client}.entry`, runs, `This entry stays on an older version without Rust support. Run: npx -y ${PACKAGE_SPEC} setup --client ${client}`);
89
89
  } else if (entry.command === "npx" || isWindowsNpxWrapper(entry)) {
90
90
  add("ok", `${client}.entry`, runs);
91
91
  } else {
package/src/paths.js CHANGED
@@ -7,7 +7,8 @@ const { fileURLToPath } = require("url");
7
7
  const JS_EXTENSIONS = new Set([".js", ".mjs", ".cjs"]);
8
8
  const TS_EXTENSIONS = new Set([".ts", ".mts", ".cts", ".tsx"]);
9
9
  const PY_EXTENSIONS = new Set([".py", ".pyi"]);
10
- const SOURCE_EXTENSIONS = new Set([...JS_EXTENSIONS, ...TS_EXTENSIONS, ...PY_EXTENSIONS]);
10
+ const RS_EXTENSIONS = new Set([".rs"]);
11
+ const SOURCE_EXTENSIONS = new Set([...JS_EXTENSIONS, ...TS_EXTENSIONS, ...PY_EXTENSIONS, ...RS_EXTENSIONS]);
11
12
 
12
13
  class AccessError extends Error {}
13
14
 
package/src/server.js CHANGED
@@ -39,10 +39,10 @@ const RISK_NOTE =
39
39
  "is unknown: e.g. readFileSync(req.query.f) is low. Start with critical files, and report a problem only after reading the code.";
40
40
 
41
41
  const INSTRUCTIONS =
42
- "sentinel-ir-mcp turns JavaScript, TypeScript and Python source into a compact structural IR (risk level, dangerous " +
42
+ "sentinel-ir-mcp turns JavaScript, TypeScript, Python and Rust source into a compact structural IR (risk level, dangerous " +
43
43
  "capabilities, routes, exports, imports, env reads, network/filesystem/process/crypto calls). " +
44
44
  "Prefer analyze_file or analyze_directory over reading raw source when you only need structure " +
45
- "or risk, then read the raw source of the files that matter. Supported: JavaScript (.js, .mjs, .cjs), TypeScript (.ts, .mts, .cts, .tsx) and Python (.py, .pyi); the language is picked from the file extension. " +
45
+ "or risk, then read the raw source of the files that matter. Supported: JavaScript (.js, .mjs, .cjs), TypeScript (.ts, .mts, .cts, .tsx), Python (.py, .pyi) and Rust (.rs); the language is picked from the file extension. " +
46
46
  "Rely on analysisStatus, world.risk, forbidden and facts; analysisStatus \"incomplete\" means the file did not parse cleanly. " +
47
47
  "analyze_directory lists such files separately in 'incomplete'; their low risk is not evidence of safety. " +
48
48
  "Suggested order: analyze_directory first, check 'incomplete', 'skipped.unreadable' and 'notAnalyzed' (set when the time budget ran out), pick the relevant files, " +
@@ -131,45 +131,45 @@ function createServer(options = {}) {
131
131
  }
132
132
 
133
133
  server.registerTool("analyze_code", {
134
- title: "Analyze JavaScript, TypeScript or Python source",
134
+ title: "Analyze JavaScript, TypeScript, Python or Rust source",
135
135
  description:
136
- "Analyze JavaScript, TypeScript or Python source text and return its compact structural IR: " +
136
+ "Analyze JavaScript, TypeScript, Python or Rust source text and return its compact structural IR: " +
137
137
  "{analysisStatus, language, mission, world.risk, constraints, forbidden, summary, facts}. " +
138
138
  "Use analyze_file instead when the code is on disk, so the source does not have to pass " +
139
139
  "through the conversation. " + UNTRUSTED_NOTE,
140
140
  inputSchema: {
141
- code: z.string().max(limits.maxFileBytes).describe("JavaScript, TypeScript or Python source"),
142
- target: z.string().max(1024).optional().describe("Optional file name shown as mission.target; its extension (.ts, .tsx, .py, ...) selects the language"),
143
- language: z.enum(["javascript", "typescript", "tsx", "python"]).optional().describe("Overrides the language picked from target (default javascript)")
141
+ code: z.string().max(limits.maxFileBytes).describe("JavaScript, TypeScript, Python or Rust source"),
142
+ target: z.string().max(1024).optional().describe("Optional file name shown as mission.target; its extension (.ts, .tsx, .py, .rs, ...) selects the language"),
143
+ language: z.enum(["javascript", "typescript", "tsx", "python", "rust"]).optional().describe("Overrides the language picked from target (default javascript)")
144
144
  },
145
- annotations: { title: "Analyze JavaScript, TypeScript or Python source", ...READ_ONLY }
145
+ annotations: { title: "Analyze JavaScript, TypeScript, Python or Rust source", ...READ_ONLY }
146
146
  }, ({ code, target, language }) => guarded(() => analyzeCode(code, target, language), auditor("analyze_code", { code, target, language })));
147
147
 
148
148
  server.registerTool("analyze_file", {
149
- title: "Analyze a JavaScript, TypeScript or Python file",
149
+ title: "Analyze a JavaScript, TypeScript, Python or Rust file",
150
150
  description:
151
- "Read one .js/.mjs/.cjs/.ts/.mts/.cts/.tsx/.py/.pyi file inside the allowed folders and return its compact structural IR. " +
151
+ "Read one .js/.mjs/.cjs/.ts/.mts/.cts/.tsx/.py/.pyi/.rs file inside the allowed folders and return its compact structural IR. " +
152
152
  `Relative paths are resolved against the first allowed folder. Files over ${limits.maxFileBytes} bytes are refused. ` +
153
153
  UNTRUSTED_NOTE + excludeNote,
154
154
  inputSchema: {
155
155
  path: z.string().min(1).describe("File path, absolute or relative to the first allowed folder")
156
156
  },
157
- annotations: { title: "Analyze a JavaScript, TypeScript or Python file", ...READ_ONLY }
157
+ annotations: { title: "Analyze a JavaScript, TypeScript, Python or Rust file", ...READ_ONLY }
158
158
  }, async ({ path }) => {
159
159
  const hashes = auditLog.active ? new Map() : null;
160
160
  return guarded(async () => analyzeFile(path, await allowedRoots(), { ...limits, excluder, hashes }), auditor("analyze_file", { path, hashes }));
161
161
  });
162
162
 
163
163
  server.registerTool("analyze_directory", {
164
- title: "Analyze a folder of JavaScript, TypeScript and Python files",
164
+ title: "Analyze a folder of JavaScript, TypeScript, Python and Rust files",
165
165
  description:
166
- "Scan a folder inside the allowed folders and return one short entry per JavaScript, TypeScript or Python file, " +
166
+ "Scan a folder inside the allowed folders and return one short entry per JavaScript, TypeScript, Python or Rust file, " +
167
167
  "sorted from highest to lowest risk, plus risk counts. By default (detail \"summary\") each entry is " +
168
168
  "{path, risk, analysisStatus, language, lines, operations, riskSignals, moreRiskSignals?, parseError?}: " +
169
169
  "riskSignals are up to 3 'kind:code@line' signals (code cut to 120 characters) that set the file's risk level (empty when the risk comes from " +
170
170
  "the text fallback of an incomplete file or from a path containing 'crypto'), moreRiskSignals counts the rest. " +
171
171
  "detail \"full\" adds forbidden, objective and all facts per file instead (much larger). " + RISK_NOTE + " " +
172
- "Skips node_modules, .git, __pycache__, venv, site-packages, hidden folders " +
172
+ "Skips node_modules, .git, __pycache__, venv, site-packages, hidden folders, Cargo build folders (target/ with a CACHEDIR.TAG, listed in skipped.cargoTarget) " +
173
173
  `and symlinks. Analyzes at most max_files files (default ${limits.maxFiles}); 'truncated' tells ` +
174
174
  "you when there were more. 'incomplete' lists files whose analysis did not finish (e.g. parse errors); " +
175
175
  "their risk is not reliable, check them separately. statusCounts counts complete vs incomplete. " +
@@ -186,7 +186,7 @@ function createServer(options = {}) {
186
186
  detail: z.enum(["summary", "full"]).optional()
187
187
  .describe("\"summary\" (default): short entries; \"full\": all facts per file, much larger")
188
188
  },
189
- annotations: { title: "Analyze a folder of JavaScript, TypeScript and Python files", ...READ_ONLY }
189
+ annotations: { title: "Analyze a folder of JavaScript, TypeScript, Python and Rust files", ...READ_ONLY }
190
190
  }, async ({ path, max_files, recursive, detail }) => {
191
191
  const hashes = auditLog.active ? new Map() : null;
192
192
  return guarded(async () => analyzeDirectory(path ?? ".", await allowedRoots(), {