snow-flow 2.9.0 โ 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.
- package/check-mcp-resources.sh +85 -0
- package/dist/config/snow-flow-config.js +1 -1
- package/dist/mcp/mcp-on-demand-proxy.d.ts +7 -0
- package/dist/mcp/mcp-on-demand-proxy.js +187 -0
- package/dist/mcp/servicenow-machine-learning-mcp.js +310 -60
- package/dist/mcp/servicenow-operations-mcp.d.ts +71 -1
- package/dist/mcp/servicenow-operations-mcp.js +10 -6
- package/dist/test-smart-limits.d.ts +13 -0
- package/dist/test-smart-limits.js +100 -0
- package/dist/utils/mcp-on-demand-manager.d.ts +69 -0
- package/dist/utils/mcp-on-demand-manager.js +309 -0
- package/dist/utils/mcp-process-manager.d.ts +58 -0
- package/dist/utils/mcp-process-manager.js +220 -0
- package/dist/utils/mcp-server-manager.js +50 -9
- package/dist/utils/mcp-singleton-lock.d.ts +4 -0
- package/dist/utils/mcp-singleton-lock.js +35 -8
- package/dist/utils/ml-data-fetcher.d.ts +66 -0
- package/dist/utils/ml-data-fetcher.js +288 -0
- package/dist/utils/servicenow-client.js +43 -17
- package/dist/utils/timeout-manager.d.ts +62 -0
- package/dist/utils/timeout-manager.js +352 -0
- package/package.json +1 -1
- package/test-ml-improvements.sh +76 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP On-Demand Manager
|
|
3
|
+
* Starts MCP servers only when needed and stops them after inactivity
|
|
4
|
+
*/
|
|
5
|
+
import { ChildProcess } from 'child_process';
|
|
6
|
+
export declare class MCPOnDemandManager {
|
|
7
|
+
private static instance;
|
|
8
|
+
private servers;
|
|
9
|
+
private inactivityTimeout;
|
|
10
|
+
private cleanupInterval?;
|
|
11
|
+
private constructor();
|
|
12
|
+
static getInstance(): MCPOnDemandManager;
|
|
13
|
+
/**
|
|
14
|
+
* Get or start an MCP server on demand
|
|
15
|
+
*/
|
|
16
|
+
getServer(serverName: string): Promise<ChildProcess>;
|
|
17
|
+
/**
|
|
18
|
+
* Start an MCP server
|
|
19
|
+
*/
|
|
20
|
+
private startServer;
|
|
21
|
+
/**
|
|
22
|
+
* Wait for a server to finish starting
|
|
23
|
+
*/
|
|
24
|
+
private waitForServer;
|
|
25
|
+
/**
|
|
26
|
+
* Stop a specific server
|
|
27
|
+
*/
|
|
28
|
+
stopServer(serverName: string): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Stop least recently used servers to free resources
|
|
31
|
+
*/
|
|
32
|
+
private stopLeastRecentlyUsed;
|
|
33
|
+
/**
|
|
34
|
+
* Stop all inactive servers
|
|
35
|
+
*/
|
|
36
|
+
private stopInactiveServers;
|
|
37
|
+
/**
|
|
38
|
+
* Start monitoring for inactive servers
|
|
39
|
+
*/
|
|
40
|
+
private startInactivityMonitor;
|
|
41
|
+
/**
|
|
42
|
+
* Stop the inactivity monitor
|
|
43
|
+
*/
|
|
44
|
+
stopInactivityMonitor(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Get the script path for a server
|
|
47
|
+
*/
|
|
48
|
+
private getScriptPath;
|
|
49
|
+
/**
|
|
50
|
+
* Get status of all servers
|
|
51
|
+
*/
|
|
52
|
+
getStatus(): {
|
|
53
|
+
total: number;
|
|
54
|
+
running: number;
|
|
55
|
+
stopped: number;
|
|
56
|
+
servers: Array<{
|
|
57
|
+
name: string;
|
|
58
|
+
status: string;
|
|
59
|
+
lastUsed: string;
|
|
60
|
+
useCount: number;
|
|
61
|
+
uptime?: string;
|
|
62
|
+
}>;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Stop all servers
|
|
66
|
+
*/
|
|
67
|
+
stopAll(): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=mcp-on-demand-manager.d.ts.map
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MCP On-Demand Manager
|
|
4
|
+
* Starts MCP servers only when needed and stops them after inactivity
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.MCPOnDemandManager = void 0;
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
12
|
+
const logger_js_1 = require("./logger.js");
|
|
13
|
+
const mcp_process_manager_js_1 = require("./mcp-process-manager.js");
|
|
14
|
+
const unified_auth_store_js_1 = require("./unified-auth-store.js");
|
|
15
|
+
const path_1 = __importDefault(require("path"));
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const logger = new logger_js_1.Logger('MCPOnDemand');
|
|
18
|
+
class MCPOnDemandManager {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.servers = new Map();
|
|
21
|
+
this.inactivityTimeout = parseInt(process.env.SNOW_MCP_INACTIVITY_TIMEOUT || '300000'); // 5 minutes default
|
|
22
|
+
this.startInactivityMonitor();
|
|
23
|
+
}
|
|
24
|
+
static getInstance() {
|
|
25
|
+
if (!MCPOnDemandManager.instance) {
|
|
26
|
+
MCPOnDemandManager.instance = new MCPOnDemandManager();
|
|
27
|
+
}
|
|
28
|
+
return MCPOnDemandManager.instance;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get or start an MCP server on demand
|
|
32
|
+
*/
|
|
33
|
+
async getServer(serverName) {
|
|
34
|
+
let server = this.servers.get(serverName);
|
|
35
|
+
if (!server) {
|
|
36
|
+
server = {
|
|
37
|
+
name: serverName,
|
|
38
|
+
lastUsed: Date.now(),
|
|
39
|
+
useCount: 0,
|
|
40
|
+
status: 'stopped'
|
|
41
|
+
};
|
|
42
|
+
this.servers.set(serverName, server);
|
|
43
|
+
}
|
|
44
|
+
// Update last used time
|
|
45
|
+
server.lastUsed = Date.now();
|
|
46
|
+
server.useCount++;
|
|
47
|
+
// If server is running, return it
|
|
48
|
+
if (server.status === 'running' && server.process) {
|
|
49
|
+
logger.debug(`โ
Reusing existing ${serverName} (used ${server.useCount} times)`);
|
|
50
|
+
return server.process;
|
|
51
|
+
}
|
|
52
|
+
// If server is starting, wait for it
|
|
53
|
+
if (server.status === 'starting') {
|
|
54
|
+
logger.debug(`โณ Waiting for ${serverName} to start...`);
|
|
55
|
+
return this.waitForServer(serverName);
|
|
56
|
+
}
|
|
57
|
+
// Start the server
|
|
58
|
+
return this.startServer(serverName);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Start an MCP server
|
|
62
|
+
*/
|
|
63
|
+
async startServer(serverName) {
|
|
64
|
+
const server = this.servers.get(serverName);
|
|
65
|
+
if (!server) {
|
|
66
|
+
throw new Error(`Server ${serverName} not found`);
|
|
67
|
+
}
|
|
68
|
+
// Check resource limits
|
|
69
|
+
const processManager = mcp_process_manager_js_1.MCPProcessManager.getInstance();
|
|
70
|
+
if (!processManager.canSpawnServer()) {
|
|
71
|
+
// Try to free up resources by stopping least recently used servers
|
|
72
|
+
await this.stopLeastRecentlyUsed();
|
|
73
|
+
if (!processManager.canSpawnServer()) {
|
|
74
|
+
throw new Error('Cannot start server: resource limits exceeded');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
server.status = 'starting';
|
|
78
|
+
logger.info(`๐ Starting ${serverName} on demand...`);
|
|
79
|
+
try {
|
|
80
|
+
// Get the script path
|
|
81
|
+
const scriptPath = this.getScriptPath(serverName);
|
|
82
|
+
// Get auth tokens
|
|
83
|
+
await unified_auth_store_js_1.unifiedAuthStore.bridgeToMCP();
|
|
84
|
+
const tokens = await unified_auth_store_js_1.unifiedAuthStore.getTokens();
|
|
85
|
+
const authEnv = {};
|
|
86
|
+
if (tokens) {
|
|
87
|
+
authEnv.SNOW_OAUTH_TOKENS = JSON.stringify(tokens);
|
|
88
|
+
authEnv.SNOW_INSTANCE = tokens.instance;
|
|
89
|
+
authEnv.SNOW_CLIENT_ID = tokens.clientId;
|
|
90
|
+
authEnv.SNOW_CLIENT_SECRET = tokens.clientSecret;
|
|
91
|
+
if (tokens.accessToken) {
|
|
92
|
+
authEnv.SNOW_ACCESS_TOKEN = tokens.accessToken;
|
|
93
|
+
}
|
|
94
|
+
if (tokens.refreshToken) {
|
|
95
|
+
authEnv.SNOW_REFRESH_TOKEN = tokens.refreshToken;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Start the process
|
|
99
|
+
const childProcess = (0, child_process_1.spawn)('node', [scriptPath], {
|
|
100
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
101
|
+
env: {
|
|
102
|
+
...process.env,
|
|
103
|
+
...authEnv,
|
|
104
|
+
SNOW_MCP_ON_DEMAND: 'true' // Flag to indicate on-demand mode
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
// Handle process events
|
|
108
|
+
childProcess.on('error', (error) => {
|
|
109
|
+
logger.error(`${serverName} error:`, error);
|
|
110
|
+
server.status = 'stopped';
|
|
111
|
+
});
|
|
112
|
+
childProcess.on('exit', (code) => {
|
|
113
|
+
logger.info(`${serverName} exited with code ${code}`);
|
|
114
|
+
server.status = 'stopped';
|
|
115
|
+
server.process = undefined;
|
|
116
|
+
});
|
|
117
|
+
// Log output for debugging
|
|
118
|
+
childProcess.stdout?.on('data', (data) => {
|
|
119
|
+
logger.debug(`${serverName} stdout:`, data.toString());
|
|
120
|
+
});
|
|
121
|
+
childProcess.stderr?.on('data', (data) => {
|
|
122
|
+
logger.debug(`${serverName} stderr:`, data.toString());
|
|
123
|
+
});
|
|
124
|
+
server.process = childProcess;
|
|
125
|
+
server.status = 'running';
|
|
126
|
+
server.startTime = Date.now();
|
|
127
|
+
logger.info(`โ
${serverName} started (PID: ${childProcess.pid})`);
|
|
128
|
+
return childProcess;
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
server.status = 'stopped';
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Wait for a server to finish starting
|
|
137
|
+
*/
|
|
138
|
+
async waitForServer(serverName, timeout = 30000) {
|
|
139
|
+
const startTime = Date.now();
|
|
140
|
+
while (Date.now() - startTime < timeout) {
|
|
141
|
+
const server = this.servers.get(serverName);
|
|
142
|
+
if (server?.status === 'running' && server.process) {
|
|
143
|
+
return server.process;
|
|
144
|
+
}
|
|
145
|
+
if (server?.status === 'stopped') {
|
|
146
|
+
throw new Error(`Server ${serverName} failed to start`);
|
|
147
|
+
}
|
|
148
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
149
|
+
}
|
|
150
|
+
throw new Error(`Timeout waiting for ${serverName} to start`);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Stop a specific server
|
|
154
|
+
*/
|
|
155
|
+
async stopServer(serverName) {
|
|
156
|
+
const server = this.servers.get(serverName);
|
|
157
|
+
if (!server || !server.process) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
server.status = 'stopping';
|
|
161
|
+
logger.info(`๐ Stopping ${serverName} (was used ${server.useCount} times)`);
|
|
162
|
+
try {
|
|
163
|
+
server.process.kill('SIGTERM');
|
|
164
|
+
// Wait for graceful shutdown
|
|
165
|
+
await new Promise((resolve) => {
|
|
166
|
+
const timeout = setTimeout(() => {
|
|
167
|
+
if (server.process) {
|
|
168
|
+
server.process.kill('SIGKILL');
|
|
169
|
+
}
|
|
170
|
+
resolve(undefined);
|
|
171
|
+
}, 5000);
|
|
172
|
+
server.process?.once('exit', () => {
|
|
173
|
+
clearTimeout(timeout);
|
|
174
|
+
resolve(undefined);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
logger.error(`Error stopping ${serverName}:`, error);
|
|
180
|
+
}
|
|
181
|
+
server.status = 'stopped';
|
|
182
|
+
server.process = undefined;
|
|
183
|
+
server.startTime = undefined;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Stop least recently used servers to free resources
|
|
187
|
+
*/
|
|
188
|
+
async stopLeastRecentlyUsed() {
|
|
189
|
+
const runningServers = Array.from(this.servers.values())
|
|
190
|
+
.filter(s => s.status === 'running')
|
|
191
|
+
.sort((a, b) => a.lastUsed - b.lastUsed);
|
|
192
|
+
if (runningServers.length > 0) {
|
|
193
|
+
const oldest = runningServers[0];
|
|
194
|
+
logger.info(`๐ฆ Stopping least recently used server: ${oldest.name}`);
|
|
195
|
+
await this.stopServer(oldest.name);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Stop all inactive servers
|
|
200
|
+
*/
|
|
201
|
+
async stopInactiveServers() {
|
|
202
|
+
const now = Date.now();
|
|
203
|
+
const promises = [];
|
|
204
|
+
for (const [name, server] of this.servers) {
|
|
205
|
+
if (server.status === 'running' &&
|
|
206
|
+
now - server.lastUsed > this.inactivityTimeout) {
|
|
207
|
+
const inactiveMinutes = Math.round((now - server.lastUsed) / 60000);
|
|
208
|
+
logger.info(`โฐ Stopping ${name} due to inactivity (${inactiveMinutes} minutes)`);
|
|
209
|
+
promises.push(this.stopServer(name));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
await Promise.all(promises);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Start monitoring for inactive servers
|
|
216
|
+
*/
|
|
217
|
+
startInactivityMonitor() {
|
|
218
|
+
// Check every minute
|
|
219
|
+
this.cleanupInterval = setInterval(() => {
|
|
220
|
+
this.stopInactiveServers().catch(error => {
|
|
221
|
+
logger.error('Error during inactivity cleanup:', error);
|
|
222
|
+
});
|
|
223
|
+
}, 60000);
|
|
224
|
+
// Don't block process exit
|
|
225
|
+
this.cleanupInterval.unref();
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Stop the inactivity monitor
|
|
229
|
+
*/
|
|
230
|
+
stopInactivityMonitor() {
|
|
231
|
+
if (this.cleanupInterval) {
|
|
232
|
+
clearInterval(this.cleanupInterval);
|
|
233
|
+
this.cleanupInterval = undefined;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Get the script path for a server
|
|
238
|
+
*/
|
|
239
|
+
getScriptPath(serverName) {
|
|
240
|
+
// Map server names to script files
|
|
241
|
+
const serverMap = {
|
|
242
|
+
'servicenow-operations': 'servicenow-operations-mcp.js',
|
|
243
|
+
'servicenow-deployment': 'servicenow-deployment-mcp.js',
|
|
244
|
+
'servicenow-machine-learning': 'servicenow-machine-learning-mcp.js',
|
|
245
|
+
'servicenow-update-set': 'servicenow-update-set-mcp.js',
|
|
246
|
+
'servicenow-platform-development': 'servicenow-platform-development-mcp.js',
|
|
247
|
+
'servicenow-integration': 'servicenow-integration-mcp.js',
|
|
248
|
+
'servicenow-automation': 'servicenow-automation-mcp.js',
|
|
249
|
+
'servicenow-security-compliance': 'servicenow-security-compliance-mcp.js',
|
|
250
|
+
'servicenow-reporting-analytics': 'servicenow-reporting-analytics-mcp.js',
|
|
251
|
+
'servicenow-flow-composer': 'servicenow-flow-composer-mcp.js',
|
|
252
|
+
'servicenow-intelligent': 'servicenow-intelligent-mcp.js',
|
|
253
|
+
'servicenow-development-assistant': 'servicenow-development-assistant-mcp.js',
|
|
254
|
+
'servicenow-graph-memory': 'servicenow-graph-memory-mcp.js',
|
|
255
|
+
'snow-flow': 'snow-flow-mcp.js'
|
|
256
|
+
};
|
|
257
|
+
const scriptFile = serverMap[serverName];
|
|
258
|
+
if (!scriptFile) {
|
|
259
|
+
throw new Error(`Unknown server: ${serverName}`);
|
|
260
|
+
}
|
|
261
|
+
// Check different possible locations
|
|
262
|
+
const possiblePaths = [
|
|
263
|
+
path_1.default.join(__dirname, '..', 'mcp', scriptFile),
|
|
264
|
+
path_1.default.join(process.cwd(), 'dist', 'mcp', scriptFile),
|
|
265
|
+
path_1.default.join('/Users/nielsvanderwerf/.nvm/versions/node/v20.15.0/lib/node_modules/snow-flow/dist/mcp', scriptFile)
|
|
266
|
+
];
|
|
267
|
+
for (const scriptPath of possiblePaths) {
|
|
268
|
+
if (fs_1.default.existsSync(scriptPath)) {
|
|
269
|
+
return scriptPath;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
throw new Error(`Script not found for ${serverName}: ${scriptFile}`);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Get status of all servers
|
|
276
|
+
*/
|
|
277
|
+
getStatus() {
|
|
278
|
+
const servers = Array.from(this.servers.values()).map(server => {
|
|
279
|
+
const lastUsedMinutes = Math.round((Date.now() - server.lastUsed) / 60000);
|
|
280
|
+
const uptime = server.startTime ?
|
|
281
|
+
Math.round((Date.now() - server.startTime) / 60000) + ' minutes' :
|
|
282
|
+
undefined;
|
|
283
|
+
return {
|
|
284
|
+
name: server.name,
|
|
285
|
+
status: server.status,
|
|
286
|
+
lastUsed: `${lastUsedMinutes} minutes ago`,
|
|
287
|
+
useCount: server.useCount,
|
|
288
|
+
uptime
|
|
289
|
+
};
|
|
290
|
+
});
|
|
291
|
+
return {
|
|
292
|
+
total: servers.length,
|
|
293
|
+
running: servers.filter(s => s.status === 'running').length,
|
|
294
|
+
stopped: servers.filter(s => s.status === 'stopped').length,
|
|
295
|
+
servers
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Stop all servers
|
|
300
|
+
*/
|
|
301
|
+
async stopAll() {
|
|
302
|
+
logger.info('๐ Stopping all MCP servers...');
|
|
303
|
+
const promises = Array.from(this.servers.keys()).map(name => this.stopServer(name));
|
|
304
|
+
await Promise.all(promises);
|
|
305
|
+
this.stopInactivityMonitor();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
exports.MCPOnDemandManager = MCPOnDemandManager;
|
|
309
|
+
//# sourceMappingURL=mcp-on-demand-manager.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Process Manager
|
|
3
|
+
* Prevents excessive MCP server spawning and manages resource limits
|
|
4
|
+
*/
|
|
5
|
+
export declare class MCPProcessManager {
|
|
6
|
+
private static instance;
|
|
7
|
+
private readonly MAX_MCP_SERVERS;
|
|
8
|
+
private readonly MAX_MEMORY_MB;
|
|
9
|
+
private readonly CLEANUP_INTERVAL;
|
|
10
|
+
private cleanupTimer?;
|
|
11
|
+
private constructor();
|
|
12
|
+
static getInstance(): MCPProcessManager;
|
|
13
|
+
/**
|
|
14
|
+
* Check if we can spawn a new MCP server
|
|
15
|
+
*/
|
|
16
|
+
canSpawnServer(): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Get current MCP system status
|
|
19
|
+
*/
|
|
20
|
+
getSystemStatus(): {
|
|
21
|
+
processCount: number;
|
|
22
|
+
memoryUsageMB: number;
|
|
23
|
+
processes: Array<{
|
|
24
|
+
pid: number;
|
|
25
|
+
memory: number;
|
|
26
|
+
name: string;
|
|
27
|
+
}>;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Kill duplicate MCP servers (keep only the newest)
|
|
31
|
+
*/
|
|
32
|
+
killDuplicates(): void;
|
|
33
|
+
/**
|
|
34
|
+
* Kill all MCP servers
|
|
35
|
+
*/
|
|
36
|
+
killAll(): void;
|
|
37
|
+
/**
|
|
38
|
+
* Clean up excessive resources
|
|
39
|
+
*/
|
|
40
|
+
cleanup(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Start periodic cleanup
|
|
43
|
+
*/
|
|
44
|
+
private startPeriodicCleanup;
|
|
45
|
+
/**
|
|
46
|
+
* Stop periodic cleanup
|
|
47
|
+
*/
|
|
48
|
+
stopPeriodicCleanup(): void;
|
|
49
|
+
/**
|
|
50
|
+
* Get resource usage summary
|
|
51
|
+
*/
|
|
52
|
+
getResourceSummary(): string;
|
|
53
|
+
/**
|
|
54
|
+
* Get health status
|
|
55
|
+
*/
|
|
56
|
+
getHealthStatus(): 'healthy' | 'warning' | 'critical';
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=mcp-process-manager.d.ts.map
|
|
@@ -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
|