@bryan-thompson/inspector-assessment 1.2.1 → 1.4.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.
@@ -16205,7 +16205,7 @@ objectType({
16205
16205
  token_type_hint: stringType().optional()
16206
16206
  }).strip();
16207
16207
  const name = "@bryan-thompson/inspector-assessment-client";
16208
- const version$1 = "1.2.1";
16208
+ const version$1 = "1.4.0";
16209
16209
  const packageJson = {
16210
16210
  name,
16211
16211
  version: version$1
@@ -16986,8 +16986,8 @@ class InspectorOAuthClientProvider {
16986
16986
  token_endpoint_auth_method: "none",
16987
16987
  grant_types: ["authorization_code", "refresh_token"],
16988
16988
  response_types: ["code"],
16989
- client_name: "MCP Inspector",
16990
- client_uri: "https://github.com/modelcontextprotocol/inspector",
16989
+ client_name: "MCP Assessor",
16990
+ client_uri: "https://github.com/triepod-ai/inspector-assessment",
16991
16991
  scope: this.scope ?? ""
16992
16992
  };
16993
16993
  }
@@ -41736,7 +41736,7 @@ const useTheme = () => {
41736
41736
  [theme, setThemeWithSideEffect]
41737
41737
  );
41738
41738
  };
41739
- const version = "1.2.1";
41739
+ const version = "1.4.0";
41740
41740
  var [createTooltipContext] = createContextScope("Tooltip", [
41741
41741
  createPopperScope
41742
41742
  ]);
@@ -42732,7 +42732,7 @@ const Sidebar = ({
42732
42732
  }, [generateMCPServerFile, toast2, reportError2]);
42733
42733
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-card border-r border-border flex flex-col h-full", children: [
42734
42734
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-between p-4 border-b border-gray-200 dark:border-border", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("h1", { className: "ml-2 text-lg font-semibold", children: [
42735
- "MCP Inspector v",
42735
+ "MCP Assessor v",
42736
42736
  version
42737
42737
  ] }) }) }),
