cf-memory-mcp 3.23.0 → 3.25.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.
@@ -434,13 +434,40 @@ class CFMemoryMCP {
434
434
  this.logDebug(`User Agent: ${this.userAgent}`);
435
435
  }
436
436
 
437
+ /**
438
+ * Append a line to the persistent bridge log file if configured.
439
+ * Honors CF_MEMORY_LOG_FILE (explicit path) or CF_MEMORY_LOG=1/true
440
+ * (defaults to ~/.cf-memory/bridge.log). Bounded: rotates when the
441
+ * file exceeds 5MB to prevent runaway disk usage.
442
+ */
443
+ appendBridgeLog(line) {
444
+ try {
445
+ const explicit = process.env.CF_MEMORY_LOG_FILE;
446
+ const enabled = explicit || process.env.CF_MEMORY_LOG === '1' || process.env.CF_MEMORY_LOG === 'true';
447
+ if (!enabled) return;
448
+ const target = explicit || path.join(os.homedir(), '.cf-memory', 'bridge.log');
449
+ const dir = path.dirname(target);
450
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
451
+ // Rotate if file > 5MB.
452
+ try {
453
+ const st = fs.statSync(target);
454
+ if (st.size > 5 * 1024 * 1024) {
455
+ fs.renameSync(target, target + '.1');
456
+ }
457
+ } catch (_) { /* file doesn't exist yet */ }
458
+ fs.appendFileSync(target, line);
459
+ } catch (_) { /* logging failure is non-fatal */ }
460
+ }
461
+
437
462
  /**
438
463
  * Log debug messages to stderr (won't interfere with MCP communication)
439
464
  */
440
465
  logDebug(message) {
466
+ const line = `[DEBUG] ${new Date().toISOString()} ${message}\n`;
441
467
  if (process.env.DEBUG || process.env.MCP_DEBUG) {
442
- process.stderr.write(`[DEBUG] ${new Date().toISOString()} ${message}\n`);
468
+ process.stderr.write(line);
443
469
  }
470
+ this.appendBridgeLog(line);
444
471
  }
445
472
 
446
473
  /**
@@ -448,9 +475,13 @@ class CFMemoryMCP {
448
475
  */
449
476
  logError(message, error = null) {
450
477
  const timestamp = new Date().toISOString();
451
- process.stderr.write(`[ERROR] ${timestamp} ${message}\n`);
478
+ const line1 = `[ERROR] ${timestamp} ${message}\n`;
479
+ process.stderr.write(line1);
480
+ this.appendBridgeLog(line1);
452
481
  if (error && error.stack) {
453
- process.stderr.write(`[ERROR] ${timestamp} ${error.stack}\n`);
482
+ const line2 = `[ERROR] ${timestamp} ${error.stack}\n`;
483
+ process.stderr.write(line2);
484
+ this.appendBridgeLog(line2);
454
485
  }
455
486
  }
456
487
 
@@ -3718,6 +3749,9 @@ Usage:
3718
3749
  npx cf-memory-mcp resume [id] Print the prior resume handoff (markdown)
3719
3750
  npx cf-memory-mcp list List recent handoffs for cwd
3720
3751
  npx cf-memory-mcp checkpoint ["<goal>"] Snapshot current state (keep_open)
3752
+ npx cf-memory-mcp status Show bridge state + server resume availability
3753
+ npx cf-memory-mcp clean Delete local disk cache for current cwd
3754
+ npx cf-memory-mcp clean --all Delete ALL local disk caches
3721
3755
  npx cf-memory-mcp --version Show version
3722
3756
  npx cf-memory-mcp --help Show this help
3723
3757
  npx cf-memory-mcp --diagnose Test connectivity and report issues
@@ -3896,6 +3930,141 @@ async function runListCli() {
3896
3930
  }
3897
3931
  }
3898
3932
 
