@bryan-thompson/inspector-assessment-cli 1.43.1 → 1.43.3

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.
@@ -9,6 +9,37 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
9
9
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
10
10
  import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
11
11
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
12
+ /**
13
+ * Returns minimal environment variables for spawned MCP servers.
14
+ * Using a curated set prevents unintended behavior from inherited env vars
15
+ * (e.g., native module loading triggered by unexpected env conditions).
16
+ *
17
+ * @see https://github.com/triepod-ai/inspector-assessment/issues/211
18
+ */
19
+ function getMinimalEnv() {
20
+ const minimal = {};
21
+ // Essential system paths
22
+ if (process.env.PATH)
23
+ minimal.PATH = process.env.PATH;
24
+ if (process.env.HOME)
25
+ minimal.HOME = process.env.HOME;
26
+ if (process.env.TMPDIR)
27
+ minimal.TMPDIR = process.env.TMPDIR;
28
+ if (process.env.TMP)
29
+ minimal.TMP = process.env.TMP;
30
+ if (process.env.TEMP)
31
+ minimal.TEMP = process.env.TEMP;
32
+ // Node.js environment
33
+ minimal.NODE_ENV = process.env.NODE_ENV || "production";
34
+ // Platform-specific essentials
35
+ if (process.env.USER)
36
+ minimal.USER = process.env.USER;
37
+ if (process.env.SHELL)
38
+ minimal.SHELL = process.env.SHELL;
39
+ if (process.env.LANG)
40
+ minimal.LANG = process.env.LANG;
41
+ return minimal;
42
+ }
12
43
  /**
13
44
  * Connect to MCP server via configured transport
14
45
  *
@@ -37,8 +68,8 @@ export async function connectToServer(config) {
37
68
  command: config.command,
38
69
  args: config.args,
39
70
  env: {
40
- ...Object.fromEntries(Object.entries(process.env).filter(([, v]) => v !== undefined)),
41
- ...config.env,
71
+ ...getMinimalEnv(),
72
+ ...config.env, // Explicit config overrides take priority
42
73
  },
43
74
  cwd: config.cwd,
44
75
  stderr: "pipe",
@@ -65,16 +96,32 @@ export async function connectToServer(config) {
65
96
  }
66
97
  catch (error) {
67
98
  const errorMessage = error instanceof Error ? error.message : String(error);
99
+ // Issue #212: Detect SIGKILL (exit code 137) which may indicate Gatekeeper blocking
100
+ const isSigkill = errorMessage.includes("exit code 137") ||
101
+ errorMessage.includes("SIGKILL") ||
102
+ errorMessage.toLowerCase().includes("killed");
68
103
  // Provide helpful context when connection fails
104
+ let contextualHelp = `Failed to connect to MCP server: ${errorMessage}\n\n`;
69
105
  if (stderrData.trim()) {
70
- throw new Error(`Failed to connect to MCP server: ${errorMessage}\n\n` +
71
- `Server stderr:\n${stderrData.trim()}\n\n` +
72
- `Common causes:\n` +
73
- ` - Missing environment variables (check .env file)\n` +
74
- ` - Required external services not running\n` +
75
- ` - Missing API credentials`);
106
+ contextualHelp += `Server stderr:\n${stderrData.trim()}\n\n`;
107
+ }
108
+ contextualHelp += `Common causes:\n`;
109
+ contextualHelp += ` - Missing environment variables (check .env file)\n`;
110
+ contextualHelp += ` - Required external services not running\n`;
111
+ contextualHelp += ` - Missing API credentials\n`;
112
+ // Issue #212: Add native module specific help for SIGKILL/timeout
113
+ if (isSigkill) {
114
+ contextualHelp += `\n\u{1F534} Exit code 137 (SIGKILL) detected - possible causes:\n`;
115
+ contextualHelp += ` - macOS Gatekeeper blocked unsigned native binaries\n`;
116
+ contextualHelp += ` - Native module (canvas, sharp, better-sqlite3) killed by security policy\n`;
117
+ contextualHelp += ` - Out of memory during native module initialization\n`;
118
+ contextualHelp += `\nSuggested actions:\n`;
119
+ contextualHelp += ` - Check pre-flight warnings above for detected native modules\n`;
120
+ contextualHelp += ` - Try: xattr -d com.apple.quarantine /path/to/binary\n`;
121
+ contextualHelp += ` - Open System Preferences > Security & Privacy to allow blocked apps\n`;
122
+ contextualHelp += ` - Consider pure JavaScript alternatives (e.g., jimp instead of sharp)\n`;
76
123
  }
77
- throw new Error(`Failed to connect to MCP server: ${errorMessage}`);
124
+ throw new Error(contextualHelp);
78
125
  }
79
126
  return client;
80
127
  }
@@ -316,6 +316,14 @@ export function parseArgs(argv) {
316
316
  // Issue #137: Stage B enrichment for Claude semantic analysis
317
317
  options.stageBVerbose = true;
318
318
  break;
319
+ case "--static-only":
320
+ // Issue #213: Static-only assessment without server connection
321
+ options.staticOnly = true;
322
+ break;
323
+ case "--fallback-static":
324
+ // Issue #213: Try runtime, fall back to static on failure
325
+ options.fallbackStatic = true;
326
+ break;
319
327
  case "--profile": {
320
328
  const profileValue = args[++i];
321
329
  if (!profileValue) {
@@ -435,6 +443,37 @@ export function parseArgs(argv) {
435
443
  options.helpRequested = true;
436
444
  return options;
437
445
  }
446
+ // Issue #213: Validate --static-only and --fallback-static options
447
+ if (options.staticOnly && options.fallbackStatic) {
448
+ console.error("Error: --static-only and --fallback-static are mutually exclusive");
449
+ console.error(" Use --static-only for static analysis only");
450
+ console.error(" Use --fallback-static to try runtime first, then fall back to static");
451
+ setTimeout(() => process.exit(1), 10);
452
+ options.helpRequested = true;
453
+ return options;
454
+ }
455
+ if (options.staticOnly && !options.sourceCodePath) {
456
+ console.error("Error: --static-only requires --source <path>");
457
+ console.error(" Provide the path to source code for static analysis");
458
+ setTimeout(() => process.exit(1), 10);
459
+ options.helpRequested = true;
460
+ return options;
461
+ }
462
+ if (options.staticOnly &&
463
+ (options.httpUrl || options.sseUrl || options.serverConfigPath)) {
464
+ console.error("Error: --static-only cannot be used with --http, --sse, or --config");
465
+ console.error(" Static-only mode does not connect to a server");
466
+ setTimeout(() => process.exit(1), 10);
467
+ options.helpRequested = true;
468
+ return options;
469
+ }
470
+ if (options.staticOnly && options.singleModule) {
471
+ console.error("Error: --static-only cannot be used with --module");
472
+ console.error(" Static mode runs all static-capable modules automatically");
473
+ setTimeout(() => process.exit(1), 10);
474
+ options.helpRequested = true;
475
+ return options;
476
+ }
438
477
  if (!options.serverName) {
439
478
  console.error("Error: --server is required");
440
479
  printHelp();
@@ -522,6 +561,10 @@ Options:
522
561
  breakdowns to tiered output (Tier 2 + Tier 3)
523
562
  --module, -m <name> Run single module directly (bypasses orchestrator for faster execution)
524
563
  Mutually exclusive with --skip-modules, --only-modules, --profile
564
+ --static-only Run static analysis only (no server connection required)
565
+ Requires --source path. Validates manifest, docs, code quality.
566
+ --fallback-static Try runtime assessment, fall back to static on failure
567
+ Use when server may have startup issues.
525
568
  --skip-modules <list> Skip specific modules (comma-separated)
526
569
  --only-modules <list> Run only specific modules (comma-separated)
527
570
  --json Output only JSON path (no console summary)
@@ -585,6 +628,23 @@ Module Tiers (13 standard + 4 opt-in):
585
628
  • File Modularization - Code quality metrics (not security)
586
629
  • External API - External service detection (informational)
587
630
 
631
+ Static Analysis (--static-only):
632
+ When a server won't start, use --static-only with --source to validate:
633
+ • Manifest - Schema compliance, required fields
634
+ • Developer Experience - README quality, documentation
635
+ • Prohibited Libs - Banned dependency detection
636
+ • Portability - Platform-specific patterns
637
+ • External API - External service detection
638
+ • Tool Annotations - Annotation presence in source
639
+ • Authentication - Credential patterns
640
+ • AUP Compliance - Policy keyword analysis
641
+ • File Modularization - Code structure metrics
642
+ • Conformance - Code quality checks
643
+
644
+ Modules skipped in static mode (require runtime):
645
+ • Functionality, Security, Temporal, Protocol Compliance
646
+ • Resources, Prompts, Cross-Capability
647
+
588
648
  Transport Options:
589
649
  --config, --http, and --sse are mutually exclusive.
590
650
  Use --http or --sse for quick testing without a config file.
@@ -612,6 +672,13 @@ Examples:
612
672
  mcp-assess-full my-server --skip-modules temporal,resources # Skip expensive modules
613
673
  mcp-assess-full my-server --only-modules functionality,toolAnnotations # Annotation PR review
614
674
 
675
+ # Static analysis (when server won't start):
676
+ mcp-assess-full my-server --source ./my-server --static-only
677
+ mcp-assess-full my-server --source ./my-server --static-only --only-modules manifestValidation,prohibitedLibraries
678
+
679
+ # Fallback mode (try runtime, fall back to static on failure):
680
+ mcp-assess-full my-server --http http://localhost:10900/mcp --source ./my-server --fallback-static
681
+
615
682
  # Advanced options:
616
683
  mcp-assess-full --server my-server --source ./my-server --output ./results.json
617
684
  mcp-assess-full --server my-server --format markdown --include-policy
@@ -133,6 +133,9 @@ export const AssessmentOptionsSchema = z
133
133
  outputFormat: OutputFormatSchema.optional(),
134
134
  autoTier: z.boolean().optional(),
135
135
  stageBVerbose: z.boolean().optional(),
136
+ // Issue #213: Static analysis mode options
137
+ staticOnly: z.boolean().optional(),
138
+ fallbackStatic: z.boolean().optional(),
136
139
  })
137
140
  .refine((data) => !(data.profile && (data.skipModules?.length || data.onlyModules?.length)), {
138
141
  message: "--profile cannot be used with --skip-modules or --only-modules",
@@ -141,6 +144,15 @@ export const AssessmentOptionsSchema = z
141
144
  .refine((data) => !(data.skipModules?.length && data.onlyModules?.length), {
142
145
  message: "--skip-modules and --only-modules are mutually exclusive",
143
146
  path: ["skipModules"],
147
+ })
148
+ // Issue #213: Static mode validations
149
+ .refine((data) => !(data.staticOnly && data.fallbackStatic), {
150
+ message: "--static-only and --fallback-static are mutually exclusive",
151
+ path: ["staticOnly"],
152
+ })
153
+ .refine((data) => !(data.staticOnly && !data.sourceCodePath), {
154
+ message: "--static-only requires --source <path>",
155
+ path: ["staticOnly"],
144
156
  });
145
157
  /**
146
158
  * Schema for validation result.
@@ -427,3 +427,32 @@ export function emitTieredOutput(outputDir, outputFormat, tiers) {
427
427
  tiers,
428
428
  });
429
429
  }
430
+ // ============================================================================
431
+ // Native Module Warning Events - Issue #212
432
+ // ============================================================================
433
+ /**
434
+ * Emit native_module_warning event when native modules detected in package.json.
435
+ * This is a pre-flight warning that doesn't block assessment, but alerts
436
+ * users to potential issues with native binaries being blocked by Gatekeeper.
437
+ *
438
+ * @param moduleName - Name of the detected native module (e.g., "canvas")
439
+ * @param category - Module category (image, database, graphics, system, crypto)
440
+ * @param severity - Impact severity (HIGH or MEDIUM)
441
+ * @param warningMessage - Human-readable warning about potential issues
442
+ * @param dependencyType - Where found (dependencies, devDependencies, optionalDependencies)
443
+ * @param version - Version specifier from package.json
444
+ * @param suggestedEnvVars - Optional environment variables to mitigate issues
445
+ */
446
+ export function emitNativeModuleWarning(moduleName, category, severity, warningMessage, dependencyType, version, suggestedEnvVars) {
447
+ emitJSONL({
448
+ event: "native_module_warning",
449
+ moduleName,
450
+ category,
451
+ severity,
452
+ warningMessage,
453
+ dependencyType,
454
+ moduleVersion: version, // Renamed to avoid collision with package version
455
+ ...(suggestedEnvVars &&
456
+ Object.keys(suggestedEnvVars).length > 0 && { suggestedEnvVars }),
457
+ });
458
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Static Analysis Module Definitions
3
+ *
4
+ * Defines which assessment modules can run without a server connection.
5
+ * These modules analyze source code, manifest, package.json, and documentation
6
+ * without requiring tool execution or server interaction.
7
+ *
8
+ * @module cli/lib/static-modules
9
+ * @see Issue #213
10
+ */
11
+ /**
12
+ * Modules that can run in static-only mode (no server connection required).
13
+ *
14
+ * These modules have `contextRequirements.needsCallTool: false` and operate on:
15
+ * - Source code files (sourceCodeFiles)
16
+ * - Package metadata (packageJson, manifestJson)
17
+ * - Documentation (readmeContent)
18
+ * - Tool schemas (tools - extracted from manifest if available)
19
+ */
20
+ export const STATIC_MODULES = [
21
+ "manifestValidation", // MCPB manifest.json schema validation
22
+ "documentation", // Documentation quality (legacy name for DeveloperExperience)
23
+ "usability", // Usability assessment (legacy name for DeveloperExperience)
24
+ "prohibitedLibraries", // Banned dependency detection
25
+ "portability", // Platform-specific pattern detection
26
+ "externalAPIScanner", // External API dependency detection
27
+ "fileModularization", // Code structure metrics
28
+ "conformance", // Code quality checks
29
+ "toolAnnotations", // Annotation presence in source
30
+ "authentication", // Credential pattern detection
31
+ "aupCompliance", // AUP keyword analysis
32
+ ];
33
+ /**
34
+ * Modules that require a running server (cannot run in static mode).
35
+ *
36
+ * These modules have `contextRequirements.needsCallTool: true` and require:
37
+ * - Active tool execution (callTool function)
38
+ * - Server connection and response validation
39
+ * - Real-time behavior testing
40
+ */
41
+ export const RUNTIME_MODULES = [
42
+ "functionality", // Tests actual tool execution
43
+ "security", // Tests security vulnerabilities via tool calls
44
+ "temporal", // Tests for rug pulls (baseline + post-call comparison)
45
+ "protocolCompliance", // Tests protocol compliance via tool calls
46
+ "resources", // Tests resource accessibility
47
+ "prompts", // Tests prompt behavior and injection
48
+ "crossCapability", // Tests tool chaining attacks
49
+ "errorHandling", // Tests error response handling (legacy, merged into protocolCompliance)
50
+ "dependencyVulnerability", // npm/yarn/pnpm audit (requires live server)
51
+ ];
52
+ /**
53
+ * Check if a module can run in static-only mode
54
+ *
55
+ * @param moduleName - Name of the assessment module
56
+ * @returns true if the module can run without server connection
57
+ */
58
+ export function isStaticModule(moduleName) {
59
+ return STATIC_MODULES.includes(moduleName);
60
+ }
61
+ /**
62
+ * Check if a module requires a running server
63
+ *
64
+ * @param moduleName - Name of the assessment module
65
+ * @returns true if the module requires server connection
66
+ */
67
+ export function isRuntimeModule(moduleName) {
68
+ return RUNTIME_MODULES.includes(moduleName);
69
+ }
70
+ /**
71
+ * Get all static module names as an array
72
+ *
73
+ * @returns Array of static module names
74
+ */
75
+ export function getStaticModules() {
76
+ return STATIC_MODULES;
77
+ }
78
+ /**
79
+ * Get all runtime module names as an array
80
+ *
81
+ * @returns Array of runtime module names
82
+ */
83
+ export function getRuntimeModules() {
84
+ return RUNTIME_MODULES;
85
+ }
86
+ /**
87
+ * Filter a list of module names to only include static-capable modules
88
+ *
89
+ * @param modules - Array of module names to filter
90
+ * @returns Array of modules that can run in static mode
91
+ */
92
+ export function filterToStaticModules(modules) {
93
+ return modules.filter(isStaticModule);
94
+ }
95
+ /**
96
+ * Filter a list of module names to only include runtime-required modules
97
+ *
98
+ * @param modules - Array of module names to filter
99
+ * @returns Array of modules that require server connection
100
+ */
101
+ export function filterToRuntimeModules(modules) {
102
+ return modules.filter(isRuntimeModule);
103
+ }
@@ -2,21 +2,46 @@ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
2
2
  import { getDefaultEnvironment, StdioClientTransport, } from "@modelcontextprotocol/sdk/client/stdio.js";
3
3
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
4
4
  import { findActualExecutable } from "spawn-rx";
5
+ /**
6
+ * Returns minimal environment variables for spawned MCP servers.
7
+ * Using a curated set prevents unintended behavior from inherited env vars.
8
+ *
9
+ * @see https://github.com/triepod-ai/inspector-assessment/issues/211
10
+ */
11
+ function getMinimalEnv() {
12
+ const minimal = {};
13
+ // Essential system paths
14
+ if (process.env.PATH)
15
+ minimal.PATH = process.env.PATH;
16
+ if (process.env.HOME)
17
+ minimal.HOME = process.env.HOME;
18
+ if (process.env.TMPDIR)
19
+ minimal.TMPDIR = process.env.TMPDIR;
20
+ if (process.env.TMP)
21
+ minimal.TMP = process.env.TMP;
22
+ if (process.env.TEMP)
23
+ minimal.TEMP = process.env.TEMP;
24
+ // Node.js environment
25
+ minimal.NODE_ENV = process.env.NODE_ENV || "production";
26
+ // Platform-specific essentials
27
+ if (process.env.USER)
28
+ minimal.USER = process.env.USER;
29
+ if (process.env.SHELL)
30
+ minimal.SHELL = process.env.SHELL;
31
+ if (process.env.LANG)
32
+ minimal.LANG = process.env.LANG;
33
+ return minimal;
34
+ }
5
35
  function createStdioTransport(options) {
6
36
  let args = [];
7
37
  if (options.args !== undefined) {
8
38
  args = options.args;
9
39
  }
10
- const processEnv = {};
11
- for (const [key, value] of Object.entries(process.env)) {
12
- if (value !== undefined) {
13
- processEnv[key] = value;
14
- }
15
- }
40
+ // Use minimal env + SDK defaults instead of full process.env
16
41
  const defaultEnv = getDefaultEnvironment();
17
42
  const env = {
18
43
  ...defaultEnv,
19
- ...processEnv,
44
+ ...getMinimalEnv(),
20
45
  };
21
46
  const { cmd: actualCommand, args: actualArgs } = findActualExecutable(options.command ?? "", args);
22
47
  return new StdioClientTransport({
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bryan-thompson/inspector-assessment-cli",
3
- "version": "1.43.1",
3
+ "version": "1.43.3",
4
4
  "description": "CLI for the Enhanced MCP Inspector with assessment capabilities",
5
5
  "license": "MIT",
6
6
  "author": "Bryan Thompson <bryan@triepod.ai>",