cf-memory-mcp 3.3.0 → 3.3.1
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 +240 -26
- 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,196 @@ class CFMemoryMCP {
|
|
|
193
227
|
process.stdout.write(JSON.stringify(errorResponse) + '\n');
|
|
194
228
|
}
|
|
195
229
|
}
|
|
196
|
-
|
|
230
|
+
|
|
197
231
|
/**
|
|
198
|
-
*
|
|
232
|
+
* Handle local project indexing
|
|
233
|
+
*/
|
|
234
|
+
async handleIndexProject(message) {
|
|
235
|
+
const { project_path, project_name } = 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. 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
|
|
278
|
+
this.logDebug(`Scanning files in ${resolvedPath}...`);
|
|
279
|
+
const files = this.scanDirectory(resolvedPath);
|
|
280
|
+
this.logDebug(`Found ${files.length} files to index.`);
|
|
281
|
+
|
|
282
|
+
// 3. Batch Upload
|
|
283
|
+
let indexed = 0;
|
|
284
|
+
const BATCH_SIZE = 10;
|
|
285
|
+
|
|
286
|
+
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
287
|
+
const batch = files.slice(i, i + BATCH_SIZE);
|
|
288
|
+
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);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// 4. Return success
|
|
307
|
+
const response = {
|
|
308
|
+
jsonrpc: '2.0',
|
|
309
|
+
id: message.id,
|
|
310
|
+
result: {
|
|
311
|
+
content: [{
|
|
312
|
+
type: 'text',
|
|
313
|
+
text: JSON.stringify({
|
|
314
|
+
project_id: projectId,
|
|
315
|
+
files_found: files.length,
|
|
316
|
+
files_indexed: indexed,
|
|
317
|
+
status: 'completed'
|
|
318
|
+
}, null, 2)
|
|
319
|
+
}]
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
323
|
+
|
|
324
|
+
} catch (error) {
|
|
325
|
+
this.logError('Index project failed:', error);
|
|
326
|
+
const response = {
|
|
327
|
+
jsonrpc: '2.0',
|
|
328
|
+
id: message.id,
|
|
329
|
+
error: {
|
|
330
|
+
code: -32000,
|
|
331
|
+
message: `Indexing failed: ${error.message}`
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Recursive scan
|
|
340
|
+
*/
|
|
341
|
+
scanDirectory(dir, basePath = '') {
|
|
342
|
+
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'];
|
|
345
|
+
|
|
346
|
+
try {
|
|
347
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
348
|
+
|
|
349
|
+
for (const entry of entries) {
|
|
350
|
+
const fullPath = path.join(dir, entry.name);
|
|
351
|
+
const relPath = path.join(basePath, entry.name);
|
|
352
|
+
|
|
353
|
+
if (entry.isDirectory()) {
|
|
354
|
+
if (!EXCLUDE_DIRS.includes(entry.name)) {
|
|
355
|
+
files = files.concat(this.scanDirectory(fullPath, relPath));
|
|
356
|
+
}
|
|
357
|
+
} else if (entry.isFile()) {
|
|
358
|
+
if (INCLUDE_EXTS.includes(path.extname(entry.name).toLowerCase())) {
|
|
359
|
+
try {
|
|
360
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
361
|
+
// Skip huge files > 1MB
|
|
362
|
+
if (content.length < 1024 * 1024) {
|
|
363
|
+
files.push({
|
|
364
|
+
relativePath: relPath,
|
|
365
|
+
content: content
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
} catch (e) { /* ignore read errors */ }
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
} catch (e) {
|
|
373
|
+
this.logError(`Scan failed for ${dir}:`, e);
|
|
374
|
+
}
|
|
375
|
+
return files;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
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)
|
|
199
413
|
*/
|
|
200
414
|
async makeRequest(message) {
|
|
201
415
|
return new Promise((resolve) => {
|
|
202
416
|
const serverUrl = this.useStreamableHttp ? this.streamableHttpUrl : this.legacyServerUrl;
|
|
203
417
|
const url = new URL(serverUrl);
|
|
204
418
|
const postData = JSON.stringify(message);
|
|
205
|
-
|
|
419
|
+
|
|
206
420
|
const options = {
|
|
207
421
|
hostname: url.hostname,
|
|
208
422
|
port: url.port || 443,
|
|
@@ -218,7 +432,7 @@ class CFMemoryMCP {
|
|
|
218
432
|
},
|
|
219
433
|
timeout: TIMEOUT_MS
|
|
220
434
|
};
|
|
221
|
-
|
|
435
|
+
|
|
222
436
|
const req = https.request(options, (res) => {
|
|
223
437
|
let body = '';
|
|
224
438
|
|
|
@@ -243,7 +457,7 @@ class CFMemoryMCP {
|
|
|
243
457
|
}
|
|
244
458
|
});
|
|
245
459
|
});
|
|
246
|
-
|
|
460
|
+
|
|
247
461
|
req.on('error', (error) => {
|
|
248
462
|
resolve({
|
|
249
463
|
jsonrpc: '2.0',
|
|
@@ -255,7 +469,7 @@ class CFMemoryMCP {
|
|
|
255
469
|
}
|
|
256
470
|
});
|
|
257
471
|
});
|
|
258
|
-
|
|
472
|
+
|
|
259
473
|
req.on('timeout', () => {
|
|
260
474
|
req.destroy();
|
|
261
475
|
resolve({
|
|
@@ -268,12 +482,12 @@ class CFMemoryMCP {
|
|
|
268
482
|
}
|
|
269
483
|
});
|
|
270
484
|
});
|
|
271
|
-
|
|
485
|
+
|
|
272
486
|
req.write(postData);
|
|
273
487
|
req.end();
|
|
274
488
|
});
|
|
275
489
|
}
|
|
276
|
-
|
|
490
|
+
|
|
277
491
|
/**
|
|
278
492
|
* Graceful shutdown
|
|
279
493
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cf-memory-mcp",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.1",
|
|
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
|
+
}
|