@mrxkun/mcfast-mcp 4.1.9 → 4.1.11

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mrxkun/mcfast-mcp",
3
- "version": "4.1.9",
4
- "description": "Ultra-fast code editing with WASM acceleration, fuzzy patching, multi-layer caching, and 8 unified tools. v4.1.8: Debug mode - Write logs to .mcfast/mcp-debug.log",
3
+ "version": "4.1.11",
4
+ "description": "Ultra-fast code editing with WASM acceleration, fuzzy patching, multi-layer caching, and 8 unified tools. v4.1.11: Suppress console output to prevent JSON-RPC stream corruption.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "mcfast-mcp": "src/index.js"
package/src/index.js CHANGED
@@ -8,47 +8,33 @@
8
8
  // CRITICAL: Suppress ALL console output in MCP mode to prevent JSON parsing errors
9
9
  // MCP protocol requires stdout to contain ONLY JSON-RPC messages
10
10
 
11
- // Also override console.log - log to file in MCP mode
11
+ // ANSI Color Codes for Terminal Output (MUST be defined before any usage)
12
+ const colors = {
13
+ reset: '\x1b[0m',
14
+ bold: '\x1b[1m',
15
+ dim: '\x1b[2m',
16
+ cyan: '\x1b[36m',
17
+ green: '\x1b[32m',
18
+ yellow: '\x1b[33m',
19
+ blue: '\x1b[34m',
20
+ magenta: '\x1b[35m',
21
+ gray: '\x1b[90m',
22
+ white: '\x1b[37m',
23
+ bgBlack: '\x1b[40m',
24
+ };
25
+
26
+ // Override console.log - suppress in MCP mode
12
27
  const originalConsoleLog = console.log;
13
28
  console.log = function(...args) {
14
- // Write to log file in project directory
15
- try {
16
- const logDir = process.cwd() + '/.mcfast';
17
- const logFile = logDir + '/mcp-debug.log';
18
- const message = `[${new Date().toISOString()}] [LOG] ${args.join(' ')}\n`;
19
-
20
- // Ensure directory exists
21
- require('fs').mkdirSync(logDir, { recursive: true });
22
- require('fs').appendFileSync(logFile, message);
23
- } catch (e) {
24
- // Ignore if can't write
25
- }
29
+ // Suppressed in MCP mode
26
30
  };
27
31
 
28
- // Override console.error - log to file in MCP mode
32
+ // Override console.error - suppress in MCP mode
29
33
  const originalConsoleError = console.error;
30
34
  console.error = function(...args) {
31
- // Write to log file in project directory
32
- try {
33
- const logDir = process.cwd() + '/.mcfast';
34
- const logFile = logDir + '/mcp-debug.log';
35
- const message = `[${new Date().toISOString()}] [ERROR] ${args.join(' ')}\n`;
36
-
37
- // Ensure directory exists
38
- require('fs').mkdirSync(logDir, { recursive: true });
39
- require('fs').appendFileSync(logFile, message);
40
- } catch (e) {
41
- // Ignore if can't write
42
- }
35
+ // Suppressed in MCP mode
43
36
  };
44
37
 
