@bryan-thompson/inspector-assessment 1.35.2 → 1.36.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.
Files changed (82) hide show
  1. package/cli/build/__tests__/assess-full-e2e.test.js +35 -9
  2. package/cli/build/__tests__/assess-full.test.js +4 -1
  3. package/cli/build/__tests__/assessment-runner/assessment-executor.test.js +15 -2
  4. package/cli/build/__tests__/assessment-runner/config-builder.test.js +6 -0
  5. package/cli/build/__tests__/assessment-runner/index.test.js +9 -4
  6. package/cli/build/__tests__/assessment-runner/path-resolver.test.js +112 -0
  7. package/cli/build/__tests__/assessment-runner/server-config.test.js +3 -0
  8. package/cli/build/__tests__/assessment-runner/server-connection.test.js +9 -1
  9. package/cli/build/__tests__/assessment-runner/source-loader.test.js +121 -16
  10. package/cli/build/__tests__/assessment-runner/tool-wrapper.test.js +3 -0
  11. package/cli/build/__tests__/assessment-runner-facade.test.js +7 -2
  12. package/cli/build/__tests__/cli-build-fixes.test.js +4 -1
  13. package/cli/build/__tests__/flag-parsing.test.js +3 -2
  14. package/cli/build/__tests__/http-transport-integration.test.js +19 -5
  15. package/cli/build/__tests__/jsonl-events.test.js +1 -1
  16. package/cli/build/__tests__/lib/server-configSchemas.test.js +4 -1
  17. package/cli/build/__tests__/lib/zodErrorFormatter.test.js +4 -1
  18. package/cli/build/__tests__/profiles.test.js +19 -8
  19. package/cli/build/__tests__/security/security-pattern-count.test.js +6 -3
  20. package/cli/build/__tests__/stage3-fix-validation.test.js +4 -1
  21. package/cli/build/__tests__/testbed-integration.test.js +19 -5
  22. package/cli/build/__tests__/transport.test.js +4 -1
  23. package/cli/build/lib/__tests__/cli-parserSchemas.test.js +4 -1
  24. package/cli/build/lib/assessment-runner/__tests__/server-configSchemas.test.js +4 -1
  25. package/cli/build/lib/assessment-runner/assessment-executor.js +23 -4
  26. package/cli/build/lib/assessment-runner/index.js +2 -0
  27. package/cli/build/lib/assessment-runner/path-resolver.js +48 -0
  28. package/cli/build/lib/assessment-runner/source-loader.js +47 -5
  29. package/cli/build/lib/cli-parser.js +10 -0
  30. package/cli/package.json +1 -1
  31. package/client/dist/assets/{OAuthCallback-jfmizOMH.js → OAuthCallback-Cfp3Vzdz.js} +1 -1
  32. package/client/dist/assets/{OAuthDebugCallback-bU5kKvnt.js → OAuthDebugCallback-7BLaxlcq.js} +1 -1
  33. package/client/dist/assets/{index-Ce63ds7G.js → index-B21S7_ML.js} +4 -4
  34. package/client/dist/index.html +1 -1
  35. package/client/lib/lib/assessment/coreTypes.d.ts +23 -0
  36. package/client/lib/lib/assessment/coreTypes.d.ts.map +1 -1
  37. package/client/lib/lib/assessment/extendedTypes.d.ts +49 -2
  38. package/client/lib/lib/assessment/extendedTypes.d.ts.map +1 -1
  39. package/client/lib/lib/assessment/jsonlEventSchemas.d.ts +4 -4
  40. package/client/lib/lib/assessment/resultTypes.d.ts +32 -1
  41. package/client/lib/lib/assessment/resultTypes.d.ts.map +1 -1
  42. package/client/lib/lib/aupPatterns.d.ts +50 -0
  43. package/client/lib/lib/aupPatterns.d.ts.map +1 -1
  44. package/client/lib/lib/aupPatterns.js +140 -0
  45. package/client/lib/lib/moduleScoring.d.ts.map +1 -1
  46. package/client/lib/lib/moduleScoring.js +39 -2
  47. package/client/lib/lib/securityPatterns.d.ts.map +1 -1
  48. package/client/lib/lib/securityPatterns.js +92 -0
  49. package/client/lib/services/assessment/modules/DeveloperExperienceAssessor.d.ts +26 -1
  50. package/client/lib/services/assessment/modules/DeveloperExperienceAssessor.d.ts.map +1 -1
  51. package/client/lib/services/assessment/modules/DeveloperExperienceAssessor.js +160 -1
  52. package/client/lib/services/assessment/modules/ErrorHandlingAssessor.d.ts.map +1 -1
  53. package/client/lib/services/assessment/modules/ErrorHandlingAssessor.js +15 -0
  54. package/client/lib/services/assessment/modules/ManifestValidationAssessor.d.ts +32 -0
  55. package/client/lib/services/assessment/modules/ManifestValidationAssessor.d.ts.map +1 -1
  56. package/client/lib/services/assessment/modules/ManifestValidationAssessor.js +218 -20
  57. package/client/lib/services/assessment/modules/ProhibitedLibrariesAssessor.d.ts +5 -0
  58. package/client/lib/services/assessment/modules/ProhibitedLibrariesAssessor.d.ts.map +1 -1
  59. package/client/lib/services/assessment/modules/ProhibitedLibrariesAssessor.js +29 -0
  60. package/client/lib/services/assessment/modules/SecurityAssessor.d.ts.map +1 -1
  61. package/client/lib/services/assessment/modules/SecurityAssessor.js +13 -0
  62. package/client/lib/services/assessment/modules/annotations/AlignmentChecker.d.ts +7 -2
  63. package/client/lib/services/assessment/modules/annotations/AlignmentChecker.d.ts.map +1 -1
  64. package/client/lib/services/assessment/modules/annotations/AlignmentChecker.js +116 -18
  65. package/client/lib/services/assessment/modules/annotations/index.d.ts +1 -1
  66. package/client/lib/services/assessment/modules/annotations/index.d.ts.map +1 -1
  67. package/client/lib/services/assessment/modules/annotations/index.js +2 -1
  68. package/client/lib/services/assessment/modules/securityTests/ConfidenceScorer.d.ts.map +1 -1
  69. package/client/lib/services/assessment/modules/securityTests/ConfidenceScorer.js +28 -0
  70. package/client/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.d.ts +95 -0
  71. package/client/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.d.ts.map +1 -1
  72. package/client/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.js +174 -0
  73. package/client/lib/services/assessment/modules/securityTests/SecurityPayloadTester.d.ts.map +1 -1
  74. package/client/lib/services/assessment/modules/securityTests/SecurityPayloadTester.js +15 -0
  75. package/client/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts +40 -0
  76. package/client/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts.map +1 -1
  77. package/client/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.js +143 -131
  78. package/client/package.json +1 -1
  79. package/package.json +1 -1
  80. package/server/build/__tests__/helpers.test.js +3 -0
  81. package/server/build/__tests__/security.test.js +3 -0
  82. package/server/package.json +1 -1
