nsauditor-ai 0.1.35 → 0.1.37

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
@@ -15,6 +15,76 @@ NSAuditor AI is the open-source core of a privacy-first security intelligence pl
15
15
 
16
16
  **Zero Data Exfiltration by design.** NSAuditor AI works fully offline. AI analysis, CVE correlation, and continuous monitoring all happen locally. External calls (to AI APIs, NVD, etc.) are opt-in and use your own API keys. We never see your scan data.
17
17
 
18
+ ## What's New (0.1.37) — šŸ›‘ SECURITY FIX: bin shim bypassed auth + license verification
19
+
20
+ **Affects all installations using Claude Desktop (or any MCP client invoking the published `nsauditor-ai-mcp` binary).** Pre-0.1.37, the bin shim at `bin/nsauditor-ai-mcp.mjs` directly called `createServer() + server.connect()` and never invoked the startup block in `mcp_server.mjs` that runs:
21
+
22
+ 1. **`authorizeMcpServerStartup()`** — the `NSA_MCP_AUTH_KEY` enforcement we shipped in EE-SEC.1 (CE 0.1.31). Skipped means **any process with stdio access to the spawned MCP child could call the tools without supplying the auth key**.
23
+ 2. **`await loadLicense()`** — JWT verification of the operator's license key. Skipped means `_tier` stuck at the module-load CE default, so paid Pro/Enterprise customers saw "Current tier: CE" responses and lost MCP access to gated tools entirely.
24
+ 3. Rotation cadence warnings, keychain-locked diagnostics — all silent.
25
+
26
+ **Root cause**: an `argv[1].endsWith('mcp_server.mjs')` guard in `mcp_server.mjs` only matched when the server was invoked directly as `node mcp_server.mjs`. Claude Desktop spawns via the published bin (`nsauditor-ai-mcp`), so the guard was always false in production. The guard existed so that test imports of the module wouldn't auto-start the server — but the fix should have been to extract the startup into a function the bin shim explicitly calls.
27
+
28
+ **Detection**: in 0.1.36 you could spot this if you noticed your MCP responses said `Current tier: Community Edition (CE)` despite `nsauditor-ai mcp tier` from the shell saying `enterprise`. The disagreement was the 0.1.37 bug surfacing.
29
+
30
+ **Fix in 0.1.37**:
31
+ - Extracted the entire startup sequence into `export async function startStdioServer()` in `mcp_server.mjs`.
32
+ - `bin/nsauditor-ai-mcp.mjs` now imports and awaits `startStdioServer()`. Every Claude Desktop spawn now runs the auth check and license verification it always should have.
33
+ - Regression test (`tests/mcp_bin_startup.test.mjs`) spawns the bin shim with no auth key in env and asserts the auth check refuses startup. If the bin shim ever regresses to bypassing startup again, this test fails.
34
+
35
+ **Action required**: upgrade immediately.
36
+
37
+ ```bash
38
+ npm install -g nsauditor-ai@0.1.37
39
+ # Restart Claude Desktop. Verify with:
40
+ # - Real MCP call from Claude → response should say "Current tier: Enterprise" (or Pro)
41
+ # - nsauditor-ai mcp verify-call <uuid> ← the 0.1.36 sentinel still works
42
+ ```
43
+
44
+ **Threat model note**: a process needing stdio access to your Claude Desktop MCP child already had to be running as your user (or able to write to your `~/Library/Application Support/Claude/` config). The auth-bypass exposure is *defense-in-depth degradation*, not "anyone on the internet can call your scanner." But the tier-stuck-at-CE bug definitely cost paying customers actual functionality, and SOC 2 evidence generated from MCP-routed CE-tier responses would fail audit because it lacked enterprise-tier checks.
45
+
46
+ Thanks to the customer who caught this in the wild while we were chasing what looked like a Claude Desktop hallucination — turned out the bug was on our side.
47
+
48
+ ---
49
+
50
+ ## What's New (0.1.36) — cryptographic per-call sentinel UUID (hallucination becomes mathematically detectable)
51
+
52
+ The version-block comparison shipped in 0.1.34/0.1.35 catches lazy hallucinations, but a sufficiently capable AI client can still copy a previously-seen version block from chat context and pass off a fabricated response. **0.1.36 closes that gap** with a per-call cryptographic sentinel that the AI cannot fake.
53
+
54
+ **How it works:**
55
+ - Each `tools/call` invocation mints a fresh server-side UUID via Node's `crypto.randomUUID()`.
56
+ - The UUID is appended to the response text under a `── Verified MCP call ──` footer.
57
+ - The same UUID is persisted to `~/.nsauditor/mcp-calls.log` (mode 0600, JSON-per-line) **before** the response is returned.
58
+ - A new CLI subcommand `nsauditor-ai mcp verify-call <uuid>` greps the log:
59
+ - **Found** → the UUID was issued by your local MCP server, so the response bearing it is genuine.
60
+ - **Not found** → the UUID was never issued, so the entire response was fabricated by the AI client.
61
+
62
+ **Customer verification workflow (10 seconds):**
63
+
64
+ ```bash
65
+ # 1. In Claude Desktop, ask Claude to use any MCP tool (e.g., list_plugins).
66
+ # 2. The response ends with:
67
+ # ── Verified MCP call ──
68
+ # call_id: 3f8a1b22-7e44-4c91-9d62-12bd0a4f5e91
69
+ # Verify: nsauditor-ai mcp verify-call 3f8a1b22-7e44-4c91-9d62-12bd0a4f5e91
70
+ # 3. Run that exact verify command in your terminal:
71
+ nsauditor-ai mcp verify-call 3f8a1b22-7e44-4c91-9d62-12bd0a4f5e91
72
+ # āœ“ Verified MCP call → genuine
73
+ # āœ— call_id not found → fabricated (response was AI-generated, not from the MCP server)
74
+ ```
75
+
76
+ This makes the hallucination detection unfakeable in principle: the AI client has no access to your local Node `crypto.randomUUID()` output, and the sentinel is generated **at the moment the call hits the server** — there's no way to forge a UUID that will appear in a log file the client cannot read or write.
77
+
78
+ The 0.1.34/0.1.35 version-block check remains as the first line of defense (instant visual mismatch). The 0.1.36 UUID is the cryptographic ground truth for any response you'd act on.
79
+
80
+ `scan_host`, `probe_service`, `get_vulnerabilities`, and `list_plugins` all mint sentinels — even Pro-tier denials carry a UUID so customers can prove the call reached the server.
81
+
82
+ ```bash
83
+ npm install -g nsauditor-ai@0.1.36
84
+ ```
85
+
86
+ ---
87
+
18
88
  ## What's New (0.1.35) — CLI provenance footer matches MCP response (so the comparison actually works)
