snow-flow 3.4.31 → 3.4.33

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,30 @@ 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
+ // If response was too large, return a summary
88
+ if (originalSize > 100000) { // > 100KB
89
+ result = response_limiter_js_1.ResponseLimiter.createSummaryResponse(result, name);
90
+ }
91
+ else {
92
+ result = limited;
93
+ }
94
+ }
95
+ // Add token tracking metadata
96
+ const responseSize = JSON.stringify(result).length;
97
+ const estimatedTokens = Math.ceil(responseSize / 4);
98
+ if (result && typeof result === 'object') {
99
+ result._meta = {
100
+ ...result._meta,
101
+ tokenCount: estimatedTokens,
102
+ responseSize,
103
+ wasLimited
104
+ };
105
+ }
82
106
  // Update metrics
83
107
  metrics.totalTime += Date.now() - startTime;
84
108
  this.toolMetrics.set(name, metrics);
@@ -457,6 +481,38 @@ class BaseMCPServer {
457
481
  getToolHandler(name) {
458
482
  return this.toolHandlers.get(name);
459
483
  }
484
+ /**
485
+ * Execute tool with retry logic
486
+ */
487
+ async executeWithRetry(name, args, maxRetries = 3) {
488
+ const handler = this.getToolHandler(name);
489
+ if (!handler) {
490
+ throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Tool '${name}' not found`);
491
+ }
492
+ let lastError;
493
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
494
+ try {
495
+ // Execute the tool handler
496
+ const result = await handler(args);
497
+ return result;
498
+ }
499
+ catch (error) {
500
+ lastError = error;
501
+ // Don't retry on non-retryable errors
502
+ if (error.code === types_js_1.ErrorCode.InvalidRequest ||
503
+ error.code === types_js_1.ErrorCode.MethodNotFound) {
504
+ throw error;
505
+ }
506
+ // Log retry attempt
507
+ if (attempt < maxRetries) {
508
+ this.logger.warn(`Tool ${name} failed (attempt ${attempt}/${maxRetries}), retrying...`, error.message);
509
+ await new Promise(resolve => setTimeout(resolve, Math.min(1000 * attempt, 5000)));
510
+ }
511
+ }
512
+ }
513
+ // All retries failed
514
+ throw lastError;
515
+ }
460
516
  /**
461
517
  * Graceful shutdown
462
518
  */
@@ -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,108 @@
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
42
+ if (typeof obj === 'string' && obj.length > 1000) {
43
+ return obj.substring(0, 1000) + '... [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).toFixed(1)}KB
87
+ - Token estimate: ${estimatedTokens}
88
+ - Limit: ${this.MAX_TOKEN_ESTIMATE} tokens
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
+ _meta: {
97
+ limited: true,
98
+ originalSize,
99
+ tokenEstimate: estimatedTokens
100
+ }
101
+ };
102
+ }
103
+ }
104
+ exports.ResponseLimiter = ResponseLimiter;
105
+ ResponseLimiter.MAX_RESPONSE_SIZE = 50000; // 50KB max per response
106
+ ResponseLimiter.MAX_ARRAY_ITEMS = 100; // Max 100 items in arrays
107
+ ResponseLimiter.MAX_TOKEN_ESTIMATE = 12500; // ~50KB / 4 chars per token
108
+ //# sourceMappingURL=response-limiter.js.map
@@ -17,9 +17,10 @@ class Logger {
17
17
  format: winston_1.default.format.combine(winston_1.default.format.timestamp(), winston_1.default.format.errors({ stack: true }), winston_1.default.format.json()),
18
18
  defaultMeta: { agent: agentName },
19
19
  transports: [
20
- // Console transport
20
+ // Console transport - use stderr to keep stdout clean for JSON-RPC
21
21
  new winston_1.default.transports.Console({
22
- format: winston_1.default.format.combine(winston_1.default.format.colorize(), winston_1.default.format.simple())
22
+ format: winston_1.default.format.combine(winston_1.default.format.colorize(), winston_1.default.format.simple()),
23
+ stderrLevels: ['error', 'warn', 'info', 'debug', 'verbose', 'silly'] // All levels to stderr
23
24
  }),
24
25
  // File transport
25
26
  new winston_1.default.transports.File({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-flow",
3
- "version": "3.4.31",
3
+ "version": "3.4.33",
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",