@mrxkun/mcfast-mcp 4.1.11 → 4.1.12
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/package.json +2 -2
- package/src/index.js +132 -11
- package/src/memory/memory-engine.js +24 -17
- package/src/memory/watchers/file-watcher.js +8 -6
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrxkun/mcfast-mcp",
|
|
3
|
-
"version": "4.1.
|
|
4
|
-
"description": "Ultra-fast code editing with WASM acceleration, fuzzy patching, multi-layer caching, and 8 unified tools. v4.1.
|
|
3
|
+
"version": "4.1.12",
|
|
4
|
+
"description": "Ultra-fast code editing with WASM acceleration, fuzzy patching, multi-layer caching, and 8 unified tools. v4.1.12: Implement proper MCP stdio transport lifecycle and cleanup to prevent zombie processes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"mcfast-mcp": "src/index.js"
|
package/src/index.js
CHANGED
|
@@ -757,6 +757,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
757
757
|
},
|
|
758
758
|
required: []
|
|
759
759
|
}
|
|
760
|
+
},
|
|
761
|
+
// HEALTH CHECK TOOL
|
|
762
|
+
{
|
|
763
|
+
name: "health_check",
|
|
764
|
+
description: "💚 Check MCP server health status and resource usage. Returns: process status, memory engine state, uptime, and system metrics.",
|
|
765
|
+
inputSchema: {
|
|
766
|
+
type: "object",
|
|
767
|
+
properties: {},
|
|
768
|
+
required: []
|
|
769
|
+
}
|
|
760
770
|
}
|
|
761
771
|
],
|
|
762
772
|
};
|
|
@@ -964,6 +974,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
964
974
|
result = await projectAnalyzeExecute(args);
|
|
965
975
|
summary = 'Project analyzed';
|
|
966
976
|
}
|
|
977
|
+
// HEALTH CHECK TOOL
|
|
978
|
+
else if (name === "health_check") {
|
|
979
|
+
result = await handleHealthCheck();
|
|
980
|
+
summary = 'Health check completed';
|
|
981
|
+
}
|
|
967
982
|
else {
|
|
968
983
|
throw new Error(`Tool not found: ${name}`);
|
|
969
984
|
}
|
|
@@ -3352,6 +3367,74 @@ async function handleSelectStrategy({ instruction, files = [] }) {
|
|
|
3352
3367
|
}
|
|
3353
3368
|
}
|
|
3354
3369
|
|
|
3370
|
+
/**
|
|
3371
|
+
* HEALTH CHECK HANDLER
|
|
3372
|
+
*/
|
|
3373
|
+
async function handleHealthCheck() {
|
|
3374
|
+
const start = Date.now();
|
|
3375
|
+
|
|
3376
|
+
try {
|
|
3377
|
+
const memoryStats = memoryEngine ? await memoryEngine.getStats() : null;
|
|
3378
|
+
const watcherStats = memoryEngine?.watcher ? memoryEngine.watcher.getStats() : null;
|
|
3379
|
+
|
|
3380
|
+
const health = {
|
|
3381
|
+
status: 'healthy',
|
|
3382
|
+
timestamp: Date.now(),
|
|
3383
|
+
pid: process.pid,
|
|
3384
|
+
uptime: process.uptime(),
|
|
3385
|
+
memoryUsage: {
|
|
3386
|
+
rss: Math.round(process.memoryUsage().rss / 1024 / 1024) + ' MB',
|
|
3387
|
+
heapTotal: Math.round(process.memoryUsage().heapTotal / 1024 / 1024) + ' MB',
|
|
3388
|
+
heapUsed: Math.round(process.memoryUsage().heapUsed / 1024 / 1024) + ' MB',
|
|
3389
|
+
external: Math.round(process.memoryUsage().external / 1024 / 1024) + ' MB'
|
|
3390
|
+
},
|
|
3391
|
+
memoryEngine: {
|
|
3392
|
+
ready: memoryEngineReady,
|
|
3393
|
+
initialized: memoryEngine?.isInitialized || false,
|
|
3394
|
+
stats: memoryStats
|
|
3395
|
+
},
|
|
3396
|
+
fileWatcher: watcherStats || {
|
|
3397
|
+
enabled: process.env.MCFAST_FILE_WATCHER !== 'false',
|
|
3398
|
+
status: 'not_running'
|
|
3399
|
+
},
|
|
3400
|
+
latencyMs: Date.now() - start
|
|
3401
|
+
};
|
|
3402
|
+
|
|
3403
|
+
reportAudit({
|
|
3404
|
+
tool: 'health_check',
|
|
3405
|
+
status: 'success',
|
|
3406
|
+
latency_ms: health.latencyMs
|
|
3407
|
+
});
|
|
3408
|
+
|
|
3409
|
+
return {
|
|
3410
|
+
content: [{
|
|
3411
|
+
type: 'text',
|
|
3412
|
+
text: JSON.stringify(health, null, 2)
|
|
3413
|
+
}]
|
|
3414
|
+
};
|
|
3415
|
+
|
|
3416
|
+
} catch (error) {
|
|
3417
|
+
reportAudit({
|
|
3418
|
+
tool: 'health_check',
|
|
3419
|
+
status: 'error',
|
|
3420
|
+
error_message: error.message,
|
|
3421
|
+
latency_ms: Date.now() - start
|
|
3422
|
+
});
|
|
3423
|
+
|
|
3424
|
+
return {
|
|
3425
|
+
content: [{
|
|
3426
|
+
type: 'text',
|
|
3427
|
+
text: JSON.stringify({
|
|
3428
|
+
status: 'error',
|
|
3429
|
+
error: error.message,
|
|
3430
|
+
timestamp: Date.now()
|
|
3431
|
+
})
|
|
3432
|
+
}],
|
|
3433
|
+
isError: true
|
|
3434
|
+
};
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3355
3438
|
/**
|
|
3356
3439
|
* Start Server
|
|
3357
3440
|
*/
|
|
@@ -3366,6 +3449,25 @@ if (!lockAcquired) {
|
|
|
3366
3449
|
|
|
3367
3450
|
const transport = new StdioServerTransport();
|
|
3368
3451
|
|
|
3452
|
+
// MCP Stdio Transport Best Practice: Detect client disconnection
|
|
3453
|
+
// https://modelcontextprotocol.info/docs/concepts/transports/
|
|
3454
|
+
process.stdin.on('end', () => {
|
|
3455
|
+
console.error('[MCP] Client disconnected (stdin closed)');
|
|
3456
|
+
gracefulShutdown('stdin.end');
|
|
3457
|
+
});
|
|
3458
|
+
|
|
3459
|
+
process.stdin.on('error', (err) => {
|
|
3460
|
+
if (err.code === 'EPIPE' || err.code === 'ECONNRESET') {
|
|
3461
|
+
console.error('[MCP] Client disconnected (broken pipe)');
|
|
3462
|
+
gracefulShutdown('stdin.error');
|
|
3463
|
+
}
|
|
3464
|
+
});
|
|
3465
|
+
|
|
3466
|
+
process.stdout.on('error', (err) => {
|
|
3467
|
+
console.error('[MCP] stdout error:', err.code);
|
|
3468
|
+
gracefulShutdown('stdout.error');
|
|
3469
|
+
});
|
|
3470
|
+
|
|
3369
3471
|
// Pre-initialize memory engine in background
|
|
3370
3472
|
backgroundInitializeMemoryEngine().catch(err => {
|
|
3371
3473
|
console.error(`${colors.yellow}[Memory]${colors.reset} Background init error: ${err.message}`);
|
|
@@ -3376,29 +3478,48 @@ backgroundInitializeMemoryEngine().catch(err => {
|
|
|
3376
3478
|
async function gracefulShutdown(signal) {
|
|
3377
3479
|
console.error(`${colors.yellow}[Shutdown]${colors.reset} Received ${signal}, cleaning up...`);
|
|
3378
3480
|
|
|
3481
|
+
const SHUTDOWN_TIMEOUT = 5000; // 5 seconds per MCP best practices
|
|
3482
|
+
|
|
3379
3483
|
try {
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3484
|
+
await Promise.race([
|
|
3485
|
+
(async () => {
|
|
3486
|
+
// Stop memory engine
|
|
3487
|
+
if (memoryEngine && memoryEngineReady) {
|
|
3488
|
+
await memoryEngine.cleanup();
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
// Flush audit queue
|
|
3492
|
+
const auditQ = getAuditQueue();
|
|
3493
|
+
await auditQ.destroy();
|
|
3494
|
+
|
|
3495
|
+
// Release lock
|
|
3496
|
+
await releaseLock();
|
|
3497
|
+
})(),
|
|
3498
|
+
new Promise(resolve => setTimeout(resolve, SHUTDOWN_TIMEOUT))
|
|
3499
|
+
]);
|
|
3391
3500
|
|
|
3392
3501
|
console.error(`${colors.green}[Shutdown]${colors.reset} Cleanup complete`);
|
|
3393
3502
|
} catch (error) {
|
|
3394
3503
|
console.error(`${colors.red}[Shutdown]${colors.reset} Error during cleanup: ${error.message}`);
|
|
3395
3504
|
}
|
|
3396
3505
|
|
|
3506
|
+
// Always exit (even if cleanup fails) to prevent zombie processes
|
|
3397
3507
|
process.exit(0);
|
|
3398
3508
|
}
|
|
3399
3509
|
|
|
3400
3510
|
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
3401
3511
|
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
3402
3512
|
|
|
3513
|
+
// Protect against zombie processes - handle unexpected errors
|
|
3514
|
+
process.on('uncaughtException', (err) => {
|
|
3515
|
+
console.error('[MCP] Uncaught exception:', err);
|
|
3516
|
+
gracefulShutdown('uncaughtException');
|
|
3517
|
+
});
|
|
3518
|
+
|
|
3519
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
3520
|
+
console.error('[MCP] Unhandled rejection:', reason);
|
|
3521
|
+
gracefulShutdown('unhandledRejection');
|
|
3522
|
+
});
|
|
3523
|
+
|
|
3403
3524
|
await server.connect(transport);
|
|
3404
3525
|
console.error("mcfast MCP v1.0.0 running on stdio");
|
|
@@ -225,23 +225,30 @@ export class MemoryEngine {
|
|
|
225
225
|
});
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
// Initialize file watcher
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
228
|
+
// Initialize file watcher (configurable via MCFAST_FILE_WATCHER env)
|
|
229
|
+
const ENABLE_FILE_WATCHER = process.env.MCFAST_FILE_WATCHER !== 'false';
|
|
230
|
+
|
|
231
|
+
if (ENABLE_FILE_WATCHER) {
|
|
232
|
+
this.watcher = new FileWatcher(projectPath, this, {
|
|
233
|
+
debounceMs: 1500,
|
|
234
|
+
ignored: [
|
|
235
|
+
'**/node_modules/**',
|
|
236
|
+
'**/.git/**',
|
|
237
|
+
'**/dist/**',
|
|
238
|
+
'**/build/**',
|
|
239
|
+
'**/.mcfast/**',
|
|
240
|
+
'**/*.log'
|
|
241
|
+
]
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
await this.watcher.start();
|
|
246
|
+
} catch (watcherError) {
|
|
247
|
+
console.error('[MemoryEngine] ⚠️ File watcher failed to start (continuing without file watching):', watcherError.message);
|
|
248
|
+
this.watcher = null;
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
console.error('[MemoryEngine] File watcher disabled (MCFAST_FILE_WATCHER=false)');
|
|
245
252
|
this.watcher = null;
|
|
246
253
|
}
|
|
247
254
|
|
|
@@ -453,19 +453,21 @@ export class FileWatcher {
|
|
|
453
453
|
|
|
454
454
|
console.error('[FileWatcher] Stopping...');
|
|
455
455
|
|
|
456
|
-
// Stop cleanup interval
|
|
456
|
+
// 1. Stop cleanup interval
|
|
457
457
|
if (this.cleanupInterval) {
|
|
458
458
|
clearInterval(this.cleanupInterval);
|
|
459
459
|
this.cleanupInterval = null;
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
462
|
+
// 2. Clear pending updates map (free memory)
|
|
463
|
+
this.pendingUpdates.clear();
|
|
464
|
+
|
|
465
|
+
// 3. Cancel debounce timeout if exists
|
|
466
|
+
if (this.flushQueue && this.flushQueue.cancel) {
|
|
467
|
+
this.flushQueue.cancel();
|
|
466
468
|
}
|
|
467
469
|
|
|
468
|
-
// Close watcher
|
|
470
|
+
// 4. Close chokidar watcher (critical - prevents persistent file watching)
|
|
469
471
|
if (this.watcher) {
|
|
470
472
|
await this.watcher.close();
|
|
471
473
|
this.watcher = null;
|