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.
- package/bin/cf-memory-mcp.js +154 -104
- package/package.json +1 -1
package/bin/cf-memory-mcp.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
//
|
|
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:
|
|
317
|
-
|
|
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
|
-
|
|
344
|
-
|
|
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
|
|
355
|
-
|
|
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
|
-
|
|
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 >
|
|
362
|
-
if (content.length <
|
|
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) {
|
|
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
|
-
*
|
|
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
|
-
'
|
|
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
|
+
"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": {
|