@@ -7,8 +7,15 @@
7
7
  *
8
8
  * Note: Tests skip gracefully when testbed servers are unavailable.
9
9
  */
10
- import { describe, it, expect, beforeAll } from "@jest/globals";
10
+ import { jest, describe, it, expect, beforeAll, afterEach, } from "@jest/globals";
11
+ /**
12
+ * Skip integration tests unless RUN_E2E_TESTS is set.
13
+ * This prevents long timeouts when testbed servers aren't running.
14
+ *
15
+ * To run: RUN_E2E_TESTS=1 npm test -- --testPathPattern="http-transport-integration"
16
+ */
11
17
  import { createTransport } from "../transport.js";
18
+ const describeE2E = process.env.RUN_E2E_TESTS ? describe : describe.skip;
12
19
  // Testbed server URLs
13
20
  const VULNERABLE_MCP_URL = "http://localhost:10900/mcp";
14
21
  const HARDENED_MCP_URL = "http://localhost:10901/mcp";
@@ -24,9 +31,9 @@ const DEFAULT_HEADERS = {
24
31
  * Check if a server is available by sending a basic HTTP request
25
32
  */
26
33
  async function checkServerAvailable(url) {
34
+ const controller = new AbortController();
35
+ const timeoutId = setTimeout(() => controller.abort(), 5000);
27
36
  try {
28
- const controller = new AbortController();
29
- const timeoutId = setTimeout(() => controller.abort(), 5000);
30
37
  const response = await fetch(url, {
31
38
  method: "POST",
32
39
  headers: DEFAULT_HEADERS,
@@ -42,13 +49,17 @@ async function checkServerAvailable(url) {
42
49
  }),
43
50
  signal: controller.signal,
44
51
  });
45
- clearTimeout(timeoutId);
46
52
  // Accept any response (200 or error) as indication server is up
47
53
  return response.status < 500;
48
54
  }
49
55
  catch {
50
56
  return false;
51
57
  }
58
+ finally {
59
+ // Always clean up timeout and abort controller to prevent memory leaks
60
+ clearTimeout(timeoutId);
61
+ controller.abort();
62
+ }
52
63
  }
53
64
  /**
54
65
  * Parse SSE response to extract JSON data
@@ -100,7 +111,10 @@ async function sendMcpRequest(url, method, params = {}, headers = {}) {
100
111
  }
101
112
  return { response, data };
102
113
  }
103
- describe("HTTP Transport Integration", () => {
114
+ describeE2E("HTTP Transport Integration", () => {
115
+ afterEach(() => {
116
+ jest.clearAllMocks();
117
+ });
104
118
  let vulnerableServerAvailable = false;
105
119
  let hardenedServerAvailable = false;
106
120
  beforeAll(async () => {
@@ -40,7 +40,7 @@ describe("JSONL Event Emission", () => {
40
40
  });
41
41
  it("should include schemaVersion field", () => {
42
42
  emitJSONL({ event: "test" });
43
- expect(emittedEvents[0]).toHaveProperty("schemaVersion", 1);
43
+ expect(emittedEvents[0]).toHaveProperty("schemaVersion", 3);
44
44
  });
45
45
  it("should handle complex nested objects", () => {
46
46
  emitJSONL({
@@ -7,7 +7,7 @@
7
7
  * Addresses QA requirement: Test that type guards are mutually exclusive
8
8
  * and correctly discriminate between transport types.
9
9
  */
