snow-flow 2.8.9 → 2.9.4

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.
@@ -0,0 +1,220 @@
1
+ "use strict";
2
+ /**
3
+ * MCP Process Manager
4
+ * Prevents excessive MCP server spawning and manages resource limits
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.MCPProcessManager = void 0;
8
+ const child_process_1 = require("child_process");
9
+ const logger_js_1 = require("./logger.js");
10
+ const logger = new logger_js_1.Logger('MCPProcessManager');
11
+ class MCPProcessManager {
12
+ constructor() {
13
+ this.MAX_MCP_SERVERS = parseInt(process.env.SNOW_MAX_MCP_SERVERS || '10');
14
+ this.MAX_MEMORY_MB = parseInt(process.env.SNOW_MCP_MEMORY_LIMIT || '1500');
15
+ this.CLEANUP_INTERVAL = 60000; // 1 minute
16
+ // Start periodic cleanup
17
+ this.startPeriodicCleanup();
18
+ }
19
+ static getInstance() {
20
+ if (!MCPProcessManager.instance) {
21
+ MCPProcessManager.instance = new MCPProcessManager();
22
+ }
23
+ return MCPProcessManager.instance;
24
+ }
25
+ /**
26
+ * Check if we can spawn a new MCP server
27
+ */
28
+ canSpawnServer() {
29
+ const status = this.getSystemStatus();
30
+ if (status.processCount >= this.MAX_MCP_SERVERS) {
31
+ logger.warn(`❌ Cannot spawn: Already at max servers (${status.processCount}/${this.MAX_MCP_SERVERS})`);
32
+ return false;
33
+ }
34
+ if (status.memoryUsageMB > this.MAX_MEMORY_MB) {
35
+ logger.warn(`❌ Cannot spawn: Memory limit exceeded (${status.memoryUsageMB}MB > ${this.MAX_MEMORY_MB}MB)`);
36
+ return false;
37
+ }
38
+ return true;
39
+ }
40
+ /**
41
+ * Get current MCP system status
42
+ */
43
+ getSystemStatus() {
44
+ try {
45
+ // Count MCP processes
46
+ const psOutput = (0, child_process_1.execSync)('ps aux | grep -E "mcp|servicenow.*mcp" | grep -v grep', {
47
+ encoding: 'utf8'
48
+ }).trim();
49
+ if (!psOutput) {
50
+ return {
51
+ processCount: 0,
52
+ memoryUsageMB: 0,
53
+ processes: []
54
+ };
55
+ }
56
+ const lines = psOutput.split('\n');
57
+ const processes = [];
58
+ let totalMemory = 0;
59
+ for (const line of lines) {
60
+ const parts = line.split(/\s+/);
61
+ if (parts.length > 10) {
62
+ const pid = parseInt(parts[1]);
63
+ const memory = Math.round(parseInt(parts[5]) / 1024); // Convert KB to MB
64
+ const name = parts.slice(10).join(' ');
65
+ processes.push({ pid, memory, name });
66
+ totalMemory += memory;
67
+ }
68
+ }
69
+ return {
70
+ processCount: processes.length,
71
+ memoryUsageMB: totalMemory,
72
+ processes
73
+ };
74
+ }
75
+ catch (error) {
76
+ // No MCP processes found
77
+ return {
78
+ processCount: 0,
79
+ memoryUsageMB: 0,
80
+ processes: []
81
+ };
82
+ }
83
+ }
84
+ /**
85
+ * Kill duplicate MCP servers (keep only the newest)
86
+ */
87
+ killDuplicates() {
88
+ const status = this.getSystemStatus();
89
+ // Group processes by server type
90
+ const serverGroups = new Map();
91
+ for (const proc of status.processes) {
92
+ // Extract server type from process name
93
+ const match = proc.name.match(/servicenow-([^-]+)-mcp\.js/);
94
+ if (match) {
95
+ const serverType = match[1];
96
+ if (!serverGroups.has(serverType)) {
97
+ serverGroups.set(serverType, []);
98
+ }
99
+ serverGroups.get(serverType).push({
100
+ pid: proc.pid,
101
+ memory: proc.memory
102
+ });
103
+ }
104
+ }
105
+ // Kill duplicates (keep the first one)
106
+ for (const [serverType, procs] of serverGroups) {
107
+ if (procs.length > 1) {
108
+ logger.warn(`Found ${procs.length} instances of ${serverType}-mcp, killing duplicates...`);
109
+ // Sort by PID (older PIDs first) and keep the first one
110
+ procs.sort((a, b) => a.pid - b.pid);
111
+ for (let i = 1; i < procs.length; i++) {
112
+ try {
113
+ process.kill(procs[i].pid, 'SIGTERM');
114
+ logger.info(`Killed duplicate ${serverType}-mcp (PID: ${procs[i].pid})`);
115
+ }
116
+ catch (error) {
117
+ // Process might already be dead
118
+ }
119
+ }
120
+ }
121
+ }
122
+ }
123
+ /**
124
+ * Kill all MCP servers
125
+ */
126
+ killAll() {
127
+ try {
128
+ (0, child_process_1.execSync)('pkill -f mcp', { encoding: 'utf8' });
129
+ logger.info('✅ Killed all MCP processes');
130
+ }
131
+ catch (error) {
132
+ // pkill returns non-zero if no processes found
133
+ }
134
+ }
135
+ /**
136
+ * Clean up excessive resources
137
+ */
138
+ cleanup() {
139
+ const status = this.getSystemStatus();
140
+ if (status.processCount > this.MAX_MCP_SERVERS) {
141
+ logger.warn(`🧹 Cleaning up excessive MCP servers (${status.processCount} > ${this.MAX_MCP_SERVERS})`);
142
+ this.killDuplicates();
143
+ }
144
+ if (status.memoryUsageMB > this.MAX_MEMORY_MB) {
145
+ logger.warn(`🧹 Memory usage too high (${status.memoryUsageMB}MB), killing oldest processes...`);
146
+ // Sort by memory usage and kill the highest consumers
147
+ const sorted = status.processes.sort((a, b) => b.memory - a.memory);
148
+ let memoryFreed = 0;
149
+ for (const proc of sorted) {
150
+ if (status.memoryUsageMB - memoryFreed <= this.MAX_MEMORY_MB * 0.8) {
151
+ break; // Stop when we're at 80% of limit
152
+ }
153
+ try {
154
+ process.kill(proc.pid, 'SIGTERM');
155
+ memoryFreed += proc.memory;
156
+ logger.info(`Killed high-memory process (PID: ${proc.pid}, ${proc.memory}MB)`);
157
+ }
158
+ catch (error) {
159
+ // Process might already be dead
160
+ }
161
+ }
162
+ }
163
+ }
164
+ /**
165
+ * Start periodic cleanup
166
+ */
167
+ startPeriodicCleanup() {
168
+ // Clear existing timer
169
+ if (this.cleanupTimer) {
170
+ clearInterval(this.cleanupTimer);
171
+ }
172
+ // Run cleanup periodically
173
+ this.cleanupTimer = setInterval(() => {
174
+ const status = this.getSystemStatus();
175
+ if (status.processCount > this.MAX_MCP_SERVERS * 0.8 ||
176
+ status.memoryUsageMB > this.MAX_MEMORY_MB * 0.8) {
177
+ logger.info('🔄 Running periodic MCP cleanup...');
178
+ this.cleanup();
179
+ }
180
+ }, this.CLEANUP_INTERVAL);
181
+ // Don't block process exit
182
+ this.cleanupTimer.unref();
183
+ }
184
+ /**
185
+ * Stop periodic cleanup
186
+ */
187
+ stopPeriodicCleanup() {
188
+ if (this.cleanupTimer) {
189
+ clearInterval(this.cleanupTimer);
190
+ this.cleanupTimer = undefined;
191
+ }
192
+ }
193
+ /**
194
+ * Get resource usage summary
195
+ */
196
+ getResourceSummary() {
197
+ const status = this.getSystemStatus();
198
+ return `MCP Resources:
199
+ Processes: ${status.processCount}/${this.MAX_MCP_SERVERS} (${Math.round(status.processCount / this.MAX_MCP_SERVERS * 100)}%)
200
+ Memory: ${status.memoryUsageMB}MB/${this.MAX_MEMORY_MB}MB (${Math.round(status.memoryUsageMB / this.MAX_MEMORY_MB * 100)}%)
201
+ Status: ${this.getHealthStatus()}`;
202
+ }
203
+ /**
204
+ * Get health status
205
+ */
206
+ getHealthStatus() {
207
+ const status = this.getSystemStatus();
208
+ const processPercent = status.processCount / this.MAX_MCP_SERVERS;
209
+ const memoryPercent = status.memoryUsageMB / this.MAX_MEMORY_MB;
210
+ if (processPercent > 0.9 || memoryPercent > 0.9) {
211
+ return 'critical';
212
+ }
213
+ if (processPercent > 0.7 || memoryPercent > 0.7) {
214
+ return 'warning';
215
+ }
216
+ return 'healthy';
217
+ }
218
+ }
219
+ exports.MCPProcessManager = MCPProcessManager;
220
+ //# sourceMappingURL=mcp-process-manager.js.map
@@ -14,6 +14,7 @@ const events_1 = require("events");
14
14
  const os_1 = __importDefault(require("os"));
