cf-memory-mcp 3.3.0 → 3.4.0

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/bin/cf-memory-mcp.js +282 -27
  2. package/package.json +2 -2
@@ -17,12 +17,15 @@ const https = require('https');
17
17
  const { URL } = require('url');
18
18
  const os = require('os');
19
19
  const process = require('process');
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+ const crypto = require('crypto');
20
23
 
21
24
  // Configuration
22
- const STREAMABLE_HTTP_URL = 'https://cf-memory-mcp.johnlam90.workers.dev/mcp';
23
- const LEGACY_SERVER_URL = 'https://cf-memory-mcp.johnlam90.workers.dev/mcp/message';
25
+ const STREAMABLE_HTTP_URL = 'https://cf-memory-mcp-simplified.johnlam90.workers.dev/mcp/message';
26
+ const LEGACY_SERVER_URL = 'https://cf-memory-mcp-simplified.johnlam90.workers.dev/mcp/message';
24
27
  const PACKAGE_VERSION = require('../package.json').version;
25
- const TIMEOUT_MS = 30000;
28
+ const TIMEOUT_MS = 60000; // Increased timeout for batch operations
26
29
  const CONNECT_TIMEOUT_MS = 10000;
27
30
 
28
31
  // Get API key from environment variable (will be checked later)
@@ -56,7 +59,7 @@ class CFMemoryMCP {
56
59
  this.logDebug(`Legacy Server URL: ${this.legacyServerUrl}`);
57
60
  this.logDebug(`User Agent: ${this.userAgent}`);
58
61
  }
59
-
62
+
60
63
  /**
61
64
  * Log debug messages to stderr (won't interfere with MCP communication)
62
65
  */
@@ -65,7 +68,7 @@ class CFMemoryMCP {
65
68
  process.stderr.write(`[DEBUG] ${new Date().toISOString()} ${message}\n`);
66
69
  }
67
70
  }
68
-
71
+
69
72
  /**
70
73
  * Log error messages to stderr
71
74
  */
@@ -76,7 +79,7 @@ class CFMemoryMCP {
76
79
  process.stderr.write(`[ERROR] ${timestamp} ${error.stack}\n`);
77
80
  }
78
81
  }
79
-
82
+
80
83
  /**
81
84
  * Start listening for MCP messages on stdin
82
85
  */
@@ -90,7 +93,7 @@ class CFMemoryMCP {
90
93
  process.exit(1);
91
94
  }
92
95
  }
93
-
96
+
94
97
  /**
95
98
  * Test connectivity to the Cloudflare Worker
96
99
  */
@@ -133,37 +136,37 @@ class CFMemoryMCP {
133
136
  }
134
137
  }
135
138
  }
136
-
139
+
137
140
  /**
138
141
  * Process stdio input/output for MCP communication
139
142
  */
140
143
  async processStdio() {
141
144
  let buffer = '';
142
-
145
+
143
146
  // Handle stdin data
144
147
  for await (const chunk of process.stdin) {
145
148
  buffer += chunk;
146
-
149
+
147
150
  // Process complete JSON-RPC messages (one per line)
148
151
  let newlineIndex;
149
152
  while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
150
153
  const line = buffer.slice(0, newlineIndex).trim();
151
154
  buffer = buffer.slice(newlineIndex + 1);
152
-
155
+
153
156
  if (line) {
154
157
  await this.handleMessage(line);
155
158
  }
156
159
  }
157
160
  }
158
-
161
+
159
162
  // Process any remaining buffer content
160
163
  if (buffer.trim()) {
161
164
  await this.handleMessage(buffer.trim());
162
165
  }
163
-
166
+
164
167
  this.logDebug('Stdin closed, shutting down...');
165
168
  }
166
-
169
+
167
170
  /**
168
171
  * Handle a single MCP message
169
172
  */