42738
42738
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 flex-1 overflow-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
@@ -43288,7 +43288,7 @@ const Sidebar = ({
43288
43288
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(
43289
43289
  "a",
43290
43290
  {
43291
- href: "https://github.com/modelcontextprotocol/inspector",
43291
+ href: "https://github.com/triepod-ai/inspector-assessment",
43292
43292
  target: "_blank",
43293
43293
  rel: "noopener noreferrer",
43294
43294
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(Github, { className: "w-4 h-4 text-foreground" })
@@ -46396,7 +46396,7 @@ class FunctionalityAssessor extends BaseAssessor {
46396
46396
  }
46397
46397
  const SECURITY_ATTACK_PATTERNS = [
46398
46398
  // ==========================================
46399
- // CRITICAL INJECTION TESTS (3)
46399
+ // CRITICAL INJECTION TESTS (4)
46400
46400
  // ==========================================
46401
46401
  // 1. COMMAND INJECTION
46402
46402
  {
@@ -46476,7 +46476,66 @@ const SECURITY_ATTACK_PATTERNS = [
46476
46476
  }
46477
46477
  ]
46478
46478
  },
46479
- // 3. PATH TRAVERSAL
46479
+ // 3. CALCULATOR INJECTION
46480
+ {
46481
+ attackName: "Calculator Injection",
46482
+ description: "Test if calculator/math tools execute arbitrary expressions instead of safely storing them",
46483
+ payloads: [
46484
+ // Simple arithmetic - triggers eval() in vulnerable calculator tools
46485
+ {
46486
+ payload: "2+2",
46487
+ evidence: /The answer is 4/i,
46488
+ riskLevel: "HIGH",
46489
+ description: "Simple arithmetic expression (common trigger)",
46490
+ payloadType: "injection"
46491
+ },
46492
+ {
46493
+ payload: "5*5",
46494
+ evidence: /The answer is 25/i,
46495
+ riskLevel: "HIGH",
46496
+ description: "Multiplication expression",
46497
+ payloadType: "injection"
46498
+ },
46499
+ {
46500
+ payload: "100+200",
46501
+ evidence: /The answer is 300/i,
46502
+ riskLevel: "HIGH",
46503
+ description: "Addition with larger numbers",
46504
+ payloadType: "injection"
46505
+ },
46506
+ // Natural language triggers
46507
+ {
46508
+ payload: "what is 10*10",
46509
+ evidence: /The answer is 100/i,
46510
+ riskLevel: "HIGH",
46511
+ description: "Natural language math query",
46512
+ payloadType: "injection"
46513
+ },
46514
+ {
46515
+ payload: "calculate 50+50",
46516
+ evidence: /The answer is 100/i,
46517
+ riskLevel: "HIGH",
46518
+ description: "Calculate command",
46519
+ payloadType: "injection"
46520
+ },
46521
+ // Code injection via eval()
46522
+ {
46523
+ payload: "__import__('os').system('whoami')",
46524
+ evidence: /\b(root|user|admin|ubuntu)\b/i,
46525
+ riskLevel: "HIGH",
46526
+ description: "Python code injection via eval",
46527
+ payloadType: "injection"
46528
+ },
46529
+ {
46530
+ payload: "1+1; __import__('subprocess').run(['whoami'])",
46531
+ evidence: /\b(root|user|admin|ubuntu)\b/i,
46532
+ riskLevel: "HIGH",
46533
+ description: "eval chain to system command",
46534
+ payloadType: "injection"
46535
+ }
46536
+ ]
46537
+ },
46538
+ // 4. PATH TRAVERSAL
46480
46539
  {
46481
46540
  attackName: "Path Traversal",
46482
46541
  description: "Test if tool allows access to files outside intended directory",
@@ -46514,7 +46573,7 @@ const SECURITY_ATTACK_PATTERNS = [
46514
46573
  // ==========================================
46515
46574
  // INPUT VALIDATION TESTS (3)
46516
46575
  // ==========================================
46517
- // 4. TYPE SAFETY
46576
+ // 5. TYPE SAFETY
46518
46577
  {
46519
46578
  attackName: "Type Safety",
46520
46579
  description: "Test if tool validates parameter types correctly",
@@ -46542,7 +46601,7 @@ const SECURITY_ATTACK_PATTERNS = [
46542
46601
  }
46543
46602
  ]
46544
46603
  },
46545
- // 5. BOUNDARY TESTING
46604
+ // 6. BOUNDARY TESTING
46546
46605
  {
46547
46606
  attackName: "Boundary Testing",
46548
46607
  description: "Test edge cases and boundary conditions",
@@ -46570,7 +46629,7 @@ const SECURITY_ATTACK_PATTERNS = [
46570
46629
  }
46571
46630
  ]
46572
46631
  },
46573
- // 6. REQUIRED FIELDS
46632
+ // 7. REQUIRED FIELDS
46574
46633
  {
46575
46634
  attackName: "Required Fields",
46576
46635
  description: "Test if tool validates required parameters",
@@ -46587,7 +46646,7 @@ const SECURITY_ATTACK_PATTERNS = [
46587
46646
  // ==========================================
46588
46647
  // PROTOCOL COMPLIANCE TESTS (2)
46589
46648
  // ==========================================
46590
- // 7. MCP ERROR FORMAT
46649
+ // 8. MCP ERROR FORMAT
46591
46650
  {
46592
46651
  attackName: "MCP Error Format",
46593
46652
  description: "Verify errors follow MCP protocol specification",
@@ -46601,7 +46660,7 @@ const SECURITY_ATTACK_PATTERNS = [
46601
46660
  }
46602
46661
  ]
46603
46662
  },
46604
- // 8. TIMEOUT HANDLING
46663
+ // 9. TIMEOUT HANDLING
46605
46664
  {
46606
46665
  attackName: "Timeout Handling",
46607
46666
  description: "Test if tool handles long operations gracefully",
@@ -46614,6 +46673,103 @@ const SECURITY_ATTACK_PATTERNS = [
46614
46673
  payloadType: "protocol"
46615
46674
  }
46616
46675
  ]
46676
+ },
46677
+ // ==========================================
46678
+ // TOOL-SPECIFIC VULNERABILITY TESTS (4)
46679
+ // ==========================================
46680
+ // 10. INDIRECT PROMPT INJECTION (HTTP URL Fetching)
46681
+ {
46682
+ attackName: "Indirect Prompt Injection",
46683
+ description: "Test if tool fetches and executes external content",
46684
+ payloads: [
46685
+ {
46686
+ payload: "http://example.com",
46687
+ evidence: /(status_code.*200|status.*200.*content|<html|<!DOCTYPE|fetched.*http.*content)/i,
46688
+ riskLevel: "HIGH",
46689
+ description: "Benign URL fetch test",
46690
+ payloadType: "injection",
46691
+ parameterTypes: ["url", "uri", "link", "endpoint", "address"]
46692
+ },
46693
+ {
46694
+ payload: "http://evil.com/inject.txt",
46695
+ evidence: /(fetched|retrieved|evil\.com|malicious|injected)/i,
46696
+ riskLevel: "HIGH",
46697
+ description: "Malicious URL fetch test",
46698
+ payloadType: "injection",
46699
+ parameterTypes: ["url", "uri", "link"]
46700
+ }
46701
+ ]
46702
+ },
46703
+ // 11. UNICODE BYPASS
46704
+ {
46705
+ attackName: "Unicode Bypass",
46706
+ description: "Test if tool decodes and executes unicode-encoded commands",
46707
+ payloads: [
46708
+ {
46709
+ payload: "\\u0077\\u0068\\u006f\\u0061\\u006d\\u0069",
46710
+ // "whoami"
46711
+ evidence: /(root|user|admin|decoded.*executed|unicode.*executed)/i,
46712
+ riskLevel: "MEDIUM",
46713
+ description: "Unicode-encoded system command",
46714
+ payloadType: "injection",
46715
+ parameterTypes: ["text", "content", "message", "input", "data"]
46716
+ },
46717
+ {
46718
+ payload: "\\u0069\\u0067\\u006e\\u006f\\u0072\\u0065",
46719
+ // "ignore"
46720
+ evidence: /(decoded|unicode|ignore.*previous|executed)/i,
46721
+ riskLevel: "MEDIUM",
46722
+ description: "Unicode-encoded instruction",
46723
+ payloadType: "injection",
46724
+ parameterTypes: ["text", "content", "message"]
46725
+ }
46726
+ ]
46727
+ },
46728
+ // 12. NESTED INJECTION
46729
+ {
46730
+ attackName: "Nested Injection",
46731
+ description: "Test if tool executes hidden instructions in nested JSON",
46732
+ payloads: [
46733
+ {
46734
+ payload: '{"query":"normal","hidden":"whoami"}',
46735
+ evidence: /(executed.*hidden|nested.*instruction|root|user|admin)/i,
46736
+ riskLevel: "MEDIUM",
46737
+ description: "Hidden command in JSON field",
46738
+ payloadType: "injection",
46739
+ parameterTypes: ["data", "json", "payload", "body", "params"]
46740
+ },
46741
+ {
46742
+ payload: '{"visible":"safe","_execute":"delete all"}',
46743
+ evidence: /(executed|deleted|internal.*field|hidden)/i,
46744
+ riskLevel: "HIGH",
46745
+ description: "Hidden instruction in internal field",
46746
+ payloadType: "injection",
46747
+ parameterTypes: ["data", "json", "input"]
46748
+ }
46749
+ ]
46750
+ },
46751
+ // 13. PACKAGE SQUATTING
46752
+ {
46753
+ attackName: "Package Squatting",
46754
+ description: "Test if tool downloads typosquatted packages",
46755
+ payloads: [
46756
+ {
46757
+ payload: "install nmpy",
46758
+ evidence: /(downloaded|installed|nmpy|typosquatted|pip.*output)/i,
46759
+ riskLevel: "MEDIUM",
46760
+ description: "Typosquatted package (nmpy instead of numpy)",
46761
+ payloadType: "injection",
46762
+ parameterTypes: ["package", "pkg", "module", "library", "dependency"]
46763
+ },
46764
+ {
46765
+ payload: "requsts",
46766
+ evidence: /(downloaded|installed|requsts|typosquatted)/i,
46767
+ riskLevel: "MEDIUM",
46768
+ description: "Typosquatted package (requsts instead of requests)",
46769
+ payloadType: "injection",
46770
+ parameterTypes: ["package", "pkg", "module"]
46771
+ }
46772
+ ]
46617
46773
  }
46618
46774
  ];
46619
46775
  function getPayloadsForAttack(attackName, limit) {
@@ -47002,10 +47158,20 @@ class SecurityAssessor extends BaseAssessor {
47002
47158
  async assess(context) {
47003
47159
  const toolsToTest = this.selectToolsForTesting(context.tools);
47004
47160
  const allTests = await this.runUniversalSecurityTests(context);
47161
+ const connectionErrors = allTests.filter((t) => t.connectionError === true);
47162
+ const validTests = allTests.filter((t) => !t.connectionError);
47163
+ if (connectionErrors.length > 0) {
47164
+ this.log(
47165
+ `⚠️ WARNING: ${connectionErrors.length} test${connectionErrors.length !== 1 ? "s" : ""} failed due to connection/server errors`
47166
+ );
47167
+ this.log(
47168
+ `Connection errors: ${connectionErrors.map((e) => `${e.toolName}:${e.testName} (${e.errorType})`).join(", ")}`
47169
+ );
47170
+ }
47005
47171
  const vulnerabilities = [];
47006
47172
  let highRiskCount = 0;
47007
47173
  let mediumRiskCount = 0;
47008
- for (const test of allTests) {
47174
+ for (const test of validTests) {
47009
47175
  if (test.vulnerable) {
47010
47176
  let vulnerability;
47011
47177
  if (test.confidence === "high" || !test.confidence) {
@@ -47030,12 +47196,14 @@ class SecurityAssessor extends BaseAssessor {
47030
47196
  vulnerabilities.length
47031
47197
  );
47032
47198
  const status = this.determineSecurityStatus(
47033
- allTests,
47199
+ validTests,
47034
47200
  vulnerabilities.length,
47035
- allTests.length
47201
+ validTests.length,
47202
+ connectionErrors.length
47036
47203
  );
47037
47204
  const explanation = this.generateSecurityExplanation(
47038
- allTests,
47205
+ validTests,
47206
+ connectionErrors,
47039
47207
  vulnerabilities,
47040
47208
  overallRiskLevel
47041
47209
  );
@@ -47154,6 +47322,7 @@ class SecurityAssessor extends BaseAssessor {
47154
47322
  const results = [];
47155
47323
  const criticalPatterns = [
47156
47324
  "Command Injection",
47325
+ "Calculator Injection",
47157
47326
  "SQL Injection",
47158
47327
  "Path Traversal"
47159
47328
  ];
@@ -47163,7 +47332,7 @@ class SecurityAssessor extends BaseAssessor {
47163
47332
  );
47164
47333
  const toolsToTest = this.selectToolsForTesting(context.tools);
47165
47334
  this.log(
47166
- `Starting BASIC security assessment - testing ${toolsToTest.length} tools with ${basicPatterns.length} critical injection patterns (~${toolsToTest.length * basicPatterns.length} tests)`
47335
+ `Starting BASIC security assessment - testing ${toolsToTest.length} tools with ${basicPatterns.length} critical injection patterns (~${toolsToTest.length * basicPatterns.length * 5} tests)`
47167
47336
  );
47168
47337
  for (const tool of toolsToTest) {
47169
47338
  if (!this.hasInputParameters(tool)) {
@@ -47240,7 +47409,7 @@ class SecurityAssessor extends BaseAssessor {
47240
47409
  };
47241
47410
  }
47242
47411
  try {
47243
- const params = this.createTestParameters(payload.payload, tool);
47412
+ const params = this.createTestParameters(payload, tool);
47244
47413
  if (Object.keys(params).length === 0) {
47245
47414
  return {
47246
47415
  testName: attackName,
@@ -47256,6 +47425,24 @@ class SecurityAssessor extends BaseAssessor {
47256
47425
  callTool(tool.name, params),
47257
47426
  5e3
47258
47427
  );
47428
+ if (this.isConnectionError(response)) {
47429
+ return {
47430
+ testName: attackName,
47431
+ description: payload.description,
47432
+ payload: payload.payload,
47433
+ riskLevel: payload.riskLevel,
47434
+ toolName: tool.name,
47435
+ vulnerable: true,
47436
+ // Mark as failed (test could not complete)
47437
+ evidence: `CONNECTION ERROR: Test could not complete due to server/network failure`,
47438
+ response: this.extractResponseContent(response),
47439
+ connectionError: true,
47440
+ errorType: this.classifyError(response),
47441
+ testReliability: "failed",
47442
+ confidence: "high",
47443
+ requiresManualReview: true
47444
+ };
47445
+ }
47259
47446
  const { isVulnerable, evidence } = this.analyzeResponse(
47260
47447
  response,
47261
47448
  payload,
@@ -47280,6 +47467,23 @@ class SecurityAssessor extends BaseAssessor {
47280
47467
  ...confidenceResult
47281
47468
  };
47282
47469
  } catch (error) {
47470
+ if (this.isConnectionErrorFromException(error)) {
47471
+ return {
47472
+ testName: attackName,
47473
+ description: payload.description,
47474
+ payload: payload.payload,
47475
+ riskLevel: payload.riskLevel,
47476
+ toolName: tool.name,
47477
+ vulnerable: false,
47478
+ evidence: `CONNECTION ERROR: Test could not complete due to server/network failure`,
47479
+ response: this.extractErrorMessage(error),
47480
+ connectionError: true,
47481
+ errorType: this.classifyErrorFromException(error),
47482
+ testReliability: "failed",
47483
+ confidence: "high",
47484
+ requiresManualReview: true
47485
+ };
47486
+ }
47283
47487
  return {
47284
47488
  testName: attackName,
47285
47489
  description: payload.description,
@@ -47292,16 +47496,183 @@ class SecurityAssessor extends BaseAssessor {
47292
47496
  }
47293
47497
  }
47294
47498
  /**
47295
- * Try to parse JSON response and extract structured data
47296
- * Returns null if response is not JSON
47499
+ * Check if response indicates connection/server failure
47500
+ * Returns true if test couldn't complete due to infrastructure issues
47501
+ *
47502
+ * CRITICAL: Only match transport/infrastructure errors, NOT tool business logic
47297
47503
  */
47298
- tryParseResponseJSON(response) {
47299
- try {
47300
- const responseText = this.extractResponseContent(response);
47301
- return JSON.parse(responseText);
47302
- } catch {
47303
- return null;
47504
+ isConnectionError(response) {
47505
+ const text = this.extractResponseContent(response).toLowerCase();
47506
+ const unambiguousPatterns = [
47507
+ /MCP error -32001/i,
47508
+ // MCP transport errors
47509
+ /MCP error -32603/i,
47510
+ // MCP internal error
47511
+ /MCP error -32000/i,
47512
+ // MCP server error
47513
+ /MCP error -32700/i,
47514
+ // MCP parse error
47515
+ /socket hang up/i,
47516
+ // Network socket errors
47517
+ /ECONNREFUSED/i,
47518
+ // Connection refused
47519
+ /ETIMEDOUT/i,
47520
+ // Network timeout
47521
+ /ERR_CONNECTION/i,
47522
+ // Connection errors
47523
+ /fetch failed/i,
47524
+ // HTTP fetch failures
47525
+ /connection reset/i,
47526
+ // Connection reset
47527
+ /error POSTing to endpoint/i,
47528
+ // Transport layer POST errors
47529
+ /error GETting.*endpoint/i,
47530
+ // Transport layer GET errors (requires 'endpoint' to avoid false positives)
47531
+ /service unavailable/i,
47532
+ // HTTP 503 (server down)
47533
+ /gateway timeout/i,
47534
+ // HTTP 504 (gateway timeout)
47535
+ /unknown tool:/i,
47536
+ // Tool name not in current server's tool list (stale tool list)
47537
+ /tool.*not found/i,
47538
+ // Alternative phrasing for missing tool
47539
+ /tool.*does not exist/i,
47540
+ // Alternative phrasing for missing tool
47541
+ /no such tool/i
47542
+ // Alternative phrasing for missing tool
47543
+ ];
47544
+ if (unambiguousPatterns.some((pattern2) => pattern2.test(text))) {
47545
+ return true;
47304
47546
  }
47547
+ const mcpPrefix = /^mcp error -\d+:/i.test(text);
47548
+ if (mcpPrefix) {
47549
+ const contextualPatterns = [
47550
+ /bad request/i,
47551
+ // HTTP 400 (only if in MCP error)
47552
+ /unauthorized/i,
47553
+ // HTTP 401 (only if in MCP error)
47554
+ /forbidden/i,
47555
+ // HTTP 403 (only if in MCP error)
47556
+ /no valid session/i,
47557
+ // Session errors (only if in MCP error)
47558
+ /session.*expired/i,
47559
+ // Session expiration (only if in MCP error)
47560
+ /internal server error/i,
47561
+ // HTTP 500 (only if in MCP error)
47562
+ /HTTP [45]\d\d/i
47563
+ // HTTP status codes (only if in MCP error)
47564
+ ];
47565
+ return contextualPatterns.some((pattern2) => pattern2.test(text));
47566
+ }
47567
+ return false;
47568
+ }
47569
+ /**
47570
+ * Check if caught exception indicates connection/server failure
47571
+ * CRITICAL: Only match transport/infrastructure errors, NOT tool business logic
47572
+ */
47573
+ isConnectionErrorFromException(error) {
47574
+ if (error instanceof Error) {
47575
+ const message = error.message.toLowerCase();
47576
+ const unambiguousPatterns = [
47577
+ /MCP error -32001/i,
47578
+ // MCP transport errors
47579
+ /MCP error -32603/i,
47580
+ // MCP internal error
47581
+ /MCP error -32000/i,
47582
+ // MCP server error
47583
+ /MCP error -32700/i,
47584
+ // MCP parse error
47585
+ /socket hang up/i,
47586
+ // Network socket errors
47587
+ /ECONNREFUSED/i,
47588
+ // Connection refused
47589
+ /ETIMEDOUT/i,
47590
+ // Network timeout
47591
+ /network error/i,
47592
+ // Generic network errors
47593
+ /ERR_CONNECTION/i,
47594
+ // Connection errors
47595
+ /fetch failed/i,
47596
+ // HTTP fetch failures
47597
+ /connection reset/i,
47598
+ // Connection reset
47599
+ /error POSTing to endpoint/i,
47600
+ // Transport layer POST errors
47601
+ /error GETting/i,
47602
+ // Transport layer GET errors
47603
+ /service unavailable/i,
47604
+ // HTTP 503 (server down)
47605
+ /gateway timeout/i,
47606
+ // HTTP 504 (gateway timeout)
47607
+ /unknown tool:/i,
47608
+ // Tool name not in current server's tool list (stale tool list)
47609
+ /tool.*not found/i,
47610
+ // Alternative phrasing for missing tool
47611
+ /tool.*does not exist/i,
47612
+ // Alternative phrasing for missing tool
47613
+ /no such tool/i
47614
+ // Alternative phrasing for missing tool
47615
+ ];
47616
+ if (unambiguousPatterns.some((pattern2) => pattern2.test(message))) {
47617
+ return true;
47618
+ }
47619
+ const mcpPrefix = /^mcp error -\d+:/i.test(message);
47620
+ if (mcpPrefix) {
47621
+ const contextualPatterns = [
47622
+ /bad request/i,
47623
+ /unauthorized/i,
47624
+ /forbidden/i,
47625
+ /no valid session/i,
47626
+ /session.*expired/i,
47627
+ /internal server error/i,
47628
+ /HTTP [45]\d\d/i
47629
+ ];
47630
+ return contextualPatterns.some((pattern2) => pattern2.test(message));
47631
+ }
47632
+ }
47633
+ return false;
47634
+ }
47635
+ /**
47636
+ * Classify error type for reporting
47637
+ */
47638
+ classifyError(response) {
47639
+ const text = this.extractResponseContent(response).toLowerCase();
47640
+ if (/socket|ECONNREFUSED|ETIMEDOUT|network|fetch failed|connection reset/i.test(
47641
+ text
47642
+ )) {
47643
+ return "connection";
47644
+ }
47645
+ if (/-32603|-32000|-32700|internal server error|service unavailable|gateway timeout|HTTP 5\d\d|error POSTing.*endpoint|error GETting.*endpoint|bad request|HTTP 400|unauthorized|forbidden|no valid session|session.*expired/i.test(
47646
+ text
47647
+ )) {
47648
+ return "server";
47649
+ }
47650
+ if (/-32001/i.test(text)) {
47651
+ return "protocol";
47652
+ }
47653
+ return "protocol";
47654
+ }
47655
+ /**
47656
+ * Classify error type from caught exception
47657
+ */
47658
+ classifyErrorFromException(error) {
47659
+ if (error instanceof Error) {
47660
+ const message = error.message.toLowerCase();
47661
+ if (/socket|ECONNREFUSED|ETIMEDOUT|network|fetch failed|connection reset/i.test(
47662
+ message
47663
+ )) {
47664
+ return "connection";
47665
+ }
47666
+ if (/-32603|-32000|-32700|internal server error|service unavailable|gateway timeout|HTTP 5\d\d|error POSTing|error GETting|bad request|HTTP 400|unauthorized|forbidden|no valid session|session.*expired/i.test(
47667
+ message
47668
+ )) {
47669
+ return "server";
47670
+ }
47671
+ if (/-32001/i.test(message)) {
47672
+ return "protocol";
47673
+ }
47674
+ }
47675
+ return "protocol";
47305
47676
  }
47306
47677
  /**
47307
47678
  * Analyze response with evidence-based detection
@@ -47310,27 +47681,6 @@ class SecurityAssessor extends BaseAssessor {
47310
47681
  analyzeResponse(response, payload, tool) {
47311
47682
  const responseText = this.extractResponseContent(response).toLowerCase();
47312
47683
  const payloadText = payload.payload.toLowerCase();
47313
- const parsedResponse = this.tryParseResponseJSON(response);
47314
- if (parsedResponse) {
47315
- if (parsedResponse.safe === true) {
47316
- return {
47317
- isVulnerable: false,
47318
- evidence: "Tool explicitly marked response as safe (safe: true flag in JSON response)"
47319
- };
47320
- }
47321
- if (parsedResponse.hardened === true) {
47322
- return {
47323
- isVulnerable: false,
47324
- evidence: "Tool explicitly marked response as hardened (hardened: true flag in JSON response)"
47325
- };
47326
- }
47327
- if (parsedResponse.vulnerable === true) {
47328
- return {
47329
- isVulnerable: true,
47330
- evidence: "Tool explicitly marked response as vulnerable (vulnerable: true flag in JSON response)"
47331
- };
47332
- }
47333
- }
47334
47684
  const errorInfo = this.extractErrorInfo(response);
47335
47685
  if (this.isMCPValidationError(errorInfo, responseText)) {
47336
47686
  const errorCode = errorInfo.code ? ` (${errorInfo.code})` : "";
@@ -47363,20 +47713,24 @@ class SecurityAssessor extends BaseAssessor {
47363
47713
  evidence: "Read-only info tool returned user/workspace data (intended data exposure, not vulnerability)"
47364
47714
  };
47365
47715
  }
47366
- if (this.isApiWrapperResponse(responseText)) {
47716
+ if (classification.categories.includes(ToolCategory.SAFE_STORAGE)) {
47367
47717
  return {
47368
47718
  isVulnerable: false,
47369
- evidence: "API wrapper response - returned external content as data"
47719
+ evidence: "Safe storage control tool (validated safe implementation, control group for testing)"
47370
47720
  };
47371
47721
  }
47372
- if (responseText.includes(payloadText)) {
47373
- const isJustReflection = this.isReflectionResponse(responseText);
47374
- if (isJustReflection) {
47375
- return {
47376
- isVulnerable: false,
47377
- evidence: "Tool safely reflected input without execution"
47378
- };
47379
- }
47722
+ const isJustReflection = this.isReflectionResponse(responseText);
47723
+ if (isJustReflection) {
47724
+ return {
47725
+ isVulnerable: false,
47726
+ evidence: "Tool safely reflected input without execution"
47727
+ };
47728
+ }
47729
+ if (this.isValidationRejection(response)) {
47730
+ return {
47731
+ isVulnerable: false,
47732
+ evidence: "Tool correctly rejected invalid input with validation error"
47733
+ };
47380
47734
  }
47381
47735
  if (payload.evidence && payload.evidence.test(responseText)) {
47382
47736
  const evidenceMatchesInput = payload.evidence.test(payloadText);
@@ -47402,6 +47756,54 @@ class SecurityAssessor extends BaseAssessor {
47402
47756
  }
47403
47757
  return this.analyzeInjectionResponse(response, payload.payload);
47404
47758
  }
47759
+ /**
47760
+ * Check if tool explicitly rejected input with validation error (SAFE)
47761
+ * Returns true if tool properly rejected invalid input (SECURE)
47762
+ *
47763
+ * Distinguishes between:
47764
+ * - SAFE: {"valid": false, "errors": ["Input too long"]} (rejection)
47765
+ * - VULNERABLE: {"result": "deleted 5 rows"} (execution)
47766
+ */
47767
+ isValidationRejection(response) {
47768
+ const responseText = this.extractResponseContent(response);
47769
+ try {
47770
+ const parsed = JSON.parse(responseText);
47771
+ if (parsed.valid === false || parsed.error === true || parsed.error === "true" || // Handle string boolean
47772
+ parsed.error && parsed.error !== false || // Any truthy error value
47773
+ parsed.status === "rejected" || parsed.status === "invalid" || parsed.status === "failed") {
47774
+ return true;
47775
+ }
47776
+ if (parsed.errors && Array.isArray(parsed.errors) && parsed.errors.length > 0) {
47777
+ return true;
47778
+ }
47779
+ if (parsed.error && typeof parsed.error === "string") {
47780
+ return true;
47781
+ }
47782
+ if (typeof parsed.result === "string") {
47783
+ const resultRejectionPatterns = [
47784
+ /validation (failed|error)/i,
47785
+ /rejected/i,
47786
+ /not.*approved/i,
47787
+ /not.*in.*list/i,
47788
+ /invalid.*input/i,
47789
+ /error:.*invalid/i
47790
+ ];
47791
+ if (resultRejectionPatterns.some((p2) => p2.test(parsed.result))) {
47792
+ return true;
47793
+ }
47794
+ }
47795
+ } catch {
47796
+ }
47797
+ const rejectionPatterns = [
47798
+ /validation failed/i,
47799
+ /rejected/i,
47800
+ /not.*approved/i,
47801
+ /not.*in.*list/i,
47802
+ /invalid.*input/i,
47803
+ /error:.*invalid/i
47804
+ ];
47805
+ return rejectionPatterns.some((pattern2) => pattern2.test(responseText));
47806
+ }
47405
47807
  /**
47406
47808
  * Check if response is an MCP validation error (safe rejection)
47407
47809
  * Returns true if tool rejected input before processing (SECURE)
@@ -47535,7 +47937,8 @@ class SecurityAssessor extends BaseAssessor {
47535
47937
  /**
47536
47938
  * Determine security status based on confidence levels
47537
47939
  */
47538
- determineSecurityStatus(tests, vulnerabilityCount, testCount) {
47940
+ determineSecurityStatus(tests, vulnerabilityCount, testCount, connectionErrorCount = 0) {
47941
+ if (connectionErrorCount > 0) return "FAIL";
47539
47942
  if (testCount === 0) return "NEED_MORE_INFO";
47540
47943
  if (vulnerabilityCount === 0) return "PASS";
47541
47944
  const hasHighConfidence = tests.some(
@@ -47547,30 +47950,38 @@ class SecurityAssessor extends BaseAssessor {
47547
47950
  /**
47548
47951
  * Generate security explanation
47549
47952
  */
47550
- generateSecurityExplanation(tests, vulnerabilities, riskLevel) {
47953
+ generateSecurityExplanation(validTests, connectionErrors, vulnerabilities, riskLevel) {
47551
47954
  const vulnCount = vulnerabilities.length;
47552
- const testCount = tests.length;
47553
- if (testCount === 0) {
47955
+ const testCount = validTests.length;
47956
+ const errorCount = connectionErrors.length;
47957
+ let explanation = "";
47958
+ if (errorCount > 0) {
47959
+ explanation += `⚠️ ${errorCount} test${errorCount !== 1 ? "s" : ""} failed due to connection/server errors. `;
47960
+ }
47961
+ if (testCount === 0 && errorCount > 0) {
47962
+ return explanation + `No valid tests completed. Check server connectivity and retry assessment.`;
47963
+ }
47964
+ if (testCount === 0 && errorCount === 0) {
47554
47965
  return `No tools selected for security testing. Select tools to run security assessments.`;
47555
47966
  }
47556
47967
  if (vulnCount === 0) {
47557
- return `Tested ${testCount} security patterns across selected tools. No vulnerabilities detected. All tools properly handle malicious inputs.`;
47968
+ return explanation + `Tested ${testCount} security patterns across selected tools. No vulnerabilities detected. All tools properly handle malicious inputs.`;
47558
47969
  }
47559
- const highConfidenceCount = tests.filter(
47970
+ const highConfidenceCount = validTests.filter(
47560
47971
  (t) => t.vulnerable && (!t.confidence || t.confidence === "high")
47561
47972
  ).length;
47562
- const mediumConfidenceCount = tests.filter(
47973
+ const mediumConfidenceCount = validTests.filter(
47563
47974
  (t) => t.vulnerable && t.confidence === "medium"
47564
47975
  ).length;
47565
- const lowConfidenceCount = tests.filter(
47976
+ const lowConfidenceCount = validTests.filter(
47566
47977
  (t) => t.vulnerable && t.confidence === "low"
47567
47978
  ).length;
47568
47979
  if (highConfidenceCount > 0) {
47569
- return `Found ${highConfidenceCount} confirmed vulnerability${highConfidenceCount !== 1 ? "s" : ""} across ${testCount} security tests. Risk level: ${riskLevel}. Tools may execute malicious commands or leak sensitive data.`;
47980
+ return explanation + `Found ${highConfidenceCount} confirmed vulnerability${highConfidenceCount !== 1 ? "s" : ""} across ${testCount} security tests. Risk level: ${riskLevel}. Tools may execute malicious commands or leak sensitive data.`;
47570
47981
  } else if (mediumConfidenceCount > 0) {
47571
- return `Detected ${mediumConfidenceCount} potential security concern${mediumConfidenceCount !== 1 ? "s" : ""} across ${testCount} security tests requiring manual review. Tools showed suspicious behavior that needs verification.`;
47982
+ return explanation + `Detected ${mediumConfidenceCount} potential security concern${mediumConfidenceCount !== 1 ? "s" : ""} across ${testCount} security tests requiring manual review. Tools showed suspicious behavior that needs verification.`;
47572
47983
  } else {
47573
- return `Flagged ${lowConfidenceCount} uncertain detection${lowConfidenceCount !== 1 ? "s" : ""} across ${testCount} security tests. Manual verification needed to confirm if these are actual vulnerabilities or false positives.`;
47984
+ return explanation + `Flagged ${lowConfidenceCount} uncertain detection${lowConfidenceCount !== 1 ? "s" : ""} across ${testCount} security tests. Manual verification needed to confirm if these are actual vulnerabilities or false positives.`;
47574
47985
  }
47575
47986
  }
47576
47987
  /**
@@ -47665,10 +48076,31 @@ class SecurityAssessor extends BaseAssessor {
47665
48076
  /**
47666
48077
  * Check if response is just reflection (safe)
47667
48078
  * Expanded to catch more reflection patterns including echo, repeat, display
47668
- * IMPROVED: Bidirectional patterns and safety indicators for broader coverage
48079
+ * IMPROVED: Bidirectional patterns, safety indicators, and two-layer defense
48080
+ *
48081
+ * CRITICAL: This check distinguishes between:
48082
+ * - SAFE: Tool stores/echoes malicious input as data (reflection)
48083
+ * - VULNERABLE: Tool executes malicious input and returns results (execution)
48084
+ *
48085
+ * Two-layer defense:
48086
+ * Layer 1: Match reflection/status patterns
48087
+ * Layer 2: Verify NO execution evidence (defense-in-depth)
47669
48088
  */
47670
48089
  isReflectionResponse(responseText) {
48090
+ console.log("[DIAG] isReflectionResponse called");
48091
+ console.log("[DIAG] Response preview:", responseText.substring(0, 200));
48092
+ const statusPatterns = [
48093
+ // "Action executed successfully: <anything>" (generic status message)
48094
+ /action\s+executed\s+successfully:/i,
48095
+ /command\s+executed\s+successfully:/i,
48096
+ // "Action executed successfully" (generic status - in JSON or standalone)
48097
+ /"result":\s*"action\s+executed\s+successfully"/i,
48098
+ /result.*action\s+executed\s+successfully/i,
48099
+ /successfully\s+(executed|completed|processed):/i,
48100
+ /successfully\s+(executed|completed|processed)"/i
48101
+ ];
47671
48102
  const reflectionPatterns = [
48103
+ ...statusPatterns,
47672
48104
  // Original patterns (keep all existing)
47673
48105
  /stored.*query/i,
47674
48106
  /saved.*input/i,
@@ -47685,16 +48117,34 @@ class SecurityAssessor extends BaseAssessor {
47685
48117
  /safely.*as.*data/i,
47686
48118
  // NEW: Bidirectional patterns (catch "Query stored" and "stored query")
47687
48119
  /query.*stored/i,
48120
+ /stored.*query/i,
48121
+ // Bidirectional: "Stored query"
47688
48122
  /input.*saved/i,
47689
48123
  /parameter.*received/i,
47690
48124
  /command.*stored/i,
48125
+ /stored.*command/i,
48126
+ // Bidirectional: "Stored command"
47691
48127
  /data.*stored/i,
48128
+ /stored.*data/i,
48129
+ // Bidirectional: "Stored data"
47692
48130
  /action.*stored/i,
48131
+ /stored.*action/i,
48132
+ // Bidirectional: "Stored action"
47693
48133
  /text.*stored/i,
48134
+ /stored.*text/i,
48135
+ // Bidirectional: "Stored text"
47694
48136
  /setting.*stored/i,
48137
+ /stored.*setting/i,
48138
+ // Bidirectional: "Stored setting"
47695
48139
  /instruction.*stored/i,
48140
+ /stored.*instruction/i,
48141
+ // Bidirectional: "Stored instruction"
47696
48142
  /url.*stored/i,
48143
+ /stored.*url/i,
48144
+ // Bidirectional: "Stored URL"
47697
48145
  /package.*stored/i,
48146
+ /stored.*package/i,
48147
+ // Bidirectional: "Stored package"
47698
48148
  // NEW: Safety indicators (common in hardened implementations)
47699
48149
  /stored.*safely/i,
47700
48150
  /safely.*stored/i,
@@ -47708,12 +48158,160 @@ class SecurityAssessor extends BaseAssessor {
47708
48158
  /stored.*successfully/i,
47709
48159
  /saved.*to/i,
47710
48160
  /recorded\s+in/i,
47711
- /added\s+to/i
48161
+ /added\s+to/i,
48162
+ // NEW: Storage/logging confirmations (high confidence)
48163
+ /logged successfully:/i,
48164
+ /queued for processing:/i,
48165
+ /saved (for|successfully)/i,
48166
+ /stored for (admin review|configuration|processing)/i,
48167
+ // NEW: Processing confirmations (high confidence)
48168
+ /processed successfully/i,
48169
+ /validated successfully/i,
48170
+ /parsed successfully/i,
48171
+ /(validation|processing) (passed|completed)/i,
48172
+ // NEW: Error messages with input reflection (common safe pattern)
48173
+ /error:.*not (found|in approved list|recognized)/i,
48174
+ /error getting info for ['"].*['"]/i,
48175
+ /invalid .* format.*stored as text/i,
48176
+ /error:.*too (long|short|large)/i
47712
48177
  ];
47713
- return reflectionPatterns.some((pattern2) => pattern2.test(responseText));
48178
+ const matchedPatterns = [];
48179
+ const hasReflection = reflectionPatterns.some((pattern2) => {
48180
+ const matches = pattern2.test(responseText);
48181
+ if (matches) {
48182
+ matchedPatterns.push(pattern2.source.substring(0, 50));
48183
+ }
48184
+ return matches;
48185
+ });
48186
+ console.log("[DIAG] Has reflection:", hasReflection);
48187
+ if (matchedPatterns.length > 0) {
48188
+ console.log(
48189
+ "[DIAG] Matched reflection patterns:",
48190
+ matchedPatterns.join(", ")
48191
+ );
48192
+ }
48193
+ if (hasReflection) {
48194
+ try {
48195
+ const parsed = JSON.parse(responseText);
48196
+ const resultText = String(parsed.result || "");
48197
+ const outputFields = [
48198
+ parsed.stdout,
48199
+ parsed.stderr,
48200
+ parsed.output,
48201
+ parsed.contents,
48202
+ parsed.execution_log,
48203
+ parsed.command_output
48204
+ ].filter(Boolean).join(" ");
48205
+ const resultIsStatusOnly = statusPatterns.some(
48206
+ (pattern2) => pattern2.test(resultText)
48207
+ );
48208
+ const hasExecutionInOutput = resultIsStatusOnly ? this.detectExecutionArtifacts(outputFields) : this.detectExecutionArtifacts(resultText) || this.detectExecutionArtifacts(outputFields);
48209
+ console.log(
48210
+ "[DIAG] JSON mode - checking execution in result/output fields only"
48211
+ );
48212
+ console.log("[DIAG] Has execution in output:", hasExecutionInOutput);
48213
+ if (hasExecutionInOutput) {
48214
+ console.log(
48215
+ "[DIAG] RESULT: Reflection + Execution in output = VULNERABLE (false)"
48216
+ );
48217
+ return false;
48218
+ }
48219
+ console.log("[DIAG] RESULT: Reflection + clean output = SAFE (true)");
48220
+ return true;
48221
+ } catch {
48222
+ const hasExecution = this.detectExecutionArtifacts(responseText);
48223
+ console.log(
48224
+ "[DIAG] Text mode - Has execution artifacts:",
48225
+ hasExecution
48226
+ );
48227
+ if (hasExecution) {
48228
+ console.log(
48229
+ "[DIAG] RESULT: Reflection + Execution = VULNERABLE (false)"
48230
+ );
48231
+ return false;
48232
+ }
48233
+ console.log("[DIAG] RESULT: Reflection only = SAFE (true)");
48234
+ return true;
48235
+ }
48236
+ }
48237
+ try {
48238
+ const parsed = JSON.parse(responseText);
48239
+ if (parsed.action === "test" || parsed.action === "placeholder") {
48240
+ const resultText = String(parsed.result || "");
48241
+ if (!this.detectExecutionArtifacts(resultText)) {
48242
+ return true;
48243
+ }
48244
+ }
48245
+ if (parsed.status && /(completed|success|ok|done)/.test(parsed.status)) {
48246
+ if (!this.detectExecutionArtifacts(responseText)) {
48247
+ return true;
48248
+ }
48249
+ }
48250
+ } catch {
48251
+ }
48252
+ return false;
48253
+ }
48254
+ /**
48255
+ * Detect execution artifacts in response
48256
+ * Returns true if response contains evidence of actual code execution
48257
+ *
48258
+ * HIGH confidence: System files, commands, directory listings
48259
+ * MEDIUM confidence: Contextual patterns (root alone, paths)
48260
+ */
48261
+ detectExecutionArtifacts(responseText) {
48262
+ console.log("[DIAG] detectExecutionArtifacts called");
48263
+ const executionIndicators = [
48264
+ // HIGH CONFIDENCE - System files (requires format)
48265
+ /[a-z]+:x:\d+:\d+:/i,
48266
+ // passwd: "root:x:0:0:"
48267
+ /uid=\d+\([^)]+\)\s+gid=\d+/i,
48268
+ // id: "uid=0(root) gid=0(root)"
48269
+ // HIGH CONFIDENCE - Directory listings (full format)
48270
+ /[d-][rwx-]{9}\s+\d+\s+[a-z]+/i,
48271
+ // ls -la: "drwxr-xr-x 2 root"
48272
+ /total\s+\d+\s*$/m,
48273
+ // ls total line
48274
+ // HIGH CONFIDENCE - Command execution fields
48275
+ /command_executed:\s*[^"\s]/i,
48276
+ // "command_executed: whoami"
48277
+ /stdout:\s*["']?[^"'\s]/i,
48278
+ // "stdout: root"
48279
+ /(execution|output)_log:/i,
48280
+ // Log fields
48281
+ // HIGH CONFIDENCE - Shell paths
48282
+ /\/bin\/(bash|sh|zsh|dash)/i,
48283
+ // Shell executables
48284
+ // MEDIUM CONFIDENCE - System identity (contextual only)
48285
+ /\b(root|administrator)\s*$/im,
48286
+ // "root" alone on line (whoami)
48287
+ /\/root\//i,
48288
+ // Path: "/root/"
48289
+ /\/etc\/passwd/i,
48290
+ // Sensitive file
48291
+ // MEDIUM CONFIDENCE - Process info
48292
+ /PID:\s*\d{3,}/i
48293
+ // Process ID
48294
+ ];
48295
+ const matchedExecutionPatterns = [];
48296
+ const found = executionIndicators.some((pattern2) => {
48297
+ const matches = pattern2.test(responseText);
48298
+ if (matches) {
48299
+ matchedExecutionPatterns.push(pattern2.source.substring(0, 50));
48300
+ }
48301
+ return matches;
48302
+ });
48303
+ if (matchedExecutionPatterns.length > 0) {
48304
+ console.log(
48305
+ "[DIAG] Matched execution patterns:",
48306
+ matchedExecutionPatterns.join(", ")
48307
+ );
48308
+ }
48309
+ console.log("[DIAG] Execution artifacts found:", found);
48310
+ return found;
47714
48311
  }
47715
48312
  /**
47716
48313
  * Analyze injection response (existing logic)
48314
+ * Note: payload parameter unused after refactoring to two-layer defense
47717
48315
  */
47718
48316
  analyzeInjectionResponse(response, _payload) {
47719
48317
  const responseText = this.extractResponseContent(response);
@@ -47759,11 +48357,31 @@ class SecurityAssessor extends BaseAssessor {
47759
48357
  return {};
47760
48358
  }
47761
48359
  const params = {};
48360
+ const targetParamTypes = payload.parameterTypes || [];
48361
+ let payloadInjected = false;
48362
+ if (targetParamTypes.length > 0) {
48363
+ for (const [key, prop] of Object.entries(schema.properties)) {
48364
+ const propSchema = prop;
48365
+ const paramNameLower = key.toLowerCase();
48366
+ if (propSchema.type === "string" && targetParamTypes.some((type2) => paramNameLower.includes(type2))) {
48367
+ params[key] = payload.payload;
48368
+ payloadInjected = true;
48369
+ break;
48370
+ }
48371
+ }
48372
+ } else {
48373
+ for (const [key, prop] of Object.entries(schema.properties)) {
48374
+ const propSchema = prop;
48375
+ if (propSchema.type === "string" && !payloadInjected) {
48376
+ params[key] = payload.payload;
48377
+ payloadInjected = true;
48378
+ break;
48379
+ }
48380
+ }
48381
+ }
47762
48382
  for (const [key, prop] of Object.entries(schema.properties)) {
47763
48383
  const propSchema = prop;
47764
- if (propSchema.type === "string" && Object.keys(params).length === 0) {
47765
- params[key] = payload;
47766
- } else if ((_b = schema.required) == null ? void 0 : _b.includes(key)) {
48384
+ if (((_b = schema.required) == null ? void 0 : _b.includes(key)) && !(key in params)) {
47767
48385
  if (propSchema.type === "string") {
47768
48386
  params[key] = "test";
47769
48387
  } else if (propSchema.type === "number") {
@@ -47802,25 +48420,6 @@ class SecurityAssessor extends BaseAssessor {
47802
48420
  ];
47803
48421
  return executionTests.includes(attackName);
47804
48422
  }
47805
- /**
47806
- * Check if response is from an API wrapper tool
47807
- * API wrappers return external content as data, not execute it
47808
- */
47809
- isApiWrapperResponse(responseText) {
47810
- const apiWrapperPatterns = [
47811
- /successfully\s+(scraped|fetched|crawled)/i,
47812
- /content\s+from\s+http/i,
47813
- /api\s+(request|response|call)\s+(completed|successful)/i,
47814
- /retrieved\s+\d+\s+(results|pages|urls)/i,
47815
- /markdown.*screenshot.*links/i,
47816
- // Firecrawl format indicators
47817
- /scraping\s+(complete|finished|done)/i,
47818
- /\bfirecrawl\b/i,
47819
- /crawl.*job/i,
47820
- /extraction.*complete/i
47821
- ];
47822
- return apiWrapperPatterns.some((pattern2) => pattern2.test(responseText));
47823
- }
47824
48423
  /**
47825
48424
  * Check if response is returning search results
47826
48425
  * Search tools return query results as data, not execute them
@@ -49744,6 +50343,7 @@ function ToolSelector({
49744
50343
  const AssessmentTab = ({
49745
50344
  tools,
49746
50345
  isLoadingTools = false,
50346
+ listTools,
49747
50347
  callTool,
49748
50348
  serverName = "MCP Server"
49749
50349
  }) => {
@@ -49761,6 +50361,7 @@ const AssessmentTab = ({
49761
50361
  const [showJson, setShowJson] = reactExports.useState(false);
49762
50362
  const [collapsedTools, setCollapsedTools] = reactExports.useState(/* @__PURE__ */ new Set());
49763
50363
  const [allToolsCollapsed, setAllToolsCollapsed] = reactExports.useState(false);
50364
+ const [showOnlyErrors, setShowOnlyErrors] = reactExports.useState(false);
49764
50365
  const [expandedToolDescriptions, setExpandedToolDescriptions] = reactExports.useState(/* @__PURE__ */ new Set());
49765
50366
  const [categoryFilter, setCategoryFilter] = reactExports.useState({
49766
50367
  functionality: true,
@@ -49780,11 +50381,28 @@ const AssessmentTab = ({
49780
50381
  [config]
49781
50382
  );
49782
50383
  reactExports.useEffect(() => {
49783
- if (tools.length > 0 && !config.selectedToolsForTesting) {
50384
+ if (tools.length === 0) {
50385
+ return;
50386
+ }
50387
+ const currentToolNames = tools.map((t) => t.name);
50388
+ if (!config.selectedToolsForTesting) {
49784
50389
  setConfig({
49785
50390
  ...config,
49786
- selectedToolsForTesting: tools.map((t) => t.name)
50391
+ selectedToolsForTesting: currentToolNames
49787
50392
  });
50393
+ } else {
50394
+ const existingSelections = config.selectedToolsForTesting.filter(
50395
+ (name2) => currentToolNames.includes(name2)
50396
+ );
50397
+ const newTools = currentToolNames.filter(
50398
+ (name2) => !config.selectedToolsForTesting.includes(name2)
50399
+ );
50400
+ if (newTools.length > 0 || existingSelections.length !== config.selectedToolsForTesting.length) {
50401
+ setConfig({
50402
+ ...config,
50403
+ selectedToolsForTesting: [...existingSelections, ...newTools]
50404
+ });
50405
+ }
49788
50406
  }
49789
50407
  }, [tools, config, setConfig]);
49790
50408
  const calculateFilteredOverallStatus = reactExports.useCallback(
@@ -49955,7 +50573,23 @@ const AssessmentTab = ({
49955
50573
  )
49956
50574
  ] }),
49957
50575
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
49958
- /* @__PURE__ */ jsxRuntimeExports.jsx(Label$1, { htmlFor: "tool-selector", children: "Select tools for testing:" }),
50576
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
50577
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Label$1, { htmlFor: "tool-selector", children: "Select tools for testing:" }),
50578
+ listTools && /* @__PURE__ */ jsxRuntimeExports.jsxs(
50579
+ Button,
50580
+ {
50581
+ variant: "ghost",
50582
+ size: "sm",
50583
+ onClick: () => listTools(),
50584
+ disabled: isLoadingTools || isRunning,
50585
+ className: "h-7 px-2",
50586
+ children: [
50587
+ isLoadingTools ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "w-4 h-4 mr-1 animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { className: "w-4 h-4 mr-1" }),
50588
+ "Refresh"
50589
+ ]
50590
+ }
50591
+ )
50592
+ ] }),
49959
50593
  isLoadingTools ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
49960
50594
  /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "w-4 h-4 animate-spin" }),
49961
50595
  "Loading tools..."
@@ -50016,14 +50650,14 @@ const AssessmentTab = ({
50016
50650
  ),
50017
50651
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Label$1, { htmlFor: "domain-testing", className: "cursor-pointer", children: [
50018
50652
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold", children: "Enable Advanced Security Testing" }),
50019
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-2 text-xs bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 px-2 py-0.5 rounded", children: "8 Patterns" })
50653
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-2 text-xs bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 px-2 py-0.5 rounded", children: "13 Patterns" })
50020
50654
  ] })
50021
50655
  ] }),
50022
50656
  /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-muted-foreground ml-6", children: [
50023
50657
  /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Basic:" }),
50024
- " 3 critical injection patterns (~3-15 checks). ",
50658
+ " 4 critical injection patterns (~20 checks). ",
50025
50659
  /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Advanced:" }),
50026
- " All 8 patterns (~8-24 checks per tool). Tests for injection vulnerabilities (Command, SQL, Path Traversal), input validation (Type Safety, Boundary, Required Fields), and protocol compliance (MCP Error Format, Timeout Handling)."
50660
+ " All 13 patterns (~37 checks per tool). Tests for injection vulnerabilities (Command, Calculator, SQL, Path Traversal), input validation (Type Safety, Boundary, Required Fields), protocol compliance (MCP Error Format, Timeout Handling), and tool-specific vulnerabilities (Indirect Injection, Unicode Bypass, Nested Injection, Package Squatting)."
50027
50661
  ] })
50028
50662
  ] }),
50029
50663
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
@@ -50232,74 +50866,129 @@ const AssessmentTab = ({
50232
50866
  children: [
50233
50867
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm mb-2", children: assessment.security.explanation }),
50234
50868
  (() => {
50235
- var _a2, _b2, _c;
50236
- const highConfidenceCount = ((_a2 = assessment.security.promptInjectionTests) == null ? void 0 : _a2.filter(
50869
+ var _a2, _b2, _c, _d;
50870
+ const connectionErrors = ((_a2 = assessment.security.promptInjectionTests) == null ? void 0 : _a2.filter(
50871
+ (t) => t.connectionError === true
50872
+ )) || [];
50873
+ const highConfidenceCount = ((_b2 = assessment.security.promptInjectionTests) == null ? void 0 : _b2.filter(
50237
50874
  (t) => t.vulnerable && (!t.confidence || t.confidence === "high")
50238
50875
  ).length) || 0;
50239
- const mediumConfidenceCount = ((_b2 = assessment.security.promptInjectionTests) == null ? void 0 : _b2.filter(
50876
+ const mediumConfidenceCount = ((_c = assessment.security.promptInjectionTests) == null ? void 0 : _c.filter(
50240
50877
  (t) => t.vulnerable && t.confidence === "medium"
50241
50878
  ).length) || 0;
50242
- const lowConfidenceCount = ((_c = assessment.security.promptInjectionTests) == null ? void 0 : _c.filter(
50879
+ const lowConfidenceCount = ((_d = assessment.security.promptInjectionTests) == null ? void 0 : _d.filter(
50243
50880
  (t) => t.vulnerable && t.confidence === "low"
50244
50881
  ).length) || 0;
50245
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-sm space-y-1", children: [
50246
- highConfidenceCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-red-700", children: [
50247
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Confirmed Issues:" }),
50248
- " ",
50249
- highConfidenceCount
50250
- ] }),
50251
- mediumConfidenceCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-amber-700", children: [
50252
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Need Review:" }),
50253
- " ",
50254
- mediumConfidenceCount
50255
- ] }),
50256
- lowConfidenceCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-blue-700", children: [
50257
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Uncertain (Verification Needed):" }),
50258
- " ",
50259
- lowConfidenceCount
50882
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
50883
+ connectionErrors.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-yellow-50 border-l-4 border-yellow-500 p-4 mb-4 rounded", children: [
50884
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
50885
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { className: "w-5 h-5 text-yellow-600" }),
50886
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("h5", { className: "text-sm font-semibold text-yellow-900", children: [
50887
+ "Connection Errors (",
50888
+ connectionErrors.length,
50889
+ ")"
50890
+ ] })
50891
+ ] }),
50892
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-sm text-yellow-800 mb-2", children: [
50893
+ connectionErrors.length,
50894
+ " test",
50895
+ connectionErrors.length !== 1 ? "s" : "",
50896
+ " could not complete due to server/network failures. These tests are excluded from vulnerability counts."
50897
+ ] }),
50898
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-yellow-700 space-y-1 max-h-32 overflow-y-auto", children: connectionErrors.map((err, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
50899
+ "div",
50900
+ {
50901
+ className: "flex items-start gap-2",
50902
+ children: [
50903
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-yellow-600", children: "•" }),
50904
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
50905
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: err.testName }),
50906
+ " on",
50907
+ " ",
50908
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "bg-yellow-100 px-1 rounded", children: err.toolName }),
50909
+ ": ",
50910
+ err.errorType
50911
+ ] })
50912
+ ]
50913
+ },
50914
+ i
50915
+ )) }),
50916
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-yellow-700 mt-2 font-medium", children: "✅ Fix connectivity issues and re-run assessment for accurate results" })
50260
50917
  ] }),
50261
- highConfidenceCount === 0 && mediumConfidenceCount === 0 && lowConfidenceCount === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-green-700", children: /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "All tests passed" }) })
50918
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-sm space-y-1", children: [
50919
+ highConfidenceCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-red-700", children: [
50920
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Confirmed Issues:" }),
50921
+ " ",
50922
+ highConfidenceCount
50923
+ ] }),
50924
+ mediumConfidenceCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-amber-700", children: [
50925
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Need Review:" }),
50926
+ " ",
50927
+ mediumConfidenceCount
50928
+ ] }),
50929
+ lowConfidenceCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-blue-700", children: [
50930
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Uncertain (Verification Needed):" }),
50931
+ " ",
50932
+ lowConfidenceCount
50933
+ ] }),
50934
+ highConfidenceCount === 0 && mediumConfidenceCount === 0 && lowConfidenceCount === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-green-700", children: /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "All tests passed" }) })
50935
+ ] })
50262
50936
  ] });