15
15
  const unified_auth_store_js_1 = require("./unified-auth-store.js");
16
16
  const mcp_singleton_lock_js_1 = require("./mcp-singleton-lock.js");
17
+ const mcp_process_manager_js_1 = require("./mcp-process-manager.js");
17
18
  class MCPServerManager extends events_1.EventEmitter {
18
19
  constructor(configPath) {
19
20
  super();
@@ -148,6 +149,16 @@ class MCPServerManager extends events_1.EventEmitter {
148
149
  if (server.status === 'running') {
149
150
  return true; // Already running
150
151
  }
152
+ // Check if we can spawn a new server
153
+ const processManager = mcp_process_manager_js_1.MCPProcessManager.getInstance();
154
+ if (!processManager.canSpawnServer()) {
155
+ // Try cleanup first
156
+ processManager.cleanup();
157
+ // Check again after cleanup
158
+ if (!processManager.canSpawnServer()) {
159
+ throw new Error('Cannot spawn server: resource limits exceeded. Too many MCP processes running.');
160
+ }
161
+ }
151
162
  server.status = 'starting';
152
163
  this.emit('serverStarting', name);
153
164
  try {
@@ -252,15 +263,20 @@ class MCPServerManager extends events_1.EventEmitter {
252
263
  try {
253
264
  // Try graceful shutdown first
254
265
  server.process.kill('SIGTERM');
255
- // Wait for graceful shutdown
266
+ // Wait for graceful shutdown with shorter timeout to prevent hanging
256
267
  await new Promise((resolve) => {
257
268
  const timeout = setTimeout(() => {
258
269
  // Force kill if graceful shutdown fails
259
270
  if (server.process) {
260
- server.process.kill('SIGKILL');
271
+ try {
272
+ server.process.kill('SIGKILL');
273
+ }
274
+ catch (e) {
275
+ // Process might already be dead
276
+ }
261
277
  }
262
278
  resolve(undefined);
263
- }, 5000);
279
+ }, 2000); // Reduced from 5000ms to 2000ms to prevent hanging
264
280
  server.process?.on('exit', () => {
265
281
  clearTimeout(timeout);
266
282
  resolve(undefined);
@@ -287,12 +303,31 @@ class MCPServerManager extends events_1.EventEmitter {
287
303
  if (!singletonLock.acquire()) {
288
304
  throw new Error('❌ MCP servers already running. Cannot start duplicate instances.');
289
305
  }
290
- console.log('✅ Starting all MCP servers (singleton protected)...');
291
- const promises = Array.from(this.servers.keys()).map(name => this.startServer(name).catch(error => {
292
- console.error(`Failed to start server '${name}':`, error);
293
- return false;
294
- }));
295
- await Promise.all(promises);
306
+ // Clean up any existing duplicates first
307
+ const processManager = mcp_process_manager_js_1.MCPProcessManager.getInstance();
308
+ processManager.killDuplicates();
309
+ console.log('✅ Starting all MCP servers (singleton protected with resource limits)...');
310
+ console.log(processManager.getResourceSummary());
311
+ // Start servers sequentially with delay to avoid resource spikes
312
+ let started = 0;
313
+ for (const name of Array.from(this.servers.keys())) {
314
+ try {
315
+ // Check resource limits before each start
316
+ if (!processManager.canSpawnServer()) {
317
+ console.warn(`⚠️ Skipping ${name} - resource limits reached`);
318
+ continue;
319
+ }
320
+ await this.startServer(name);
321
+ started++;
322
+ // Small delay between starts to avoid CPU spike
323
+ await new Promise(resolve => setTimeout(resolve, 200));
324
+ }
325
+ catch (error) {
326
+ console.error(`Failed to start server '${name}':`, error);
327
+ }
328
+ }
329
+ console.log(`✅ Started ${started}/${this.servers.size} MCP servers`);
330
+ console.log(processManager.getResourceSummary());
296
331
  }
297
332
  /**
298
333
  * Stop all running MCP servers
@@ -303,6 +338,12 @@ class MCPServerManager extends events_1.EventEmitter {
303
338
  return false;
304
339
  }));
305
340
  await Promise.all(promises);
341
+ // Release singleton lock after stopping all servers
342
+ const singletonLock = (0, mcp_singleton_lock_js_1.getMCPSingletonLock)();
343
+ if (singletonLock.isAcquired()) {
344
+ singletonLock.release();
345
+ console.log('✅ Released MCP singleton lock after stopping all servers');
346
+ }
306
347
  }
307
348
  /**
308
349
  * Get status of a specific server
@@ -15,6 +15,10 @@ export declare class MCPSingletonLock {
15
15
  * Release the singleton lock
16
16
  */
17
17
  release(): void;
18
+ /**
19
+ * Release the singleton lock without blocking (for graceful shutdown)
20
+ */
21
+ releaseAsync(): Promise<void>;
18
22
  /**
19
23
  * Check if lock is currently held by this process
20
24
  */
@@ -71,6 +71,18 @@ class MCPSingletonLock {
71
71
  }
72
72
  }
73
73
  }
74
+ /**
75
+ * Release the singleton lock without blocking (for graceful shutdown)
76
+ */
77
+ releaseAsync() {
78
+ return new Promise((resolve) => {
79
+ // Use setImmediate to avoid blocking
80
+ setImmediate(() => {
81
+ this.release();
82
+ resolve();
83
+ });
84
+ });
85
+ }
74
86
  /**
75
87
  * Check if lock is currently held by this process
76
88
  */
@@ -82,20 +94,35 @@ class MCPSingletonLock {
82
94
  */
83
95
  setupCleanupHandlers() {
84
96
  const cleanup = () => {
85
- this.release();
97
+ // Use non-blocking release to prevent hanging during shutdown
98
+ setImmediate(() => {
99
+ this.release();
100
+ });
86
101
  };
87
- process.on('exit', cleanup);
88
- process.on('SIGINT', cleanup);
89
- process.on('SIGTERM', cleanup);
90
- process.on('uncaughtException', (error) => {
102
+ // Remove existing handlers to prevent duplicate registrations
103
+ process.removeAllListeners('exit');
104
+ process.removeAllListeners('SIGINT');
105
+ process.removeAllListeners('SIGTERM');
106
+ process.once('exit', cleanup);
107
+ process.once('SIGINT', () => {
108
+ cleanup();
109
+ // Allow graceful exit after cleanup
110
+ setTimeout(() => process.exit(0), 100);
111
+ });
112
+ process.once('SIGTERM', () => {
113
+ cleanup();
114
+ // Allow graceful exit after cleanup
115
+ setTimeout(() => process.exit(0), 100);
116
+ });
117
+ process.once('uncaughtException', (error) => {
91
118
  logger.error('Uncaught exception, releasing MCP lock:', error);
92
119
  cleanup();
93
- process.exit(1);
120
+ setTimeout(() => process.exit(1), 100);
94
121
  });
95
- process.on('unhandledRejection', (reason) => {
122
+ process.once('unhandledRejection', (reason) => {
96
123
  logger.error('Unhandled rejection, releasing MCP lock:', reason);
97
124
  cleanup();
98
- process.exit(1);
125
+ setTimeout(() => process.exit(1), 100);
99
126
  });
100
127
  }
101
128
  /**
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Smart ML Data Fetcher
3
+ * Handles intelligent data fetching for ML training with batching and field discovery
4
+ * Prevents token limit errors and optimizes data retrieval
5
+ */
6
+ export interface MLDataFetchOptions {
7
+ table: string;
8
+ query?: string;
9
+ totalSamples?: number;
10
+ batchSize?: number;
11
+ fields?: string[];
12
+ discoverFields?: boolean;
13
+ includeContent?: boolean;
14
+ }
15
+ export interface FieldDiscoveryResult {
16
+ allFields: string[];
17
+ recommendedFields: string[];
18
+ sampleData: any[];
19
+ }
20
+ export interface BatchFetchResult {
21
+ data: any[];
22
+ totalFetched: number;
23
+ batchesProcessed: number;
24
+ fields: string[];
25
+ }
26
+ export declare class MLDataFetcher {
27
+ private operationsMCP;
28
+ constructor(operationsMCP: any);
29
+ /**
30
+ * Smart fetch with automatic field discovery and batching
31
+ */
32
+ smartFetch(options: MLDataFetchOptions): Promise<BatchFetchResult>;
33
+ /**
34
+ * Get total count of records matching query
35
+ */
36
+ private getRecordCount;
37
+ /**
38
+ * Discover available fields by sampling a few records
39
+ */
40
+ private discoverFields;
41
+ /**
42
+ * Fetch a single batch of data
43
+ */
44
+ private fetchBatch;
45
+ /**
46
+ * Extract data from MCP tool result
47
+ */
48
+ private extractDataFromResult;
49
+ /**
50
+ * Calculate optimal batch size based on data characteristics
51
+ */
52
+ private calculateOptimalBatchSize;
53
+ /**
54
+ * Select appropriate fields for ML training
55
+ */
56
+ private selectMLFields;
57
+ /**
58
+ * Get default fields for a table type
59
+ */
60
+ private getDefaultFields;
61
+ /**
62
+ * Get key fields that should always be included
63
+ */
64
+ private getKeyFields;
65
+ }
66
+ //# sourceMappingURL=ml-data-fetcher.d.ts.map