10
- import { describe, it, expect } from "@jest/globals";
10
+ import { jest, describe, it, expect } from "@jest/globals";
11
11
  import { z } from "zod";
12
12
  // Define schemas inline to avoid import issues with client/lib in CLI tests
13
13
  const HttpSseServerConfigSchema = z.object({
@@ -36,6 +36,9 @@ function isStdioConfig(entry) {
36
36
  return "command" in entry && !("url" in entry);
37
37
  }
38
38
  describe("server-configSchemas type guards", () => {
39
+ afterEach(() => {
40
+ jest.clearAllMocks();
41
+ });
39
42
  describe("isHttpSseConfig", () => {
40
43
  it("should return true for HTTP transport config", () => {
41
44
  const config = {
@@ -4,10 +4,13 @@
4
4
  * Tests for formatZodError utility to ensure helpful error messages.
5
5
  * Addresses QA requirement: verify Zod error messages are helpful (not just generic "Invalid").
6
6
  */
7
- import { describe, it, expect } from "@jest/globals";
7
+ import { jest, describe, it, expect } from "@jest/globals";
8
8
  import { z } from "zod";
9
9
  import { formatZodError, formatZodIssue, formatZodErrorIndented, zodErrorToArray, formatUserFriendlyError, formatZodErrorForJson, } from "../../lib/zodErrorFormatter.js";
10
10
  describe("zodErrorFormatter", () => {
11
+ afterEach(() => {
12
+ jest.clearAllMocks();
13
+ });
11
14
  describe("formatZodIssue", () => {
12
15
  it("should format issue with path", () => {
13
16
  const issue = {
@@ -7,6 +7,9 @@
7
7
  import { jest, describe, it, expect } from "@jest/globals";
8
8
  import { ASSESSMENT_PROFILES, PROFILE_METADATA, MODULE_ALIASES, DEPRECATED_MODULES, TIER_1_CORE_SECURITY, TIER_2_COMPLIANCE, TIER_3_CAPABILITY, TIER_4_EXTENDED, ALL_MODULES, resolveModuleNames, getProfileModules, isValidProfileName, getProfileHelpText, mapLegacyConfigToModules, modulesToLegacyConfig, } from "../profiles.js";
9
9
  describe("Profile Definitions", () => {
10
+ afterEach(() => {
11
+ jest.restoreAllMocks();
12
+ });
10
13
  describe("Profile Constants", () => {
11
14
  it("should have four profiles defined", () => {
12
15
  const profiles = Object.keys(ASSESSMENT_PROFILES);
@@ -124,17 +127,25 @@ describe("resolveModuleNames", () => {
124
127
  });
125
128
  it("should emit warnings for deprecated modules when warn=true", () => {
126
129
  const consoleSpy = jest.spyOn(console, "warn").mockImplementation(() => { });
127
- const modules = ["documentation"];
128
- resolveModuleNames(modules, true);
129
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("deprecated"));
130
- consoleSpy.mockRestore();
130
+ try {
131
+ const modules = ["documentation"];
132
+ resolveModuleNames(modules, true);
133
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("deprecated"));
134
+ }
135
+ finally {
136
+ consoleSpy.mockRestore();
137
+ }
131
138
  });
132
139
  it("should not emit warnings when warn=false", () => {
133
140
  const consoleSpy = jest.spyOn(console, "warn").mockImplementation(() => { });
134
- const modules = ["documentation"];
135
- resolveModuleNames(modules, false);
136
- expect(consoleSpy).not.toHaveBeenCalled();
137
- consoleSpy.mockRestore();
141
+ try {
142
+ const modules = ["documentation"];
143
+ resolveModuleNames(modules, false);
144
+ expect(consoleSpy).not.toHaveBeenCalled();
145
+ }
146
+ finally {
147
+ consoleSpy.mockRestore();
148
+ }
138
149
  });
139
150
  });
140
151
  describe("getProfileModules", () => {
@@ -13,7 +13,7 @@
13
13
  * CLI tools override this to 30 patterns for comprehensive security assessment.
14
14
  * This test verifies consistency within each context.
15
15
  */
16
- import { describe, it, expect } from "@jest/globals";
16
+ import { jest, describe, it, expect, afterEach } from "@jest/globals";
17
17
  import * as fs from "fs";
18
18
  import * as path from "path";
19
19
  import { fileURLToPath } from "url";
@@ -22,6 +22,9 @@ const __filename = fileURLToPath(import.meta.url);
22
22
  const __dirname = path.dirname(__filename);
23
23
  const projectRoot = path.resolve(__dirname, "../../../.."); // From cli/src/__tests__/security to root
24
24
  describe("Security Pattern Count Consistency", () => {
25
+ afterEach(() => {
26
+ jest.clearAllMocks();
27
+ });
25
28
  describe("CLI assess-security references", () => {
26
29
  it("should use consistent pattern count in assess-security.ts", () => {
27
30
  const filePath = path.join(projectRoot, "cli/src/assess-security.ts");
@@ -131,9 +134,9 @@ describe("Security Pattern Count Consistency", () => {
131
134
  // Default: "default all 8" or "default 8"
132
135
  expect(content).toMatch(/securityPatternsToTest\?\s*:\s*number;.*default.*8/i);
133
136
  // Reviewer mode: "Test only 3 critical"
134
- expect(content).toMatch(/securityPatternsToTest:\s*3;.*3 critical/i);
137
+ expect(content).toMatch(/securityPatternsToTest:\s*3,.*3 critical/i);
135
138
  // Developer/audit modes: "all security patterns" or "all 8"
136
- expect(content).toMatch(/securityPatternsToTest:\s*8;.*all.*8|8.*patterns/i);
139
+ expect(content).toMatch(/securityPatternsToTest:\s*8,.*all.*8|8.*patterns/i);
137
140
  });
138
141
  it("should document pattern count in help text comments", () => {
139
142
  const filePath = path.join(projectRoot, "cli/src/assess-security.ts");
@@ -6,9 +6,12 @@
6
6
  *
7
7
  * @see https://github.com/triepod-ai/inspector-assessment/issues/137
8
8
  */
9
- import { describe, it, expect } from "@jest/globals";
9
+ import { jest, describe, it, expect, afterEach } from "@jest/globals";
10
10
  import { AssessmentOptionsSchema, safeParseAssessmentOptions, validateAssessmentOptions, } from "../lib/cli-parserSchemas.js";
11
11
  describe("Stage 3 Fix Validation Tests", () => {
12
+ afterEach(() => {
13
+ jest.clearAllMocks();
14
+ });
12
15
  describe("[TEST-001] cli-parserSchemas.ts - stageBVerbose field (FIX-001)", () => {
13
16
  describe("stageBVerbose field validation", () => {
14
17
  it("should accept stageBVerbose with true value (happy path)", () => {
@@ -11,7 +11,14 @@
11
11
  *
12
12
  * Note: Tests skip gracefully when testbed servers are unavailable.
13
13
  */
14
- import { describe, it, expect, beforeAll } from "@jest/globals";
14
+ import { jest, describe, it, expect, beforeAll, afterEach, } from "@jest/globals";
15
+ /**
16
+ * Skip integration tests unless RUN_E2E_TESTS is set.
17
+ * This prevents long timeouts when testbed servers aren't running.
18
+ *
19
+ * To run: RUN_E2E_TESTS=1 npm test -- --testPathPattern="testbed-integration"
20
+ */
21
+ const describeE2E = process.env.RUN_E2E_TESTS ? describe : describe.skip;
15
22
  // Testbed server URLs
16
23
  const VULNERABLE_URL = "http://localhost:10900/mcp";
17
24
  const HARDENED_URL = "http://localhost:10901/mcp";
@@ -26,9 +33,9 @@ const DEFAULT_HEADERS = {
26
33
  * Check if a server is available by sending an initialize request
27
34
  */
28
35
  async function checkServerAvailable(url) {
36
+ const controller = new AbortController();
37
+ const timeoutId = setTimeout(() => controller.abort(), 5000);
29
38
  try {
30
- const controller = new AbortController();
31
- const timeoutId = setTimeout(() => controller.abort(), 5000);
32
39
  const response = await fetch(url, {
33
40
  method: "POST",
34
41
  headers: DEFAULT_HEADERS,
@@ -44,12 +51,16 @@ async function checkServerAvailable(url) {
44
51
  }),
45
52
  signal: controller.signal,
46
53
  });
47
- clearTimeout(timeoutId);
48
54
  return response.status < 500;
49
55
  }
50
56
  catch {
51
57
  return false;
52
58
  }
59
+ finally {
60
+ // Always clean up timeout and abort controller to prevent memory leaks
61
+ clearTimeout(timeoutId);
62
+ controller.abort();
63
+ }
53
64
  }
54
65
  /**
55
66
  * Parse SSE response to extract JSON data
@@ -119,7 +130,10 @@ async function callTool(url, toolName, args) {
119
130
  });
120
131
  return data;
121
132
  }
122
- describe("Testbed A/B Comparison", () => {
133
+ describeE2E("Testbed A/B Comparison", () => {
134
+ afterEach(() => {
135
+ jest.clearAllMocks();
136
+ });
123
137
  let bothServersAvailable = false;
124
138
  let vulnerableAvailable = false;
125
139
  let hardenedAvailable = false;
@@ -5,9 +5,12 @@
5
5
  * Tests focus on input validation and error handling rather than
6
6
  * mocked transport implementations due to ESM limitations.
7
7
  */
8
- import { describe, it, expect } from "@jest/globals";
8
+ import { jest, describe, it, expect, afterEach } from "@jest/globals";
9
9
  import { createTransport } from "../transport.js";
10
10
  describe("Transport Creation", () => {
11
+ afterEach(() => {
12
+ jest.clearAllMocks();
13
+ });
11
14
  describe("Input Validation", () => {
12
15
  it("should throw error when URL is missing for HTTP transport", () => {
13
16
  const options = {
@@ -5,10 +5,13 @@
5
5
  *
6
6
  * @module cli/lib/__tests__/cli-parserSchemas
7
7
  */
8
- // Uses Jest globals (describe, test, expect)
8
+ import { jest, describe, test, expect, afterEach } from "@jest/globals";
9
9
  import { ZodError } from "zod";
10
10
  import { AssessmentProfileNameSchema, AssessmentModuleNameSchema, ServerConfigSchema, AssessmentOptionsSchema, ValidationResultSchema, validateAssessmentOptions, validateServerConfig, parseAssessmentOptions, safeParseAssessmentOptions, parseModuleNames, safeParseModuleNames, LogLevelSchema, ReportFormatSchema, TransportTypeSchema, ZOD_SCHEMA_VERSION, } from "../cli-parserSchemas.js";
11
11
  describe("cli-parserSchemas", () => {
12
+ afterEach(() => {
13
+ jest.clearAllMocks();
14
+ });
12
15
  describe("Re-exported schemas", () => {
13
16
  test("exports ZOD_SCHEMA_VERSION", () => {
14
17
  expect(ZOD_SCHEMA_VERSION).toBe(1);
@@ -5,10 +5,13 @@
5
5
  *
6
6
  * @module cli/lib/assessment-runner/__tests__/server-configSchemas
7
7
  */
8
- // Uses Jest globals (describe, test, expect)
8
+ import { jest, describe, test, expect, afterEach } from "@jest/globals";
9
9
  import { ZodError } from "zod";
10
10
  import { HttpSseServerConfigSchema, StdioServerConfigSchema, ServerEntrySchema, ClaudeDesktopConfigSchema, StandaloneConfigSchema, ConfigFileSchema, parseConfigFile, safeParseConfigFile, validateServerEntry, isHttpSseConfig, isStdioConfig, TransportTypeSchema, } from "../server-configSchemas.js";
11
11
  describe("server-configSchemas", () => {
12
+ afterEach(() => {
13
+ jest.clearAllMocks();
14
+ });
12
15
  describe("Re-exported schemas", () => {
13
16
  test("exports TransportTypeSchema", () => {
14
17
  expect(TransportTypeSchema.safeParse("stdio").success).toBe(true);
@@ -12,9 +12,12 @@ import { AssessmentStateManager } from "../../assessmentState.js";
12
12
  import { emitServerConnected, emitToolDiscovered, emitToolsDiscoveryComplete, emitAssessmentComplete, emitTestBatch, emitVulnerabilityFound, emitAnnotationMissing, emitAnnotationMisaligned, emitAnnotationReviewRecommended, emitAnnotationAligned, emitModulesConfigured, emitPhaseStarted, emitPhaseComplete, emitToolTestComplete, emitValidationSummary, } from "../jsonl-events.js";
13
13
  import { loadServerConfig } from "./server-config.js";
14
14
  import { loadSourceFiles } from "./source-loader.js";
15
+ import { resolveSourcePath } from "./path-resolver.js";
15
16
  import { connectToServer } from "./server-connection.js";
16
17
  import { createCallToolWrapper } from "./tool-wrapper.js";
17
18
  import { buildConfig } from "./config-builder.js";
19
+ // Issue #155: Import annotation debug mode setter
20
+ import { setAnnotationDebugMode } from "../../../../client/lib/services/assessment/modules/annotations/AlignmentChecker.js";
18
21
  /**
19
22
  * Run full assessment against an MCP server
20
23
  *
@@ -22,6 +25,13 @@ import { buildConfig } from "./config-builder.js";
22
25
  * @returns Assessment results
23
26
  */
24
27
  export async function runFullAssessment(options) {
28
+ // Issue #155: Enable annotation debug mode if flag is set
29
+ if (options.debugAnnotations) {
30
+ setAnnotationDebugMode(true);
31
+ if (!options.jsonOnly) {
32
+ console.log("🔍 Annotation debug mode enabled (--debug-annotations)");
33
+ }
34
+ }
25
35
  if (!options.jsonOnly) {
26
36
  console.log(`\n🔍 Starting full assessment for: ${options.serverName}`);
27
37
  }
@@ -199,10 +209,19 @@ export async function runFullAssessment(options) {
199
209
  }
200
210
  }
201
211
  let sourceFiles = {};
202
- if (options.sourceCodePath && fs.existsSync(options.sourceCodePath)) {
203
- sourceFiles = loadSourceFiles(options.sourceCodePath);
204
- if (!options.jsonOnly) {
205
- console.log(`📁 Loaded source files from: ${options.sourceCodePath}`);
212
+ if (options.sourceCodePath) {
213
+ // Resolve path using utility (handles ~, relative paths, symlinks)
214
+ const resolvedSourcePath = resolveSourcePath(options.sourceCodePath);
215
+ if (fs.existsSync(resolvedSourcePath)) {
216
+ sourceFiles = loadSourceFiles(resolvedSourcePath, options.debugSource);
217
+ if (!options.jsonOnly) {
218
+ console.log(`📁 Loaded source files from: ${resolvedSourcePath}`);
219
+ }
220
+ }
221
+ else if (!options.jsonOnly) {
222
+ // Issue #154: Always show warning, not just with --debug-source
223
+ console.log(`⚠️ Source path not found: ${options.sourceCodePath} (resolved: ${resolvedSourcePath})`);
224
+ console.log(` Use --source <existing-path> to enable full source file analysis.`);
206
225
  }
207
226
  }
208
227
  // Create readResource wrapper for ResourceAssessor
@@ -10,6 +10,8 @@
10
10
  export { loadServerConfig } from "./server-config.js";
11
11
  // Source File Loading
12
12
  export { loadSourceFiles } from "./source-loader.js";
13
+ // Path Resolution
14
+ export { resolveSourcePath } from "./path-resolver.js";
13
15
  // Server Connection
14
16
  export { connectToServer } from "./server-connection.js";
15
17
  // Tool Wrapper
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Path Resolution Utilities
3
+ *
4
+ * Handles path normalization for source code paths including:
5
+ * - Home directory (~) expansion
6
+ * - Relative path resolution
7
+ * - Symlink resolution (for MCPB temp extraction paths)
8
+ *
9
+ * @module cli/lib/assessment-runner/path-resolver
10
+ */
11
+ import * as fs from "fs";
12
+ import * as os from "os";
13
+ import * as path from "path";
14
+ /**
15
+ * Resolve a source code path to an absolute, real path
16
+ *
17
+ * Handles:
18
+ * - Tilde (~) expansion to home directory
19
+ * - Relative paths resolved to absolute
20
+ * - Symlinks followed to real paths (important for MCPB temp extraction)
21
+ *
22
+ * @param sourcePath - The source path to resolve (may be relative, contain ~, or be a symlink)
23
+ * @returns The resolved absolute path, or the original path if resolution fails
24
+ *
25
+ * @example
26
+ * resolveSourcePath("~/project") // => "/home/user/project"
27
+ * resolveSourcePath("./src") // => "/current/working/dir/src"
28
+ * resolveSourcePath("/tmp/symlink") // => "/actual/target/path"
29
+ */
30
+ export function resolveSourcePath(sourcePath) {
31
+ let resolved = sourcePath;
32
+ // Expand home directory (~)
33
+ if (resolved.startsWith("~")) {
34
+ resolved = path.join(os.homedir(), resolved.slice(1));
35
+ }
36
+ // Resolve to absolute path
37
+ resolved = path.resolve(resolved);
38
+ // Follow symlinks if path exists (handles MCPB temp extraction paths)
39
+ try {
40
+ if (fs.existsSync(resolved)) {
41
+ resolved = fs.realpathSync(resolved);
42
+ }
43
+ }
44
+ catch {
45
+ // realpathSync can fail on broken symlinks, continue with resolved path
46
+ }
47
+ return resolved;
48
+ }
@@ -6,6 +6,7 @@
6
6
  * @module cli/lib/assessment-runner/source-loader
7
7
  */
8
8
  import * as fs from "fs";
9
+ import * as os from "os";
9
10
  import * as path from "path";
10
11
  /** Maximum file size (in characters) to include in source code analysis */
11
12
  const MAX_SOURCE_FILE_SIZE = 100_000;
@@ -13,34 +14,65 @@ const MAX_SOURCE_FILE_SIZE = 100_000;
13
14
  * Load optional files from source code path
14
15
  *
15
16
  * @param sourcePath - Path to source code directory
17
+ * @param debug - Enable debug logging for path resolution troubleshooting
16
18
  * @returns Object containing loaded source files
17
19
  */
18
- export function loadSourceFiles(sourcePath) {
20
+ export function loadSourceFiles(sourcePath, debug = false) {
19
21
  const result = {};
22
+ // Debug logging helper - masks home directory for privacy in logs
23
+ const log = (msg) => {
24
+ if (!debug)
25
+ return;
26
+ const maskedMsg = msg.replace(os.homedir(), "~");
27
+ console.log(`[source-loader] ${maskedMsg}`);
28
+ };
29
+ log(`Starting source file loading from: ${sourcePath}`);
20
30
  // Search for README in source directory and parent directories (up to 3 levels)
21
31
  // This handles cases where --source points to a subdirectory but README is at repo root
22
- const readmePaths = ["README.md", "readme.md", "Readme.md"];
32
+ // Extended patterns to handle various README naming conventions
33
+ const readmePaths = [
34
+ "README.md",
35
+ "readme.md",
36
+ "Readme.md",
37
+ "README.markdown",
38
+ "readme.markdown",
39
+ "README.txt",
40
+ "readme.txt",
41
+ "README",
42
+ "Readme",
43
+ ];
23
44
  let readmeFound = false;
45
+ log(`Searching for README variants: ${readmePaths.join(", ")}`);
24
46
  // First try the source directory itself
25
47
  for (const readmePath of readmePaths) {
26
48
  const fullPath = path.join(sourcePath, readmePath);
27
- if (fs.existsSync(fullPath)) {
49
+ const exists = fs.existsSync(fullPath);
50
+ log(` Checking: ${fullPath} - exists: ${exists}`);
51
+ if (exists) {
28
52
  result.readmeContent = fs.readFileSync(fullPath, "utf-8");
53
+ log(` ✓ Found README: ${fullPath} (${result.readmeContent.length} bytes)`);
29
54
  readmeFound = true;
30
55
  break;
31
56
  }
32
57
  }
33
58
  // If not found, search parent directories (up to 3 levels)
34
59
  if (!readmeFound) {
60
+ log(`README not found in source directory, searching parent directories...`);
35
61
  let currentDir = sourcePath;
36
62
  for (let i = 0; i < 3; i++) {
37
63
  const parentDir = path.dirname(currentDir);
38
- if (parentDir === currentDir)
64
+ if (parentDir === currentDir) {
65
+ log(` Reached filesystem root, stopping parent search`);
39
66
  break; // Reached filesystem root
67
+ }
68
+ log(` Searching parent level ${i + 1}: ${parentDir}`);
40
69
  for (const readmePath of readmePaths) {
41
70
  const fullPath = path.join(parentDir, readmePath);
42
- if (fs.existsSync(fullPath)) {
71
+ const exists = fs.existsSync(fullPath);
72
+ log(` Checking: ${fullPath} - exists: ${exists}`);
73
+ if (exists) {
43
74
  result.readmeContent = fs.readFileSync(fullPath, "utf-8");
75
+ log(` ✓ Found README: ${fullPath} (${result.readmeContent.length} bytes)`);
44
76
  readmeFound = true;
45
77
  break;
46
78
  }
@@ -50,6 +82,9 @@ export function loadSourceFiles(sourcePath) {
50
82
  currentDir = parentDir;
51
83
  }
52
84
  }
85
+ if (!readmeFound) {
86
+ log(`✗ No README found in source directory or parent directories`);
87
+ }
53
88
  const packagePath = path.join(sourcePath, "package.json");
54
89
  if (fs.existsSync(packagePath)) {
55
90
  result.packageJson = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
@@ -135,5 +170,12 @@ export function loadSourceFiles(sourcePath) {
135
170
  catch (e) {
136
171
  console.warn("[Assessment] Could not load source files:", e);
137
172
  }
173
+ // Summary logging
174
+ const sourceCodeFiles = result.sourceCodeFiles;
175
+ log(`Source loading complete:`);
176
+ log(` - README: ${result.readmeContent ? "found" : "not found"}`);
177
+ log(` - package.json: ${result.packageJson ? "found" : "not found"}`);
178
+ log(` - manifest.json: ${result.manifestJson ? "found" : "not found"}`);
179
+ log(` - Source files loaded: ${sourceCodeFiles.size}`);
138
180
  return result;
139
181
  }
@@ -94,6 +94,13 @@ export function parseArgs(argv) {
94
94
  case "--source":
95
95
  options.sourceCodePath = args[++i];
96
96
  break;
97
+ case "--debug-source":
98
+ options.debugSource = true;
99
+ break;
100
+ case "--debug-annotations":
101
+ // Issue #155: Enable debug logging for annotation extraction
102
+ options.debugAnnotations = true;
103
+ break;
97
104
  case "--pattern-config":
98
105
  case "-p":
99
106
  options.patternConfigPath = args[++i];
@@ -374,6 +381,9 @@ Options:
374
381
  --config, -c <path> Path to server config JSON
375
382
  --output, -o <path> Output path (default: /tmp/inspector-full-assessment-<server>.<ext>)
376
383
  --source <path> Source code path for deep analysis (AUP, portability, etc.)
384
+ --debug-source Enable debug logging for source file loading (Issue #151)
385
+ --debug-annotations Enable debug logging for annotation extraction (Issue #155)
386
+ Shows raw tool keys, annotations object, and direct hints
377
387
  --pattern-config, -p <path> Path to custom annotation pattern JSON
378
388
  --performance-config <path> Path to performance tuning JSON (batch sizes, timeouts, etc.)
379
389
  --format, -f <type> Output format: json (default) or markdown
package/cli/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bryan-thompson/inspector-assessment-cli",
3
- "version": "1.35.2",
3
+ "version": "1.36.0",
4
4
  "description": "CLI for the Enhanced MCP Inspector with assessment capabilities",
5
5
  "license": "MIT",
6
6
  "author": "Bryan Thompson <bryan@triepod.ai>",
@@ -1,4 +1,4 @@
1
- import { u as useToast, r as reactExports, j as jsxRuntimeExports, p as parseOAuthCallbackParams, g as generateOAuthErrorDescription, S as SESSION_KEYS, I as InspectorOAuthClientProvider, a as auth } from "./index-Ce63ds7G.js";
1
+ import { u as useToast, r as reactExports, j as jsxRuntimeExports, p as parseOAuthCallbackParams, g as generateOAuthErrorDescription, S as SESSION_KEYS, I as InspectorOAuthClientProvider, a as auth } from "./index-B21S7_ML.js";
2
2
  const OAuthCallback = ({ onConnect }) => {
3
3
  const { toast } = useToast();
4
4
  const hasProcessedRef = reactExports.useRef(false);
@@ -1,4 +1,4 @@
1
- import { r as reactExports, S as SESSION_KEYS, p as parseOAuthCallbackParams, j as jsxRuntimeExports, g as generateOAuthErrorDescription } from "./index-Ce63ds7G.js";
1
+ import { r as reactExports, S as SESSION_KEYS, p as parseOAuthCallbackParams, j as jsxRuntimeExports, g as generateOAuthErrorDescription } from "./index-B21S7_ML.js";
2
2
  const OAuthDebugCallback = ({ onConnect }) => {
3
3
  reactExports.useEffect(() => {
4
4
  let isProcessed = false;
@@ -16373,7 +16373,7 @@ object({
16373
16373
  token_type_hint: string().optional()
16374
16374
  }).strip();
16375
16375
  const name = "@bryan-thompson/inspector-assessment-client";
16376
- const version$1 = "1.35.2";
16376
+ const version$1 = "1.36.0";
16377
16377
  const packageJson = {
16378
16378
  name,
16379
16379
  version: version$1
@@ -48920,7 +48920,7 @@ const useTheme = () => {
48920
48920
  [theme, setThemeWithSideEffect]
48921
48921
  );
48922
48922
  };
48923
- const version = "1.35.2";
48923
+ const version = "1.36.0";
48924
48924
  var [createTooltipContext] = createContextScope("Tooltip", [
48925
48925
  createPopperScope
48926
48926
  ]);
@@ -52515,13 +52515,13 @@ const App = () => {
52515
52515
  ) });
52516
52516
  if (window.location.pathname === "/oauth/callback") {
52517
52517
  const OAuthCallback = React.lazy(
52518
- () => __vitePreload(() => import("./OAuthCallback-jfmizOMH.js"), true ? [] : void 0)
52518
+ () => __vitePreload(() => import("./OAuthCallback-Cfp3Vzdz.js"), true ? [] : void 0)
52519
52519
  );
52520
52520
  return /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(OAuthCallback, { onConnect: onOAuthConnect }) });
52521
52521
  }
52522
52522
  if (window.location.pathname === "/oauth/callback/debug") {
52523
52523
  const OAuthDebugCallback = React.lazy(
52524
- () => __vitePreload(() => import("./OAuthDebugCallback-bU5kKvnt.js"), true ? [] : void 0)
52524
+ () => __vitePreload(() => import("./OAuthDebugCallback-7BLaxlcq.js"), true ? [] : void 0)
52525
52525
  );
52526
52526
  return /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(OAuthDebugCallback, { onConnect: onOAuthDebugConnect }) });
52527
52527
  }
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/mcp.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>MCP Inspector</title>
8
- <script type="module" crossorigin src="/assets/index-Ce63ds7G.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-B21S7_ML.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-BoUA5OL1.css">
10
10
  </head>
11
11
  <body>
@@ -280,4 +280,27 @@ export interface PackageJson {
280
280
  export type ToolInputSchema = JSONSchema7 & {
281
281
  type: "object";
282
282
  };
283
+ /**
284
+ * Result of namespace/prefix detection for tool naming.
285
+ * Used to identify when tools share a common namespace (e.g., calc_add, calc_subtract).
286
+ * This helps downstream analyzers (like mcp-auditor) understand intentional naming patterns
287
+ * and reduces false positives for "naming conflicts".
288
+ * @public
289
+ */
290
+ export interface NamespaceDetectionResult {
291
+ /** Whether a namespace was detected */
292
+ detected: boolean;
293
+ /** The detected namespace/prefix (e.g., "calc" from calc_add, calc_subtract) */
294
+ namespace?: string;
295
+ /** Confidence level of the detection */
296
+ confidence: "high" | "medium" | "low";
297
+ /** Number of tools that match this namespace */
298
+ toolsCovered: number;
299
+ /** Total number of tools analyzed */
300
+ totalTools: number;
301
+ /** How the namespace was detected */
302
+ matchPattern?: "prefix" | "serverName" | "none";
303
+ /** Sample tool names showing the pattern */
304
+ evidence?: string[];
305
+ }
283
306
  //# sourceMappingURL=coreTypes.d.ts.map