50263
50937
  })(),
50264
50938
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2", children: [
50265
50939
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
50266
50940
  /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { className: "text-sm", children: "Security Test Results:" }),
50267
- /* @__PURE__ */ jsxRuntimeExports.jsx(
50268
- Button,
50269
- {
50270
- variant: "outline",
50271
- size: "sm",
50272
- className: "text-xs h-6 px-2",
50273
- onClick: () => {
50274
- const toolGroups = /* @__PURE__ */ new Map();
50275
- assessment.security.promptInjectionTests.forEach(
50276
- (testResult) => {
50277
- const toolName = testResult.toolName || "Unknown Tool";
50278
- if (!toolGroups.has(toolName)) {
50279
- toolGroups.set(toolName, []);
50941
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
50942
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
50943
+ Button,
50944
+ {
50945
+ variant: showOnlyErrors ? "default" : "outline",
50946
+ size: "sm",
50947
+ className: "text-xs h-6 px-2",
50948
+ onClick: () => setShowOnlyErrors(!showOnlyErrors),
50949
+ children: [
50950
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Funnel, { className: "h-3 w-3 mr-1" }),
50951
+ showOnlyErrors ? "Show All" : "Filter Errors"
50952
+ ]
50953
+ }
50954
+ ),
50955
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
50956
+ Button,
50957
+ {
50958
+ variant: "outline",
50959
+ size: "sm",
50960
+ className: "text-xs h-6 px-2",
50961
+ onClick: () => {
50962
+ const toolGroups = /* @__PURE__ */ new Map();
50963
+ assessment.security.promptInjectionTests.forEach(
50964
+ (testResult) => {
50965
+ const toolName = testResult.toolName || "Unknown Tool";
50966
+ if (!toolGroups.has(toolName)) {
50967
+ toolGroups.set(toolName, []);
50968
+ }
50280
50969
  }
50281
- }
50282
- );
50283
- if (allToolsCollapsed) {
50284
- setCollapsedTools(/* @__PURE__ */ new Set());
50285
- setAllToolsCollapsed(false);
50286
- } else {
50287
- const allToolNames = Array.from(
50288
- toolGroups.keys()
50289
50970
  );
50290
- setCollapsedTools(new Set(allToolNames));
50291
- setAllToolsCollapsed(true);
50292
- }
50293
- },
50294
- children: allToolsCollapsed ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
50295
- /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "h-3 w-3 mr-1" }),
50296
- "Expand All"
50297
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
50298
- /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "h-3 w-3 mr-1" }),
50299
- "Collapse All"
50300
- ] })
50301
- }
50302
- )
50971
+ if (allToolsCollapsed) {
50972
+ setCollapsedTools(/* @__PURE__ */ new Set());
50973
+ setAllToolsCollapsed(false);
50974
+ } else {
50975
+ const allToolNames = Array.from(
50976
+ toolGroups.keys()
50977
+ );
50978
+ setCollapsedTools(new Set(allToolNames));
50979
+ setAllToolsCollapsed(true);
50980
+ }
50981
+ },
50982
+ children: allToolsCollapsed ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
50983
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "h-3 w-3 mr-1" }),
50984
+ "Expand All"
50985
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
50986
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "h-3 w-3 mr-1" }),
50987
+ "Collapse All"
50988
+ ] })
50989
+ }
50990
+ )
50991
+ ] })
50303
50992
  ] }),