@@ -171,15 +174,46 @@ class CFMemoryMCP {
171
174
  try {
172
175
  const message = JSON.parse(messageStr);
173
176
  this.logDebug(`Processing message: ${message.method} (id: ${message.id})`);
174
-
177
+
178
+ // Handle lifecycle methods locally
179
+ if (message.method === 'initialize') {
180
+ const response = {
181
+ jsonrpc: '2.0',
182
+ id: message.id,
183
+ result: {
184
+ protocolVersion: '2025-03-26',
185
+ capabilities: {
186
+ tools: {}
187
+ },
188
+ serverInfo: {
189
+ name: 'cf-memory-mcp',
190
+ version: PACKAGE_VERSION
191
+ }
192
+ }
193
+ };
194
+ process.stdout.write(JSON.stringify(response) + '\n');
195
+ return;
196
+ }
197
+
198
+ if (message.method === 'notifications/initialized') {
199
+ this.logDebug('MCP handshake completed');
200
+ return;
201
+ }
202
+
203
+ // Intercept index_project tool call to perform local scanning
204
+ if (message.method === 'tools/call' && message.params && message.params.name === 'index_project') {
205
+ await this.handleIndexProject(message);
206
+ return;
207
+ }
208
+
175
209
  const response = await this.makeRequest(message);
176
-
210
+
177
211
  // Send response to stdout
178
212
  process.stdout.write(JSON.stringify(response) + '\n');
179
-
213
+
180
214
  } catch (error) {
181
215
  this.logError('Error handling message:', error);
182
-
216
+
183
217
  // Send error response
184
218
  const errorResponse = {
185
219
  jsonrpc: '2.0',
@@ -193,16 +227,236 @@ class CFMemoryMCP {
193
227
  process.stdout.write(JSON.stringify(errorResponse) + '\n');
194
228
  }
195
229
  }
196
-
230
+
231
+ /**
232
+ * Handle local project indexing - scans local files and sends them via MCP
233
+ */
234
+ async handleIndexProject(message) {
235
+ const { project_path, project_name, include_patterns, exclude_patterns } = message.params.arguments;
236
+ const resolvedPath = path.resolve(project_path);
237
+ const name = project_name || path.basename(resolvedPath);
238
+
239
+ this.logDebug(`Intercepted index_project for: ${resolvedPath} (${name})`);
240
+
241
+ try {
242
+ // 1. Scan Local Files
243
+ this.logDebug(`Scanning files in ${resolvedPath}...`);
244
+ const files = this.scanDirectory(resolvedPath, '', include_patterns, exclude_patterns);
245
+ this.logDebug(`Found ${files.length} files to index.`);
246
+
247
+ if (files.length === 0) {
248
+ const response = {
249
+ jsonrpc: '2.0',
250
+ id: message.id,
251
+ result: {
252
+ content: [{
253
+ type: 'text',
254
+ text: JSON.stringify({
255
+ project_id: null,
256
+ project_name: name,
257
+ files_found: 0,
258
+ files_indexed: 0,
259
+ chunks_created: 0,
260
+ status: 'complete',
261
+ message: 'No matching files found in directory'
262
+ }, null, 2)
263
+ }]
264
+ }
265
+ };
266
+ process.stdout.write(JSON.stringify(response) + '\n');
267
+ return;
268
+ }
269
+
270
+ // 2. Send files directly via MCP tool call (batched to avoid timeouts)
271
+ const BATCH_SIZE = 20; // Process 20 files at a time
272
+ let totalIndexed = 0;
273
+ let totalChunks = 0;
274
+ let projectId = null;
275
+
276
+ for (let i = 0; i < files.length; i += BATCH_SIZE) {
277
+ const batch = files.slice(i, i + BATCH_SIZE);
278
+ this.logDebug(`Processing batch ${Math.floor(i/BATCH_SIZE) + 1}/${Math.ceil(files.length/BATCH_SIZE)} (${batch.length} files)`);
279
+
280
+ const projectResult = await this.makeRequest({
281
+ jsonrpc: '2.0',
282
+ id: `index-batch-${Date.now()}-${i}`,
283
+ method: 'tools/call',
284
+ params: {
285
+ name: 'index_project',
286
+ arguments: {
287
+ project_path: resolvedPath,
288
+ project_name: name,
289
+ files: batch.map(f => ({
290
+ path: f.relativePath,
291
+ content: f.content
292
+ })),
293
+ include_patterns,
294
+ exclude_patterns
295
+ }
296
+ }
297
+ });
298
+
299
+ if (projectResult.error) {
300
+ this.logError(`Batch ${i} failed:`, projectResult.error);
301
+ continue;
302
+ }
303
+
304
+ try {
305
+ const content = JSON.parse(projectResult.result.content[0].text);
306
+ projectId = content.project_id;
307
+ totalIndexed += content.files_indexed || 0;
308
+ totalChunks += content.chunks_created || 0;
309
+ } catch (e) {
310
+ this.logError('Could not parse batch result:', e);
311
+ }
312
+ }
313
+
314
+ // 3. Return aggregated success
315
+ const response = {
316
+ jsonrpc: '2.0',
317
+ id: message.id,
318
+ result: {
319
+ content: [{
320
+ type: 'text',
321
+ text: JSON.stringify({
322
+ project_id: projectId,
323
+ project_name: name,
324
+ files_found: files.length,
325
+ files_indexed: totalIndexed,
326
+ chunks_created: totalChunks,
327
+ status: 'complete'
328
+ }, null, 2)
329
+ }]
330
+ }
331
+ };
332
+ process.stdout.write(JSON.stringify(response) + '\n');
333
+
334
+ } catch (error) {
335
+ this.logError('Index project failed:', error);
336
+ const response = {
337
+ jsonrpc: '2.0',
338
+ id: message.id,
339
+ error: {
340
+ code: -32000,
341
+ message: `Indexing failed: ${error.message}`
342
+ }
343
+ };
344
+ process.stdout.write(JSON.stringify(response) + '\n');
345
+ }
346
+ }
347
+
197
348
  /**
198
- * Make HTTP request to the Cloudflare Worker
349
+ * Recursive directory scan with filtering
350
+ */
351
+ scanDirectory(dir, basePath = '', includePatterns = null, excludePatterns = null) {
352
+ let files = [];
353
+
354
+ // Default exclusions (always excluded)
355
+ const DEFAULT_EXCLUDE_DIRS = [
356
+ 'node_modules', '.git', '.svn', '.hg',
357
+ 'dist', 'build', 'out', '.next', '.nuxt',
358
+ '__pycache__', '.pytest_cache', 'venv', '.venv',
359
+ 'coverage', '.coverage', '.nyc_output',
360
+ '.wrangler', '.turbo', '.cache'
361
+ ];
362
+
363
+ // Default file extensions to include
364
+ const DEFAULT_INCLUDE_EXTS = [
365
+ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
366
+ '.py', '.rb', '.go', '.rs', '.java', '.kt', '.scala',
367
+ '.cs', '.cpp', '.c', '.h', '.hpp', '.swift', '.php',
368
+ '.sql', '.sh', '.bash',
369
+ '.json', '.yaml', '.yml', '.toml',
370
+ '.html', '.css', '.scss', '.vue', '.svelte',
371
+ '.md', '.mdx'
372
+ ];
373
+
374
+ // Merge custom exclude patterns
375
+ const effectiveExcludes = excludePatterns
376
+ ? [...DEFAULT_EXCLUDE_DIRS, ...excludePatterns]
377
+ : DEFAULT_EXCLUDE_DIRS;
378
+
379
+ try {
380
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
381
+
382
+ for (const entry of entries) {
383
+ const fullPath = path.join(dir, entry.name);
384
+ const relPath = path.join(basePath, entry.name);
385
+
386
+ if (entry.isDirectory()) {
387
+ // Check if directory should be excluded
388
+ const shouldExclude = effectiveExcludes.some(pattern => {
389
+ if (pattern.includes('*')) {
390
+ // Simple glob matching
391
+ const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
392
+ return regex.test(entry.name);
393
+ }
394
+ return entry.name === pattern;
395
+ });
396
+
397
+ if (!shouldExclude) {
398
+ files = files.concat(this.scanDirectory(fullPath, relPath, includePatterns, excludePatterns));
399
+ }
400
+ } else if (entry.isFile()) {
401
+ const ext = path.extname(entry.name).toLowerCase();
402
+
403
+ // Check if file should be excluded
404
+ const fileExcluded = effectiveExcludes.some(pattern => {
405
+ if (pattern.startsWith('*.')) {
406
+ return ext === pattern.substring(1);
407
+ }
408
+ return entry.name === pattern || relPath.includes(pattern);
409
+ });
410
+
411
+ if (fileExcluded) continue;
412
+
413
+ // Check if file matches include patterns
414
+ let shouldInclude = false;
415
+ if (includePatterns && includePatterns.length > 0) {
416
+ shouldInclude = includePatterns.some(pattern => {
417
+ if (pattern.startsWith('*.')) {
418
+ return ext === pattern.substring(1);
419
+ }
420
+ return entry.name.includes(pattern) || relPath.includes(pattern);
421
+ });
422
+ } else {
423
+ // Use default extensions
424
+ shouldInclude = DEFAULT_INCLUDE_EXTS.includes(ext);
425
+ }
426
+
427
+ if (shouldInclude) {
428
+ try {
429
+ const content = fs.readFileSync(fullPath, 'utf8');
430
+ // Skip huge files > 500KB
431
+ if (content.length < 512 * 1024) {
432
+ files.push({
433
+ relativePath: relPath,
434
+ content: content
435
+ });
436
+ } else {
437
+ this.logDebug(`Skipping large file (${Math.round(content.length/1024)}KB): ${relPath}`);
438
+ }
439
+ } catch (e) {
440
+ this.logDebug(`Could not read file: ${relPath}`);
441
+ }
442
+ }
443
+ }
444
+ }
445
+ } catch (e) {
446
+ this.logError(`Scan failed for ${dir}:`, e);
447
+ }
448
+ return files;
449
+ }
450
+
451
+ /**
452
+ * Make HTTP request to the Cloudflare Worker (MCP JSON-RPC)
199
453
  */
200
454
  async makeRequest(message) {
201
455
  return new Promise((resolve) => {
202
456
  const serverUrl = this.useStreamableHttp ? this.streamableHttpUrl : this.legacyServerUrl;
203
457
  const url = new URL(serverUrl);
204
458
  const postData = JSON.stringify(message);
205
-
459
+
206
460
  const options = {
207
461
  hostname: url.hostname,
208
462
  port: url.port || 443,
@@ -213,12 +467,13 @@ class CFMemoryMCP {
213
467
  'Accept': 'application/json',
214
468
  'Accept-Encoding': 'identity',
215
469
  'User-Agent': this.userAgent,
216
- 'X-API-Key': API_KEY,
470
+ 'Authorization': `Bearer ${API_KEY}`,
471
+ 'X-API-Key': API_KEY, // Also include for backwards compatibility
217
472
  'Content-Length': Buffer.byteLength(postData)
218
473
  },
219
474
  timeout: TIMEOUT_MS
220
475
  };
221
-
476
+
222
477
  const req = https.request(options, (res) => {
223
478
  let body = '';
224
479
 
@@ -243,7 +498,7 @@ class CFMemoryMCP {
243
498
  }
244
499
  });
245
500
  });
246
-
501
+
247
502
  req.on('error', (error) => {
248
503
  resolve({
249
504
  jsonrpc: '2.0',
@@ -255,7 +510,7 @@ class CFMemoryMCP {
255
510
  }
256
511
  });
257
512
  });
258
-
513
+
259
514
  req.on('timeout', () => {
260
515
  req.destroy();
261
516
  resolve({
@@ -268,12 +523,12 @@ class CFMemoryMCP {
268
523
  }
269
524
  });
270
525
  });
271
-
526
+
272
527
  req.write(postData);
273
528
  req.end();
274
529
  });
275
530
  }
276
-
531
+
277
532
  /**
278
533
  * Graceful shutdown
279
534
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cf-memory-mcp",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "Best-in-class MCP (Model Context Protocol) server for AI memory storage with MIRIX-Inspired Specialized Memory Types (Core, Episodic, Semantic, Procedural, Resource, Knowledge Vault), Progressive Disclosure, AI-Powered Summaries, Context Window Optimization, Summary Memory Feature (TL;DR), Enhanced JSON Processing, Temporal Relationship Tracking, AI-Enhanced Context-Aware + Temporal Intelligence, Smart Tool Recommendation Engine, Memory Intelligence Engine, autonomous optimization, A/B testing, self-improving algorithms, intelligent search, smart auto-features, memory collections, project onboarding workflows, advanced lifecycle management, Project Intelligence for seamless agent handoff, and Unified Project Intelligence Tool with 57% performance improvement",
5
5
  "main": "bin/cf-memory-mcp.js",
6
6
  "bin": {
@@ -131,4 +131,4 @@
131
131
  "typescript": "^5.7.3",
132
132
  "wrangler": "^4.21.2"
133
133
  }
134
- }
134
+ }