@mrxkun/mcfast-mcp 4.1.12 → 4.1.14

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/src/index.js CHANGED
@@ -25,15 +25,15 @@ 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
- // Override console.error - suppress in MCP mode
33
- const originalConsoleError = console.error;
34
- console.error = function(...args) {
35
- // Suppressed in MCP mode
36
- };
32
+ // Keep console.error enabled for debugging and MCP logging
33
+ // const originalConsoleError = console.error;
34
+ // console.error = function(...args) {
35
+ // // Suppressed in MCP mode
36
+ // };
37
37
 
38
38
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
39
39
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -86,70 +86,82 @@ 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
+ // Robust stale check: stale if > 5 mins OR timestamp is in the future (prevent stuck lock)
116
+ // OR check if PID is actually running
117
+ let isStale = lockAge > 5 * 60 * 1000 || lockAge < -60 * 1000;
118
+
119
+ if (!isStale && pid) {
120
+ try {
121
+ process.kill(parseInt(pid), 0);
122
+ } catch (e) {
123
+ // PID not running
124
+ isStale = true;
125
+ }
126
+ }
127
+
128
+ if (isStale) {
129
+ console.error(`${colors.yellow}[Lock]${colors.reset} Stale or invalid lock detected, removing...`);
130
+ await fs.unlink(LOCK_FILE_PATH);
131
+ return acquireLock(); // Retry
132
+ }
133
+
134
+ console.error(`${colors.red}[Lock]${colors.reset} Another instance is already running (PID: ${pid})`);
135
+ console.error(`${colors.yellow}[Lock]${colors.reset} To force start, delete: ${LOCK_FILE_PATH}`);
136
+ return false;
137
+ } catch (readError) {
138
+ console.error(`${colors.red}[Lock]${colors.reset} Failed to read lock file: ${readError.message}`);
139
+ return false;
140
+ }
141
+ }
142
+ console.error(`${colors.red}[Lock]${colors.reset} Failed to acquire lock: ${error.message}`);
127
143
  return false;
128
- }
129
144
  }
130
- console.error(`${colors.red}[Lock]${colors.reset} Failed to acquire lock: ${error.message}`);
131
- return false;
132
- }
133
145
  }
134
146
 
135
147
  async function releaseLock() {
136
- if (lockFileHandle) {
148
+ if (lockFileHandle) {
149
+ try {
150
+ await lockFileHandle.close();
151
+ lockFileHandle = null;
152
+ } catch (e) {
153
+ // Ignore close errors
154
+ }
155
+ }
156
+
137
157
  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
- }
158
+ await fs.unlink(LOCK_FILE_PATH);
159
+ console.error(`${colors.cyan}[Lock]${colors.reset} Released lock file`);
160
+ } catch (error) {
161
+ if (error.code !== 'ENOENT') {
162
+ console.error(`${colors.yellow}[Lock]${colors.reset} Failed to release lock: ${error.message}`);
163
+ }
164
+ }
153
165
  }
154
166
 
155
167
  // ============================================================================
@@ -171,36 +183,36 @@ let memoryEngineReady = false;
171
183
  */