19
89
 
20
90
  0.1.34 added the version-provenance block to the MCP server's `list_plugins` response, but **the CLI baseline (`license --plugins` / `license --status`) didn't show versions** — so customers couldn't easily compare. 0.1.35 fixes that asymmetry.
@@ -403,20 +473,23 @@ nsauditor-ai scan --host 192.168.1.0/24 --plugins all \
403
473
  >
404
474
  > When you use NSAuditor AI through **Claude Desktop's** MCP integration, the AI may **fabricate scan results, plugin lists, vulnerability findings, and tier information without actually invoking the MCP tools**. We've confirmed this empirically: Claude Desktop's permission system shows tool calls being approved, but the actual `tools/call` JSON-RPC messages never reach our server (other MCP servers in the same config receive their calls correctly).
405
475
  >
406
- > **Mandatory verification for any output you'd act on**:
476
+ > **Mandatory verification for any output you'd act on (NEW in 0.1.36 — works for any MCP client):**
407
477
  >
408
478
  > ```bash
409
- > # Real tier check (ground truth — bypasses Claude AI synthesis):
479
+ > # Cryptographic ground truth: copy the call_id from the response footer
480
+ > # ("── Verified MCP call ──") and run:
481
+ > nsauditor-ai mcp verify-call <call_id>
482
+ > # āœ“ Verified MCP call → genuine, response is trustworthy
483
+ > # āœ— call_id not found → fabricated, IGNORE the response
484
+ >
485
+ > # Real tier check (bypasses Claude AI synthesis):
410
486
  > nsauditor-ai mcp tier
411
487
  >
412
- > # Real scan (always hits the network):
488
+ > # Real scan (always hits the network, no MCP client involved):
413
489
  > nsauditor-ai scan --host <X> --plugins all --out <dir>
414
- >
415
- > # Confirm Claude Desktop actually called the MCP server today:
416
- > grep '"method":"tools/call"' ~/Library/Logs/Claude/mcp-server-nsauditor-ai.log | tail -5
417
490
  > ```
