cf-memory-mcp 3.3.1 → 3.5.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 +154 -104
  2. package/package.json +1 -1
@@ -229,81 +229,89 @@ class CFMemoryMCP {
229
229
  }
230
230
 
231
231
  /**
232
- * Handle local project indexing
232
+ * Handle local project indexing - scans local files and sends them via MCP
233
233
  */
234
234
  async handleIndexProject(message) {
235
- const { project_path, project_name } = message.params.arguments;
235
+ const { project_path, project_name, include_patterns, exclude_patterns } = message.params.arguments;
236
236
  const resolvedPath = path.resolve(project_path);
237
237
  const name = project_name || path.basename(resolvedPath);
238
238
 
239
239
  this.logDebug(`Intercepted index_project for: ${resolvedPath} (${name})`);
240
240
 
241
241
  try {
242
- // 1. Check/Create Project remotely
243
- const projectResult = await this.makeRequest({
244
- jsonrpc: '2.0',
245
- id: `create-proj-${Date.now()}`,
246
- method: 'tools/call',
247
- params: {
248
- name: 'index_project',
249
- arguments: {
250
- project_path: resolvedPath,
251
- project_name: name
252
- }
253
- }
254
- });
255
-
256
- if (projectResult.error) {
257
- throw new Error(`Remote project creation failed: ${projectResult.error.message}`);
258
- }
259
-
260
- // Parse the result to get project ID
261
- let projectId;
262
- try {
263
- const content = JSON.parse(projectResult.result.content[0].text);
264
- projectId = content.project_id;
265
- } catch (e) {
266
- // Fallback: try to find existing project if creation didn't return generic ID
267
- this.logDebug('Could not parse create result, listing projects...');
268
- const listResp = await this.makeAuthenticatedRequest('/api/projects', 'GET');
269
- const existing = listResp.projects?.find(p => p.name === name);
270
- if (existing) {
271
- projectId = existing.id;
272
- } else {
273
- throw new Error('Could not determine project ID');
274
- }
275
- }
276
-
277
- // 2. Scan Local Files
242
+ // 1. Scan Local Files
278
243
  this.logDebug(`Scanning files in ${resolvedPath}...`);
279
- const files = this.scanDirectory(resolvedPath);
244
+ const files = this.scanDirectory(resolvedPath, '', include_patterns, exclude_patterns);
280
245
  this.logDebug(`Found ${files.length} files to index.`);
281
246
 
282
- // 3. Batch Upload
283
- let indexed = 0;
284
- const BATCH_SIZE = 10;
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;
285
275
 
286
276
  for (let i = 0; i < files.length; i += BATCH_SIZE) {
287
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
+
288
304
  try {
289
- const payload = {
290
- files: batch.map(f => ({
291
- path: f.relativePath,
292
- content: f.content
293
- }))
294
- };
295
-
296
- await this.makeAuthenticatedRequest(`/api/projects/${projectId}/files/batch`, 'POST', payload);
297
- indexed += batch.length;
298
-
299
- // Optional: Send progress notification to client (if supported)
300
- // this.logDebug(`Indexed ${indexed}/${files.length}`);
301
- } catch (err) {
302
- this.logError(`Batch upload failed at index ${i}`, err);
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);
303
311
  }
304
312
  }
305
313
 
306
- // 4. Return success
314
+ // 3. Return aggregated success
307
315
  const response = {
308
316
  jsonrpc: '2.0',
309
317
  id: message.id,
@@ -312,9 +320,11 @@ class CFMemoryMCP {
312
320
  type: 'text',
313
321
  text: JSON.stringify({
314
322
  project_id: projectId,
323
+ project_name: name,
315
324
  files_found: files.length,
316
- files_indexed: indexed,
317
- status: 'completed'
325
+ files_indexed: totalIndexed,
326
+ chunks_created: totalChunks,
327
+ status: 'complete'
318
328
  }, null, 2)
319
329
  }]
320
330
  }
@@ -336,12 +346,44 @@ class CFMemoryMCP {
336
346
  }
337
347
 
338
348
  /**
339
- * Recursive scan
349
+ * Recursive directory scan with filtering
340
350
  */