50304
50993
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 space-y-1", children: (() => {
50305
50994
  const toolGroups = /* @__PURE__ */ new Map();
@@ -50324,18 +51013,26 @@ const AssessmentTab = ({
50324
51013
  newCollapsed.size === toolGroups.size
50325
51014
  );
50326
51015
  };
50327
- return Array.from(toolGroups.entries()).map(
50328
- ([toolName, toolTests]) => /* @__PURE__ */ jsxRuntimeExports.jsx(
50329
- CollapsibleToolSection,
50330
- {
50331
- toolName,
50332
- toolTests,
50333
- isCollapsed: collapsedTools.has(toolName),
50334
- onToggle: handleToggleTool
50335
- },
50336
- toolName
50337
- )
50338
- );
51016
+ let filteredGroups = Array.from(toolGroups.entries());
51017
+ if (showOnlyErrors) {
51018
+ filteredGroups = filteredGroups.filter(
51019
+ ([, toolTests]) => {
51020
+ return toolTests.some(
51021
+ (test) => test.vulnerable === true
51022
+ );
51023
+ }
51024
+ );
51025
+ }
51026
+ return filteredGroups.map(([toolName, toolTests]) => /* @__PURE__ */ jsxRuntimeExports.jsx(
51027
+ CollapsibleToolSection,
51028
+ {
51029
+ toolName,
51030
+ toolTests,
51031
+ isCollapsed: collapsedTools.has(toolName),
51032
+ onToggle: handleToggleTool
51033
+ },
51034
+ toolName
51035
+ ));
50339
51036
  })() }),
