@mrxkun/mcfast-mcp 4.1.12 → 4.1.13

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +231 -235
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrxkun/mcfast-mcp",
3
- "version": "4.1.12",
3
+ "version": "4.1.13",
4
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": {
package/src/index.js CHANGED
@@ -25,13 +25,13 @@ const colors = {
25
25
 
26
26
  // Override console.log - suppress in MCP mode
27
27
  const originalConsoleLog = console.log;
28
- console.log = function(...args) {
28
+ console.log = function (...args) {
29
29
  // Suppressed in MCP mode
30
30
  };
31
31
 
32
32
  // Override console.error - suppress in MCP mode
33
33
  const originalConsoleError = console.error;
34
- console.error = function(...args) {
34
+ console.error = function (...args) {
35
35
  // Suppressed in MCP mode
36
36
  };
37
37
 
@@ -86,70 +86,70 @@ const LOCK_FILE_PATH = path.join(process.cwd(), '.mcfast', '.mcp-instance.lock')
86
86
  let lockFileHandle = null;
87
87
 
88
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}`);
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}`);
127
131
  return false;
128
- }
129
132
  }
130
- console.error(`${colors.red}[Lock]${colors.reset} Failed to acquire lock: ${error.message}`);
131
- return false;
132
- }
133
133
  }
134
134
 
135
135
  async function releaseLock() {
136
- if (lockFileHandle) {
136
+ if (lockFileHandle) {
137
+ try {
138
+ await lockFileHandle.close();
139
+ lockFileHandle = null;
140
+ } catch (e) {
141
+ // Ignore close errors
142
+ }
143
+ }
144
+
137
145
  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
- }
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
153
  }
154
154
 
155
155
  // ============================================================================
@@ -171,36 +171,36 @@ let memoryEngineReady = false;
171
171
  */