45
- // Write startup marker
46
- try {
47
- const logDir = process.cwd() + '/.mcfast';
48
- require('fs').mkdirSync(logDir, { recursive: true });
49
- require('fs').appendFileSync(logDir + '/mcp-debug.log', `[${new Date().toISOString()}] [START] MCP initializing...\n`);
50
- } catch (e) {}
51
-
52
38
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
53
39
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
54
40
  import { ListToolsRequestSchema, CallToolRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types.js";
@@ -89,9 +75,87 @@ import { getAuditQueue } from './utils/audit-queue.js';
89
75
  import { getIntelligenceCache } from './utils/intelligence-cache.js';
90
76
  import { getContextPrefetcher } from './utils/context-prefetcher.js';
91
77
  import { parallelMemorySearch } from './utils/parallel-search.js';
78
+ import { execute as projectAnalyzeExecute } from './tools/project_analyze.js';
92
79
 
93
80
  const execAsync = promisify(exec);
94
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
+
95
159
  const API_URL = "https://mcfast.vercel.app/api/v1";
96
160
  const TOKEN = process.env.MCFAST_TOKEN;
97
161
  const VERBOSE = process.env.MCFAST_VERBOSE !== 'false'; // Default: true
@@ -109,28 +173,49 @@ async function backgroundInitializeMemoryEngine() {
109
173
  if (memoryEngineInitPromise) return memoryEngineInitPromise;
110
174
 
111
175
  memoryEngineInitPromise = (async () => {
112
- try {
113
- memoryEngine = new MemoryEngine({
114
- apiKey: TOKEN,
115
- enableSync: true
116
- });
117
- // Use a shorter timeout for initialization
118
- const initTimeout = new Promise((_, reject) => {
119
- setTimeout(() => reject(new Error('Memory engine init timeout')), 25000);
120
- });
121
-
122
- await Promise.race([
123
- memoryEngine.initialize(process.cwd()),
124
- initTimeout
125
- ]);
126
-
127
- memoryEngineReady = true;
128
- console.error(`${colors.cyan}[Memory]${colors.reset} Engine initialized successfully`);
129
- } catch (error) {
130
- console.error(`${colors.yellow}[Memory]${colors.reset} Engine initialization failed: ${error.message}`);
131
- // Don't throw - allow partial initialization
132
- memoryEngineReady = false;
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
+ }
133
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;
134
219
  })();
135
220
 
136
221
  return memoryEngineInitPromise;
@@ -331,21 +416,6 @@ async function analyzeRenameImpact(symbolName, currentFile = null) {
331
416
  }
332
417
  }
333
418
 
334
- // ANSI Color Codes for Terminal Output
335
- const colors = {
336
- reset: '\x1b[0m',
337
- bold: '\x1b[1m',
338
- dim: '\x1b[2m',
339
- cyan: '\x1b[36m',
340
- green: '\x1b[32m',
341
- yellow: '\x1b[33m',
342
- blue: '\x1b[34m',
343
- magenta: '\x1b[35m',
344
- gray: '\x1b[90m',
345
- white: '\x1b[37m',
346
- bgBlack: '\x1b[40m',
347
- };
348
-
349
419
  // Tool icons (v2.0 - 4 core tools)
350
420
  const toolIcons = {
351
421
  edit: '✏️',
@@ -630,11 +700,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
630
700
  // MEMORY TOOL 2: memory_get
631
701
  {
632
702
  name: "memory_get",
633
- description: "📚 Get stored memories: today's log, curated memories, or intelligence stats. Use to review recent changes or long-term knowledge.",
703
+ description: "📚 Get stored memories: today's log, curated memories, stats, or trigger re-index. Use to review recent changes or long-term knowledge.",
634
704
  inputSchema: {
635
705
  type: "object",
636
706
  properties: {
637
- type: { type: "string", enum: ["today_log", "curated", "stats", "intelligence"], description: "Type of memory to retrieve" }
707
+ type: { type: "string", enum: ["today_log", "curated", "stats", "intelligence", "reindex"], description: "Type of memory to retrieve or 'reindex' to trigger codebase re-indexing" }
638
708
  },
639
709
  required: ["type"]
640
710
  }
@@ -674,6 +744,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
674
744
  },
675
745
  required: ["instruction"]
676
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
+ }
677
760
  }
678
761
  ],
679
762
  };
@@ -876,6 +959,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
876
959
  result = await handleSelectStrategy(args);
877
960
  summary = 'Strategy selected';
878
961
  }
962
+ // PROJECT ANALYSIS TOOL
963
+ else if (name === "project_analyze") {
964
+ result = await projectAnalyzeExecute(args);
965
+ summary = 'Project analyzed';
966
+ }
879
967
  else {
880
968
  throw new Error(`Tool not found: ${name}`);
881
969
  }