341
- scanDirectory(dir, basePath = '') {
351
+ scanDirectory(dir, basePath = '', includePatterns = null, excludePatterns = null) {
342
352
  let files = [];
343
- const EXCLUDE_DIRS = ['node_modules', '.git', 'dist', 'build', '.wrangler', 'coverage'];
344
- const INCLUDE_EXTS = ['.ts', '.tsx', '.js', '.jsx', '.py', '.md', '.sql', '.css', '.html'];
353
+
354
+ // Default exclusions (always excluded)
355
+ const DEFAULT_EXCLUDE_DIRS = [
356
+ // Version control
357
+ '.git', '.svn', '.hg',
358
+ // Dependencies
359
+ 'node_modules', 'vendor', 'packages',
360
+ // Build outputs
361
+ 'dist', 'build', 'out', '.next', '.nuxt',
362
+ // Python
363
+ '__pycache__', '.pytest_cache', 'venv', '.venv',
364
+ // Test coverage
365
+ 'coverage', '.coverage', '.nyc_output',
366
+ // IDE/Editor history & settings
367
+ '.history', '.vscode', '.idea', '.vs',
368
+ // Cloudflare/tooling
369
+ '.wrangler', '.turbo', '.cache'
370
+ ];
371
+
372
+ // Default file extensions to include
373
+ const DEFAULT_INCLUDE_EXTS = [
374
+ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
375
+ '.py', '.rb', '.go', '.rs', '.java', '.kt', '.scala',
376
+ '.cs', '.cpp', '.c', '.h', '.hpp', '.swift', '.php',
377
+ '.sql', '.sh', '.bash',
378
+ '.json', '.yaml', '.yml', '.toml',
379
+ '.html', '.css', '.scss', '.vue', '.svelte',
380
+ '.md', '.mdx'
381
+ ];
382
+
383
+ // Merge custom exclude patterns
384
+ const effectiveExcludes = excludePatterns
385
+ ? [...DEFAULT_EXCLUDE_DIRS, ...excludePatterns]
386
+ : DEFAULT_EXCLUDE_DIRS;
345
387
 
346
388
  try {
347
389
  const entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -351,21 +393,61 @@ class CFMemoryMCP {
351
393
  const relPath = path.join(basePath, entry.name);
352
394
 
353
395
  if (entry.isDirectory()) {
354
- if (!EXCLUDE_DIRS.includes(entry.name)) {
355
- files = files.concat(this.scanDirectory(fullPath, relPath));
396
+ // Check if directory should be excluded
397
+ const shouldExclude = effectiveExcludes.some(pattern => {
398
+ if (pattern.includes('*')) {
399
+ // Simple glob matching
400
+ const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
401
+ return regex.test(entry.name);
402
+ }
403
+ return entry.name === pattern;
404
+ });
405
+
406
+ if (!shouldExclude) {
407
+ files = files.concat(this.scanDirectory(fullPath, relPath, includePatterns, excludePatterns));
356
408
  }
357
409
  } else if (entry.isFile()) {
358
- if (INCLUDE_EXTS.includes(path.extname(entry.name).toLowerCase())) {
410
+ const ext = path.extname(entry.name).toLowerCase();
411
+
412
+ // Check if file should be excluded
413
+ const fileExcluded = effectiveExcludes.some(pattern => {
414
+ if (pattern.startsWith('*.')) {
415
+ return ext === pattern.substring(1);
416
+ }
417
+ return entry.name === pattern || relPath.includes(pattern);
418
+ });
419
+
420
+ if (fileExcluded) continue;
421
+
422
+ // Check if file matches include patterns
423
+ let shouldInclude = false;
424
+ if (includePatterns && includePatterns.length > 0) {
425
+ shouldInclude = includePatterns.some(pattern => {
426
+ if (pattern.startsWith('*.')) {
427
+ return ext === pattern.substring(1);
428
+ }
429
+ return entry.name.includes(pattern) || relPath.includes(pattern);
430
+ });
431
+ } else {
432
+ // Use default extensions
433
+ shouldInclude = DEFAULT_INCLUDE_EXTS.includes(ext);
434
+ }
435
+
436
+ if (shouldInclude) {
359
437
  try {
360
438
  const content = fs.readFileSync(fullPath, 'utf8');
361
- // Skip huge files > 1MB
362
- if (content.length < 1024 * 1024) {
439
+ // Skip huge files > 500KB
440
+ if (content.length < 512 * 1024) {
363
441
  files.push({
364
442
  relativePath: relPath,
365
443
  content: content
366
444
  });
445
+ } else {
446
+ this.logDebug(`Skipping large file (${Math.round(content.length/1024)}KB): ${relPath}`);
367
447
  }
368
- } catch (e) { /* ignore read errors */ }
448
+ } catch (e) {
449
+ this.logDebug(`Could not read file: ${relPath}`);
450
+ }
369
451
  }
370
452
  }
371
453
  }
@@ -376,40 +458,7 @@ class CFMemoryMCP {
376
458
  }
377
459
 
378
460
  /**
379
- * Helper for Direct API calls (non-JSON-RPC)
380
- */
381
- async makeAuthenticatedRequest(endpoint, method, body) {
382
- return new Promise((resolve, reject) => {
383
- const serverUrl = 'https://cf-memory-mcp-simplified.johnlam90.workers.dev' + endpoint; // Hardcoded base for simplicity in this helper
384
- const url = new URL(serverUrl);
385
- const postData = body ? JSON.stringify(body) : '';
386
-
387
- const options = {
388
- hostname: url.hostname,
389
- port: 443,
390
- path: url.pathname,
391
- method: method,
392
- headers: {
393
- 'Content-Type': 'application/json',
394
- 'X-API-Key': process.env.CF_MEMORY_API_KEY || API_KEY
395
- }
396
- };
397
-
398
- const req = https.request(options, (res) => {
399
- let data = '';
400
- res.on('data', c => data += c);
401
- res.on('end', () => {
402
- try { resolve(JSON.parse(data)); } catch (e) { resolve({}); }
403
- });
404
- });
405
- req.on('error', reject);
406
- if (postData) req.write(postData);
407
- req.end();
408
- });
409
- }
410
-
411
- /**
412
- * Make HTTP request to the Cloudflare Worker (Standard MCP)
461
+ * Make HTTP request to the Cloudflare Worker (MCP JSON-RPC)
413
462
  */
414
463
  async makeRequest(message) {
415
464
  return new Promise((resolve) => {
@@ -427,7 +476,8 @@ class CFMemoryMCP {
427
476
  'Accept': 'application/json',
428
477
  'Accept-Encoding': 'identity',
429
478
  'User-Agent': this.userAgent,
430
- 'X-API-Key': API_KEY,
479
+ 'Authorization': `Bearer ${API_KEY}`,
480
+ 'X-API-Key': API_KEY, // Also include for backwards compatibility
431
481
  'Content-Length': Buffer.byteLength(postData)
432
482
  },
433
483
  timeout: TIMEOUT_MS
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cf-memory-mcp",
3
- "version": "3.3.1",
3
+ "version": "3.5.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": {