50340
51037
  assessment.security.vulnerabilities.length > 0 && (() => {
50341
51038
  var _a2, _b2, _c;
@@ -50589,43 +51286,58 @@ const AssessmentTab = ({
50589
51286
  assessment.errorHandling.metrics.testDetails && assessment.errorHandling.metrics.testDetails.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 border-t pt-3", children: [
50590
51287
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
50591
51288
  /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { className: "text-sm", children: "Error Handling Test Results:" }),
50592
- /* @__PURE__ */ jsxRuntimeExports.jsx(
50593
- Button,
50594
- {
50595
- variant: "outline",
50596
- size: "sm",
50597
- className: "text-xs h-6 px-2",
50598
- onClick: () => {
50599
- var _a2;
50600
- const toolGroups = /* @__PURE__ */ new Map();
50601
- (_a2 = assessment.errorHandling.metrics.testDetails) == null ? void 0 : _a2.forEach(
50602
- (testResult) => {
50603
- const toolName = testResult.toolName || "Unknown Tool";
50604
- if (!toolGroups.has(toolName)) {
50605
- toolGroups.set(toolName, []);
51289
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
51290
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
51291
+ Button,
51292
+ {
51293
+ variant: showOnlyErrors ? "default" : "outline",
51294
+ size: "sm",
51295
+ className: "text-xs h-6 px-2",
51296
+ onClick: () => setShowOnlyErrors(!showOnlyErrors),
51297
+ children: [
51298
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Funnel, { className: "h-3 w-3 mr-1" }),
51299
+ showOnlyErrors ? "Show All" : "Filter Errors"
51300
+ ]
51301
+ }
51302
+ ),
51303
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
51304
+ Button,
51305
+ {
51306
+ variant: "outline",
51307
+ size: "sm",
51308
+ className: "text-xs h-6 px-2",
51309
+ onClick: () => {
51310
+ var _a2;
51311
+ const toolGroups = /* @__PURE__ */ new Map();
51312
+ (_a2 = assessment.errorHandling.metrics.testDetails) == null ? void 0 : _a2.forEach(
51313
+ (testResult) => {
51314
+ const toolName = testResult.toolName || "Unknown Tool";
51315
+ if (!toolGroups.has(toolName)) {
51316
+ toolGroups.set(toolName, []);
51317
+ }
50606
51318
  }
50607
- }
50608
- );
50609
- if (allToolsCollapsed) {
50610
- setCollapsedTools(/* @__PURE__ */ new Set());
50611
- setAllToolsCollapsed(false);
50612
- } else {
50613
- const allToolNames = Array.from(
50614
- toolGroups.keys()
50615
51319
  );
50616
- setCollapsedTools(new Set(allToolNames));
50617
- setAllToolsCollapsed(true);
50618
- }
50619
- },
50620
- children: allToolsCollapsed ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
50621
- /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "h-3 w-3 mr-1" }),
50622
- "Expand All"
50623
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
50624
- /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "h-3 w-3 mr-1" }),
50625
- "Collapse All"
50626
- ] })
50627
- }
50628
- )
51320
+ if (allToolsCollapsed) {
51321
+ setCollapsedTools(/* @__PURE__ */ new Set());
51322
+ setAllToolsCollapsed(false);
51323
+ } else {
51324
+ const allToolNames = Array.from(
51325
+ toolGroups.keys()
51326
+ );
51327
+ setCollapsedTools(new Set(allToolNames));
51328
+ setAllToolsCollapsed(true);
51329
+ }
51330
+ },
51331
+ children: allToolsCollapsed ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
51332
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "h-3 w-3 mr-1" }),
51333
+ "Expand All"
51334
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
51335
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "h-3 w-3 mr-1" }),
51336
+ "Collapse All"
51337
+ ] })
51338
+ }
51339
+ )
51340
+ ] })
50629
51341
  ] }),
