@mrxkun/mcfast-mcp 4.1.10 → 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 +297 -21
- package/src/memory/memory-engine.js +232 -25
- package/src/memory/stores/base-database.js +223 -0
- package/src/memory/utils/chunker.js +1 -0
- package/src/memory/utils/indexer.js +110 -4
- package/src/memory/utils/logger.js +162 -0
- package/src/memory/utils/vector-index.js +241 -0
- package/src/memory/watchers/file-watcher.js +257 -103
- package/src/tools/project_analyze.js +491 -0
- package/src/utils/audit-queue.js +1 -0
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
|
@@ -75,9 +75,87 @@ import { getAuditQueue } from './utils/audit-queue.js';
|
|
|
75
75
|
import { getIntelligenceCache } from './utils/intelligence-cache.js';
|
|
76
76
|
import { getContextPrefetcher } from './utils/context-prefetcher.js';
|
|
77
77
|
import { parallelMemorySearch } from './utils/parallel-search.js';
|
|
78
|
+
import { execute as projectAnalyzeExecute } from './tools/project_analyze.js';
|
|
78
79
|
|
|
79
80
|
const execAsync = promisify(exec);
|
|
80
81
|
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// LOCK FILE MECHANISM - Prevent multiple instances running simultaneously
|
|
84
|
+
// ============================================================================
|
|
85
|
+
const LOCK_FILE_PATH = path.join(process.cwd(), '.mcfast', '.mcp-instance.lock');
|
|
86
|
+
let lockFileHandle = null;
|
|
87
|
+
|
|
88
|
+
async function acquireLock() {
|
|
89
|
+
try {
|
|
90
|
+
const lockDir = path.dirname(LOCK_FILE_PATH);
|
|
91
|
+
await fs.mkdir(lockDir, { recursive: true });
|
|
92
|
+
|
|
93
|
+
lockFileHandle = await fs.open(LOCK_FILE_PATH, 'wx');
|
|
94
|
+
|
|
95
|
+
// Write current PID
|
|
96
|
+
await lockFileHandle.write(`${process.pid}\n${Date.now()}\n`, 0);
|
|
97
|
+
await lockFileHandle.sync();
|
|
98
|
+
|
|
99
|
+
// Cleanup on exit
|
|
100
|
+
process.on('beforeExit', releaseLock);
|
|
101
|
+
process.on('SIGINT', releaseLock);
|
|
102
|
+
process.on('SIGTERM', releaseLock);
|
|
103
|
+
process.on('uncaughtException', releaseLock);
|
|
104
|
+
|
|
105
|
+
console.error(`${colors.cyan}[Lock]${colors.reset} Acquired lock file: ${LOCK_FILE_PATH}`);
|
|
106
|
+
return true;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (error.code === 'EEXIST') {
|
|
109
|
+
// Lock file exists, check if it's stale
|
|
110
|
+
try {
|
|
111
|
+
const lockContent = await fs.readFile(LOCK_FILE_PATH, 'utf-8');
|
|
112
|
+
const [pid, timestamp] = lockContent.trim().split('\n');
|
|
113
|
+
const lockAge = Date.now() - parseInt(timestamp);
|
|
114
|
+
|
|
115
|
+
// Check if process is still running (stale lock if > 5 minutes)
|
|
116
|
+
if (lockAge > 5 * 60 * 1000) {
|
|
117
|
+
console.error(`${colors.yellow}[Lock]${colors.reset} Stale lock detected (age: ${Math.round(lockAge / 1000)}s), removing...`);
|
|
118
|
+
await fs.unlink(LOCK_FILE_PATH);
|
|
119
|
+
return acquireLock(); // Retry
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.error(`${colors.red}[Lock]${colors.reset} Another instance is already running (PID: ${pid})`);
|
|
123
|
+
console.error(`${colors.yellow}[Lock]${colors.reset} To force start, delete: ${LOCK_FILE_PATH}`);
|
|
124
|
+
return false;
|
|
125
|
+
} catch (readError) {
|
|
126
|
+
console.error(`${colors.red}[Lock]${colors.reset} Failed to read lock file: ${readError.message}`);
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
console.error(`${colors.red}[Lock]${colors.reset} Failed to acquire lock: ${error.message}`);
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function releaseLock() {
|
|
136
|
+
if (lockFileHandle) {
|
|
137
|
+
try {
|
|
138
|
+
await lockFileHandle.close();
|
|
139
|
+
lockFileHandle = null;
|
|
140
|
+
} catch (e) {
|
|
141
|
+
// Ignore close errors
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
await fs.unlink(LOCK_FILE_PATH);
|
|
147
|
+
console.error(`${colors.cyan}[Lock]${colors.reset} Released lock file`);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if (error.code !== 'ENOENT') {
|
|
150
|
+
console.error(`${colors.yellow}[Lock]${colors.reset} Failed to release lock: ${error.message}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ============================================================================
|
|
156
|
+
// END LOCK FILE MECHANISM
|
|
157
|
+
// ============================================================================
|
|
158
|
+
|
|
81
159
|
const API_URL = "https://mcfast.vercel.app/api/v1";
|
|
82
160
|
const TOKEN = process.env.MCFAST_TOKEN;
|
|
83
161
|
const VERBOSE = process.env.MCFAST_VERBOSE !== 'false'; // Default: true
|
|
@@ -95,28 +173,49 @@ async function backgroundInitializeMemoryEngine() {
|
|
|
95
173
|
if (memoryEngineInitPromise) return memoryEngineInitPromise;
|
|
96
174
|
|
|
97
175
|
memoryEngineInitPromise = (async () => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
176
|
+
const maxRetries = 2;
|
|
177
|
+
let lastError = null;
|
|
178
|
+
|
|
179
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
180
|
+
try {
|
|
181
|
+
memoryEngine = new MemoryEngine({
|
|
182
|
+
apiKey: TOKEN,
|
|
183
|
+
enableSync: true
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Use timeout for initialization
|
|
187
|
+
const initTimeout = new Promise((_, reject) => {
|
|
188
|
+
setTimeout(() => reject(new Error('Memory engine init timeout')), 25000);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await Promise.race([
|
|
192
|
+
memoryEngine.initialize(process.cwd()),
|
|
193
|
+
initTimeout
|
|
194
|
+
]);
|
|
195
|
+
|
|
196
|
+
memoryEngineReady = true;
|
|
197
|
+
console.error(`${colors.cyan}[Memory]${colors.reset} Engine initialized successfully`);
|
|
198
|
+
return memoryEngine;
|
|
199
|
+
|
|
200
|
+
} catch (error) {
|
|
201
|
+
lastError = error;
|
|
202
|
+
console.error(`${colors.yellow}[Memory]${colors.reset} Initialization attempt ${attempt}/${maxRetries} failed: ${error.message}`);
|
|
203
|
+
|
|
204
|
+
if (attempt < maxRetries) {
|
|
205
|
+
// Exponential backoff before retry
|
|
206
|
+
const backoffMs = 1000 * Math.pow(2, attempt - 1);
|
|
207
|
+
console.error(`${colors.yellow}[Memory]${colors.reset} Retrying in ${backoffMs}ms...`);
|
|
208
|
+
await new Promise(resolve => setTimeout(resolve, backoffMs));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
119
211
|
}
|
|
212
|
+
|
|
213
|
+
// All retries failed - graceful degradation
|
|
214
|
+
console.error(`${colors.yellow}[Memory]${colors.reset} All initialization attempts failed. Running in degraded mode.`);
|
|
215
|
+
console.error(`${colors.yellow}[Memory]${colors.reset} Error: ${lastError?.message || 'Unknown error'}`);
|
|
216
|
+
console.error(`${colors.yellow}[Memory]${colors.reset} Tools requiring memory will be unavailable.`);
|
|
217
|
+
memoryEngineReady = false;
|
|
218
|
+
return null;
|
|
120
219
|
})();
|
|
121
220
|
|
|
122
221
|
return memoryEngineInitPromise;
|
|
@@ -645,6 +744,29 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
645
744
|
},
|
|
646
745
|
required: ["instruction"]
|
|
647
746
|
}
|
|
747
|
+
},
|
|
748
|
+
// PROJECT ANALYSIS TOOL
|
|
749
|
+
{
|
|
750
|
+
name: "project_analyze",
|
|
751
|
+
description: "🔍 Analyze project structure using AI (Mercury). Returns: Project Overview, Technologies, API Endpoints, Main Features. Auto-updates MEMORY.md. Requires MCFAST_TOKEN.",
|
|
752
|
+
inputSchema: {
|
|
753
|
+
type: "object",
|
|
754
|
+
properties: {
|
|
755
|
+
force: { type: "boolean", description: "Force re-analysis (ignore cache)" },
|
|
756
|
+
updateMemory: { type: "boolean", description: "Update MEMORY.md with results (default: true)" }
|
|
757
|
+
},
|
|
758
|
+
required: []
|
|
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
|
+
}
|
|
648
770
|
}
|
|
649
771
|
],
|
|
650
772
|
};
|
|
@@ -847,6 +969,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
847
969
|
result = await handleSelectStrategy(args);
|
|
848
970
|
summary = 'Strategy selected';
|
|
849
971
|
}
|
|
972
|
+
// PROJECT ANALYSIS TOOL
|
|
973
|
+
else if (name === "project_analyze") {
|
|
974
|
+
result = await projectAnalyzeExecute(args);
|
|
975
|
+
summary = 'Project analyzed';
|
|
976
|
+
}
|
|
977
|
+
// HEALTH CHECK TOOL
|
|
978
|
+
else if (name === "health_check") {
|
|
979
|
+
result = await handleHealthCheck();
|
|
980
|
+
summary = 'Health check completed';
|
|
981
|
+
}
|
|
850
982
|
else {
|
|
851
983
|
throw new Error(`Tool not found: ${name}`);
|
|
852
984
|
}
|
|
@@ -3235,14 +3367,158 @@ async function handleSelectStrategy({ instruction, files = [] }) {
|
|
|
3235
3367
|
}
|
|
3236
3368
|
}
|
|
3237
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
|
+
|
|
3238
3438
|
/**
|
|
3239
3439
|
* Start Server
|
|
3240
3440
|
*/
|
|
3441
|
+
|
|
3442
|
+
// Acquire lock before starting - prevents multiple instances
|
|
3443
|
+
const lockAcquired = await acquireLock();
|
|
3444
|
+
if (!lockAcquired) {
|
|
3445
|
+
console.error(`${colors.red}[ERROR]${colors.reset} Failed to acquire lock. Another mcfast-mcp instance may be running.`);
|
|
3446
|
+
console.error(`${colors.yellow}[HINT]${colors.reset} Delete ${LOCK_FILE_PATH} if you're sure no other instance is running.`);
|
|
3447
|
+
process.exit(1);
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3241
3450
|
const transport = new StdioServerTransport();
|
|
3242
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
|
+
|
|
3243
3471
|
// Pre-initialize memory engine in background
|
|
3244
3472
|
backgroundInitializeMemoryEngine().catch(err => {
|
|
3245
3473
|
console.error(`${colors.yellow}[Memory]${colors.reset} Background init error: ${err.message}`);
|
|
3474
|
+
// Don't exit - continue without memory
|
|
3475
|
+
});
|
|
3476
|
+
|
|
3477
|
+
// Graceful shutdown handler
|
|
3478
|
+
async function gracefulShutdown(signal) {
|
|
3479
|
+
console.error(`${colors.yellow}[Shutdown]${colors.reset} Received ${signal}, cleaning up...`);
|
|
3480
|
+
|
|
3481
|
+
const SHUTDOWN_TIMEOUT = 5000; // 5 seconds per MCP best practices
|
|
3482
|
+
|
|
3483
|
+
try {
|
|
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
|
+
]);
|
|
3500
|
+
|
|
3501
|
+
console.error(`${colors.green}[Shutdown]${colors.reset} Cleanup complete`);
|
|
3502
|
+
} catch (error) {
|
|
3503
|
+
console.error(`${colors.red}[Shutdown]${colors.reset} Error during cleanup: ${error.message}`);
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
// Always exit (even if cleanup fails) to prevent zombie processes
|
|
3507
|
+
process.exit(0);
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3510
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
3511
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
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');
|
|
3246
3522
|
});
|
|
3247
3523
|
|
|
3248
3524
|
await server.connect(transport);
|