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.
- package/bin/cf-memory-mcp.js +282 -27
- package/package.json +2 -2
package/bin/cf-memory-mcp.js
CHANGED
|
@@ -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 =
|
|
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
|
-
*
|
|
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
|
-
'
|
|
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
|
+
"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
|
+
}
|