3933
+ async function runCleanCli() {
3934
+ const { flags, positional } = parseCliArgs(process.argv.slice(3));
3935
+ const all = positional.includes('--all') || process.argv.includes('--all');
3936
+ const server = new CFMemoryMCP();
3937
+ server.logDebug = () => {};
3938
+ const removed = [];
3939
+ try {
3940
+ if (all) {
3941
+ // Clear ALL disk caches in ~/.cf-memory/.
3942
+ const dir = path.join(os.homedir(), '.cf-memory');
3943
+ if (fs.existsSync(dir)) {
3944
+ for (const entry of fs.readdirSync(dir)) {
3945
+ if (entry.startsWith('handoff-') && entry.endsWith('.json')) {
3946
+ const full = path.join(dir, entry);
3947
+ fs.unlinkSync(full);
3948
+ removed.push(full);
3949
+ }
3950
+ }
3951
+ }
3952
+ } else {
3953
+ const p = server.getDiskCachePath();
3954
+ if (p && fs.existsSync(p)) {
3955
+ fs.unlinkSync(p);
3956
+ removed.push(p);
3957
+ }
3958
+ }
3959
+ if (flags.json) {
3960
+ process.stdout.write(JSON.stringify({ removed }, null, 2) + '\n');
3961
+ process.exit(0);
3962
+ }
3963
+ if (removed.length === 0) {
3964
+ process.stdout.write('(nothing to clean)\n');
3965
+ } else {
3966
+ process.stdout.write(`Removed ${removed.length} cache file${removed.length === 1 ? '' : 's'}:\n`);
3967
+ for (const r of removed) process.stdout.write(` ${r}\n`);
3968
+ }
3969
+ process.exit(0);
3970
+ } catch (err) {
3971
+ console.error('clean command failed:', err.message);
3972
+ process.exit(1);
3973
+ }
3974
+ }
3975
+
3976
+ async function runStatusCli() {
3977
+ const { flags } = parseCliArgs(process.argv.slice(3));
3978
+ const server = new CFMemoryMCP();
3979
+ server.logDebug = () => {};
3980
+ server.logError = (...a) => process.stderr.write(a.join(' ') + '\n');
3981
+ try {
3982
+ // Local bridge state (no network call required).
3983
+ const meta = server.getRepoMetadata();
3984
+ const cwd = process.env.CF_MEMORY_WATCH_PATH || process.cwd();
3985
+ const diskCachePath = server.getDiskCachePath();
3986
+ const diskCacheExists = diskCachePath && fs.existsSync(diskCachePath);
3987
+ let diskCacheAge = null;
3988
+ if (diskCacheExists) {
3989
+ try {
3990
+ const entry = JSON.parse(fs.readFileSync(diskCachePath, 'utf8'));
3991
+ diskCacheAge = Math.round((Date.now() - new Date(entry.cached_at).getTime()) / 60000);
3992
+ } catch (_) { /* unreadable cache */ }
3993
+ }
3994
+ // Lookup the live worker count of handoffs for this cwd (best-effort).
3995
+ let serverHandoffCount = null;
3996
+ let resumeAvailable = false;
3997
+ let latestGoal = null;
3998
+ if (API_KEY) {
3999
+ try {
4000
+ const args = { resume: true };
4001
+ if (meta.repo_path) args.repo_path = meta.repo_path;
4002
+ if (meta.branch) args.branch = meta.branch;
4003
+ const fake = { params: { name: 'retrieve_context', arguments: {} } };
4004
+ await server.maybeFillProjectId(fake);
4005
+ if (fake.params.arguments.project_id) args.project_id = fake.params.arguments.project_id;
4006
+ const response = await server.makeRequest({
4007
+ jsonrpc: '2.0',
4008
+ id: `cli-status-${Date.now()}`,
4009
+ method: 'tools/call',
4010
+ params: { name: 'get_context_bootstrap', arguments: args },
4011
+ });
4012
+ const text = response?.result?.content?.[0]?.text;
4013
+ const payload = JSON.parse(text || '{}');
4014
+ serverHandoffCount = Array.isArray(payload.recent_handoffs) ? payload.recent_handoffs.length : 0;
4015
+ resumeAvailable = !!payload.resume_handoff;
4016
+ latestGoal = payload.resume_handoff?.handoff?.goal || null;
4017
+ } catch (err) {
4018
+ // Network unreachable — that's OK, we still have local info.
4019
+ }
4020
+ }
4021
+
4022
+ const status = {
4023
+ version: PACKAGE_VERSION,
4024
+ cwd,
4025
+ repo_path: meta.repo_path || null,
4026
+ branch: meta.branch || null,
4027
+ base_url: BASE_URL,
4028
+ api_key_set: !!API_KEY,
4029
+ disk_cache: diskCacheExists
4030
+ ? { path: diskCachePath, age_minutes: diskCacheAge }
4031
+ : null,
4032
+ server: API_KEY
4033
+ ? { handoff_count: serverHandoffCount, resume_available: resumeAvailable, latest_goal: latestGoal }
4034
+ : { reachable: false, reason: 'CF_MEMORY_API_KEY not set' },
4035
+ };
4036
+
4037
+ if (flags.json) {
4038
+ process.stdout.write(JSON.stringify(status, null, 2) + '\n');
4039
+ process.exit(0);
4040
+ }
4041
+ process.stdout.write(`cf-memory-mcp v${PACKAGE_VERSION}\n`);
4042
+ process.stdout.write(` cwd: ${cwd}\n`);
4043
+ if (meta.repo_path) process.stdout.write(` repo: ${meta.repo_path}\n`);
4044
+ if (meta.branch) process.stdout.write(` branch: ${meta.branch}\n`);
4045
+ process.stdout.write(` server: ${BASE_URL}${API_KEY ? '' : ' (no API key set)'}\n`);
4046
+ if (diskCacheExists) {
4047
+ process.stdout.write(` cache: ${diskCachePath} (${diskCacheAge}m old)\n`);
4048
+ } else {
4049
+ process.stdout.write(` cache: (none)\n`);
4050
+ }
4051
+ if (API_KEY) {
4052
+ if (resumeAvailable) {
4053
+ process.stdout.write(` resume: ✓ available — "${latestGoal || '(no goal)'}"\n`);
4054
+ } else {
4055
+ process.stdout.write(` resume: (no handoff for this context)\n`);
4056
+ }
4057
+ if (serverHandoffCount !== null) {
4058
+ process.stdout.write(` threads: ${serverHandoffCount} recent handoff${serverHandoffCount === 1 ? '' : 's'}\n`);
4059
+ }
4060
+ }
4061
+ process.exit(0);
4062
+ } catch (err) {
4063
+ console.error('status command failed:', err.message);
4064
+ process.exit(1);
4065
+ }
4066
+ }
4067
+
3899
4068
  async function runCheckpointCli() {
3900
4069
  if (!API_KEY) {
3901
4070
  console.error('Error: CF_MEMORY_API_KEY environment variable is required');
@@ -3983,6 +4152,16 @@ if (process.argv[2] === 'checkpoint') {
3983
4152
  return;
3984
4153
  }
3985
4154
 
4155
+ if (process.argv[2] === 'status') {
4156
+ runStatusCli();
4157
+ return;
4158
+ }
4159
+
4160
+ if (process.argv[2] === 'clean') {
4161
+ runCleanCli();
4162
+ return;
4163
+ }
4164
+
3986
4165
  if (process.argv.includes('--diagnose')) {
3987
4166
  (async () => {
3988
4167
  console.log(`CF Memory MCP v${PACKAGE_VERSION} - Diagnostics`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cf-memory-mcp",
3
- "version": "3.23.0",
3
+ "version": "3.25.0",
4
4
  "description": "Cloudflare-hosted MCP server for code indexing, retrieval, and assistant memory with a direct remote MCP endpoint and local stdio bridge.",
5
5
  "main": "bin/cf-memory-mcp.js",
6
6
  "bin": {