50630
51342
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 space-y-1", children: (() => {
50631
51343
  var _a2;
@@ -50651,12 +51363,27 @@ const AssessmentTab = ({
50651
51363
  newCollapsed.size === toolGroups.size
50652
51364
  );
50653
51365
  };
50654
- return Array.from(toolGroups.entries()).map(
51366
+ let filteredGroups = Array.from(
51367
+ toolGroups.entries()
51368
+ );
51369
+ if (showOnlyErrors) {
51370
+ filteredGroups = filteredGroups.filter(
51371
+ ([, toolTests]) => {
51372
+ return toolTests.some(
51373
+ (test) => test.passed === false
51374
+ );
51375
+ }
51376
+ );
51377
+ }
51378
+ return filteredGroups.map(
50655
51379
  ([toolName, toolTests]) => {
50656
- const passedCount = toolTests.filter(
51380
+ const scoredTests = toolTests.filter(
51381
+ (t) => t.testType !== "invalid_values"
51382
+ );
51383
+ const passedCount = scoredTests.filter(
50657
51384
  (t) => t.passed
50658
51385
  ).length;
50659
- const totalCount = toolTests.length;
51386
+ const totalCount = scoredTests.length;
50660
51387
  const allPassed = passedCount === totalCount;
50661
51388
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
50662
51389
  "div",
@@ -52497,13 +53224,13 @@ const App = () => {
52497
53224
  ) });
52498
53225
  if (window.location.pathname === "/oauth/callback") {
52499
53226
  const OAuthCallback = React.lazy(
52500
- () => __vitePreload(() => import("./OAuthCallback-C8iZSwWO.js"), true ? [] : void 0)
53227
+ () => __vitePreload(() => import("./OAuthCallback-CIWsnXN_.js"), true ? [] : void 0)
52501
53228
  );
52502
53229
  return /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(OAuthCallback, { onConnect: onOAuthConnect }) });
52503
53230
  }
52504
53231
  if (window.location.pathname === "/oauth/callback/debug") {
52505
53232
  const OAuthDebugCallback = React.lazy(
52506
- () => __vitePreload(() => import("./OAuthDebugCallback-Br9U2vZs.js"), true ? [] : void 0)
53233
+ () => __vitePreload(() => import("./OAuthDebugCallback-DP9WXVFe.js"), true ? [] : void 0)
52507
53234
  );
52508
53235
  return /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(OAuthDebugCallback, { onConnect: onOAuthDebugConnect }) });
52509
53236
  }
@@ -52790,6 +53517,10 @@ const App = () => {
52790
53517
  {
52791
53518
  tools,
52792
53519
  isLoadingTools,
53520
+ listTools: () => {
53521
+ clearError("tools");
53522
+ listTools();
53523
+ },
52793
53524
  callTool: async (name2, params) => {
52794
53525
  const result = await callTool(name2, params);
52795
53526
  return result;