418
491
  >
419
- > **SOC 2 evidence + compliance reports MUST be generated via the CLI** — never via the Claude Desktop MCP integration — until this is resolved upstream. Other MCP clients (Claude Code, custom MCP clients via the SDK) appear unaffected. See [What's New (0.1.33)](#whats-new-0133----mcp-integration-with-claude-desktop-is-unreliable) for full details.
492
+ > **SOC 2 evidence + compliance reports MUST be generated via the CLI** — never via the Claude Desktop MCP integration — unless every response you act on has a verified call_id. Other MCP clients (Claude Code, custom MCP clients via the SDK) appear unaffected. See [What's New (0.1.36)](#whats-new-0136--cryptographic-per-call-sentinel-uuid-hallucination-becomes-mathematically-detectable) for the cryptographic mitigation and [What's New (0.1.33)](#whats-new-0133----mcp-integration-with-claude-desktop-is-unreliable) for the original advisory.
420
493
 
421
494
  Expose scanning capabilities to AI assistants via [Model Context Protocol](https://modelcontextprotocol.io):
422
495
 
@@ -567,7 +640,16 @@ claude mcp add nsauditor-ai \
567
640
 
568
641
  > Use the `list_plugins` MCP tool right now and show me the raw tool response verbatim, including the exact text after the JSON.
569
642
 
570
- Then check the MCP log to verify a real call happened:
643
+ Then verify a real call happened. The 0.1.36+ way (works for any client, not just Claude Desktop):
644
+
645
+ ```bash
646
+ # Copy the call_id from the response footer that Claude returned, then:
647
+ nsauditor-ai mcp verify-call <call_id>
648
+ # āœ“ Verified MCP call → genuine, response is trustworthy
649
+ # āœ— call_id not found → fabricated, IGNORE the response
650
+ ```
651
+
652
+ The pre-0.1.36 fallback (Claude Desktop log archeology):
571
653
 
572
654
  ```bash
573
655
  grep '"method":"tools/call"' ~/Library/Logs/Claude/mcp-server-nsauditor-ai.log | tail -5
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { createServer } from '../mcp_server.mjs';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
2
+ // CE 0.1.37 (SECURITY): delegate to startStdioServer() so the auth
3
+ // check + license verification + rotation warnings actually run.
4
+ // Pre-0.1.37 this file inlined `createServer() + server.connect()`,
5
+ // which silently bypassed the entire startup block in mcp_server.mjs
6
+ // (it was guarded by an argv[1].endsWith('mcp_server.mjs') check that
7
+ // only matched when invoked directly, never via this shim). Result:
8
+ // every Claude Desktop session ran unauthenticated and tier-stuck-at-CE.
9
+ import { startStdioServer } from '../mcp_server.mjs';
4
10
 
5
- const server = createServer();
6
- const transport = new StdioServerTransport();
7
- await server.connect(transport);
11
+ await startStdioServer();
package/cli.mjs CHANGED
@@ -1539,6 +1539,70 @@ Docs: https://www.nsauditor.com/ai/ | Pricing: https://www.nsauditor.com/ai/
1539
1539
  console.log(` ⚠ ${MCP_AUTH_DISABLE_ENV_VAR}=1 is set — server will start without auth.`);
1540
1540
  }
1541
1541
  }
1542
+ } else if (subCmd === 'verify-call') {
1543
+ // CE 0.1.36 (Thread L Phase 2): cryptographic ground truth for
1544
+ // "did Claude actually call the MCP server, or hallucinate a
1545
+ // response?" Server mints a fresh UUID per tools/call, embeds it
1546
+ // in the response text, AND appends to ~/.nsauditor/mcp-calls.log.
1547
+ // Customer pastes the UUID here; we grep the log. UUID present →
1548
+ // proven real call. UUID absent → fabricated (or log was rotated/
1549
+ // deleted; we say "unverifiable" rather than "fake").
1550
+ const { readFile, stat } = await import('node:fs/promises');
1551
+ const { join: _join } = await import('node:path');
1552
+ const { homedir: _homedir } = await import('node:os');
1553
+ const logPath = _join(_homedir(), '.nsauditor', 'mcp-calls.log');
1554
+ const uuid = rawArgs[2];
1555
+ if (!uuid) {
1556
+ console.error('Usage: nsauditor-ai mcp verify-call <uuid>');
1557
+ console.error(' Paste the call_id from the MCP tool response footer.');
1558
+ process.exit(2);
1559
+ }
1560
+ // Conservative UUID v4 shape check — avoid grepping the log with
1561
+ // arbitrary user input.
1562
+ if (!/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(uuid)) {
1563
+ console.error(`āœ— Not a valid UUID: ${uuid}`);
1564
+ console.error(' Expected format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx');
1565
+ process.exit(2);
1566
+ }
1567
+ let logExists = false;
1568
+ try { await stat(logPath); logExists = true; } catch { /* missing */ }
1569
+ if (!logExists) {
1570
+ console.log(`āœ— No MCP call log at ${logPath}`);
1571
+ console.log(' Either: (a) MCP server has never been invoked from this account, or');
1572
+ console.log(' (b) the log was deleted/rotated.');
1573
+ console.log(' Trigger one real call (e.g., ask Claude "use list_plugins") then retry.');
1574
+ process.exit(1);
1575
+ }
1576
+ const raw = await readFile(logPath, 'utf8');
1577
+ // Look for an exact JSON-string match on call_id to avoid prefix collisions.
1578
+ const needle = `"call_id":"${uuid.toLowerCase()}"`;
1579
+ const lines = raw.split('\n').filter((l) => l.includes(needle));
1580
+ if (lines.length === 0) {
1581
+ console.log(`āœ— call_id not found in ${logPath}`);
1582
+ console.log('');
1583
+ console.log(` ${uuid}`);
1584
+ console.log('');
1585
+ console.log(' This UUID was NOT issued by this MCP server. Most likely cause:');
1586
+ console.log(' Claude Desktop fabricated the response without invoking the server.');
1587
+ console.log(' (See README §"Verifying that Claude actually called the MCP server".)');
1588
+ process.exit(1);
1589
+ }
1590
+ try {
1591
+ const entry = JSON.parse(lines[lines.length - 1]);
1592
+ console.log(`āœ“ Verified MCP call`);
1593
+ console.log(` call_id: ${entry.call_id}`);
1594
+ console.log(` tool: ${entry.tool}`);
1595
+ console.log(` ts: ${entry.ts}`);
1596
+ console.log(` log: ${logPath}`);
1597
+ console.log('');
1598
+ console.log(' This UUID was issued by the local MCP server, so the response');
1599
+ console.log(' bearing it was a genuine tool call (not a hallucination).');
1600
+ process.exit(0);
1601
+ } catch {
1602
+ console.log(`āœ“ Verified MCP call (matched ${lines.length} log line(s) for this UUID)`);
1603
+ console.log(` log: ${logPath}`);
1604
+ process.exit(0);
1605
+ }
1542
1606
  } else {
1543
1607
  console.log('Usage:');
1544
1608
  console.log(' nsauditor-ai mcp install-key Generate a new key, persist, print Claude config');
@@ -1547,6 +1611,7 @@ Docs: https://www.nsauditor.com/ai/ | Pricing: https://www.nsauditor.com/ai/
1547
1611
  console.log(' nsauditor-ai mcp rotate-key Replace the stored key with a fresh one');
1548
1612
  console.log(' nsauditor-ai mcp status Show storage source without revealing the key');
1549
1613
  console.log(' nsauditor-ai mcp tier Print actual MCP server tier (ground truth, bypasses Claude AI synthesis)');
1614
+ console.log(' nsauditor-ai mcp verify-call <uuid> Prove a tool response came from the real MCP server (not Claude hallucination)');
1550
1615
  console.log('');
1551
1616
  console.log('Environment variables:');
1552
1617
  console.log(` ${MCP_AUTH_ENV_VAR} Read by mcp_server.mjs at startup; client supplies via Claude config`);
package/mcp_server.mjs CHANGED
@@ -8,8 +8,11 @@
8
8
  // import { createServer, toolHandlers } from './mcp_server.mjs' — for testing
9
9
 
10
10
  import { createRequire } from 'node:module';
11
- import { dirname } from 'node:path';
11
+ import { dirname, join } from 'node:path';
12
12
  import { fileURLToPath } from 'node:url';
13
+ import { homedir } from 'node:os';
14
+ import { randomUUID } from 'node:crypto';
15
+ import { appendFile, mkdir, chmod } from 'node:fs/promises';
13
16
 
14
17
  const __dirname = dirname(fileURLToPath(import.meta.url));
15
18
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
@@ -99,6 +102,56 @@ export function _setValidateHost(fn) {
99
102
  _validateHostFn = fn ?? validateHost;
100
103
  }
101
104
 
105
+ // ---------------------------------------------------------------------------
106
+ // Per-call cryptographic sentinel (CE 0.1.36 — Thread L Phase 2)
107
+ // ---------------------------------------------------------------------------
108
+ //
109
+ // Why this exists: even after CE 0.1.34 embedded the resolved tier and CE 0.1.35
110
+ // added a CLI provenance footer, Claude Desktop was empirically observed
111
+ // (2026-05-09, kankanyan@gmail.com) fabricating list_plugins responses
112
+ // WITHOUT routing to this server (per-server log: 0 tools/call entries
113
+ // while other configured MCP servers received 50+ in the same session).
114
+ // A fabricated response can copy any text it has seen — including version
115
+ // numbers from the CLI footer. The only thing it cannot copy is a
116
+ // random UUID generated server-side AT THE MOMENT of the call.
117
+ //
118
+ // Each tool invocation gets a fresh UUID. The UUID is:
119
+ // 1. Embedded in the response text (Claude cannot omit; it's the payload)
120
+ // 2. Persisted to ~/.nsauditor/mcp-calls.log (append-only, mode 0600)
121
+ //
122
+ // Customer verification: paste the UUID from Claude into
123
+ // nsauditor-ai mcp verify-call <uuid>
124
+ // If it appears in the local log → real call. If not → hallucinated.
125
+ // (See cli.mjs `verify-call` subcommand.)
126
+ const MCP_CALL_LOG_PATH = join(homedir(), '.nsauditor', 'mcp-calls.log');
127
+
128
+ async function recordToolCall(toolName) {
129
+ const callId = randomUUID();
130
+ const ts = new Date().toISOString();
131
+ // Best-effort: a log-write failure must not break the customer's tool
132
+ // call. Verification will simply fail-closed (UUID-not-found → treat as
133
+ // unverifiable rather than as proof-of-fake).
134
+ try {
135
+ await mkdir(dirname(MCP_CALL_LOG_PATH), { recursive: true });
136
+ const line = JSON.stringify({ call_id: callId, tool: toolName, ts }) + '\n';
137
+ await appendFile(MCP_CALL_LOG_PATH, line, { encoding: 'utf8' });
138
+ // Tighten on first write; chmod is idempotent so cheap to repeat.
139
+ try { await chmod(MCP_CALL_LOG_PATH, 0o600); } catch { /* non-fatal on Windows */ }
140
+ } catch (err) {
141
+ process.stderr.write(`[nsauditor-mcp] call-log write failed: ${err.message}\n`);
142
+ }
143
+ return callId;
144
+ }
145
+
146
+ function appendCallSentinel(text, callId) {
147
+ return (
148
+ `${text}\n\n── Verified MCP call ──\n` +
149
+ `call_id: ${callId}\n` +
150
+ `Verify (proves Claude actually called this server, not hallucinated):\n` +
151
+ ` nsauditor-ai mcp verify-call ${callId}`
152
+ );
153
+ }
154
+
102
155
  // ---------------------------------------------------------------------------
103
156
  // Tool definitions (JSON Schema for input validation)
104
157
  // ---------------------------------------------------------------------------
@@ -357,10 +410,23 @@ export function createServer() {
357
410
  };
358
411
  }
359
412
 
413
+ // CE 0.1.36 (Thread L Phase 2): mint a per-call sentinel UUID and
414
+ // log it BEFORE the Pro-gate so even denials prove the call hit
415
+ // the server. Fabricated responses cannot include a UUID that
416
+ // exists in the customer's local log file.
417
+ const callId = await recordToolCall(name);
418
+
360
419
  // Gate Pro-tier tools at the MCP dispatch layer
361
420
  if (name === 'probe_service' || name === 'get_vulnerabilities') {
362
421
  const denied = requireProCapability(name);
363
- if (denied) return denied;
422
+ if (denied) {
423
+ return {
424
+ ...denied,
425
+ content: denied.content.map((c) =>
426
+ c.type === 'text' ? { ...c, text: appendCallSentinel(c.text, callId) } : c,
427
+ ),
428
+ };
429
+ }
364
430
  }
365
431
 
366
432
  try {
@@ -408,16 +474,16 @@ export function createServer() {
408
474
  const tierSuffix = `\n\nCurrent tier: ${tierLabel[_tier] ?? _tier}. ${_capabilities.proMCP ? '' : 'Upgrade to Pro for probe_service, get_vulnerabilities, risk_summary, and more.'}`;
409
475
 
410
476
  return {
411
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) + tierSuffix + versionLines }],
477
+ content: [{ type: 'text', text: appendCallSentinel(JSON.stringify(result, null, 2) + tierSuffix + versionLines, callId) }],
412
478
  };
413
479
  }
414
480
 
415
481
  return {
416
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
482
+ content: [{ type: 'text', text: appendCallSentinel(JSON.stringify(result, null, 2), callId) }],
417
483
  };
418
484
  } catch (err) {
419
485
  return {
420
- content: [{ type: 'text', text: JSON.stringify({ error: err.message }) }],
486
+ content: [{ type: 'text', text: appendCallSentinel(JSON.stringify({ error: err.message }), callId) }],
421
487
  isError: true,
422
488
  };
423
489
  }
@@ -427,16 +493,22 @@ export function createServer() {
427
493
  }
428
494
 
429
495
  // ---------------------------------------------------------------------------
430
- // Standalone entry point
496
+ // Stdio entry point — used by bin/nsauditor-ai-mcp.mjs AND `node mcp_server.mjs`
431
497
  // ---------------------------------------------------------------------------
432
-
433
- const isMainModule =
434
- typeof process !== 'undefined' &&
435
- process.argv[1] &&
436
- (process.argv[1].endsWith('mcp_server.mjs') ||
437
- process.argv[1].endsWith('mcp_server'));
438
-
439
- if (isMainModule) {
498
+ //
499
+ // CE 0.1.37 (SECURITY): this used to be guarded by a brittle
500
+ // `process.argv[1].endsWith('mcp_server.mjs')` check. The bin shim
501
+ // (which Claude Desktop spawns) sets argv[1] to `nsauditor-ai-mcp.mjs`,
502
+ // so the guard was false and the entire startup block — auth check,
503
+ // license verification, rotation warnings — was SKIPPED. Result: Claude
504
+ // Desktop's MCP child ran unauthenticated, with _tier stuck at the CE
505
+ // default, regardless of the operator's installed license. Customers
506
+ // paying for Pro/Enterprise saw "Current tier: CE" responses and lost
507
+ // MCP access to gated tools entirely.
508
+ //
509
+ // Fix: extract the startup into an exported function. The bin shim now
510
+ // calls it explicitly, so the auth + license path runs every time.
511
+ export async function startStdioServer() {
440
512
  // EE-SEC.1: enforce MCP server authentication BEFORE accepting any
441
513
  // tool calls. Pre-fold any process running as the operator could
442
514
  // spawn the server and call Pro/Enterprise tools — including the
@@ -523,4 +595,16 @@ if (isMainModule) {
523
595
  const server = createServer();
524
596
  const transport = new StdioServerTransport();
525
597
  await server.connect(transport);
598
+ return server;
599
+ }
600
+
601
+ // Backward-compat: still callable as `node mcp_server.mjs` directly.
602
+ const isMainModule =
603
+ typeof process !== 'undefined' &&
604
+ process.argv[1] &&
605
+ (process.argv[1].endsWith('mcp_server.mjs') ||
606
+ process.argv[1].endsWith('mcp_server'));
607
+
608
+ if (isMainModule) {
609
+ await startStdioServer();
526
610
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nsauditor-ai",
3
- "version": "0.1.35",
3
+ "version": "0.1.37",
4
4
  "description": "Modular AI-assisted network security audit platform — Community Edition",
5
5
  "type": "module",
6
6
  "private": false,