172
172
  async function backgroundInitializeMemoryEngine() {
173
173
  if (memoryEngineInitPromise) return memoryEngineInitPromise;
174
-
174
+
175
175
  memoryEngineInitPromise = (async () => {
176
176
  const maxRetries = 2;
177
177
  let lastError = null;
178
-
178
+
179
179
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
180
180
  try {
181
181
  memoryEngine = new MemoryEngine({
182
182
  apiKey: TOKEN,
183
183
  enableSync: true
184
184
  });
185
-
185
+
186
186
  // Use timeout for initialization
187
187
  const initTimeout = new Promise((_, reject) => {
188
188
  setTimeout(() => reject(new Error('Memory engine init timeout')), 25000);
189
189
  });
190
-
190
+
191
191
  await Promise.race([
192
192
  memoryEngine.initialize(process.cwd()),
193
193
  initTimeout
194
194
  ]);
195
-
195
+
196
196
  memoryEngineReady = true;
197
197
  console.error(`${colors.cyan}[Memory]${colors.reset} Engine initialized successfully`);
198
198
  return memoryEngine;
199
-
199
+
200
200
  } catch (error) {
201
201
  lastError = error;
202
202
  console.error(`${colors.yellow}[Memory]${colors.reset} Initialization attempt ${attempt}/${maxRetries} failed: ${error.message}`);
203
-
203
+
204
204
  if (attempt < maxRetries) {
205
205
  // Exponential backoff before retry
206
206
  const backoffMs = 1000 * Math.pow(2, attempt - 1);
@@ -209,7 +209,7 @@ async function backgroundInitializeMemoryEngine() {
209
209
  }
210
210
  }
211
211
  }
212
-
212
+
213
213
  // All retries failed - graceful degradation
214
214
  console.error(`${colors.yellow}[Memory]${colors.reset} All initialization attempts failed. Running in degraded mode.`);
215
215
  console.error(`${colors.yellow}[Memory]${colors.reset} Error: ${lastError?.message || 'Unknown error'}`);
@@ -217,7 +217,7 @@ async function backgroundInitializeMemoryEngine() {
217
217
  memoryEngineReady = false;
218
218
  return null;
219
219
  })();
220
-
220
+
221
221
  return memoryEngineInitPromise;
222
222
  }
223
223
 
@@ -228,7 +228,7 @@ async function getMemoryEngine() {
228
228
  if (memoryEngine && memoryEngineReady) {
229
229
  return memoryEngine;
230
230
  }
231
-
231
+
232
232
  // Wait for initialization with timeout
233
233
  if (memoryEngineInitPromise) {
234
234
  try {
@@ -240,12 +240,12 @@ async function getMemoryEngine() {
240
240
  console.error(`${colors.yellow}[Memory]${colors.reset} Waiting for engine: ${error.message}`);
241
241
  }
242
242
  }
243
-
243
+
244
244
  // If still not ready, try to initialize now
245
245
  if (!memoryEngine) {
246
246
  await backgroundInitializeMemoryEngine();
247
247
  }
248
-
248
+
249
249
  return memoryEngine;
250
250
  }
251
251
 
@@ -255,18 +255,18 @@ async function getMemoryEngine() {
255
255
  async function searchMemoryContext(instruction, maxResults = 5) {
256
256
  try {
257
257
  const engine = await getMemoryEngine();
258
-
258
+
259
259
  // Use intelligent search for best context
260
- const searchResult = await engine.intelligentSearch(instruction, {
261
- limit: maxResults
260
+ const searchResult = await engine.intelligentSearch(instruction, {
261
+ limit: maxResults
262
262
  });
263
-
263
+
264
264
  if (!searchResult.results || searchResult.results.length === 0) {
265
265
  return '';
266
266
  }
267
267
 
268
268
  let context = '';
269
-
269
+
270
270
  // Add code context from search results
271
271
  context += '\n\n--- MEMORY CONTEXT (Relevant Code) ---\n';
272
272
  searchResult.results.forEach((result, idx) => {
@@ -277,7 +277,7 @@ async function searchMemoryContext(instruction, maxResults = 5) {
277
277
  context += `${result.code.substring(0, 300)}${result.code.length > 300 ? '...' : ''}\n`;
278
278
  }
279
279
  });
280
-
280
+
281
281
  // Add facts if available
282
282
  const facts = await engine.searchFacts(instruction, 5);
283
283
  if (facts && facts.length > 0) {
@@ -286,7 +286,7 @@ async function searchMemoryContext(instruction, maxResults = 5) {
286
286
  context += `• ${fact.type}: ${fact.name} (${fact.file_id})\n`;
287
287
  });
288
288
  }
289
-
289
+
290
290
  return context;
291
291
  } catch (error) {
292
292
  console.error(`${colors.yellow}[Memory]${colors.reset} Context search failed: ${error.message}`);
@@ -304,44 +304,44 @@ async function autoTriggerIntelligence(filePath, instruction) {
304
304
  if (process.env.MCFAST_INTELLIGENCE_AUTO !== 'true') {
305
305
  return;
306
306
  }
307
-
307
+
308
308
  const engine = await getMemoryEngine();
309
309
  const cache = getIntelligenceCache();
310
-
310
+
311
311
  // 1. Detect patterns (cached for 5 minutes)
312
312
  const patternCacheKey = { file: filePath, type: 'patterns' };
313
313
  let patterns = cache.get('patterns', patternCacheKey);
314
-
314
+
315
315
  if (!patterns) {
316
316
  patterns = await engine.detectPatterns();
317
317
  if (patterns && patterns.length > 0) {
318
318
  cache.set('patterns', patternCacheKey, patterns);
319
319
  console.error(`${colors.cyan}[Intelligence]${colors.reset} 💡 Detected ${patterns.length} patterns`);
320
-
320
+
321
321
  // Log high-confidence patterns
322
322
  patterns.filter(p => p.confidence > 0.8).forEach(p => {
323
323
  console.error(`${colors.yellow}[Pattern]${colors.reset} ${p.type}: ${p.message}`);
324
324
  });
325
325
  }
326
326
  }
327
-
327
+
328
328
  // 2. Get suggestions (cached for 5 minutes)
329
329
  const suggestionCacheKey = { file: filePath, type: 'suggestions' };
330
330
  let suggestions = cache.get('suggestions', suggestionCacheKey);
331
-
331
+
332
332
  if (!suggestions) {
333
333
  suggestions = await engine.getSuggestions({ currentFile: filePath });
334
334
  if (suggestions && suggestions.length > 0) {
335
335
  cache.set('suggestions', suggestionCacheKey, suggestions);
336
336
  console.error(`${colors.cyan}[Intelligence]${colors.reset} 💡 ${suggestions.length} suggestions available`);
337
-
337
+
338
338
  // Show top suggestions
339
339
  suggestions.slice(0, 3).forEach(s => {
340
340
  console.error(`${colors.yellow}[Suggestion]${colors.reset} ${s.type}: ${s.message}`);
341
341
  });
342
342
  }
343
343
  }
344
-
344
+
345
345
  } catch (error) {
346
346
  // Silent fail - intelligence should not break the tool
347
347
  if (VERBOSE) {
@@ -357,7 +357,7 @@ async function logEditToMemory({ instruction, files, strategy, success, errorMes
357
357
  try {
358
358
  const engine = await getMemoryEngine();
359
359
  const diffSize = Object.keys(files).reduce((total, fp) => total + (files[fp]?.length || 0), 0);
360
-
360
+
361
361
  engine.logEditToMemory({
362
362
  instruction: instruction.substring(0, 500),
363
363
  files: Object.keys(files),
@@ -367,7 +367,7 @@ async function logEditToMemory({ instruction, files, strategy, success, errorMes
367
367
  durationMs,
368
368
  errorMessage
369
369
  });
370
-
370
+
371
371
  console.error(`${colors.cyan}[Memory]${colors.reset} Edit logged to history`);
372
372
  } catch (error) {
373
373
  console.error(`${colors.yellow}[Memory]${colors.reset} Log failed: ${error.message}`);
@@ -380,13 +380,13 @@ async function logEditToMemory({ instruction, files, strategy, success, errorMes
380
380
  async function analyzeRenameImpact(symbolName, currentFile = null) {
381
381
  try {
382
382
  const engine = await getMemoryEngine();
383
-
383
+
384
384
  // Search for symbol references across codebase
385
- const searchResult = await engine.intelligentSearch(symbolName, {
385
+ const searchResult = await engine.intelligentSearch(symbolName, {
386
386
  limit: 20,
387
387
  currentFile
388
388
  });
389
-
389
+
390
390
  const impactedFiles = new Set();
391
391
  const references = [];
392
392
 
@@ -1056,9 +1056,9 @@ async function reportAudit(data) {
1056
1056
  flushInterval: 5000,
1057
1057
  verbose: VERBOSE
1058
1058
  });
1059
-
1059
+
1060
1060
  queue.add(auditData);
1061
-
1061
+
1062
1062
  if (VERBOSE) {
1063
1063
  const stats = queue.getStats();
1064
1064
  console.error(`${colors.dim}[Audit] Queued for batching. Queue size: ${stats.queued}${colors.reset}`);
@@ -1097,9 +1097,9 @@ async function handleRegexSearch({ query, path: searchPath, caseSensitive = fals
1097
1097
  if (!validation.valid) {
1098
1098
  const latency = Date.now() - start;
1099
1099
  return {
1100
- content: [{
1101
- type: "text",
1102
- text: `⚠️ Invalid regex pattern: ${validation.error}\n\nQuery: "${query}"\n\n💡 Tips:\n• Basic regex works: \\bword\\b, [a-z]+, (foo|bar)\n• Avoid lookbehind (?<=...) and lookahead (?=...) - not supported by grep\n• Use word boundaries \\b instead of lookarounds\n• Escape special chars: \\\\, \\+, \\*`
1100
+ content: [{
1101
+ type: "text",
1102
+ text: `⚠️ Invalid regex pattern: ${validation.error}\n\nQuery: "${query}"\n\n💡 Tips:\n• Basic regex works: \\bword\\b, [a-z]+, (foo|bar)\n• Avoid lookbehind (?<=...) and lookahead (?=...) - not supported by grep\n• Use word boundaries \\b instead of lookarounds\n• Escape special chars: \\\\, \\+, \\*`
1103
1103
  }],
1104
1104
  isError: true
1105
1105
  };
@@ -1135,12 +1135,12 @@ async function handleRegexSearch({ query, path: searchPath, caseSensitive = fals
1135
1135
 
1136
1136
  try {
1137
1137
  const { stdout, stderr } = await execAsync(command, { maxBuffer: 10 * 1024 * 1024 }); // 10MB buffer
1138
-
1138
+
1139
1139
  // Check for grep regex errors in stderr
1140
1140
  if (stderr && stderr.includes('Invalid regular expression')) {
1141
1141
  throw new Error(`grep: ${stderr}`);
1142
1142
  }
1143
-
1143
+
1144
1144
  const results = stdout.trim().split('\n').filter(Boolean);
1145
1145
 
1146
1146
  const latency = Date.now() - start;
@@ -1205,12 +1205,12 @@ async function handleRegexSearch({ query, path: searchPath, caseSensitive = fals
1205
1205
  content: [{ type: "text", text: `🔍 Regex Search: No matches found for "${query}" in ${searchPath} (${latency}ms)` }]
1206
1206
  };
1207
1207
  }
1208
-
1208
+
1209
1209
  // Handle regex syntax errors
1210
1210
  if (execErr.stderr && execErr.stderr.includes('Invalid')) {
1211
1211
  throw new Error(`Invalid regex syntax: ${execErr.stderr}. Try simplifying your pattern.`);
1212
1212
  }
1213
-
1213
+
1214
1214
  throw execErr;
1215
1215
  }
1216
1216
  } catch (error) {
@@ -1237,14 +1237,14 @@ async function handleRegexSearch({ query, path: searchPath, caseSensitive = fals
1237
1237
  */
1238
1238
  function validateRegex(pattern) {
1239
1239
  const warnings = [];
1240
-
1240
+
1241
1241
  try {
1242
1242
  // Test if it's valid JavaScript regex
1243
1243
  new RegExp(pattern);
1244
1244
  } catch (e) {
1245
1245
  return { valid: false, error: e.message, warnings };
1246
1246
  }
1247
-
1247
+
1248
1248
  // Check for patterns not supported by basic grep (PCRE-only features)
1249
1249
  const unsupportedPatterns = [
1250
1250
  { regex: /\(\?<[=!]/, name: 'Lookbehind (?<=...) or negative lookbehind (?<!...)' },
@@ -1256,18 +1256,18 @@ function validateRegex(pattern) {
1256
1256
  { regex: /\(\?\d*\)/, name: 'Subroutine calls (?1)' },
1257
1257
  { regex: /\(\?&\w+\)/, name: 'Subroutine calls (?&name)' }
1258
1258
  ];
1259
-
1259
+
1260
1260
  for (const { regex, name } of unsupportedPatterns) {
1261
1261
  if (regex.test(pattern)) {
1262
1262
  warnings.push(`Pattern uses ${name} which may not be supported by grep. Results may be incomplete.`);
1263
1263
  }
1264
1264
  }
1265
-
1265
+
1266
1266
  // Check for potentially dangerous patterns (catastrophic backtracking)
1267
1267
  if (/\(\w+\+\+?\)|\(\w+\*\*?\)\*\*?/.test(pattern)) {
1268
1268
  warnings.push('Pattern may cause performance issues (nested quantifiers)');
1269
1269
  }
1270
-
1270
+
1271
1271
  return { valid: true, warnings };
1272
1272
  }
1273
1273
 
@@ -1297,7 +1297,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
1297
1297
  // Convert literal query to glob pattern for file search
1298
1298
  pattern = `**/*${query}*`;
1299
1299
  }
1300
-
1300
+
1301
1301
  // Use fast-glob to find files
1302
1302
  const files = await fg([pattern], {
1303
1303
  cwd,
@@ -1309,7 +1309,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
1309
1309
 
1310
1310
  // Also search file contents if it's not a file pattern
1311
1311
  const contentMatches = [];
1312
-
1312
+
1313
1313
  // For simple queries, also search in common code files
1314
1314
  if (!isRegex && query.length > 1) {
1315
1315
  const codeFiles = await fg(['**/*.{js,ts,jsx,tsx,py,go,rs,java,rb,php}'], {
@@ -1317,7 +1317,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
1317
1317
  ignore: ['node_modules/**', '.git/**'],
1318
1318
  absolute: false
1319
1319
  });
1320
-
1320
+
1321
1321
  // Search in first 50 files to avoid performance issues
1322
1322
  for (const file of codeFiles.slice(0, 50)) {
1323
1323
  try {
@@ -1325,7 +1325,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
1325
1325
  if (content.includes(query)) {
1326
1326
  const lines = content.split('\n');
1327
1327
  const matches = [];
1328
-
1328
+
1329
1329
  lines.forEach((line, idx) => {
1330
1330
  if (line.includes(query)) {
1331
1331
  matches.push({
@@ -1335,7 +1335,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
1335
1335
  });
1336
1336
  }
1337
1337
  });
1338
-
1338
+
1339
1339
  if (matches.length > 0) {
1340
1340
  contentMatches.push({
1341
1341
  file,
@@ -1350,7 +1350,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
1350
1350
  }
1351
1351
 
1352
1352
  const latency = Date.now() - start;
1353
-
1353
+
1354
1354
  // Format output
1355
1355
  let output = `${colors.bold}Filesystem Search Results${colors.reset}\n\n`;
1356
1356
  output += `Query: "${query}"\n`;
@@ -1396,7 +1396,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
1396
1396
  };
1397
1397
  } catch (error) {
1398
1398
  const latency = Date.now() - start;
1399
-
1399
+
1400
1400
  reportAudit({
1401
1401
  tool: 'search_filesystem',
1402
1402
  instruction: `Search: ${query}`,
@@ -1456,7 +1456,7 @@ async function handleReapply({ instruction, files, errorContext = "", attempt =
1456
1456
  */
1457
1457
  async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1458
1458
  const editStartTime = Date.now();
1459
-
1459
+
1460
1460
  // Validate required parameters
1461
1461
  if (!instruction) {
1462
1462
  return {
@@ -1464,16 +1464,16 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1464
1464
  isError: true
1465
1465
  };
1466
1466
  }
1467
-
1467
+
1468
1468
  if (!files || Object.keys(files).length === 0) {
1469
1469
  return {
1470
1470
  content: [{ type: "text", text: "❌ Error: 'files' parameter is required and must contain at least one file" }],
1471
1471
  isError: true
1472
1472
  };
1473
1473
  }
1474
-
1474
+
1475
1475
  const firstFile = Object.keys(files)[0];
1476
-
1476
+
1477
1477
  // 1. Retrieve memory context for enhanced understanding
1478
1478
  let memoryContext = '';
1479
1479
  try {
@@ -1484,7 +1484,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1484
1484
  } catch (error) {
1485
1485
  console.error(`${colors.yellow}[Memory]${colors.reset} Context retrieval failed: ${error.message}`);
1486
1486
  }
1487
-
1487
+
1488
1488
  // 2. Analyze impact for rename operations
1489
1489
  let impactAnalysis = null;
1490
1490
  const renamePattern = instruction.match(/(?:rename|change)\s+(?:function|class|const|let|var|method)\s+(\w+)\s+to\s+(\w+)/i);
@@ -1499,7 +1499,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1499
1499
  console.error(`${colors.yellow}[IMPACT]${colors.reset} Analysis failed: ${error.message}`);
1500
1500
  }
1501
1501
  }
1502
-
1502
+
1503
1503
  // Check for multi-file edits first
1504
1504
  const multiFileEdit = detectCrossFileEdit(instruction, files);
1505
1505
 
@@ -1533,7 +1533,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1533
1533
  errorMessage: result.message || result.error,
1534
1534
  durationMs: Date.now() - editStartTime
1535
1535
  });
1536
-
1536
+
1537
1537
  return {
1538
1538
  content: [{
1539
1539
  type: "text",
@@ -1571,7 +1571,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1571
1571
  errorMessage: error.message,
1572
1572
  durationMs: Date.now() - editStartTime
1573
1573
  });
1574
-
1574
+
1575
1575
  return {
1576
1576
  content: [{
1577
1577
  type: "text",
@@ -1676,7 +1676,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1676
1676
  errorMessage: error.message,
1677
1677
  durationMs: Date.now() - editStartTime
1678
1678
  });
1679
-
1679
+
1680
1680
  return {
1681
1681
  content: [{
1682
1682
  type: "text",
@@ -1807,7 +1807,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1807
1807
 
1808
1808
  // Include impact analysis in result if available
1809
1809
  let resultText = `✅ AST Refactor Applied Successfully\n\nOperation: ${pattern.type}\nChanges: ${transformResult.count || transformResult.replacements || 0} locations\n\nBackup: ${editResult.backupPath}`;
1810
-
1810
+
1811
1811
  if (impactAnalysis && impactAnalysis.suggestion) {
1812
1812
  resultText += `\n\n${impactAnalysis.suggestion}`;
1813
1813
  if (impactAnalysis.impactedFiles.length > 0) {
@@ -1838,7 +1838,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1838
1838
  errorMessage: error.message,
1839
1839
  durationMs: Date.now() - editStartTime
1840
1840
  });
1841
-
1841
+
1842
1842
  return {
1843
1843
  content: [{
1844
1844
  type: "text",
@@ -1861,7 +1861,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1861
1861
  dryRun,
1862
1862
  toolName: 'edit'
1863
1863
  });
1864
-
1864
+
1865
1865
  // Log to memory
1866
1866
  await logEditToMemory({
1867
1867
  instruction,
@@ -1871,7 +1871,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1871
1871
  errorMessage: result.isError ? result.content?.[0]?.text : null,
1872
1872
  durationMs: Date.now() - editStartTime
1873
1873
  });
1874
-
1874
+
1875
1875
  return result;
1876
1876
  }
1877
1877
  }
@@ -1884,7 +1884,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1884
1884
  dryRun,
1885
1885
  toolName: 'edit'
1886
1886
  });
1887
-
1887
+
1888
1888
  // Log to memory
1889
1889
  await logEditToMemory({
1890
1890
  instruction,
@@ -1894,7 +1894,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1894
1894
  errorMessage: result.isError ? result.content?.[0]?.text : null,
1895
1895
  durationMs: Date.now() - editStartTime
1896
1896
  });
1897
-
1897
+
1898
1898
  return result;
1899
1899
  }
1900
1900
 
@@ -1904,14 +1904,14 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1904
1904
  if (memoryContext) {
1905
1905
  enhancedInstruction = `${instruction}\n\n--- CONTEXT FROM CODEBASE ---${memoryContext}`;
1906
1906
  }
1907
-
1907
+
1908
1908
  const result = await handleApplyFast({
1909
1909
  instruction: enhancedInstruction,
1910
1910
  files,
1911
1911
  dryRun,
1912
1912
  toolName: 'edit'
1913
1913
  });
1914
-
1914
+
1915
1915
  // Log to memory
1916
1916
  await logEditToMemory({
1917
1917
  instruction,
@@ -1921,13 +1921,13 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1921
1921
  errorMessage: result.isError ? result.content?.[0]?.text : null,
1922
1922
  durationMs: Date.now() - editStartTime
1923
1923
  });
1924
-
1924
+
1925
1925
  // Auto-trigger intelligence tools in background (non-blocking)
1926
1926
  if (!result.isError) {
1927
1927
  const firstFile = Object.keys(files)[0];
1928
- autoTriggerIntelligence(firstFile, instruction).catch(() => {});
1928
+ autoTriggerIntelligence(firstFile, instruction).catch(() => { });
1929
1929
  }
1930
-
1930
+
1931
1931
  return result;
1932
1932
  }
1933
1933
 
@@ -1939,7 +1939,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1939
1939
  */
1940
1940
  async function handleSearch({ query, files, path, mode = 'auto', regex = false, caseSensitive = false, contextLines = 2, maxResults = 10 }) {
1941
1941
  const searchStartTime = Date.now();
1942
-
1942
+
1943
1943
  // For regex mode without files, we need to do content-based regex search
1944
1944
  // since fast-glob doesn't support full regex patterns
1945
1945
  if (regex && !files && mode === 'auto') {
@@ -1954,9 +1954,9 @@ async function handleSearch({ query, files, path, mode = 'auto', regex = false,
1954
1954
  if ((detectedMode === 'auto' || detectedMode === 'local') && !regex) {
1955
1955
  try {
1956
1956
  const engine = await getMemoryEngine();
1957
-
1957
+
1958
1958
  // Use intelligent search (UltraHybrid + Smart Routing)
1959
- const searchResult = await engine.intelligentSearch(query, {
1959
+ const searchResult = await engine.intelligentSearch(query, {
1960
1960
  limit: maxResults,
1961
1961
  currentFile: files ? Object.keys(files)[0] : null
1962
1962
  });
@@ -2032,7 +2032,7 @@ async function handleRead({ filePath, filePaths, start_line, end_line, max_lines
2032
2032
  if (filePaths && Array.isArray(filePaths) && filePaths.length > 0) {
2033
2033
  return await handleBatchRead(filePaths, start_line, end_line, max_lines_per_file);
2034
2034
  }
2035
-
2035
+
2036
2036
  // Single file mode (backward compatible)
2037
2037
  return await handleReadFile({ path: filePath, start_line, end_line });
2038
2038
  }
@@ -2043,50 +2043,50 @@ async function handleRead({ filePath, filePaths, start_line, end_line, max_lines
2043
2043
  async function handleBatchRead(filePaths, start_line, end_line, max_lines_per_file) {
2044
2044
  const start = Date.now();
2045
2045
  const MAX_BATCH_SIZE = 50;
2046
-
2046
+
2047
2047
  if (filePaths.length > MAX_BATCH_SIZE) {
2048
2048
  return {
2049
2049
  content: [{ type: "text", text: `❌ Batch read limit exceeded: ${filePaths.length} files (max ${MAX_BATCH_SIZE})` }],
2050
2050
  isError: true
2051
2051
  };
2052
2052
  }
2053
-
2053
+
2054
2054
  console.error(`${colors.cyan}[BATCH READ]${colors.reset} ${filePaths.length} files`);
2055
-
2055
+
2056
2056
  // Read all files in parallel with Promise.all
2057
2057
  const results = await Promise.all(
2058
2058
  filePaths.map(async (filePath) => {
2059
2059
  try {
2060
- const result = await handleReadFileInternal({
2061
- path: filePath,
2062
- start_line,
2060
+ const result = await handleReadFileInternal({
2061
+ path: filePath,
2062
+ start_line,
2063
2063
  end_line,
2064
- max_lines: max_lines_per_file
2064
+ max_lines: max_lines_per_file
2065
2065
  });
2066
2066
  return { path: filePath, ...result, success: true };
2067
2067
  } catch (error) {
2068
- return {
2069
- path: filePath,
2070
- success: false,
2068
+ return {
2069
+ path: filePath,
2070
+ success: false,
2071
2071
  error: error.message,
2072
2072
  content: `❌ Error reading ${filePath}: ${error.message}`
2073
2073
  };
2074
2074
  }
2075
2075
  })
2076
2076
  );
2077
-
2077
+
2078
2078
  // Calculate stats
2079
2079
  const successful = results.filter(r => r.success).length;
2080
2080
  const failed = results.filter(r => !r.success).length;
2081
2081
  const totalLines = results.reduce((sum, r) => sum + (r.lines || 0), 0);
2082
2082
  const totalSize = results.reduce((sum, r) => sum + (r.size || 0), 0);
2083
-
2083
+
2084
2084
  // Format output
2085
2085
  let output = `📚 Batch Read Complete (${filePaths.length} files)\n`;
2086
2086
  output += `✅ Successful: ${successful} ❌ Failed: ${failed}\n`;
2087
2087
  output += `📊 Total: ${totalLines} lines, ${(totalSize / 1024).toFixed(1)} KB\n`;
2088
2088
  output += `⏱️ Latency: ${Date.now() - start}ms\n\n`;
2089
-
2089
+
2090
2090
  // Add each file's content
2091
2091
  results.forEach((result, index) => {
2092
2092
  output += `[${index + 1}/${filePaths.length}] `;
@@ -2105,7 +2105,7 @@ async function handleBatchRead(filePaths, start_line, end_line, max_lines_per_fi
2105
2105
  }
2106
2106
  output += `\n\n`;
2107
2107
  });
2108
-
2108
+
2109
2109
  // Report audit for batch operation
2110
2110
  reportAudit({
2111
2111
  tool: 'read_file',
@@ -2116,7 +2116,7 @@ async function handleBatchRead(filePaths, start_line, end_line, max_lines_per_fi
2116
2116
  input_tokens: Math.ceil(filePaths.join(',').length / 4),
2117
2117
  output_tokens: Math.ceil(output.length / 4)
2118
2118
  });
2119
-
2119
+
2120
2120
  return {
2121
2121
  content: [{ type: "text", text: output }]
2122
2122
  };
@@ -2134,7 +2134,7 @@ async function handleReadFileInternal({ path: filePath, start_line, end_line, ma
2134
2134
  }
2135
2135
 
2136
2136
  const STREAM_THRESHOLD = 1024 * 1024; // 1MB
2137
-
2137
+
2138
2138
  let startLine = start_line ? parseInt(start_line) : 1;
2139
2139
  let endLine = end_line ? parseInt(end_line) : -1;
2140
2140
  let outputContent;
@@ -2262,12 +2262,12 @@ async function handleWarpgrep({ query, include = ".", isRegex = false, caseSensi
2262
2262
  });
2263
2263
  return { content: [{ type: "text", text: msg }] };
2264
2264
  }
2265
-
2265
+
2266
2266
  // Handle regex syntax errors
2267
2267
  if (execErr.stderr && execErr.stderr.includes('Invalid')) {
2268
2268
  throw new Error(`Invalid regex syntax: ${execErr.stderr}. Try simplifying your pattern.`);
2269
2269
  }
2270
-
2270
+
2271
2271
  throw execErr;
2272
2272
  }
2273
2273
  } catch (error) {
@@ -3093,18 +3093,18 @@ async function handleMemorySearch({ query, type = 'all', maxResults = 6, minScor
3093
3093
  */
3094
3094
  async function handleMemoryGet({ type }) {
3095
3095
  const start = Date.now();
3096
-
3096
+
3097
3097
  // Early return if memory engine not ready
3098
3098
  if (!memoryEngineReady) {
3099
3099
  return {
3100
- content: [{
3101
- type: "text",
3102
- text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
3100
+ content: [{
3101
+ type: "text",
3102
+ text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
3103
3103
  }],
3104
3104
  isError: true
3105
3105
  };
3106
3106
  }
3107
-
3107
+
3108
3108
  try {
3109
3109
  const engine = await getMemoryEngine();
3110
3110
  let output = '';
@@ -3138,12 +3138,12 @@ async function handleMemoryGet({ type }) {
3138
3138
  } else if (type === 'reindex') {
3139
3139
  output = `🔄 Re-indexing Codebase\n\n`;
3140
3140
  output += `Starting re-index in background...\n`;
3141
-
3141
+
3142
3142
  // Trigger re-index in background
3143
3143
  engine.performInitialScan().catch(err => {
3144
3144
  console.error('[Reindex] Error:', err.message);
3145
3145
  });
3146
-
3146
+
3147
3147
  output += `✅ Re-index started. Check stats in a few seconds.\n`;
3148
3148
  output += `Use 'memory_get stats' to check progress.`;
3149
3149
  } else if (type === 'intelligence') {
@@ -3191,18 +3191,18 @@ async function handleMemoryGet({ type }) {
3191
3191
  */
3192
3192
  async function handleDetectPatterns() {
3193
3193
  const start = Date.now();
3194
-
3194
+
3195
3195
  // Early return if memory engine not ready
3196
3196
  if (!memoryEngineReady) {
3197
3197
  return {
3198
- content: [{
3199
- type: "text",
3200
- text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
3198
+ content: [{
3199
+ type: "text",
3200
+ text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
3201
3201
  }],
3202
3202
  isError: true
3203
3203
  };
3204
3204
  }
3205
-
3205
+
3206
3206
  try {
3207
3207
  const engine = await getMemoryEngine();
3208
3208
  const patterns = await engine.detectPatterns();
@@ -3250,18 +3250,18 @@ async function handleDetectPatterns() {
3250
3250
  */
3251
3251
  async function handleGetSuggestions({ currentFile, currentLine }) {
3252
3252
  const start = Date.now();
3253
-
3253
+
3254
3254
  // Early return if memory engine not ready
3255
3255
  if (!memoryEngineReady) {
3256
3256
  return {
3257
- content: [{
3258
- type: "text",
3259
- text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
3257
+ content: [{
3258
+ type: "text",
3259
+ text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
3260
3260
  }],
3261
3261
  isError: true
3262
3262
  };
3263
3263
  }
3264
-
3264
+
3265
3265
  try {
3266
3266
  const engine = await getMemoryEngine();
3267
3267
  const suggestions = await engine.getSuggestions({
@@ -3315,18 +3315,18 @@ async function handleGetSuggestions({ currentFile, currentLine }) {
3315
3315
  */
3316
3316
  async function handleSelectStrategy({ instruction, files = [] }) {
3317
3317
  const start = Date.now();
3318
-
3318
+
3319
3319
  // Early return if memory engine not ready
3320
3320
  if (!memoryEngineReady) {
3321
3321
  return {
3322
- content: [{
3323
- type: "text",
3324
- text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
3322
+ content: [{
3323
+ type: "text",
3324
+ text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
3325
3325
  }],
3326
3326
  isError: true
3327
3327
  };
3328
3328
  }
3329
-
3329
+
3330
3330
  try {
3331
3331
  const engine = await getMemoryEngine();
3332
3332
  const result = await engine.selectStrategy(instruction, { files });
@@ -3372,11 +3372,11 @@ async function handleSelectStrategy({ instruction, files = [] }) {
3372
3372
  */
3373
3373
  async function handleHealthCheck() {
3374
3374
  const start = Date.now();
3375
-
3375
+
3376
3376
  try {
3377
3377
  const memoryStats = memoryEngine ? await memoryEngine.getStats() : null;
3378
3378
  const watcherStats = memoryEngine?.watcher ? memoryEngine.watcher.getStats() : null;
3379
-
3379
+
3380
3380
  const health = {
3381
3381
  status: 'healthy',
3382
3382
  timestamp: Date.now(),
@@ -3399,20 +3399,20 @@ async function handleHealthCheck() {
3399
3399
  },
3400
3400
  latencyMs: Date.now() - start
3401
3401
  };
3402
-
3402
+
3403
3403
  reportAudit({
3404
3404
  tool: 'health_check',
3405
3405
  status: 'success',
3406
3406
  latency_ms: health.latencyMs
3407
3407
  });
3408
-
3408
+
3409
3409
  return {
3410
3410
  content: [{
3411
3411
  type: 'text',
3412
3412
  text: JSON.stringify(health, null, 2)
3413
3413
  }]
3414
3414
  };
3415
-
3415
+
3416
3416
  } catch (error) {
3417
3417
  reportAudit({
3418
3418
  tool: 'health_check',
@@ -3420,7 +3420,7 @@ async function handleHealthCheck() {
3420
3420
  error_message: error.message,
3421
3421
  latency_ms: Date.now() - start
3422
3422
  });
3423
-
3423
+
3424
3424
  return {
3425
3425
  content: [{
3426
3426
  type: 'text',
@@ -3442,30 +3442,26 @@ async function handleHealthCheck() {
3442
3442
  // Acquire lock before starting - prevents multiple instances
3443
3443
  const lockAcquired = await acquireLock();
3444
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);
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
3448
  }
3449
3449
 
3450
3450
  const transport = new StdioServerTransport();
3451
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
-
3452
+ // NOTE: Do NOT add process.stdin.on('end') handler here.
3453
+ // VSCode and some MCP clients send ephemeral EOF / half-close signals during
3454
+ // the initialize handshake, which would cause premature gracefulShutdown
3455
+ // before the server has a chance to respond, resulting in the
3456
+ // "Error: calling 'initialize': EOF" error.
3457
+ //
3458
+ // The MCP SDK's StdioServerTransport already manages transport lifecycle.
3459
+ // Only handle unrecoverable stdout errors.
3466
3460
  process.stdout.on('error', (err) => {
3467
- console.error('[MCP] stdout error:', err.code);
3468
- gracefulShutdown('stdout.error');
3461
+ if (err.code === 'EPIPE' || err.code === 'ERR_STREAM_DESTROYED') {
3462
+ console.error('[MCP] stdout closed, shutting down:', err.code);
3463
+ gracefulShutdown('stdout.error');
3464
+ }
3469
3465
  });
3470
3466
 
3471
3467
  // Pre-initialize memory engine in background
@@ -3476,35 +3472,35 @@ backgroundInitializeMemoryEngine().catch(err => {
3476
3472
 
3477
3473
  // Graceful shutdown handler
3478
3474
  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);
3475
+ console.error(`${colors.yellow}[Shutdown]${colors.reset} Received ${signal}, cleaning up...`);
3476
+
3477
+ const SHUTDOWN_TIMEOUT = 5000; // 5 seconds per MCP best practices
3478
+
3479
+ try {
3480
+ await Promise.race([
3481
+ (async () => {
3482
+ // Stop memory engine
3483
+ if (memoryEngine && memoryEngineReady) {
3484
+ await memoryEngine.cleanup();
3485
+ }
3486
+
3487
+ // Flush audit queue
3488
+ const auditQ = getAuditQueue();
3489
+ await auditQ.destroy();
3490
+
3491
+ // Release lock
3492
+ await releaseLock();
3493
+ })(),
3494
+ new Promise(resolve => setTimeout(resolve, SHUTDOWN_TIMEOUT))
3495
+ ]);
3496
+
3497
+ console.error(`${colors.green}[Shutdown]${colors.reset} Cleanup complete`);
3498
+ } catch (error) {
3499
+ console.error(`${colors.red}[Shutdown]${colors.reset} Error during cleanup: ${error.message}`);
3500
+ }
3501
+
3502
+ // Always exit (even if cleanup fails) to prevent zombie processes
3503
+ process.exit(0);
3508
3504
  }
3509
3505
 
3510
3506
  process.on('SIGINT', () => gracefulShutdown('SIGINT'));
@@ -3512,13 +3508,13 @@ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
3512
3508
 
3513
3509
  // Protect against zombie processes - handle unexpected errors
3514
3510
  process.on('uncaughtException', (err) => {
3515
- console.error('[MCP] Uncaught exception:', err);
3516
- gracefulShutdown('uncaughtException');
3511
+ console.error('[MCP] Uncaught exception:', err);
3512
+ gracefulShutdown('uncaughtException');
3517
3513
  });
3518
3514
 
3519
3515
  process.on('unhandledRejection', (reason, promise) => {
3520
- console.error('[MCP] Unhandled rejection:', reason);
3521
- gracefulShutdown('unhandledRejection');
3516
+ console.error('[MCP] Unhandled rejection:', reason);
3517
+ gracefulShutdown('unhandledRejection');
3522
3518
  });
3523
3519
 
3524
3520
  await server.connect(transport);