172
184
  async function backgroundInitializeMemoryEngine() {
173
185
  if (memoryEngineInitPromise) return memoryEngineInitPromise;
174
-
186
+
175
187
  memoryEngineInitPromise = (async () => {
176
188
  const maxRetries = 2;
177
189
  let lastError = null;
178
-
190
+
179
191
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
180
192
  try {
181
193
  memoryEngine = new MemoryEngine({
182
194
  apiKey: TOKEN,
183
195
  enableSync: true
184
196
  });
185
-
197
+
186
198
  // Use timeout for initialization
187
199
  const initTimeout = new Promise((_, reject) => {
188
200
  setTimeout(() => reject(new Error('Memory engine init timeout')), 25000);
189
201
  });
190
-
202
+
191
203
  await Promise.race([
192
204
  memoryEngine.initialize(process.cwd()),
193
205
  initTimeout
194
206
  ]);
195
-
207
+
196
208
  memoryEngineReady = true;
197
209
  console.error(`${colors.cyan}[Memory]${colors.reset} Engine initialized successfully`);
198
210
  return memoryEngine;
199
-
211
+
200
212
  } catch (error) {
201
213
  lastError = error;
202
214
  console.error(`${colors.yellow}[Memory]${colors.reset} Initialization attempt ${attempt}/${maxRetries} failed: ${error.message}`);
203
-
215
+
204
216
  if (attempt < maxRetries) {
205
217
  // Exponential backoff before retry
206
218
  const backoffMs = 1000 * Math.pow(2, attempt - 1);
@@ -209,7 +221,7 @@ async function backgroundInitializeMemoryEngine() {
209
221
  }
210
222
  }
211
223
  }
212
-
224
+
213
225
  // All retries failed - graceful degradation
214
226
  console.error(`${colors.yellow}[Memory]${colors.reset} All initialization attempts failed. Running in degraded mode.`);
215
227
  console.error(`${colors.yellow}[Memory]${colors.reset} Error: ${lastError?.message || 'Unknown error'}`);
@@ -217,7 +229,7 @@ async function backgroundInitializeMemoryEngine() {
217
229
  memoryEngineReady = false;
218
230
  return null;
219
231
  })();
220
-
232
+
221
233
  return memoryEngineInitPromise;
222
234
  }
223
235
 
@@ -228,7 +240,7 @@ async function getMemoryEngine() {
228
240
  if (memoryEngine && memoryEngineReady) {
229
241
  return memoryEngine;
230
242
  }
231
-
243
+
232
244
  // Wait for initialization with timeout
233
245
  if (memoryEngineInitPromise) {
234
246
  try {
@@ -240,12 +252,12 @@ async function getMemoryEngine() {
240
252
  console.error(`${colors.yellow}[Memory]${colors.reset} Waiting for engine: ${error.message}`);
241
253
  }
242
254
  }
243
-
255
+
244
256
  // If still not ready, try to initialize now
245
257
  if (!memoryEngine) {
246
258
  await backgroundInitializeMemoryEngine();
247
259
  }
248
-
260
+
249
261
  return memoryEngine;
250
262
  }
251
263
 
@@ -255,18 +267,18 @@ async function getMemoryEngine() {
255
267
  async function searchMemoryContext(instruction, maxResults = 5) {
256
268
  try {
257
269
  const engine = await getMemoryEngine();
258
-
270
+
259
271
  // Use intelligent search for best context
260
- const searchResult = await engine.intelligentSearch(instruction, {
261
- limit: maxResults
272
+ const searchResult = await engine.intelligentSearch(instruction, {
273
+ limit: maxResults
262
274
  });
263
-
275
+
264
276
  if (!searchResult.results || searchResult.results.length === 0) {
265
277
  return '';
266
278
  }
267
279
 
268
280
  let context = '';
269
-
281
+
270
282
  // Add code context from search results
271
283
  context += '\n\n--- MEMORY CONTEXT (Relevant Code) ---\n';
272
284
  searchResult.results.forEach((result, idx) => {
@@ -277,7 +289,7 @@ async function searchMemoryContext(instruction, maxResults = 5) {
277
289
  context += `${result.code.substring(0, 300)}${result.code.length > 300 ? '...' : ''}\n`;
278
290
  }
279
291
  });
280
-
292
+
281
293
  // Add facts if available
282
294
  const facts = await engine.searchFacts(instruction, 5);
283
295
  if (facts && facts.length > 0) {
@@ -286,7 +298,7 @@ async function searchMemoryContext(instruction, maxResults = 5) {
286
298
  context += `• ${fact.type}: ${fact.name} (${fact.file_id})\n`;
287
299
  });
288
300
  }
289
-
301
+
290
302
  return context;
291
303
  } catch (error) {
292
304
  console.error(`${colors.yellow}[Memory]${colors.reset} Context search failed: ${error.message}`);
@@ -304,44 +316,44 @@ async function autoTriggerIntelligence(filePath, instruction) {
304
316
  if (process.env.MCFAST_INTELLIGENCE_AUTO !== 'true') {
305
317
  return;
306
318
  }
307
-
319
+
308
320
  const engine = await getMemoryEngine();
309
321
  const cache = getIntelligenceCache();
310
-
322
+
311
323
  // 1. Detect patterns (cached for 5 minutes)
312
324
  const patternCacheKey = { file: filePath, type: 'patterns' };
313
325
  let patterns = cache.get('patterns', patternCacheKey);
314
-
326
+
315
327
  if (!patterns) {
316
328
  patterns = await engine.detectPatterns();
317
329
  if (patterns && patterns.length > 0) {
318
330
  cache.set('patterns', patternCacheKey, patterns);
319
331
  console.error(`${colors.cyan}[Intelligence]${colors.reset} 💡 Detected ${patterns.length} patterns`);
320
-
332
+
321
333
  // Log high-confidence patterns
322
334
  patterns.filter(p => p.confidence > 0.8).forEach(p => {
323
335
  console.error(`${colors.yellow}[Pattern]${colors.reset} ${p.type}: ${p.message}`);
324
336
  });
325
337
  }
326
338
  }
327
-
339
+
328
340
  // 2. Get suggestions (cached for 5 minutes)
329
341
  const suggestionCacheKey = { file: filePath, type: 'suggestions' };
330
342
  let suggestions = cache.get('suggestions', suggestionCacheKey);
331
-
343
+
332
344
  if (!suggestions) {
333
345
  suggestions = await engine.getSuggestions({ currentFile: filePath });
334
346
  if (suggestions && suggestions.length > 0) {
335
347
  cache.set('suggestions', suggestionCacheKey, suggestions);
336
348
  console.error(`${colors.cyan}[Intelligence]${colors.reset} 💡 ${suggestions.length} suggestions available`);
337
-
349
+
338
350
  // Show top suggestions
339
351
  suggestions.slice(0, 3).forEach(s => {
340
352
  console.error(`${colors.yellow}[Suggestion]${colors.reset} ${s.type}: ${s.message}`);
341
353
  });
342
354
  }
343
355
  }
344
-
356
+
345
357
  } catch (error) {
346
358
  // Silent fail - intelligence should not break the tool
347
359
  if (VERBOSE) {
@@ -357,7 +369,7 @@ async function logEditToMemory({ instruction, files, strategy, success, errorMes
357
369
  try {
358
370
  const engine = await getMemoryEngine();
359
371
  const diffSize = Object.keys(files).reduce((total, fp) => total + (files[fp]?.length || 0), 0);
360
-
372
+
361
373
  engine.logEditToMemory({
362
374
  instruction: instruction.substring(0, 500),
363
375
  files: Object.keys(files),
@@ -367,7 +379,7 @@ async function logEditToMemory({ instruction, files, strategy, success, errorMes
367
379
  durationMs,
368
380
  errorMessage
369
381
  });
370
-
382
+
371
383
  console.error(`${colors.cyan}[Memory]${colors.reset} Edit logged to history`);
372
384
  } catch (error) {
373
385
  console.error(`${colors.yellow}[Memory]${colors.reset} Log failed: ${error.message}`);
@@ -380,13 +392,13 @@ async function logEditToMemory({ instruction, files, strategy, success, errorMes
380
392
  async function analyzeRenameImpact(symbolName, currentFile = null) {
381
393
  try {
382
394
  const engine = await getMemoryEngine();
383
-
395
+
384
396
  // Search for symbol references across codebase
385
- const searchResult = await engine.intelligentSearch(symbolName, {
397
+ const searchResult = await engine.intelligentSearch(symbolName, {
386
398
  limit: 20,
387
399
  currentFile
388
400
  });
389
-
401
+
390
402
  const impactedFiles = new Set();
391
403
  const references = [];
392
404
 
@@ -1056,9 +1068,9 @@ async function reportAudit(data) {
1056
1068
  flushInterval: 5000,
1057
1069
  verbose: VERBOSE
1058
1070
  });
1059
-
1071
+
1060
1072
  queue.add(auditData);
1061
-
1073
+
1062
1074
  if (VERBOSE) {
1063
1075
  const stats = queue.getStats();
1064
1076
  console.error(`${colors.dim}[Audit] Queued for batching. Queue size: ${stats.queued}${colors.reset}`);
@@ -1097,9 +1109,9 @@ async function handleRegexSearch({ query, path: searchPath, caseSensitive = fals
1097
1109
  if (!validation.valid) {
1098
1110
  const latency = Date.now() - start;
1099
1111
  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: \\\\, \\+, \\*`
1112
+ content: [{
1113
+ type: "text",
1114
+ 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
1115
  }],
1104
1116
  isError: true
1105
1117
  };
@@ -1135,12 +1147,12 @@ async function handleRegexSearch({ query, path: searchPath, caseSensitive = fals
1135
1147
 
1136
1148
  try {
1137
1149
  const { stdout, stderr } = await execAsync(command, { maxBuffer: 10 * 1024 * 1024 }); // 10MB buffer
1138
-
1150
+
1139
1151
  // Check for grep regex errors in stderr
1140
1152
  if (stderr && stderr.includes('Invalid regular expression')) {
1141
1153
  throw new Error(`grep: ${stderr}`);
1142
1154
  }
1143
-
1155
+
1144
1156
  const results = stdout.trim().split('\n').filter(Boolean);
1145
1157
 
1146
1158
  const latency = Date.now() - start;
@@ -1205,12 +1217,12 @@ async function handleRegexSearch({ query, path: searchPath, caseSensitive = fals
1205
1217
  content: [{ type: "text", text: `🔍 Regex Search: No matches found for "${query}" in ${searchPath} (${latency}ms)` }]
1206
1218
  };
1207
1219
  }
1208
-
1220
+
1209
1221
  // Handle regex syntax errors
1210
1222
  if (execErr.stderr && execErr.stderr.includes('Invalid')) {
1211
1223
  throw new Error(`Invalid regex syntax: ${execErr.stderr}. Try simplifying your pattern.`);
1212
1224
  }
1213
-
1225
+
1214
1226
  throw execErr;
1215
1227
  }
1216
1228
  } catch (error) {
@@ -1237,14 +1249,14 @@ async function handleRegexSearch({ query, path: searchPath, caseSensitive = fals
1237
1249
  */
1238
1250
  function validateRegex(pattern) {
1239
1251
  const warnings = [];
1240
-
1252
+
1241
1253
  try {
1242
1254
  // Test if it's valid JavaScript regex
1243
1255
  new RegExp(pattern);
1244
1256
  } catch (e) {
1245
1257
  return { valid: false, error: e.message, warnings };
1246
1258
  }
1247
-
1259
+
1248
1260
  // Check for patterns not supported by basic grep (PCRE-only features)
1249
1261
  const unsupportedPatterns = [
1250
1262
  { regex: /\(\?<[=!]/, name: 'Lookbehind (?<=...) or negative lookbehind (?<!...)' },
@@ -1256,18 +1268,18 @@ function validateRegex(pattern) {
1256
1268
  { regex: /\(\?\d*\)/, name: 'Subroutine calls (?1)' },
1257
1269
  { regex: /\(\?&\w+\)/, name: 'Subroutine calls (?&name)' }
1258
1270
  ];
1259
-
1271
+
1260
1272
  for (const { regex, name } of unsupportedPatterns) {
1261
1273
  if (regex.test(pattern)) {
1262
1274
  warnings.push(`Pattern uses ${name} which may not be supported by grep. Results may be incomplete.`);
1263
1275
  }
1264
1276
  }
1265
-
1277
+
1266
1278
  // Check for potentially dangerous patterns (catastrophic backtracking)
1267
1279
  if (/\(\w+\+\+?\)|\(\w+\*\*?\)\*\*?/.test(pattern)) {
1268
1280
  warnings.push('Pattern may cause performance issues (nested quantifiers)');
1269
1281
  }
1270
-
1282
+
1271
1283
  return { valid: true, warnings };
1272
1284
  }
1273
1285
 
@@ -1297,7 +1309,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
1297
1309
  // Convert literal query to glob pattern for file search
1298
1310
  pattern = `**/*${query}*`;
1299
1311
  }
1300
-
1312
+
1301
1313
  // Use fast-glob to find files
1302
1314
  const files = await fg([pattern], {
1303
1315
  cwd,
@@ -1309,7 +1321,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
1309
1321
 
1310
1322
  // Also search file contents if it's not a file pattern
1311
1323
  const contentMatches = [];
1312
-
1324
+
1313
1325
  // For simple queries, also search in common code files
1314
1326
  if (!isRegex && query.length > 1) {
1315
1327
  const codeFiles = await fg(['**/*.{js,ts,jsx,tsx,py,go,rs,java,rb,php}'], {
@@ -1317,7 +1329,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
1317
1329
  ignore: ['node_modules/**', '.git/**'],
1318
1330
  absolute: false
1319
1331
  });
1320
-
1332
+
1321
1333
  // Search in first 50 files to avoid performance issues
1322
1334
  for (const file of codeFiles.slice(0, 50)) {
1323
1335
  try {
@@ -1325,7 +1337,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
1325
1337
  if (content.includes(query)) {
1326
1338
  const lines = content.split('\n');
1327
1339
  const matches = [];
1328
-
1340
+
1329
1341
  lines.forEach((line, idx) => {
1330
1342
  if (line.includes(query)) {
1331
1343
  matches.push({
@@ -1335,7 +1347,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
1335
1347
  });
1336
1348
  }
1337
1349
  });
1338
-
1350
+
1339
1351
  if (matches.length > 0) {
1340
1352
  contentMatches.push({
1341
1353
  file,
@@ -1350,7 +1362,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
1350
1362
  }
1351
1363
 
1352
1364
  const latency = Date.now() - start;
1353
-
1365
+
1354
1366
  // Format output
1355
1367
  let output = `${colors.bold}Filesystem Search Results${colors.reset}\n\n`;
1356
1368
  output += `Query: "${query}"\n`;
@@ -1396,7 +1408,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
1396
1408
  };
1397
1409
  } catch (error) {
1398
1410
  const latency = Date.now() - start;
1399
-
1411
+
1400
1412
  reportAudit({
1401
1413
  tool: 'search_filesystem',
1402
1414
  instruction: `Search: ${query}`,
@@ -1456,7 +1468,7 @@ async function handleReapply({ instruction, files, errorContext = "", attempt =
1456
1468
  */
1457
1469
  async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1458
1470
  const editStartTime = Date.now();
1459
-
1471
+
1460
1472
  // Validate required parameters
1461
1473
  if (!instruction) {
1462
1474
  return {
@@ -1464,16 +1476,16 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1464
1476
  isError: true
1465
1477
  };
1466
1478
  }
1467
-
1479
+
1468
1480
  if (!files || Object.keys(files).length === 0) {
1469
1481
  return {
1470
1482
  content: [{ type: "text", text: "❌ Error: 'files' parameter is required and must contain at least one file" }],
1471
1483
  isError: true
1472
1484
  };
1473
1485
  }
1474
-
1486
+
1475
1487
  const firstFile = Object.keys(files)[0];
1476
-
1488
+
1477
1489
  // 1. Retrieve memory context for enhanced understanding
1478
1490
  let memoryContext = '';
1479
1491
  try {
@@ -1484,7 +1496,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1484
1496
  } catch (error) {
1485
1497
  console.error(`${colors.yellow}[Memory]${colors.reset} Context retrieval failed: ${error.message}`);
1486
1498
  }
1487
-
1499
+
1488
1500
  // 2. Analyze impact for rename operations
1489
1501
  let impactAnalysis = null;
1490
1502
  const renamePattern = instruction.match(/(?:rename|change)\s+(?:function|class|const|let|var|method)\s+(\w+)\s+to\s+(\w+)/i);
@@ -1499,7 +1511,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1499
1511
  console.error(`${colors.yellow}[IMPACT]${colors.reset} Analysis failed: ${error.message}`);
1500
1512
  }
1501
1513
  }
1502
-
1514
+
1503
1515
  // Check for multi-file edits first
1504
1516
  const multiFileEdit = detectCrossFileEdit(instruction, files);
1505
1517
 
@@ -1533,7 +1545,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1533
1545
  errorMessage: result.message || result.error,
1534
1546
  durationMs: Date.now() - editStartTime
1535
1547
  });
1536
-
1548
+
1537
1549
  return {
1538
1550
  content: [{
1539
1551
  type: "text",
@@ -1571,7 +1583,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1571
1583
  errorMessage: error.message,
1572
1584
  durationMs: Date.now() - editStartTime
1573
1585
  });
1574
-
1586
+
1575
1587
  return {
1576
1588
  content: [{
1577
1589
  type: "text",
@@ -1676,7 +1688,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1676
1688
  errorMessage: error.message,
1677
1689
  durationMs: Date.now() - editStartTime
1678
1690
  });
1679
-
1691
+
1680
1692
  return {
1681
1693
  content: [{
1682
1694
  type: "text",
@@ -1807,7 +1819,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1807
1819
 
1808
1820
  // Include impact analysis in result if available
1809
1821
  let resultText = `✅ AST Refactor Applied Successfully\n\nOperation: ${pattern.type}\nChanges: ${transformResult.count || transformResult.replacements || 0} locations\n\nBackup: ${editResult.backupPath}`;
1810
-
1822
+
1811
1823
  if (impactAnalysis && impactAnalysis.suggestion) {
1812
1824
  resultText += `\n\n${impactAnalysis.suggestion}`;
1813
1825
  if (impactAnalysis.impactedFiles.length > 0) {
@@ -1838,7 +1850,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1838
1850
  errorMessage: error.message,
1839
1851
  durationMs: Date.now() - editStartTime
1840
1852
  });
1841
-
1853
+
1842
1854
  return {
1843
1855
  content: [{
1844
1856
  type: "text",
@@ -1861,7 +1873,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1861
1873
  dryRun,
1862
1874
  toolName: 'edit'
1863
1875
  });
1864
-
1876
+
1865
1877
  // Log to memory
1866
1878
  await logEditToMemory({
1867
1879
  instruction,
@@ -1871,7 +1883,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1871
1883
  errorMessage: result.isError ? result.content?.[0]?.text : null,
1872
1884
  durationMs: Date.now() - editStartTime
1873
1885
  });
1874
-
1886
+
1875
1887
  return result;
1876
1888
  }
1877
1889
  }
@@ -1884,7 +1896,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1884
1896
  dryRun,
1885
1897
  toolName: 'edit'
1886
1898
  });
1887
-
1899
+
1888
1900
  // Log to memory
1889
1901
  await logEditToMemory({
1890
1902
  instruction,
@@ -1894,7 +1906,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1894
1906
  errorMessage: result.isError ? result.content?.[0]?.text : null,
1895
1907
  durationMs: Date.now() - editStartTime
1896
1908
  });
1897
-
1909
+
1898
1910
  return result;
1899
1911
  }
1900
1912
 
@@ -1904,14 +1916,14 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1904
1916
  if (memoryContext) {
1905
1917
  enhancedInstruction = `${instruction}\n\n--- CONTEXT FROM CODEBASE ---${memoryContext}`;
1906
1918
  }
1907
-
1919
+
1908
1920
  const result = await handleApplyFast({
1909
1921
  instruction: enhancedInstruction,
1910
1922
  files,
1911
1923
  dryRun,
1912
1924
  toolName: 'edit'
1913
1925
  });
1914
-
1926
+
1915
1927
  // Log to memory
1916
1928
  await logEditToMemory({
1917
1929
  instruction,
@@ -1921,13 +1933,13 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1921
1933
  errorMessage: result.isError ? result.content?.[0]?.text : null,
1922
1934
  durationMs: Date.now() - editStartTime
1923
1935
  });
1924
-
1936
+
1925
1937
  // Auto-trigger intelligence tools in background (non-blocking)
1926
1938
  if (!result.isError) {
1927
1939
  const firstFile = Object.keys(files)[0];
1928
- autoTriggerIntelligence(firstFile, instruction).catch(() => {});
1940
+ autoTriggerIntelligence(firstFile, instruction).catch(() => { });
1929
1941
  }
1930
-
1942
+
1931
1943
  return result;
1932
1944
  }
1933
1945
 
@@ -1939,7 +1951,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
1939
1951
  */
1940
1952
  async function handleSearch({ query, files, path, mode = 'auto', regex = false, caseSensitive = false, contextLines = 2, maxResults = 10 }) {
1941
1953
  const searchStartTime = Date.now();
1942
-
1954
+
1943
1955
  // For regex mode without files, we need to do content-based regex search
1944
1956
  // since fast-glob doesn't support full regex patterns
1945
1957
  if (regex && !files && mode === 'auto') {
@@ -1954,9 +1966,9 @@ async function handleSearch({ query, files, path, mode = 'auto', regex = false,
1954
1966
  if ((detectedMode === 'auto' || detectedMode === 'local') && !regex) {
1955
1967
  try {
1956
1968
  const engine = await getMemoryEngine();
1957
-
1969
+
1958
1970
  // Use intelligent search (UltraHybrid + Smart Routing)
1959
- const searchResult = await engine.intelligentSearch(query, {
1971
+ const searchResult = await engine.intelligentSearch(query, {
1960
1972
  limit: maxResults,
1961
1973
  currentFile: files ? Object.keys(files)[0] : null
1962
1974
  });
@@ -2032,7 +2044,7 @@ async function handleRead({ filePath, filePaths, start_line, end_line, max_lines
2032
2044
  if (filePaths && Array.isArray(filePaths) && filePaths.length > 0) {
2033
2045
  return await handleBatchRead(filePaths, start_line, end_line, max_lines_per_file);
2034
2046
  }
2035
-
2047
+
2036
2048
  // Single file mode (backward compatible)
2037
2049
  return await handleReadFile({ path: filePath, start_line, end_line });
2038
2050
  }
@@ -2043,50 +2055,50 @@ async function handleRead({ filePath, filePaths, start_line, end_line, max_lines
2043
2055
  async function handleBatchRead(filePaths, start_line, end_line, max_lines_per_file) {
2044
2056
  const start = Date.now();
2045
2057
  const MAX_BATCH_SIZE = 50;
2046
-
2058
+
2047
2059
  if (filePaths.length > MAX_BATCH_SIZE) {
2048
2060
  return {
2049
2061
  content: [{ type: "text", text: `❌ Batch read limit exceeded: ${filePaths.length} files (max ${MAX_BATCH_SIZE})` }],
2050
2062
  isError: true
2051
2063
  };
2052
2064
  }
2053
-
2065
+
2054
2066
  console.error(`${colors.cyan}[BATCH READ]${colors.reset} ${filePaths.length} files`);
2055
-
2067
+
2056
2068
  // Read all files in parallel with Promise.all
2057
2069
  const results = await Promise.all(
2058
2070
  filePaths.map(async (filePath) => {
2059
2071
  try {
2060
- const result = await handleReadFileInternal({
2061
- path: filePath,
2062
- start_line,
2072
+ const result = await handleReadFileInternal({
2073
+ path: filePath,
2074
+ start_line,
2063
2075
  end_line,
2064
- max_lines: max_lines_per_file
2076
+ max_lines: max_lines_per_file
2065
2077
  });
2066
2078
  return { path: filePath, ...result, success: true };
2067
2079
  } catch (error) {
2068
- return {
2069
- path: filePath,
2070
- success: false,
2080
+ return {
2081
+ path: filePath,
2082
+ success: false,
2071
2083
  error: error.message,
2072
2084
  content: `❌ Error reading ${filePath}: ${error.message}`
2073
2085
  };
2074
2086
  }
2075
2087
  })
2076
2088
  );
2077
-
2089
+
2078
2090
  // Calculate stats
2079
2091
  const successful = results.filter(r => r.success).length;
2080
2092
  const failed = results.filter(r => !r.success).length;
2081
2093
  const totalLines = results.reduce((sum, r) => sum + (r.lines || 0), 0);
2082
2094
  const totalSize = results.reduce((sum, r) => sum + (r.size || 0), 0);
2083
-
2095
+
2084
2096
  // Format output
2085
2097
  let output = `📚 Batch Read Complete (${filePaths.length} files)\n`;
2086
2098
  output += `✅ Successful: ${successful} ❌ Failed: ${failed}\n`;
2087
2099
  output += `📊 Total: ${totalLines} lines, ${(totalSize / 1024).toFixed(1)} KB\n`;
2088
2100
  output += `⏱️ Latency: ${Date.now() - start}ms\n\n`;
2089
-
2101
+
2090
2102
  // Add each file's content
2091
2103
  results.forEach((result, index) => {
2092
2104
  output += `[${index + 1}/${filePaths.length}] `;
@@ -2105,7 +2117,7 @@ async function handleBatchRead(filePaths, start_line, end_line, max_lines_per_fi
2105
2117
  }
2106
2118
  output += `\n\n`;
2107
2119
  });
2108
-
2120
+
2109
2121
  // Report audit for batch operation
2110
2122
  reportAudit({
2111
2123
  tool: 'read_file',
@@ -2116,7 +2128,7 @@ async function handleBatchRead(filePaths, start_line, end_line, max_lines_per_fi
2116
2128
  input_tokens: Math.ceil(filePaths.join(',').length / 4),
2117
2129
  output_tokens: Math.ceil(output.length / 4)
2118
2130
  });
2119
-
2131
+
2120
2132
  return {
2121
2133
  content: [{ type: "text", text: output }]
2122
2134
  };
@@ -2134,7 +2146,7 @@ async function handleReadFileInternal({ path: filePath, start_line, end_line, ma
2134
2146
  }
2135
2147
 
2136
2148
  const STREAM_THRESHOLD = 1024 * 1024; // 1MB
2137
-
2149
+
2138
2150
  let startLine = start_line ? parseInt(start_line) : 1;
2139
2151
  let endLine = end_line ? parseInt(end_line) : -1;
2140
2152
  let outputContent;
@@ -2262,12 +2274,12 @@ async function handleWarpgrep({ query, include = ".", isRegex = false, caseSensi
2262
2274
  });
2263
2275
  return { content: [{ type: "text", text: msg }] };
2264
2276
  }
2265
-
2277
+
2266
2278
  // Handle regex syntax errors
2267
2279
  if (execErr.stderr && execErr.stderr.includes('Invalid')) {
2268
2280
  throw new Error(`Invalid regex syntax: ${execErr.stderr}. Try simplifying your pattern.`);
2269
2281
  }
2270
-
2282
+
2271
2283
  throw execErr;
2272
2284
  }
2273
2285
  } catch (error) {
@@ -3093,18 +3105,18 @@ async function handleMemorySearch({ query, type = 'all', maxResults = 6, minScor
3093
3105
  */
3094
3106
  async function handleMemoryGet({ type }) {
3095
3107
  const start = Date.now();
3096
-
3108
+
3097
3109
  // Early return if memory engine not ready
3098
3110
  if (!memoryEngineReady) {
3099
3111
  return {
3100
- content: [{
3101
- type: "text",
3102
- text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
3112
+ content: [{
3113
+ type: "text",
3114
+ text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
3103
3115
  }],
3104
3116
  isError: true
3105
3117
  };
3106
3118
  }
3107
-
3119
+
3108
3120
  try {
3109
3121
  const engine = await getMemoryEngine();
3110
3122
  let output = '';
@@ -3138,12 +3150,12 @@ async function handleMemoryGet({ type }) {
3138
3150
  } else if (type === 'reindex') {
3139
3151
  output = `🔄 Re-indexing Codebase\n\n`;
3140
3152
  output += `Starting re-index in background...\n`;
3141
-
3153
+
3142
3154
  // Trigger re-index in background
3143
3155
  engine.performInitialScan().catch(err => {
3144
3156
  console.error('[Reindex] Error:', err.message);
3145
3157
  });
3146
-
3158
+
3147
3159
  output += `✅ Re-index started. Check stats in a few seconds.\n`;
3148
3160
  output += `Use 'memory_get stats' to check progress.`;
3149
3161
  } else if (type === 'intelligence') {
@@ -3191,18 +3203,18 @@ async function handleMemoryGet({ type }) {
3191
3203
  */
3192
3204
  async function handleDetectPatterns() {
3193
3205
  const start = Date.now();
3194
-
3206
+
3195
3207
  // Early return if memory engine not ready
3196
3208
  if (!memoryEngineReady) {
3197
3209
  return {
3198
- content: [{
3199
- type: "text",
3200
- text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
3210
+ content: [{
3211
+ type: "text",
3212
+ text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
3201
3213
  }],
3202
3214
  isError: true
3203
3215
  };
3204
3216
  }
3205
-
3217
+
3206
3218
  try {
3207
3219
  const engine = await getMemoryEngine();
3208
3220
  const patterns = await engine.detectPatterns();
@@ -3250,18 +3262,18 @@ async function handleDetectPatterns() {
3250
3262
  */
3251
3263
  async function handleGetSuggestions({ currentFile, currentLine }) {
3252
3264
  const start = Date.now();
3253
-
3265
+
3254
3266
  // Early return if memory engine not ready
3255
3267
  if (!memoryEngineReady) {
3256
3268
  return {
3257
- content: [{
3258
- type: "text",
3259
- text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
3269
+ content: [{
3270
+ type: "text",
3271
+ text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
3260
3272
  }],
3261
3273
  isError: true
3262
3274
  };
3263
3275
  }
3264
-
3276
+
3265
3277
  try {
3266
3278
  const engine = await getMemoryEngine();
3267
3279
  const suggestions = await engine.getSuggestions({
@@ -3315,18 +3327,18 @@ async function handleGetSuggestions({ currentFile, currentLine }) {
3315
3327
  */
3316
3328
  async function handleSelectStrategy({ instruction, files = [] }) {
3317
3329
  const start = Date.now();
3318
-
3330
+
3319
3331
  // Early return if memory engine not ready
3320
3332
  if (!memoryEngineReady) {
3321
3333
  return {
3322
- content: [{
3323
- type: "text",
3324
- text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
3334
+ content: [{
3335
+ type: "text",
3336
+ text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
3325
3337
  }],
3326
3338
  isError: true
3327
3339
  };
3328
3340
  }
3329
-
3341
+
3330
3342
  try {
3331
3343
  const engine = await getMemoryEngine();
3332
3344
  const result = await engine.selectStrategy(instruction, { files });
@@ -3372,11 +3384,11 @@ async function handleSelectStrategy({ instruction, files = [] }) {
3372
3384
  */
3373
3385
  async function handleHealthCheck() {
3374
3386
  const start = Date.now();
3375
-
3387
+
3376
3388
  try {
3377
3389
  const memoryStats = memoryEngine ? await memoryEngine.getStats() : null;
3378
3390
  const watcherStats = memoryEngine?.watcher ? memoryEngine.watcher.getStats() : null;
3379
-
3391
+
3380
3392
  const health = {
3381
3393
  status: 'healthy',
3382
3394
  timestamp: Date.now(),
@@ -3399,20 +3411,20 @@ async function handleHealthCheck() {
3399
3411
  },
3400
3412
  latencyMs: Date.now() - start
3401
3413
  };
3402
-
3414
+
3403
3415
  reportAudit({
3404
3416
  tool: 'health_check',
3405
3417
  status: 'success',
3406
3418
  latency_ms: health.latencyMs
3407
3419
  });
3408
-
3420
+
3409
3421
  return {
3410
3422
  content: [{
3411
3423
  type: 'text',
3412
3424
  text: JSON.stringify(health, null, 2)
3413
3425
  }]
3414
3426
  };
3415
-
3427
+
3416
3428
  } catch (error) {
3417
3429
  reportAudit({
3418
3430
  tool: 'health_check',
@@ -3420,7 +3432,7 @@ async function handleHealthCheck() {
3420
3432
  error_message: error.message,
3421
3433
  latency_ms: Date.now() - start
3422
3434
  });
3423
-
3435
+
3424
3436
  return {
3425
3437
  content: [{
3426
3438
  type: 'text',
@@ -3442,30 +3454,26 @@ async function handleHealthCheck() {
3442
3454
  // Acquire lock before starting - prevents multiple instances
3443
3455
  const lockAcquired = await acquireLock();
3444
3456
  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);
3457
+ console.error(`${colors.red}[ERROR]${colors.reset} Failed to acquire lock. Another mcfast-mcp instance may be running.`);
3458
+ console.error(`${colors.yellow}[HINT]${colors.reset} Delete ${LOCK_FILE_PATH} if you're sure no other instance is running.`);
3459
+ process.exit(1);
3448
3460
  }
3449
3461
 
3450
3462
  const transport = new StdioServerTransport();
3451
3463
 
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
-
3464
+ // NOTE: Do NOT add process.stdin.on('end') handler here.
3465
+ // VSCode and some MCP clients send ephemeral EOF / half-close signals during
3466
+ // the initialize handshake, which would cause premature gracefulShutdown
3467
+ // before the server has a chance to respond, resulting in the
3468
+ // "Error: calling 'initialize': EOF" error.
3469
+ //
3470
+ // The MCP SDK's StdioServerTransport already manages transport lifecycle.
3471
+ // Only handle unrecoverable stdout errors.
3466
3472
  process.stdout.on('error', (err) => {
3467
- console.error('[MCP] stdout error:', err.code);
3468
- gracefulShutdown('stdout.error');
3473
+ if (err.code === 'EPIPE' || err.code === 'ERR_STREAM_DESTROYED') {
3474
+ console.error('[MCP] stdout closed, shutting down:', err.code);
3475
+ gracefulShutdown('stdout.error');
3476
+ }
3469
3477
  });
3470
3478
 
3471
3479
  // Pre-initialize memory engine in background
@@ -3476,35 +3484,35 @@ backgroundInitializeMemoryEngine().catch(err => {
3476
3484
 
3477
3485
  // Graceful shutdown handler
3478
3486
  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);
3487
+ console.error(`${colors.yellow}[Shutdown]${colors.reset} Received ${signal}, cleaning up...`);
3488
+
3489
+ const SHUTDOWN_TIMEOUT = 5000; // 5 seconds per MCP best practices
3490
+
3491
+ try {
3492
+ await Promise.race([
3493
+ (async () => {
3494
+ // Stop memory engine
3495
+ if (memoryEngine && memoryEngineReady) {
3496
+ await memoryEngine.cleanup();
3497
+ }
3498
+
3499
+ // Flush audit queue
3500
+ const auditQ = getAuditQueue();
3501
+ await auditQ.destroy();
3502
+
3503
+ // Release lock
3504
+ await releaseLock();
3505
+ })(),
3506
+ new Promise(resolve => setTimeout(resolve, SHUTDOWN_TIMEOUT))
3507
+ ]);
3508
+
3509
+ console.error(`${colors.green}[Shutdown]${colors.reset} Cleanup complete`);
3510
+ } catch (error) {
3511
+ console.error(`${colors.red}[Shutdown]${colors.reset} Error during cleanup: ${error.message}`);
3512
+ }
3513
+
3514
+ // Always exit (even if cleanup fails) to prevent zombie processes
3515
+ process.exit(0);
3508
3516
  }
3509
3517
 
3510
3518
  process.on('SIGINT', () => gracefulShutdown('SIGINT'));
@@ -3512,13 +3520,13 @@ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
3512
3520
 
3513
3521
  // Protect against zombie processes - handle unexpected errors
3514
3522
  process.on('uncaughtException', (err) => {
3515
- console.error('[MCP] Uncaught exception:', err);
3516
- gracefulShutdown('uncaughtException');
3523
+ console.error('[MCP] Uncaught exception:', err);
3524
+ gracefulShutdown('uncaughtException');
3517
3525
  });
3518
3526
 
3519
3527
  process.on('unhandledRejection', (reason, promise) => {
3520
- console.error('[MCP] Unhandled rejection:', reason);
3521
- gracefulShutdown('unhandledRejection');
3528
+ console.error('[MCP] Unhandled rejection:', reason);
3529
+ gracefulShutdown('unhandledRejection');
3522
3530
  });
3523
3531
 
3524
3532
  await server.connect(transport);