@bryan-thompson/inspector-assessment-client 1.26.4 → 1.26.6

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 (30) hide show
  1. package/dist/assets/{OAuthCallback-DRmaIku9.js → OAuthCallback-CCWVtjr7.js} +1 -1
  2. package/dist/assets/{OAuthDebugCallback-BU8UZdx8.js → OAuthDebugCallback-DqbXfUi4.js} +1 -1
  3. package/dist/assets/{index-Dd4pL57l.js → index-CsDJSSWq.js} +4 -4
  4. package/dist/index.html +1 -1
  5. package/lib/lib/securityPatterns.d.ts.map +1 -1
  6. package/lib/lib/securityPatterns.js +26 -0
  7. package/lib/services/assessment/modules/securityTests/ConfidenceScorer.d.ts +57 -0
  8. package/lib/services/assessment/modules/securityTests/ConfidenceScorer.d.ts.map +1 -0
  9. package/lib/services/assessment/modules/securityTests/ConfidenceScorer.js +199 -0
  10. package/lib/services/assessment/modules/securityTests/ErrorClassifier.d.ts +57 -0
  11. package/lib/services/assessment/modules/securityTests/ErrorClassifier.d.ts.map +1 -0
  12. package/lib/services/assessment/modules/securityTests/ErrorClassifier.js +113 -0
  13. package/lib/services/assessment/modules/securityTests/ExecutionArtifactDetector.d.ts +49 -0
  14. package/lib/services/assessment/modules/securityTests/ExecutionArtifactDetector.d.ts.map +1 -0
  15. package/lib/services/assessment/modules/securityTests/ExecutionArtifactDetector.js +74 -0
  16. package/lib/services/assessment/modules/securityTests/MathAnalyzer.d.ts +58 -0
  17. package/lib/services/assessment/modules/securityTests/MathAnalyzer.d.ts.map +1 -0
  18. package/lib/services/assessment/modules/securityTests/MathAnalyzer.js +251 -0
  19. package/lib/services/assessment/modules/securityTests/SafeResponseDetector.d.ts +59 -0
  20. package/lib/services/assessment/modules/securityTests/SafeResponseDetector.d.ts.map +1 -0
  21. package/lib/services/assessment/modules/securityTests/SafeResponseDetector.js +151 -0
  22. package/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.d.ts +229 -0
  23. package/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.d.ts.map +1 -0
  24. package/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.js +566 -0
  25. package/lib/services/assessment/modules/securityTests/SecurityPayloadGenerator.d.ts.map +1 -1
  26. package/lib/services/assessment/modules/securityTests/SecurityPayloadGenerator.js +49 -1
  27. package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts +63 -85
  28. package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts.map +1 -1
  29. package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.js +270 -1159
  30. package/package.json +1 -1
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Math Analyzer
3
+ * Detects computed math expression results (Calculator Injection)
4
+ *
5
+ * Extracted from SecurityResponseAnalyzer.ts (Issue #53)
6
+ * Handles: math computation detection, coincidental numeric detection
7
+ */
8
+ import { Tool } from "@modelcontextprotocol/sdk/types.js";
9
+ /**
10
+ * Result of computed math analysis with confidence level (Issue #58)
11
+ */
12
+ export interface MathResultAnalysis {
13
+ isComputed: boolean;
14
+ confidence: "high" | "medium" | "low";
15
+ reason?: string;
16
+ }
17
+ /**
18
+ * Analyzes tool responses for math computation evidence (Calculator Injection)
19
+ */
20
+ export declare class MathAnalyzer {
21
+ /**
22
+ * Enhanced computed math result analysis with tool context (Issue #58)
23
+ *
24
+ * Returns a confidence level indicating how likely this is a real Calculator Injection:
25
+ * - high: Strong evidence of computation (should flag as vulnerable)
26
+ * - medium: Ambiguous (excluded from vulnerability count per user decision)
27
+ * - low: Likely coincidental data (excluded from vulnerability count)
28
+ */
29
+ analyzeComputedMathResult(payload: string, responseText: string, tool?: Tool): MathResultAnalysis;
30
+ /**
31
+ * Legacy method for backward compatibility
32
+ * @deprecated Use analyzeComputedMathResult instead
33
+ */
34
+ isComputedMathResult(payload: string, responseText: string): boolean;
35
+ /**
36
+ * Check if numeric value appears in structured data context (not as computation result)
37
+ * Distinguishes {"records": 4} from computed "4" (Issue #58)
38
+ *
39
+ * @param result The computed numeric result to check for
40
+ * @param responseText The response text to analyze
41
+ * @returns true if the number appears to be coincidental data, not a computed result
42
+ */
43
+ isCoincidentalNumericInStructuredData(result: number, responseText: string): boolean;
44
+ /**
45
+ * Recursively check JSON object for coincidental numeric values
46
+ */
47
+ private checkObjectForCoincidentalNumeric;
48
+ /**
49
+ * Check text for structured patterns containing coincidental numerics
50
+ */
51
+ private checkTextForCoincidentalNumeric;
52
+ /**
53
+ * Compute math expression from regex match
54
+ * Returns null if invalid operator
55
+ */
56
+ private computeExpression;
57
+ }
58
+ //# sourceMappingURL=MathAnalyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MathAnalyzer.d.ts","sourceRoot":"","sources":["../../../../../src/services/assessment/modules/securityTests/MathAnalyzer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAW1D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,qBAAa,YAAY;IACvB;;;;;;;OAOG;IACH,yBAAyB,CACvB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,IAAI,GACV,kBAAkB;IAkIrB;;;OAGG;IACH,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO;IAKpE;;;;;;;OAOG;IACH,qCAAqC,CACnC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GACnB,OAAO;IAWV;;OAEG;IACH,OAAO,CAAC,iCAAiC;IAyCzC;;OAEG;IACH,OAAO,CAAC,+BAA+B;IAqBvC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;CA+C1B"}
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Math Analyzer
3
+ * Detects computed math expression results (Calculator Injection)
4
+ *
5
+ * Extracted from SecurityResponseAnalyzer.ts (Issue #53)
6
+ * Handles: math computation detection, coincidental numeric detection
7
+ */
8
+ import { ToolClassifier, ToolCategory } from "../../ToolClassifier.js";
9
+ import { SIMPLE_MATH_PATTERN, COMPUTATIONAL_INDICATORS, STRUCTURED_DATA_FIELD_NAMES, READ_ONLY_TOOL_NAME_PATTERN, isHttpError, matchesAny, } from "./SecurityPatternLibrary.js";
10
+ /**
11
+ * Analyzes tool responses for math computation evidence (Calculator Injection)
12
+ */
13
+ export class MathAnalyzer {
14
+ /**
15
+ * Enhanced computed math result analysis with tool context (Issue #58)
16
+ *
17
+ * Returns a confidence level indicating how likely this is a real Calculator Injection:
18
+ * - high: Strong evidence of computation (should flag as vulnerable)
19
+ * - medium: Ambiguous (excluded from vulnerability count per user decision)
20
+ * - low: Likely coincidental data (excluded from vulnerability count)
21
+ */
22
+ analyzeComputedMathResult(payload, responseText, tool) {
23
+ // Skip HTTP error responses
24
+ if (isHttpError(responseText)) {
25
+ return {
26
+ isComputed: false,
27
+ confidence: "high",
28
+ reason: "HTTP error response",
29
+ };
30
+ }
31
+ // Parse math expression
32
+ const match = payload.match(SIMPLE_MATH_PATTERN);
33
+ if (!match) {
34
+ return {
35
+ isComputed: false,
36
+ confidence: "high",
37
+ reason: "Not a math expression",
38
+ };
39
+ }
40
+ try {
41
+ const result = this.computeExpression(match);
42
+ if (result === null) {
43
+ return {
44
+ isComputed: false,
45
+ confidence: "high",
46
+ reason: "Invalid operator",
47
+ };
48
+ }
49
+ const resultStr = result.toString();
50
+ const hasComputedResult = responseText.includes(resultStr);
51
+ const normalizedPayload = payload.replace(/\s+/g, "");
52
+ const hasOriginalExpression = responseText.includes(payload) ||
53
+ responseText.includes(normalizedPayload);
54
+ // Basic detection: result present without original expression
55
+ const basicDetection = hasComputedResult && !hasOriginalExpression;
56
+ if (!basicDetection) {
57
+ return {
58
+ isComputed: false,
59
+ confidence: "high",
60
+ reason: "No computed result found",
61
+ };
62
+ }
63
+ // Layer 1: Check if numeric appears in structured data context (Issue #58)
64
+ if (this.isCoincidentalNumericInStructuredData(result, responseText)) {
65
+ return {
66
+ isComputed: false,
67
+ confidence: "low",
68
+ reason: "Numeric value appears in structured data field (e.g., count, records)",
69
+ };
70
+ }
71
+ // Layer 2: Tool classification heuristics (Issue #58)
72
+ if (tool) {
73
+ const classifier = new ToolClassifier();
74
+ const classification = classifier.classify(tool.name, tool.description);
75
+ // Check for read-only/data fetcher categories
76
+ if (classification.categories.includes(ToolCategory.DATA_FETCHER) ||
77
+ classification.categories.includes(ToolCategory.API_WRAPPER) ||
78
+ classification.categories.includes(ToolCategory.SEARCH_RETRIEVAL)) {
79
+ return {
80
+ isComputed: false,
81
+ confidence: "low",
82
+ reason: `Tool classified as ${classification.categories[0]} - unlikely to compute math`,
83
+ };
84
+ }
85
+ // Check for "get_", "list_", "fetch_" patterns in tool name
86
+ if (READ_ONLY_TOOL_NAME_PATTERN.test(tool.name)) {
87
+ return {
88
+ isComputed: false,
89
+ confidence: "low",
90
+ reason: "Tool name indicates read-only operation",
91
+ };
92
+ }
93
+ }
94
+ // Layer 3: Check for computational language in response
95
+ const hasComputationalContext = matchesAny(COMPUTATIONAL_INDICATORS, responseText);
96
+ if (hasComputationalContext) {
97
+ return {
98
+ isComputed: true,
99
+ confidence: "high",
100
+ reason: "Response contains computational language",
101
+ };
102
+ }
103
+ // Layer 4: Longer responses without computational language are likely data
104
+ if (responseText.length > 50) {
105
+ return {
106
+ isComputed: false,
107
+ confidence: "medium",
108
+ reason: "Response lacks computational language, likely coincidental data",
109
+ };
110
+ }
111
+ // Short response with just the number - this is suspicious
112
+ if (responseText.trim() === resultStr) {
113
+ return {
114
+ isComputed: true,
115
+ confidence: "high",
116
+ reason: "Response is exactly the computed result",
117
+ };
118
+ }
119
+ // Default: medium confidence (excluded per user decision)
120
+ return {
121
+ isComputed: false,
122
+ confidence: "medium",
123
+ reason: "Ambiguous - numeric match without computational context",
124
+ };
125
+ }
126
+ catch {
127
+ return { isComputed: false, confidence: "high", reason: "Parse error" };
128
+ }
129
+ }
130
+ /**
131
+ * Legacy method for backward compatibility
132
+ * @deprecated Use analyzeComputedMathResult instead
133
+ */
134
+ isComputedMathResult(payload, responseText) {
135
+ const analysis = this.analyzeComputedMathResult(payload, responseText);
136
+ return analysis.isComputed && analysis.confidence === "high";
137
+ }
138
+ /**
139
+ * Check if numeric value appears in structured data context (not as computation result)
140
+ * Distinguishes {"records": 4} from computed "4" (Issue #58)
141
+ *
142
+ * @param result The computed numeric result to check for
143
+ * @param responseText The response text to analyze
144
+ * @returns true if the number appears to be coincidental data, not a computed result
145
+ */
146
+ isCoincidentalNumericInStructuredData(result, responseText) {
147
+ // Try to parse as JSON
148
+ try {
149
+ const parsed = JSON.parse(responseText);
150
+ return this.checkObjectForCoincidentalNumeric(parsed, result);
151
+ }
152
+ catch {
153
+ // Not JSON - check for structured text patterns
154
+ return this.checkTextForCoincidentalNumeric(result, responseText);
155
+ }
156
+ }
157
+ /**
158
+ * Recursively check JSON object for coincidental numeric values
159
+ */
160
+ checkObjectForCoincidentalNumeric(obj, result, depth = 0) {
161
+ if (depth > 5)
162
+ return false; // Prevent deep recursion
163
+ if (typeof obj !== "object" || obj === null)
164
+ return false;
165
+ for (const [key, value] of Object.entries(obj)) {
166
+ // Check if numeric value matches result and key is a data field
167
+ if (value === result) {
168
+ const keyLower = key.toLowerCase();
169
+ if (STRUCTURED_DATA_FIELD_NAMES.some((pattern) => keyLower.includes(pattern))) {
170
+ return true;
171
+ }
172
+ }
173
+ // Recurse into nested objects
174
+ if (typeof value === "object" && value !== null) {
175
+ if (this.checkObjectForCoincidentalNumeric(value, result, depth + 1)) {
176
+ return true;
177
+ }
178
+ }
179
+ // Check arrays
180
+ if (Array.isArray(value)) {
181
+ for (const item of value) {
182
+ if (typeof item === "object" &&
183
+ this.checkObjectForCoincidentalNumeric(item, result, depth + 1)) {
184
+ return true;
185
+ }
186
+ }
187
+ }
188
+ }
189
+ return false;
190
+ }
191
+ /**
192
+ * Check text for structured patterns containing coincidental numerics
193
+ */
194
+ checkTextForCoincidentalNumeric(result, responseText) {
195
+ const structuredPatterns = [
196
+ new RegExp(`(records|count|total|page|items|results|employees|entries|rows)[:\\s]+${result}\\b`, "i"),
197
+ new RegExp(`\\b${result}\\s+(records|items|results|entries|employees|rows)\\b`, "i"),
198
+ new RegExp(`page\\s+\\d+\\s+of\\s+${result}\\b`, "i"),
199
+ new RegExp(`total[:\\s]+${result}\\b`, "i"),
200
+ new RegExp(`found\\s+${result}\\s+(results|items|entries)`, "i"),
201
+ ];
202
+ return structuredPatterns.some((pattern) => pattern.test(responseText));
203
+ }
204
+ /**
205
+ * Compute math expression from regex match
206
+ * Returns null if invalid operator
207
+ */
208
+ computeExpression(match) {
209
+ const num1 = parseInt(match[1], 10);
210
+ const op1 = match[2];
211
+ const num2 = parseInt(match[3], 10);
212
+ const op2 = match[4];
213
+ const num3 = match[5] ? parseInt(match[5], 10) : undefined;
214
+ let result;
215
+ switch (op1) {
216
+ case "+":
217
+ result = num1 + num2;
218
+ break;
219
+ case "-":
220
+ result = num1 - num2;
221
+ break;
222
+ case "*":
223
+ result = num1 * num2;
224
+ break;
225
+ case "/":
226
+ result = Math.floor(num1 / num2);
227
+ break;
228
+ default:
229
+ return null;
230
+ }
231
+ if (op2 && num3 !== undefined) {
232
+ switch (op2) {
233
+ case "+":
234
+ result = result + num3;
235
+ break;
236
+ case "-":
237
+ result = result - num3;
238
+ break;
239
+ case "*":
240
+ result = result * num3;
241
+ break;
242
+ case "/":
243
+ result = Math.floor(result / num3);
244
+ break;
245
+ default:
246
+ return null;
247
+ }
248
+ }
249
+ return result;
250
+ }
251
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Safe Response Detector
3
+ * Detects safe (non-vulnerable) response patterns
4
+ *
5
+ * Extracted from SecurityResponseAnalyzer.ts (Issue #53)
6
+ * Handles: MCP validation, HTTP errors, reflection detection, validation rejection
7
+ */
8
+ import { CompatibilityCallToolResult } from "@modelcontextprotocol/sdk/types.js";
9
+ /**
10
+ * Error info extracted from response
11
+ */
12
+ export interface ErrorInfo {
13
+ code?: string | number;
14
+ message?: string;
15
+ }
16
+ /**
17
+ * Result of safe response check
18
+ */
19
+ export interface SafeResponseResult {
20
+ isSafe: boolean;
21
+ reason?: string;
22
+ }
23
+ /**
24
+ * Detects safe response patterns indicating proper tool behavior
25
+ */
26
+ export declare class SafeResponseDetector {
27
+ private executionDetector;
28
+ constructor();
29
+ /**
30
+ * Check if response is an MCP validation error (safe rejection)
31
+ */
32
+ isMCPValidationError(errorInfo: ErrorInfo, responseText: string): boolean;
33
+ /**
34
+ * Check if response is an HTTP error (Issue #26)
35
+ */
36
+ isHttpErrorResponse(responseText: string): boolean;
37
+ /**
38
+ * Check if response is just reflection (safe)
39
+ * Two-layer defense: Match reflection patterns, verify NO execution evidence
40
+ */
41
+ isReflectionResponse(responseText: string): boolean;
42
+ /**
43
+ * Check if response is returning search results
44
+ */
45
+ isSearchResultResponse(responseText: string): boolean;
46
+ /**
47
+ * Check if response is from a creation/modification operation
48
+ */
49
+ isCreationResponse(responseText: string): boolean;
50
+ /**
51
+ * Check if tool explicitly rejected input with validation error (SAFE)
52
+ */
53
+ isValidationRejection(response: CompatibilityCallToolResult): boolean;
54
+ /**
55
+ * Extract response content from MCP response
56
+ */
57
+ extractResponseContent(response: CompatibilityCallToolResult): string;
58
+ }
59
+ //# sourceMappingURL=SafeResponseDetector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SafeResponseDetector.d.ts","sourceRoot":"","sources":["../../../../../src/services/assessment/modules/securityTests/SafeResponseDetector.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;AAcjF;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,iBAAiB,CAA4B;;IAMrD;;OAEG;IACH,oBAAoB,CAAC,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO;IAQzE;;OAEG;IACH,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAIlD;;;OAGG;IACH,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAkEnD;;OAEG;IACH,sBAAsB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAIrD;;OAEG;IACH,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAIjD;;OAEG;IACH,qBAAqB,CAAC,QAAQ,EAAE,2BAA2B,GAAG,OAAO;IA0CrE;;OAEG;IACH,sBAAsB,CAAC,QAAQ,EAAE,2BAA2B,GAAG,MAAM;CAUtE"}
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Safe Response Detector
3
+ * Detects safe (non-vulnerable) response patterns
4
+ *
5
+ * Extracted from SecurityResponseAnalyzer.ts (Issue #53)
6
+ * Handles: MCP validation, HTTP errors, reflection detection, validation rejection
7
+ */
8
+ import { VALIDATION_ERROR_PATTERNS, STATUS_PATTERNS, REFLECTION_PATTERNS, SEARCH_RESULT_PATTERNS, CREATION_PATTERNS, TEXT_REJECTION_PATTERNS, RESULT_REJECTION_PATTERNS, isHttpError, matchesAny, } from "./SecurityPatternLibrary.js";
9
+ import { ExecutionArtifactDetector } from "./ExecutionArtifactDetector.js";
10
+ /**
11
+ * Detects safe response patterns indicating proper tool behavior
12
+ */
13
+ export class SafeResponseDetector {
14
+ executionDetector;
15
+ constructor() {
16
+ this.executionDetector = new ExecutionArtifactDetector();
17
+ }
18
+ /**
19
+ * Check if response is an MCP validation error (safe rejection)
20
+ */
21
+ isMCPValidationError(errorInfo, responseText) {
22
+ if (errorInfo.code === -32602 || errorInfo.code === "-32602") {
23
+ return true;
24
+ }
25
+ return matchesAny(VALIDATION_ERROR_PATTERNS, responseText);
26
+ }
27
+ /**
28
+ * Check if response is an HTTP error (Issue #26)
29
+ */
30
+ isHttpErrorResponse(responseText) {
31
+ return isHttpError(responseText);
32
+ }
33
+ /**
34
+ * Check if response is just reflection (safe)
35
+ * Two-layer defense: Match reflection patterns, verify NO execution evidence
36
+ */
37
+ isReflectionResponse(responseText) {
38
+ // Combine status patterns and reflection patterns
39
+ const allReflectionPatterns = [...STATUS_PATTERNS, ...REFLECTION_PATTERNS];
40
+ const hasReflection = matchesAny(allReflectionPatterns, responseText);
41
+ if (hasReflection) {
42
+ try {
43
+ const parsed = JSON.parse(responseText);
44
+ const resultText = String(parsed.result || "");
45
+ const outputFields = [
46
+ parsed.stdout,
47
+ parsed.stderr,
48
+ parsed.output,
49
+ parsed.contents,
50
+ parsed.execution_log,
51
+ parsed.command_output,
52
+ ]
53
+ .filter(Boolean)
54
+ .join(" ");
55
+ const resultIsStatusOnly = matchesAny(STATUS_PATTERNS, resultText);
56
+ const hasExecutionInOutput = resultIsStatusOnly
57
+ ? this.executionDetector.detectExecutionArtifacts(outputFields)
58
+ : this.executionDetector.detectExecutionArtifacts(resultText) ||
59
+ this.executionDetector.detectExecutionArtifacts(outputFields);
60
+ if (hasExecutionInOutput) {
61
+ return false;
62
+ }
63
+ return true;
64
+ }
65
+ catch {
66
+ const hasExecution = this.executionDetector.detectExecutionArtifacts(responseText);
67
+ if (hasExecution) {
68
+ return false;
69
+ }
70
+ return true;
71
+ }
72
+ }
73
+ // Check for JSON status patterns
74
+ try {
75
+ const parsed = JSON.parse(responseText);
76
+ if (parsed.action === "test" || parsed.action === "placeholder") {
77
+ const resultText = String(parsed.result || "");
78
+ if (!this.executionDetector.detectExecutionArtifacts(resultText)) {
79
+ return true;
80
+ }
81
+ }
82
+ if (parsed.status && /(completed|success|ok|done)/.test(parsed.status)) {
83
+ if (!this.executionDetector.detectExecutionArtifacts(responseText)) {
84
+ return true;
85
+ }
86
+ }
87
+ }
88
+ catch {
89
+ // Not JSON
90
+ }
91
+ return false;
92
+ }
93
+ /**
94
+ * Check if response is returning search results
95
+ */
96
+ isSearchResultResponse(responseText) {
97
+ return matchesAny(SEARCH_RESULT_PATTERNS, responseText);
98
+ }
99
+ /**
100
+ * Check if response is from a creation/modification operation
101
+ */
102
+ isCreationResponse(responseText) {
103
+ return matchesAny(CREATION_PATTERNS, responseText);
104
+ }
105
+ /**
106
+ * Check if tool explicitly rejected input with validation error (SAFE)
107
+ */
108
+ isValidationRejection(response) {
109
+ const responseText = this.extractResponseContent(response);
110
+ try {
111
+ const parsed = JSON.parse(responseText);
112
+ if (parsed.valid === false ||
113
+ parsed.error === true ||
114
+ parsed.error === "true" ||
115
+ (parsed.error && parsed.error !== false) ||
116
+ parsed.status === "rejected" ||
117
+ parsed.status === "invalid" ||
118
+ parsed.status === "failed") {
119
+ return true;
120
+ }
121
+ if (parsed.errors &&
122
+ Array.isArray(parsed.errors) &&
123
+ parsed.errors.length > 0) {
124
+ return true;
125
+ }
126
+ if (parsed.error && typeof parsed.error === "string") {
127
+ return true;
128
+ }
129
+ if (typeof parsed.result === "string") {
130
+ if (matchesAny(RESULT_REJECTION_PATTERNS, parsed.result)) {
131
+ return true;
132
+ }
133
+ }
134
+ }
135
+ catch {
136
+ // Not JSON, check text patterns
137
+ }
138
+ return matchesAny(TEXT_REJECTION_PATTERNS, responseText);
139
+ }
140
+ /**
141
+ * Extract response content from MCP response
142
+ */
143
+ extractResponseContent(response) {
144
+ if (response.content && Array.isArray(response.content)) {
145
+ return response.content
146
+ .map((c) => c.type === "text" ? c.text : "")
147
+ .join(" ");
148
+ }
149
+ return String(response.content || "");
150
+ }
151
+ }