snow-flow 3.4.32 → 3.4.34

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.
@@ -67,11 +67,6 @@ export declare abstract class BaseMCPServer {
67
67
  * Validate authentication with smart caching
68
68
  */
69
69
  protected validateAuth(): Promise<AuthResult>;
70
- /**
71
- * 🔴 SNOW-003 FIX: Enhanced retry logic with intelligent backoff and circuit breaker
72
- * Addresses the 19% failure rate with better retry strategies and failure prevention
73
- */
74
- private executeWithRetry;
75
70
  /**
76
71
  * 🔴 SNOW-003 FIX: Calculate intelligent backoff based on error type and attempt
77
72
  */
@@ -17,6 +17,7 @@ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
17
17
  const servicenow_client_js_1 = require("../utils/servicenow-client.js");
18
18
  const snow_oauth_js_1 = require("../utils/snow-oauth.js");
19
19
  const logger_js_1 = require("../utils/logger.js");
20
+ const response_limiter_js_1 = require("./shared/response-limiter.js");
20
21
  /**
21
22
  * Base class for all ServiceNow MCP servers
22
23
  * Provides common functionality to eliminate code duplication
@@ -78,7 +79,31 @@ class BaseMCPServer {
78
79
  }
79
80
  }
80
81
  // Execute tool with retry logic
81
- const result = await this.executeWithRetry(name, args);
82
+ let result = await this.executeWithRetry(name, args);
83
+ // Limit response size to prevent timeouts
84
+ const { limited, wasLimited, originalSize } = response_limiter_js_1.ResponseLimiter.limitResponse(result);
85
+ if (wasLimited) {
86
+ this.logger.warn(`Response limited for ${name}: ${originalSize} bytes -> ${JSON.stringify(limited).length} bytes`);
87
+ // Only create summary for EXTREMELY large responses (>2MB)
88
+ // Normal widgets/flows should pass through fine with 500KB limit
89
+ if (originalSize > 2000000) { // > 2MB - truly excessive
90
+ result = response_limiter_js_1.ResponseLimiter.createSummaryResponse(result, name);
91
+ }
92
+ else {
93
+ result = limited;
94
+ }
95
+ }
96
+ // Add token tracking metadata
97
+ const responseSize = JSON.stringify(result).length;
98
+ const estimatedTokens = Math.ceil(responseSize / 4);
99
+ if (result && typeof result === 'object') {
100
+ result._meta = {
101
+ ...result._meta,
102
+ tokenCount: estimatedTokens,
103
+ responseSize,
104
+ wasLimited
105
+ };
106
+ }
82
107
  // Update metrics
83
108
  metrics.totalTime += Date.now() - startTime;
84
109
  this.toolMetrics.set(name, metrics);
@@ -457,6 +482,38 @@ class BaseMCPServer {
457
482
  getToolHandler(name) {
458
483
  return this.toolHandlers.get(name);
459
484
  }
485
+ /**
486
+ * Execute tool with retry logic
487
+ */
488
+ async executeWithRetry(name, args, maxRetries = 3) {
489
+ const handler = this.getToolHandler(name);
490
+ if (!handler) {
491
+ throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Tool '${name}' not found`);
492
+ }
493
+ let lastError;
494
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
495
+ try {
496
+ // Execute the tool handler
497
+ const result = await handler(args);
498
+ return result;
499
+ }
500
+ catch (error) {
501
+ lastError = error;
502
+ // Don't retry on non-retryable errors
503
+ if (error.code === types_js_1.ErrorCode.InvalidRequest ||
504
+ error.code === types_js_1.ErrorCode.MethodNotFound) {
505
+ throw error;
506
+ }
507
+ // Log retry attempt
508
+ if (attempt < maxRetries) {
509
+ this.logger.warn(`Tool ${name} failed (attempt ${attempt}/${maxRetries}), retrying...`, error.message);
510
+ await new Promise(resolve => setTimeout(resolve, Math.min(1000 * attempt, 5000)));
511
+ }
512
+ }
513
+ }
514
+ // All retries failed
515
+ throw lastError;
516
+ }
460
517
  /**
461
518
  * Graceful shutdown
462
519
  */
@@ -0,0 +1,26 @@
1
+ /**
2
+ * MCP Response Limiter
3
+ * Prevents oversized responses that cause timeouts in Claude Code
4
+ */
5
+ export declare class ResponseLimiter {
6
+ private static readonly MAX_RESPONSE_SIZE;
7
+ private static readonly MAX_ARRAY_ITEMS;
8
+ private static readonly MAX_TOKEN_ESTIMATE;
9
+ /**
10
+ * Limit response size to prevent timeouts
11
+ */
12
+ static limitResponse(data: any): {
13
+ limited: any;
14
+ wasLimited: boolean;
15
+ originalSize?: number;
16
+ };
17
+ /**
18
+ * Recursively limit object size
19
+ */
20
+ private static limitObject;
21
+ /**
22
+ * Create a summary response when data is too large
23
+ */
24
+ static createSummaryResponse(data: any, operation: string): any;
25
+ }
26
+ //# sourceMappingURL=response-limiter.d.ts.map
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ /**
3
+ * MCP Response Limiter
4
+ * Prevents oversized responses that cause timeouts in Claude Code
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.ResponseLimiter = void 0;
8
+ class ResponseLimiter {
9
+ /**
10
+ * Limit response size to prevent timeouts
11
+ */
12
+ static limitResponse(data) {
13
+ const originalString = JSON.stringify(data);
14
+ const originalSize = originalString.length;
15
+ // If response is small enough, return as-is
16
+ if (originalSize <= this.MAX_RESPONSE_SIZE) {
17
+ return { limited: data, wasLimited: false };
18
+ }
19
+ // Response too large - need to limit it
20
+ const limited = this.limitObject(data);
21
+ return {
22
+ limited,
23
+ wasLimited: true,
24
+ originalSize
25
+ };
26
+ }
27
+ /**
28
+ * Recursively limit object size
29
+ */
30
+ static limitObject(obj, depth = 0) {
31
+ // Don't go too deep
32
+ if (depth > 5) {
33
+ return '[DEPTH_LIMITED]';
34
+ }
35
+ // Handle null/undefined
36
+ if (obj === null || obj === undefined) {
37
+ return obj;
38
+ }
39
+ // Handle primitives
40
+ if (typeof obj !== 'object') {
41
+ // Limit string length - 10KB per string field is reasonable
42
+ if (typeof obj === 'string' && obj.length > 10000) {
43
+ return obj.substring(0, 10000) + '... [TRUNCATED]';
44
+ }
45
+ return obj;
46
+ }
47
+ // Handle arrays
48
+ if (Array.isArray(obj)) {
49
+ if (obj.length > this.MAX_ARRAY_ITEMS) {
50
+ return [
51
+ ...obj.slice(0, this.MAX_ARRAY_ITEMS).map(item => this.limitObject(item, depth + 1)),
52
+ `[... ${obj.length - this.MAX_ARRAY_ITEMS} more items]`
53
+ ];
54
+ }
55
+ return obj.map(item => this.limitObject(item, depth + 1));
56
+ }
57
+ // Handle objects
58
+ const limited = {};
59
+ const keys = Object.keys(obj);
60
+ // Limit number of keys
61
+ const maxKeys = 50;
62
+ const keysToProcess = keys.slice(0, maxKeys);
63
+ for (const key of keysToProcess) {
64
+ limited[key] = this.limitObject(obj[key], depth + 1);
65
+ }
66
+ if (keys.length > maxKeys) {
67
+ limited._truncated = `${keys.length - maxKeys} more properties omitted`;
68
+ }
69
+ return limited;
70
+ }
71
+ /**
72
+ * Create a summary response when data is too large
73
+ */
74
+ static createSummaryResponse(data, operation) {
75
+ const originalSize = JSON.stringify(data).length;
76
+ const estimatedTokens = Math.ceil(originalSize / 4);
77
+ return {
78
+ content: [{
79
+ type: 'text',
80
+ text: `⚠️ Response too large (${estimatedTokens} tokens, ${originalSize} bytes)
81
+
82
+ Operation: ${operation}
83
+ Status: Success (data limited to prevent timeout)
84
+
85
+ Summary:
86
+ - Original size: ${(originalSize / 1024 / 1024).toFixed(2)}MB
87
+ - Token estimate: ${estimatedTokens}
88
+ - Response limit: ${(this.MAX_RESPONSE_SIZE / 1024).toFixed(0)}KB
89
+
90
+ 💡 Tips to reduce response size:
91
+ 1. Use specific field queries instead of '*'
92
+ 2. Add pagination with smaller limits
93
+ 3. Filter results more specifically
94
+ 4. Use count operations instead of full data retrieval
95
+
96
+ Note: You can increase limits via environment variables:
97
+ - MCP_MAX_RESPONSE_SIZE (default: 500000 bytes)
98
+ - MCP_MAX_ARRAY_ITEMS (default: 500 items)`
99
+ }],
100
+ _meta: {
101
+ limited: true,
102
+ originalSize,
103
+ tokenEstimate: estimatedTokens
104
+ }
105
+ };
106
+ }
107
+ }
108
+ exports.ResponseLimiter = ResponseLimiter;
109
+ // Configurable via environment variable, default to 500KB (reasonable for widgets/flows)
110
+ ResponseLimiter.MAX_RESPONSE_SIZE = parseInt(process.env.MCP_MAX_RESPONSE_SIZE || '500000'); // 500KB default
111
+ ResponseLimiter.MAX_ARRAY_ITEMS = parseInt(process.env.MCP_MAX_ARRAY_ITEMS || '500'); // 500 items default
112
+ ResponseLimiter.MAX_TOKEN_ESTIMATE = 125000; // ~500KB / 4 chars per token
113
+ //# sourceMappingURL=response-limiter.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-flow",
3
- "version": "3.4.32",
3
+ "version": "3.4.34",
4
4
  "description": "ServiceNow development framework with MCP server integration. Executes background scripts using ES5 JavaScript only (ServiceNow Rhino engine requirement). Provides 17 MCP servers for complete ServiceNow operations including widget deployment with coherence validation, table operations, script execution, machine learning, advanced features, and comprehensive platform management.",
5
5
  "main": "dist/index.js",
6
6
  "type": "commonjs",