@mrxkun/mcfast-mcp 4.0.1 → 4.0.3

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.
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Sync Engine
3
3
  * Local ↔ Cloud synchronization for memory
4
+ * Uses MCFAST_TOKEN for authentication (same as Dashboard)
4
5
  */
5
6
 
6
7
  import fs from 'fs';
@@ -11,8 +12,11 @@ import crypto from 'crypto';
11
12
  export class SyncEngine {
12
13
  constructor(options = {}) {
13
14
  this.memoryPath = options.memoryPath || path.join(os.homedir(), '.mcfast', 'memory');
14
- this.cloudEndpoint = options.cloudEndpoint || null;
15
- this.apiKey = options.apiKey || null;
15
+
16
+ // Use Dashboard URL for sync (same as DashboardClient)
17
+ this.baseUrl = options.baseUrl || process.env.MCFAST_DASHBOARD_URL || 'https://mcfast.vercel.app';
18
+ this.apiKey = options.apiKey || process.env.MCFAST_TOKEN;
19
+
16
20
  this.syncInterval = options.syncInterval || 5 * 60 * 1000; // 5 minutes
17
21
  this.lastSyncTime = null;
18
22
  this.syncQueue = [];
@@ -28,6 +32,17 @@ export class SyncEngine {
28
32
  }
29
33
  }
30
34
 
35
+ /**
36
+ * Get headers for API calls
37
+ */
38
+ getHeaders() {
39
+ return {
40
+ 'Content-Type': 'application/json',
41
+ 'Authorization': `Bearer ${this.apiKey}`,
42
+ 'X-MCP-Client': 'mcfast-mcp-v4'
43
+ };
44
+ }
45
+
31
46
  /**
32
47
  * Initialize sync engine
33
48
  */
@@ -144,8 +159,8 @@ export class SyncEngine {
144
159
  * Perform sync
145
160
  */
146
161
  async sync() {
147
- if (!this.isOnline || this.isSyncing || !this.cloudEndpoint) {
148
- return { status: this.isOnline ? 'idle' : 'offline', queued: this.syncQueue.length };
162
+ if (!this.isOnline || this.isSyncing || !this.apiKey) {
163
+ return { status: !this.apiKey ? 'no_token' : this.isOnline ? 'idle' : 'offline', queued: this.syncQueue.length };
149
164
  }
150
165
 
151
166
  this.isSyncing = true;
@@ -155,7 +170,7 @@ export class SyncEngine {
155
170
  // 1. Get local changes since last sync
156
171
  const localChanges = await this.getLocalChanges();
157
172
 
158
- // 2. Push local changes to cloud
173
+ // 2. Push local changes to cloud (via Dashboard API)
159
174
  if (localChanges.length > 0) {
160
175
  await this.pushToCloud(localChanges);
161
176
  }
@@ -233,15 +248,14 @@ export class SyncEngine {
233
248
  }
234
249
 
235
250
  /**
236
- * Push changes to cloud
251
+ * Push changes to cloud via Dashboard API
237
252
  */
238
253
  async pushToCloud(changes) {
239
- const response = await fetch(`${this.cloudEndpoint}/sync/push`, {
254
+ const url = `${this.baseUrl}/api/v1/memory/sync/push`;
255
+
256
+ const response = await fetch(url, {
240
257
  method: 'POST',
241
- headers: {
242
- 'Content-Type': 'application/json',
243
- 'Authorization': `Bearer ${this.apiKey}`
244
- },
258
+ headers: this.getHeaders(),
245
259
  body: JSON.stringify({
246
260
  changes,
247
261
  lastSyncTime: this.lastSyncTime
@@ -256,14 +270,14 @@ export class SyncEngine {
256
270
  }
257
271
 
258
272
  /**
259
- * Pull changes from cloud
273
+ * Pull changes from cloud via Dashboard API
260
274
  */
261
275
  async pullFromCloud() {
262
- const response = await fetch(`${this.cloudEndpoint}/sync/pull?since=${this.lastSyncTime || 0}`, {
276
+ const url = `${this.baseUrl}/api/v1/memory/sync/pull?since=${this.lastSyncTime || 0}`;
277
+
278
+ const response = await fetch(url, {
263
279
  method: 'GET',
264
- headers: {
265
- 'Authorization': `Bearer ${this.apiKey}`
266
- }
280
+ headers: this.getHeaders()
267
281
  });
268
282
 
269
283
  if (!response.ok) {
@@ -327,7 +341,8 @@ export class SyncEngine {
327
341
  isSyncing: this.isSyncing,
328
342
  lastSyncTime: this.lastSyncTime,
329
343
  queueLength: this.syncQueue.length,
330
- syncInterval: this.syncInterval
344
+ syncInterval: this.syncInterval,
345
+ hasToken: !!this.apiKey
331
346
  };
332
347
  }
333
348
 
@@ -1,12 +1,27 @@
1
1
  /**
2
- * memory_get Tool
3
- * Read content from memory storage
4
- * Version: 1.0.0
2
+ * memory_get Tool v2.0
3
+ * Read content from memory storage using MemoryEngine
4
+ * Version: 2.0.0
5
5
  */
6
6
 
7
- const fs = require('fs');
8
- const path = require('path');
9
- const os = require('os');
7
+ import fs from 'fs/promises';
8
+ import path from 'path';
9
+ import os from 'os';
10
+ import { MemoryEngine } from '../memory/index.js';
11
+
12
+ // Memory engine instance (initialized lazily)
13
+ let memoryEngine = null;
14
+
15
+ async function getMemoryEngine() {
16
+ if (!memoryEngine) {
17
+ memoryEngine = new MemoryEngine({
18
+ apiKey: process.env.MCFAST_TOKEN,
19
+ enableSync: true
20
+ });
21
+ await memoryEngine.initialize(process.cwd());
22
+ }
23
+ return memoryEngine;
24
+ }
10
25
 
11
26
  /**
12
27
  * Execute memory get
@@ -14,222 +29,314 @@ const os = require('os');
14
29
  * @param {string} args.path - Path to memory file (relative to ~/.mcfast/memory/)
15
30
  * @param {number} [args.from=1] - Start line
16
31
  * @param {number} [args.lines=50] - Number of lines to read
17
- * @param {Object} context - Runtime context
18
32
  * @returns {Promise<Object>} File content
19
33
  */
20
- async function execute(args, context = {}) {
21
- const {
22
- path: relativePath,
23
- from = 1,
24
- lines = 50
25
- } = args;
26
-
27
- if (!relativePath) {
28
- return {
29
- content: [{
30
- type: "text",
31
- text: "❌ Error: 'path' parameter is required"
32
- }],
33
- isError: true
34
- };
35
- }
36
-
37
- try {
38
- // Security: Only allow reading from memory directory
39
- if (!relativePath.startsWith('memory/') && !relativePath.startsWith('/')) {
40
- return {
41
- content: [{
42
- type: "text",
43
- text: "❌ Error: Invalid path. Must be in memory/ directory"
44
- }],
45
- isError: true
46
- };
47
- }
34
+ export async function execute(args) {
35
+ const {
36
+ path: relativePath,
37
+ from = 1,
38
+ lines = 50
39
+ } = args;
48
40
 
49
- // Construct full path
50
- const memoryDir = path.join(os.homedir(), '.mcfast', 'memory');
51
- const fullPath = path.join(memoryDir, relativePath.replace(/^\//, ''));
52
-
53
- // Security: Ensure path is within memory directory
54
- if (!fullPath.startsWith(memoryDir)) {
55
- return {
56
- content: [{
57
- type: "text",
58
- text: "❌ Error: Path traversal not allowed"
59
- }],
60
- isError: true
61
- };
41
+ if (!relativePath) {
42
+ return {
43
+ content: [{
44
+ type: "text",
45
+ text: "❌ Error: 'path' parameter is required"
46
+ }],
47
+ isError: true
48
+ };
62
49
  }
63
50
 
64
- // Check if file exists
65
- if (!fs.existsSync(fullPath)) {
66
- // Check if it's a special memory path
67
- if (relativePath === 'memory/logs' || relativePath === 'logs') {
68
- return await getMemoryLogs(memoryDir, from, lines);
69
- }
70
-
71
- if (relativePath === 'memory/stats' || relativePath === 'stats') {
72
- return await getMemoryStats(context.store);
73
- }
74
-
75
- return {
76
- content: [{
77
- type: "text",
78
- text: `❌ File not found: ${relativePath}`
79
- }],
80
- isError: true
81
- };
51
+ try {
52
+ // Special paths
53
+ if (relativePath === 'logs' || relativePath === 'memory/logs') {
54
+ return await getMemoryLogs(from, lines);
55
+ }
56
+
57
+ if (relativePath === 'stats' || relativePath === 'memory/stats') {
58
+ return await getMemoryStats();
59
+ }
60
+
61
+ if (relativePath === 'today' || relativePath === 'memory/today') {
62
+ return await getTodayLog();
63
+ }
64
+
65
+ if (relativePath === 'curated' || relativePath === 'memory/curated') {
66
+ return await getCuratedMemories();
67
+ }
68
+
69
+ // Security: Only allow reading from memory directory
70
+ if (!relativePath.startsWith('memory/') && !relativePath.startsWith('/')) {
71
+ return {
72
+ content: [{
73
+ type: "text",
74
+ text: "❌ Error: Invalid path. Must be in memory/ directory"
75
+ }],
76
+ isError: true
77
+ };
78
+ }
79
+
80
+ // Construct full path
81
+ const memoryDir = path.join(os.homedir(), '.mcfast', 'memory');
82
+ const fullPath = path.join(memoryDir, relativePath.replace(/^\//, ''));
83
+
84
+ // Security: Ensure path is within memory directory
85
+ if (!fullPath.startsWith(memoryDir)) {
86
+ return {
87
+ content: [{
88
+ type: "text",
89
+ text: "❌ Error: Path traversal not allowed"
90
+ }],
91
+ isError: true
92
+ };
93
+ }
94
+
95
+ // Check if file exists
96
+ try {
97
+ const stats = await fs.stat(fullPath);
98
+ if (!stats.isFile()) {
99
+ return {
100
+ content: [{
101
+ type: "text",
102
+ text: "❌ Path is not a file"
103
+ }],
104
+ isError: true
105
+ };
106
+ }
107
+ } catch (e) {
108
+ return {
109
+ content: [{
110
+ type: "text",
111
+ text: `❌ File not found: ${relativePath}`
112
+ }],
113
+ isError: true
114
+ };
115
+ }
116
+
117
+ // Read file content
118
+ const content = await fs.readFile(fullPath, 'utf8');
119
+ const allLines = content.split('\n');
120
+
121
+ // Extract specific lines
122
+ const startLine = Math.max(1, from);
123
+ const endLine = Math.min(allLines.length, from - 1 + lines);
124
+ const extractedLines = allLines.slice(startLine - 1, endLine);
125
+ const extractedContent = extractedLines.join('\n');
126
+
127
+ // Format output
128
+ let output = `📄 Memory: ${relativePath}\n`;
129
+ output += `Lines: ${startLine}-${endLine} of ${allLines.length}\n`;
130
+ output += '─'.repeat(40) + '\n';
131
+ output += extractedContent;
132
+
133
+ return {
134
+ content: [{
135
+ type: "text",
136
+ text: output
137
+ }],
138
+ metadata: {
139
+ path: relativePath,
140
+ lines: extractedLines.length,
141
+ totalLines: allLines.length
142
+ }
143
+ };
144
+ } catch (error) {
145
+ console.error('[memory_get] Error:', error);
146
+ return {
147
+ content: [{
148
+ type: "text",
149
+ text: `❌ Error reading memory: ${error.message}`
150
+ }],
151
+ isError: true
152
+ };
82
153
  }
154
+ }
155
+
156
+ /**
157
+ * Get today's daily log
158
+ */
159
+ async function getTodayLog() {
160
+ try {
161
+ const engine = await getMemoryEngine();
162
+ const todayLog = engine.getTodayLog();
163
+
164
+ if (!todayLog || todayLog.length === 0) {
165
+ return {
166
+ content: [{
167
+ type: "text",
168
+ text: "📝 No activity logged today"
169
+ }],
170
+ metadata: { empty: true }
171
+ };
172
+ }
173
+
174
+ let output = `📅 Today's Activity Log\n`;
175
+ output += '─'.repeat(40) + '\n\n';
176
+ output += todayLog;
83
177
 
84
- const stats = fs.statSync(fullPath);
85
- if (!stats.isFile()) {
86
- return {
87
- content: [{
88
- type: "text",
89
- text: "❌ Path is not a file"
90
- }],
91
- isError: true
92
- };
178
+ return {
179
+ content: [{
180
+ type: "text",
181
+ text: output
182
+ }]
183
+ };
184
+ } catch (error) {
185
+ return {
186
+ content: [{
187
+ type: "text",
188
+ text: `❌ Error getting today's log: ${error.message}`
189
+ }],
190
+ isError: true
191
+ };
93
192
  }
193
+ }
94
194
 
95
- // Read file content
96
- const content = fs.readFileSync(fullPath, 'utf8');
97
- const allLines = content.split('\n');
98
-
99
- // Extract specific lines
100
- const startLine = Math.max(1, from);
101
- const endLine = Math.min(allLines.length, from - 1 + lines);
102
- const extractedLines = allLines.slice(startLine - 1, endLine);
103
- const extractedContent = extractedLines.join('\n');
104
-
105
- // Format output
106
- const output = `📄 Memory: ${relativePath}\n`;
107
- output += `Lines: ${startLine}-${endLine} of ${allLines.length}\n`;
108
- output += '─'.repeat(40) + '\n';
109
- output += extractedContent;
110
-
111
- return {
112
- content: [{
113
- type: "text",
114
- text: output
115
- }],
116
- metadata: {
117
- path: relativePath,
118
- lines: extractedLines.length,
119
- totalLines: allLines.length
120
- }
121
- };
122
- } catch (error) {
123
- console.error('[memory_get] Error:', error);
124
- return {
125
- content: [{
126
- type: "text",
127
- text: `❌ Error reading memory: ${error.message}`
128
- }],
129
- isError: true
130
- };
131
- }
195
+ /**
196
+ * Get curated memories
197
+ */
198
+ async function getCuratedMemories() {
199
+ try {
200
+ const engine = await getMemoryEngine();
201
+ const memories = engine.getCuratedMemories();
202
+
203
+ if (!memories || memories.length === 0) {
204
+ return {
205
+ content: [{
206
+ type: "text",
207
+ text: "📝 No curated memories found"
208
+ }],
209
+ metadata: { empty: true }
210
+ };
211
+ }
212
+
213
+ let output = `📚 Curated Memories\n`;
214
+ output += `Found: ${memories.length} memory(s)\n`;
215
+ output += '─'.repeat(40) + '\n\n';
216
+
217
+ memories.forEach((memory, idx) => {
218
+ output += `[${idx + 1}] ${memory.title}\n`;
219
+ output += `Tags: ${memory.tags?.join(', ') || 'none'}\n`;
220
+ output += `${memory.content?.substring(0, 200) || ''}...\n\n`;
221
+ });
222
+
223
+ return {
224
+ content: [{
225
+ type: "text",
226
+ text: output
227
+ }]
228
+ };
229
+ } catch (error) {
230
+ return {
231
+ content: [{
232
+ type: "text",
233
+ text: `❌ Error getting curated memories: ${error.message}`
234
+ }],
235
+ isError: true
236
+ };
237
+ }
132
238
  }
133
239
 
134
240
  /**
135
241
  * Get memory logs (daily logs)
136
242
  */
137
- async function getMemoryLogs(memoryDir, from, lines) {
138
- const logsDir = path.join(memoryDir, 'logs');
139
-
140
- if (!fs.existsSync(logsDir)) {
141
- return {
142
- content: [{
143
- type: "text",
144
- text: "📝 No memory logs found"
145
- }],
146
- metadata: { empty: true }
147
- };
148
- }
149
-
150
- // Get all log files
151
- const logFiles = fs.readdirSync(logsDir)
152
- .filter(f => f.endsWith('.md'))
153
- .sort()
154
- .reverse();
155
-
156
- if (logFiles.length === 0) {
157
- return {
158
- content: [{
159
- type: "text",
160
- text: "📝 No memory logs found"
161
- }],
162
- metadata: { empty: true }
163
- };
164
- }
165
-
166
- // Read latest logs
167
- let output = `📝 Memory Logs\n`;
168
- output += `Found: ${logFiles.length} log file(s)\n`;
169
- output += ''.repeat(40) + '\n\n';
170
-
171
- const filesToRead = logFiles.slice(0, 5);
172
- for (const file of filesToRead) {
173
- const filePath = path.join(logsDir, file);
174
- const content = fs.readFileSync(filePath, 'utf8');
175
- const lines = content.split('\n').slice(0, 20);
176
-
177
- output += `📅 ${file.replace('.md', '')}\n`;
178
- output += lines.join('\n');
179
- output += '\n\n---\n\n';
180
- }
181
-
182
- return {
183
- content: [{
184
- type: "text",
185
- text: output
186
- }]
187
- };
243
+ async function getMemoryLogs(from, lines) {
244
+ const memoryDir = path.join(os.homedir(), '.mcfast', 'memory');
245
+ const logsDir = path.join(memoryDir, 'logs');
246
+
247
+ try {
248
+ const entries = await fs.readdir(logsDir);
249
+ const logFiles = entries
250
+ .filter(f => f.endsWith('.md'))
251
+ .sort()
252
+ .reverse();
253
+
254
+ if (logFiles.length === 0) {
255
+ return {
256
+ content: [{
257
+ type: "text",
258
+ text: "📝 No memory logs found"
259
+ }],
260
+ metadata: { empty: true }
261
+ };
262
+ }
263
+
264
+ // Read latest logs
265
+ let output = `📝 Memory Logs\n`;
266
+ output += `Found: ${logFiles.length} log file(s)\n`;
267
+ output += '─'.repeat(40) + '\n\n';
268
+
269
+ const filesToRead = logFiles.slice(0, 5);
270
+ for (const file of filesToRead) {
271
+ const filePath = path.join(logsDir, file);
272
+ const content = await fs.readFile(filePath, 'utf8');
273
+ const fileLines = content.split('\n').slice(0, 20);
274
+
275
+ output += `📅 ${file.replace('.md', '')}\n`;
276
+ output += fileLines.join('\n');
277
+ output += '\n\n---\n\n';
278
+ }
279
+
280
+ return {
281
+ content: [{
282
+ type: "text",
283
+ text: output
284
+ }]
285
+ };
286
+ } catch (e) {
287
+ return {
288
+ content: [{
289
+ type: "text",
290
+ text: "📝 No memory logs found"
291
+ }],
292
+ metadata: { empty: true }
293
+ };
294
+ }
188
295
  }
189
296
 
190
297
  /**
191
298
  * Get memory statistics
192
299
  */
193
- async function getMemoryStats(store) {
194
- if (!store) {
195
- return {
196
- content: [{
197
- type: "text",
198
- text: "❌ Memory store not initialized"
199
- }],
200
- isError: true
201
- };
202
- }
203
-
204
- try {
205
- const stats = store.getStats();
206
- const syncState = store.getSyncState();
207
-
208
- let output = `📊 Memory Statistics\n`;
209
- output += '─'.repeat(40) + '\n';
210
- output += `Indexed Files: ${stats.fileCount}\n`;
211
- output += `Facts Stored: ${stats.factCount}\n`;
212
- output += `Code Chunks: ${stats.chunkCount}\n`;
213
- output += `Edit History: ${stats.editCount}\n\n`;
214
- output += `Last Sync: ${syncState?.last_sync_time ? new Date(syncState.last_sync_time).toISOString() : 'Never'}\n`;
215
- output += `Sync Status: ${syncState?.last_sync_status || 'unknown'}\n`;
216
-
217
- return {
218
- content: [{
219
- type: "text",
220
- text: output
221
- }],
222
- metadata: stats
223
- };
224
- } catch (error) {
225
- return {
226
- content: [{
227
- type: "text",
228
- text: `❌ Error getting stats: ${error.message}`
229
- }],
230
- isError: true
231
- };
232
- }
300
+ async function getMemoryStats() {
301
+ try {
302
+ const engine = await getMemoryEngine();
303
+ const stats = engine.getStats();
304
+ const syncStatus = engine.getSyncStatus();
305
+
306
+ let output = `📊 Memory Statistics\n`;
307
+ output += '─'.repeat(40) + '\n';
308
+ output += `Indexed Files: ${stats.files || 0}\n`;
309
+ output += `Facts Stored: ${stats.facts || 0}\n`;
310
+ output += `Code Chunks: ${stats.chunks || 0}\n`;
311
+ output += `Embeddings: ${stats.embeddings || 0}\n`;
312
+ output += `Edit History: ${stats.edits || 0}\n\n`;
313
+
314
+ output += `Sync Engine: ${syncStatus.enabled ? '✅ Enabled' : '❌ Disabled'}\n`;
315
+ if (syncStatus.enabled) {
316
+ output += `Sync Status: ${syncStatus.status || 'unknown'}\n`;
317
+ output += `Last Sync: ${syncStatus.lastSync ? new Date(syncStatus.lastSync).toISOString() : 'Never'}\n`;
318
+ output += `Pending Changes: ${syncStatus.pendingChanges || 0}\n`;
319
+ }
320
+
321
+ output += `\nProject: ${stats.projectPath || 'N/A'}\n`;
322
+ output += `Smart Routing: ${engine.smartRoutingEnabled ? '✅ Enabled' : '❌ Disabled'}\n`;
323
+
324
+ return {
325
+ content: [{
326
+ type: "text",
327
+ text: output
328
+ }],
329
+ metadata: stats
330
+ };
331
+ } catch (error) {
332
+ return {
333
+ content: [{
334
+ type: "text",
335
+ text: `❌ Error getting stats: ${error.message}`
336
+ }],
337
+ isError: true
338
+ };
339
+ }
233
340
  }
234
341
 
235
- module.exports = { execute };
342
+ export default { execute };