cf-memory-mcp 3.8.4 โ 3.8.6
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/README.md +95 -669
- package/bin/cf-memory-mcp-indexer.js +149 -69
- package/bin/cf-memory-mcp.js +1205 -63
- package/package.json +15 -53
|
@@ -25,20 +25,51 @@ const { URL } = require('url');
|
|
|
25
25
|
const os = require('os');
|
|
26
26
|
const process = require('process');
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
// Resolve API URL, overriding the dead workers.dev URL if it's still in the
|
|
29
|
+
// shell environment from before the memcp.ai migration.
|
|
30
|
+
function _resolveApiUrl() {
|
|
31
|
+
const raw = process.env.CF_MEMORY_API_URL || process.env.CF_MEMORY_BASE_URL;
|
|
32
|
+
if (!raw || raw.includes('workers.dev')) {
|
|
33
|
+
return 'https://memcp.ai';
|
|
34
|
+
}
|
|
35
|
+
return raw;
|
|
36
|
+
}
|
|
37
|
+
const API_URL = _resolveApiUrl();
|
|
29
38
|
const API_KEY = process.env.CF_MEMORY_API_KEY;
|
|
30
39
|
const CACHE_DIR = path.join(os.homedir(), '.cache', 'cf-memory-indexer');
|
|
31
40
|
|
|
32
41
|
// File patterns to include/exclude
|
|
42
|
+
// Mirrors src-simplified/utils/index.ts DEFAULT_INCLUDE_EXTENSIONS (110+ languages)
|
|
33
43
|
const DEFAULT_INCLUDE = [
|
|
34
|
-
|
|
35
|
-
'**/*.
|
|
36
|
-
'**/*.
|
|
44
|
+
// Mainstream
|
|
45
|
+
'**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.mjs', '**/*.cjs',
|
|
46
|
+
'**/*.py', '**/*.rb', '**/*.go', '**/*.rs', '**/*.java', '**/*.kt', '**/*.scala',
|
|
47
|
+
'**/*.cs', '**/*.cpp', '**/*.c', '**/*.h', '**/*.hpp', '**/*.swift', '**/*.php',
|
|
48
|
+
// Functional & systems
|
|
49
|
+
'**/*.lua', '**/*.ex', '**/*.exs', '**/*.hs', '**/*.lhs',
|
|
50
|
+
'**/*.clj', '**/*.cljs', '**/*.cljc', '**/*.jl', '**/*.elm', '**/*.purs',
|
|
51
|
+
'**/*.dart', '**/*.zig', '**/*.nim', '**/*.cr', '**/*.erl', '**/*.hrl',
|
|
52
|
+
'**/*.ml', '**/*.fs', '**/*.fsi',
|
|
53
|
+
// Web & data
|
|
54
|
+
'**/*.html', '**/*.css', '**/*.scss', '**/*.vue', '**/*.svelte', '**/*.astro',
|
|
55
|
+
'**/*.json', '**/*.yaml', '**/*.yml', '**/*.toml',
|
|
56
|
+
'**/*.md', '**/*.mdx',
|
|
57
|
+
// Infra & build
|
|
58
|
+
'**/*.tf', '**/*.hcl', '**/*.sql', '**/*.sh', '**/*.bash',
|
|
59
|
+
'**/*.proto', '**/*.graphql', '**/*.gql',
|
|
60
|
+
// Shaders & GPU
|
|
61
|
+
'**/*.glsl', '**/*.hlsl', '**/*.wgsl', '**/*.metal', '**/*.cu', '**/*.cuh',
|
|
37
62
|
];
|
|
63
|
+
|
|
38
64
|
const DEFAULT_EXCLUDE = [
|
|
39
65
|
'**/node_modules/**', '**/.git/**', '**/dist/**',
|
|
40
|
-
'**/build/**', '**/.next/**', '
|
|
41
|
-
'
|
|
66
|
+
'**/build/**', '**/out/**', '**/.next/**', '**/.nuxt/**',
|
|
67
|
+
'**/coverage/**', '**/__pycache__/**', '**/venv/**', '**/.venv/**',
|
|
68
|
+
'**/.wrangler/**', '**/.turbo/**', '**/.cache/**',
|
|
69
|
+
'**/.history/**', '**/.vscode/**', '**/.idea/**', '**/.vs/**',
|
|
70
|
+
'**/.augment/**', '**/.kiro/**', '**/.intent/**', '**/.husky/**', '**/.claude/**',
|
|
71
|
+
'**/*.min.js', '**/*.min.css', '**/*.map', '**/*.d.ts',
|
|
72
|
+
'**/package-lock.json', '**/yarn.lock', '**/pnpm-lock.yaml',
|
|
42
73
|
];
|
|
43
74
|
|
|
44
75
|
class IncrementalIndexer {
|
|
@@ -205,6 +236,16 @@ class IncrementalIndexer {
|
|
|
205
236
|
console.log(`๐ค Uploading ${changedFiles.length} files...`);
|
|
206
237
|
await this.uploadFiles(changedFiles);
|
|
207
238
|
}
|
|
239
|
+
|
|
240
|
+
if (this.cache.projectId && deletedFiles.length > 0) {
|
|
241
|
+
console.log(`๐งน Cleaning up ${deletedFiles.length} deleted files...`);
|
|
242
|
+
await this.cleanupDeletedFiles(deletedFiles);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (this.cache.projectId && (changedFiles.length > 0 || deletedFiles.length > 0)) {
|
|
246
|
+
console.log('๐ Finalizing project stats...');
|
|
247
|
+
await this.finalizeProject();
|
|
248
|
+
}
|
|
208
249
|
|
|
209
250
|
// Update cache
|
|
210
251
|
for (const file of changedFiles) {
|
|
@@ -238,59 +279,101 @@ class IncrementalIndexer {
|
|
|
238
279
|
}
|
|
239
280
|
|
|
240
281
|
async uploadBatch(files) {
|
|
241
|
-
|
|
242
|
-
const
|
|
282
|
+
if (!this.cache.projectId) {
|
|
283
|
+
const initResult = await this.callMcpTool('index_project', {
|
|
243
284
|
project_path: this.projectPath,
|
|
244
|
-
project_name: this.projectName
|
|
245
|
-
files: files.map(f => ({
|
|
246
|
-
path: f.path,
|
|
247
|
-
content: f.content
|
|
248
|
-
}))
|
|
285
|
+
project_name: this.projectName
|
|
249
286
|
});
|
|
250
|
-
|
|
251
|
-
|
|
287
|
+
const initContent = this.parseToolTextResult(initResult);
|
|
288
|
+
|
|
289
|
+
if (!initContent.project_id) {
|
|
290
|
+
throw new Error('Project initialization did not return project_id');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
this.cache.projectId = initContent.project_id;
|
|
294
|
+
this.saveCache();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return this.postJson(`/api/projects/${encodeURIComponent(this.cache.projectId)}/files/batch`, {
|
|
298
|
+
files: files.map(f => ({
|
|
299
|
+
file_path: f.path,
|
|
300
|
+
content: f.content
|
|
301
|
+
}))
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async callMcpTool(name, args) {
|
|
306
|
+
return this.postJson('/mcp', {
|
|
307
|
+
jsonrpc: '2.0',
|
|
308
|
+
id: `idx-${Date.now()}`,
|
|
309
|
+
method: 'tools/call',
|
|
310
|
+
params: {
|
|
311
|
+
name,
|
|
312
|
+
arguments: args
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async cleanupDeletedFiles(filePaths) {
|
|
318
|
+
return this.postJson(`/api/projects/${encodeURIComponent(this.cache.projectId)}/files/cleanup`, {
|
|
319
|
+
file_paths: filePaths
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async finalizeProject() {
|
|
324
|
+
return this.postJson(`/api/projects/${encodeURIComponent(this.cache.projectId)}/finalize`, {});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
parseToolTextResult(result) {
|
|
328
|
+
const text = result?.result?.content?.[0]?.text;
|
|
329
|
+
if (!text) {
|
|
330
|
+
throw new Error('Tool response did not contain text content');
|
|
331
|
+
}
|
|
332
|
+
return JSON.parse(text);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async postJson(route, payload) {
|
|
336
|
+
return new Promise((resolve, reject) => {
|
|
337
|
+
const body = JSON.stringify(payload);
|
|
338
|
+
const url = new URL(`${API_URL}${route}`);
|
|
252
339
|
const options = {
|
|
253
340
|
hostname: url.hostname,
|
|
254
|
-
|
|
341
|
+
port: url.port || 443,
|
|
342
|
+
path: url.pathname + url.search,
|
|
255
343
|
method: 'POST',
|
|
256
344
|
headers: {
|
|
257
345
|
'Content-Type': 'application/json',
|
|
258
346
|
'Authorization': `Bearer ${API_KEY}`,
|
|
259
|
-
'
|
|
347
|
+
'X-API-Key': API_KEY,
|
|
348
|
+
'Content-Length': Buffer.byteLength(body)
|
|
260
349
|
},
|
|
261
350
|
timeout: 120000
|
|
262
351
|
};
|
|
263
|
-
|
|
352
|
+
|
|
264
353
|
const req = https.request(options, (res) => {
|
|
265
354
|
let data = '';
|
|
266
355
|
res.on('data', chunk => data += chunk);
|
|
267
356
|
res.on('end', () => {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (content.project_id) {
|
|
274
|
-
this.cache.projectId = content.project_id;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
resolve(result);
|
|
278
|
-
} catch (err) {
|
|
279
|
-
reject(new Error(`Invalid response: ${err.message}`));
|
|
357
|
+
try {
|
|
358
|
+
const parsed = JSON.parse(data || '{}');
|
|
359
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
360
|
+
resolve(parsed);
|
|
361
|
+
return;
|
|
280
362
|
}
|
|
281
|
-
} else {
|
|
282
363
|
reject(new Error(`HTTP ${res.statusCode}: ${data}`));
|
|
364
|
+
} catch (err) {
|
|
365
|
+
reject(new Error(`Invalid response: ${err.message}`));
|
|
283
366
|
}
|
|
284
367
|
});
|
|
285
368
|
});
|
|
286
|
-
|
|
369
|
+
|
|
287
370
|
req.on('error', reject);
|
|
288
371
|
req.on('timeout', () => {
|
|
289
372
|
req.destroy();
|
|
290
373
|
reject(new Error('Request timeout'));
|
|
291
374
|
});
|
|
292
|
-
|
|
293
|
-
req.write(
|
|
375
|
+
|
|
376
|
+
req.write(body);
|
|
294
377
|
req.end();
|
|
295
378
|
});
|
|
296
379
|
}
|
|
@@ -302,49 +385,41 @@ class IncrementalIndexer {
|
|
|
302
385
|
// Initial index
|
|
303
386
|
await this.index();
|
|
304
387
|
|
|
305
|
-
//
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
console.log('\n๐ Changes detected, re-indexing...');
|
|
320
|
-
await this.index();
|
|
321
|
-
console.log('');
|
|
322
|
-
}, 1000);
|
|
388
|
+
// Use Node's built-in fs.watch (no external deps needed)
|
|
389
|
+
const fs = require('fs');
|
|
390
|
+
const watchDir = (dir) => {
|
|
391
|
+
let watcher;
|
|
392
|
+
try {
|
|
393
|
+
watcher = fs.watch(dir, { recursive: true }, (eventType, filename) => {
|
|
394
|
+
if (filename) {
|
|
395
|
+
this.handleFileChange(dir, filename);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
} catch (err) {
|
|
399
|
+
// Ignore errors for now
|
|
400
|
+
}
|
|
401
|
+
return watcher;
|
|
323
402
|
};
|
|
324
403
|
|
|
325
|
-
watcher
|
|
326
|
-
.on('add', path => handleChange())
|
|
327
|
-
.on('change', path => handleChange())
|
|
328
|
-
.on('unlink', path => handleChange());
|
|
404
|
+
const watcher = watchDir(this.projectPath);
|
|
329
405
|
|
|
330
406
|
// Keep process alive
|
|
331
407
|
process.on('SIGINT', () => {
|
|
332
408
|
console.log('\n๐ Stopping watcher...');
|
|
333
|
-
watcher.close();
|
|
409
|
+
if (watcher) watcher.close();
|
|
334
410
|
process.exit(0);
|
|
335
411
|
});
|
|
336
412
|
}
|
|
337
413
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
console.log(
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
return require('chokidar');
|
|
346
|
-
}
|
|
414
|
+
handleFileChange(baseDir, filename) {
|
|
415
|
+
// Debounce rapid changes
|
|
416
|
+
if (this._debounceTimer) clearTimeout(this._debounceTimer);
|
|
417
|
+
this._debounceTimer = setTimeout(async () => {
|
|
418
|
+
console.log(`\n๐ File changed: ${filename}`);
|
|
419
|
+
await this.index();
|
|
420
|
+
}, 1000);
|
|
347
421
|
}
|
|
422
|
+
|
|
348
423
|
}
|
|
349
424
|
|
|
350
425
|
// CLI
|
|
@@ -358,15 +433,20 @@ async function main() {
|
|
|
358
433
|
|
|
359
434
|
// Detect command from how the script was called (cf-memory-index vs cf-memory-watch)
|
|
360
435
|
const scriptName = path.basename(process.argv[1]);
|
|
361
|
-
let command =
|
|
362
|
-
let projectPath =
|
|
436
|
+
let command = 'index';
|
|
437
|
+
let projectPath = '.';
|
|
363
438
|
|
|
364
|
-
if (
|
|
439
|
+
if (args[0] === 'watch' || args[0] === 'index') {
|
|
440
|
+
command = args[0];
|
|
441
|
+
projectPath = args[1] || '.';
|
|
442
|
+
} else if (scriptName.includes('watch')) {
|
|
365
443
|
command = 'watch';
|
|
366
444
|
projectPath = args[0] || '.';
|
|
367
445
|
} else if (scriptName.includes('index')) {
|
|
368
446
|
command = 'index';
|
|
369
447
|
projectPath = args[0] || '.';
|
|
448
|
+
} else {
|
|
449
|
+
projectPath = args[0] || '.';
|
|
370
450
|
}
|
|
371
451
|
|
|
372
452
|
const options = {
|