@@ -2075,10 +2163,6 @@ async function handleReadFileInternal({ path: filePath, start_line, end_line, ma
2075
2163
 
2076
2164
  if (startLine < 1) startLine = 1;
2077
2165
  if (endLine < 1 || endLine > totalLines) endLine = totalLines;
2078
- if (startLine > endLine) {
2079
- throw new Error(`Invalid line range: start_line (${startLine}) > end_line (${endLine})`);
2080
- }
2081
-
2082
2166
  if (start_line || end_line) {
2083
2167
  outputContent = lines.slice(startLine - 1, endLine).join('\n');
2084
2168
  } else if (max_lines && lines.length > max_lines) {
@@ -2930,7 +3014,7 @@ async function handleMemorySearch({ query, type = 'all', maxResults = 6, minScor
2930
3014
  score: fact.confidence || 0.8
2931
3015
  }));
2932
3016
  } else if (type === 'code') {
2933
- const searchResult = await engine.ultraSearch(query, { limit: maxResults });
3017
+ const searchResult = await engine.intelligentSearch(query, { limit: maxResults });
2934
3018
  results = searchResult.results.map(result => ({
2935
3019
  type: 'chunk',
2936
3020
  content: result.code || result.content,
@@ -3022,10 +3106,31 @@ async function handleMemoryGet({ type }) {
3022
3106
  } else if (type === 'stats') {
3023
3107
  const stats = await engine.getStats();
3024
3108
  output = `📊 Memory Stats\n\n`;
3025
- output += `Total chunks: ${stats.totalChunks || 0}\n`;
3026
- output += `Total facts: ${stats.totalFacts || 0}\n`;
3027
- output += `Index size: ${stats.indexSize || 0}\n`;
3028
- output += `Last indexed: ${stats.lastIndexed || 'Never'}`;
3109
+ output += `📝 Memory Index\n`;
3110
+ output += ` Files: ${stats.memory?.files || 0}\n`;
3111
+ output += ` Chunks: ${stats.memory?.chunks || 0}\n`;
3112
+ output += ` Embeddings: ${stats.memory?.embeddings || 0}\n\n`;
3113
+ output += `💻 Codebase Index\n`;
3114
+ output += ` Files: ${stats.codebase?.files || 0}\n`;
3115
+ output += ` Facts: ${stats.codebase?.facts || 0}\n`;
3116
+ output += ` Chunks: ${stats.codebase?.chunks || 0}\n`;
3117
+ output += ` Embeddings: ${stats.codebase?.embeddings || 0}\n`;
3118
+ output += ` Edits: ${stats.codebase?.edits || 0}\n\n`;
3119
+ output += `🔄 Indexing\n`;
3120
+ output += ` Complete: ${stats.indexing?.is_complete ? '✅' : '⏳'}\n`;
3121
+ output += ` Indexed: ${stats.indexing?.indexed_files || 0}/${stats.indexing?.total_files || 0}\n`;
3122
+ output += ` Failed: ${stats.indexing?.failed_files || 0}\n`;
3123
+ } else if (type === 'reindex') {
3124
+ output = `🔄 Re-indexing Codebase\n\n`;
3125
+ output += `Starting re-index in background...\n`;
3126
+
3127
+ // Trigger re-index in background
3128
+ engine.performInitialScan().catch(err => {
3129
+ console.error('[Reindex] Error:', err.message);
3130
+ });
3131
+
3132
+ output += `✅ Re-index started. Check stats in a few seconds.\n`;
3133
+ output += `Use 'memory_get stats' to check progress.`;
3029
3134
  } else if (type === 'intelligence') {
3030
3135
  const intelStats = engine.getIntelligenceStats();
3031
3136
  output = `🧠 Intelligence Stats\n\n`;
@@ -3250,12 +3355,50 @@ async function handleSelectStrategy({ instruction, files = [] }) {
3250
3355
  /**
3251
3356
  * Start Server
3252
3357
  */
3358
+
3359
+ // Acquire lock before starting - prevents multiple instances
3360
+ const lockAcquired = await acquireLock();
3361
+ if (!lockAcquired) {
3362
+ console.error(`${colors.red}[ERROR]${colors.reset} Failed to acquire lock. Another mcfast-mcp instance may be running.`);
3363
+ console.error(`${colors.yellow}[HINT]${colors.reset} Delete ${LOCK_FILE_PATH} if you're sure no other instance is running.`);
3364
+ process.exit(1);
3365
+ }
3366
+
3253
3367
  const transport = new StdioServerTransport();
3254
3368
 
3255
3369
  // Pre-initialize memory engine in background
3256
3370
  backgroundInitializeMemoryEngine().catch(err => {
3257
3371
  console.error(`${colors.yellow}[Memory]${colors.reset} Background init error: ${err.message}`);
3372
+ // Don't exit - continue without memory
3258
3373
  });
3259
3374
 
3375
+ // Graceful shutdown handler
3376
+ async function gracefulShutdown(signal) {
3377
+ console.error(`${colors.yellow}[Shutdown]${colors.reset} Received ${signal}, cleaning up...`);
3378
+
3379
+ try {
3380
+ // Stop memory engine
3381
+ if (memoryEngine && memoryEngineReady) {
3382
+ await memoryEngine.cleanup();
3383
+ }
3384
+
3385
+ // Flush audit queue
3386
+ const auditQ = getAuditQueue();
3387
+ await auditQ.destroy();
3388
+
3389
+ // Release lock
3390
+ await releaseLock();
3391
+
3392
+ console.error(`${colors.green}[Shutdown]${colors.reset} Cleanup complete`);
3393
+ } catch (error) {
3394
+ console.error(`${colors.red}[Shutdown]${colors.reset} Error during cleanup: ${error.message}`);
3395
+ }
3396
+
3397
+ process.exit(0);
3398
+ }
3399
+
3400
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
3401
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
3402
+
3260
3403
  await server.connect(transport);
3261
3404
  console.error("mcfast MCP v1.0.0 running on stdio");