cf-memory-mcp 3.36.0 → 3.37.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.
@@ -217,7 +217,8 @@ const TOOLS_LIST = [
217
217
  max_age_minutes: { type: 'number', description: 'Hard cutoff: hide handoffs older than this many minutes. Without it, older handoffs surface with reduced confidence.' },
218
218
  goal_contains: { type: 'string', description: 'Substring filter on handoff content (goal/notes/decisions). Case-insensitive. Lets agents find a specific thread by what it was about.' },
219
219
  since: { type: 'string', description: 'Lower bound on handoff timestamp (ISO 8601). Scope resume to a time window.' },
220
- status_filter: { description: 'Restrict matches to handoffs with this status. Single string or array of strings (e.g., ["in_progress","blocked"]).' }
220
+ status_filter: { description: 'Restrict matches to handoffs with this status. Single string or array of strings (e.g., ["in_progress","blocked"]).' },
221
+ recent_handoff_limit: { type: 'number', description: 'Override the default recent_handoffs cap (5). Range 1-50.' }
221
222
  }
222
223
  }
223
224
  },
@@ -3806,7 +3807,7 @@ if (process.argv.includes('--version') || process.argv.includes('-v')) {
3806
3807
 
3807
3808
  // Global --help only when no subcommand is present. With a subcommand, fall
3808
3809
  // through to the per-command help dispatch below.
3809
- const SUBCOMMANDS = new Set(['resume', 'list', 'checkpoint', 'status', 'clean', 'export', 'import', 'doctor', 'completion', 'delete', 'env']);
3810
+ const SUBCOMMANDS = new Set(['resume', 'list', 'checkpoint', 'status', 'clean', 'export', 'import', 'doctor', 'completion', 'delete', 'env', 'history']);
3810
3811
  const hasSubcommand = process.argv[2] && SUBCOMMANDS.has(process.argv[2]);
3811
3812
 
3812
3813
  if (!hasSubcommand && (process.argv.includes('--help') || process.argv.includes('-h'))) {
@@ -3818,7 +3819,8 @@ A portable MCP (Model Context Protocol) server for AI memory storage using Cloud
3818
3819
  Usage:
3819
3820
  npx cf-memory-mcp Start the MCP server
3820
3821
  npx cf-memory-mcp resume [id] Print the prior resume handoff (markdown)
3821
- npx cf-memory-mcp list List recent handoffs for cwd
3822
+ npx cf-memory-mcp list List recent handoffs for cwd (status-ranked)
3823
+ npx cf-memory-mcp history List all handoffs for cwd, chronological
3822
3824
  npx cf-memory-mcp checkpoint ["<goal>"] Snapshot current state (keep_open)
3823
3825
  npx cf-memory-mcp status Show bridge state + server resume availability
3824
3826
  npx cf-memory-mcp clean Delete local disk cache for current cwd
@@ -3882,6 +3884,8 @@ function parseCliArgs(rest) {
3882
3884
  flags.decisions_only = true;
3883
3885
  } else if (a === '--blockers-only') {
3884
3886
  flags.blockers_only = true;
3887
+ } else if (a === '--chain') {
3888
+ flags.chain = true;
3885
3889
  } else if (a === '--older-than') {
3886
3890
  // Accept "7d" / "30d" / "12h" / raw number (days).
3887
3891
  const raw = rest[++i] || '';
@@ -4035,6 +4039,59 @@ async function runResumeCli() {
4035
4039
  process.exit(0);
4036
4040
  }
4037
4041
 
4042
+ // --chain: walk parent_session_id back through the thread history.
4043
+ // Bounded at 20 iterations to avoid an infinite loop on malformed
4044
+ // data. Prints each handoff as "<id> [age] status — goal".
4045
+ if (flags.chain) {
4046
+ if (!found) {
4047
+ process.stderr.write('No resume handoff available to walk back from.\n');
4048
+ process.exit(3);
4049
+ }
4050
+ const fmtAge = (iso) => {
4051
+ if (!iso) return '?';
4052
+ const min = Math.round((Date.now() - new Date(iso).getTime()) / 60000);
4053
+ if (min < 60) return `${min}m`;
4054
+ if (min < 1440) return `${Math.round(min/60)}h`;
4055
+ return `${Math.round(min/1440)}d`;
4056
+ };
4057
+ const chain = [];
4058
+ let cur = payload.resume_handoff;
4059
+ chain.push(cur);
4060
+ for (let i = 0; i < 20; i++) {
4061
+ const parentId = cur?.handoff?.parent_session_id;
4062
+ if (!parentId) break;
4063
+ // Fetch parent
4064
+ const parentRes = await server.makeRequest({
4065
+ jsonrpc: '2.0', id: `cli-chain-${i}-${Date.now()}`,
4066
+ method: 'tools/call',
4067
+ params: { name: 'get_context_bootstrap', arguments: { resume: true, session_id_hint: parentId } },
4068
+ });
4069
+ const parentText = parentRes?.result?.content?.[0]?.text;
4070
+ const parentPayload = JSON.parse(parentText || '{}');
4071
+ if (!parentPayload.resume_handoff) break;
4072
+ chain.push(parentPayload.resume_handoff);
4073
+ cur = parentPayload.resume_handoff;
4074
+ }
4075
+ if (flags.json) {
4076
+ process.stdout.write(JSON.stringify({ chain }, null, 2) + '\n');
4077
+ process.exit(0);
4078
+ }
4079
+ process.stdout.write(`Thread chain (newest first, ${chain.length} session${chain.length === 1 ? '' : 's'}):\n\n`);
4080
+ for (let i = 0; i < chain.length; i++) {
4081
+ const e = chain[i];
4082
+ const shortId = (e.session_id || '').slice(0, 8);
4083
+ const age = fmtAge(e.ended_at || e.started_at);
4084
+ const status = e.handoff?.status || '?';
4085
+ const goal = e.handoff?.goal || '(no goal)';
4086
+ const prefix = i === 0 ? '→' : ' ';
4087
+ process.stdout.write(`${prefix} ${shortId} ${status.padEnd(11)} ${age.padStart(4)} ago ${goal}\n`);
4088
+ }
4089
+ if (chain[chain.length-1]?.handoff?.parent_session_id) {
4090
+ process.stdout.write(`\n(chain truncated at 20 — increase with multiple --chain calls)\n`);
4091
+ }
4092
+ process.exit(0);
4093
+ }
4094
+
4038
4095
  // --md <path>: write the rendered markdown to a file instead of
4039
4096
  // (or in addition to) stdout. Useful for piping to a markdown
4040
4097
  // viewer or saving to the project.
@@ -4175,6 +4232,71 @@ async function runListCli() {
4175
4232
  }
4176
4233
  }
4177
4234
 
4235
+ async function runHistoryCli() {
4236
+ if (!API_KEY) {
4237
+ console.error('Error: CF_MEMORY_API_KEY environment variable is required');
4238
+ process.exit(1);
4239
+ }
4240
+ const { flags } = parseCliArgs(process.argv.slice(3));
4241
+ const limit = flags.limit || 25;
4242
+ const server = new CFMemoryMCP();
4243
+ server.logDebug = () => {};
4244
+ try {
4245
+ const meta = server.getRepoMetadata();
4246
+ const args = { resume: true, recent_handoff_limit: limit };
4247
+ if (meta.repo_path) args.repo_path = meta.repo_path;
4248
+ if (meta.branch) args.branch = meta.branch;
4249
+ if (flags.repo_path) args.repo_path = flags.repo_path;
4250
+ if (flags.since) args.since = flags.since;
4251
+ if (!flags.repo_path) {
4252
+ const fake = { params: { name: 'retrieve_context', arguments: {} } };
4253
+ await server.maybeFillProjectId(fake);
4254
+ if (fake.params.arguments.project_id) args.project_id = fake.params.arguments.project_id;
4255
+ }
4256
+
4257
+ const response = await server.makeRequest({
4258
+ jsonrpc: '2.0',
4259
+ id: `cli-history-${Date.now()}`,
4260
+ method: 'tools/call',
4261
+ params: { name: 'get_context_bootstrap', arguments: args },
4262
+ });
4263
+ const text = response?.result?.content?.[0]?.text;
4264
+ const payload = JSON.parse(text || '{}');
4265
+ const all = (payload.recent_handoffs || []).slice();
4266
+ // Chronological order (oldest first).
4267
+ all.sort((a, b) => {
4268
+ const ta = new Date(a.ended_at || a.started_at).getTime();
4269
+ const tb = new Date(b.ended_at || b.started_at).getTime();
4270
+ return ta - tb;
4271
+ });
4272
+ if (flags.json) {
4273
+ process.stdout.write(JSON.stringify({
4274
+ repo_path: args.repo_path,
4275
+ count: all.length,
4276
+ handoffs: all,
4277
+ }, null, 2) + '\n');
4278
+ process.exit(all.length === 0 ? 3 : 0);
4279
+ }
4280
+ if (all.length === 0) {
4281
+ process.stderr.write((payload.empty_hint || 'No handoffs for this context.') + '\n');
4282
+ process.exit(3);
4283
+ }
4284
+ const fmtDate = (iso) => iso ? new Date(iso).toISOString().slice(0, 16).replace('T', ' ') : '?';
4285
+ process.stdout.write(`History for ${args.repo_path || 'this cwd'} (${all.length} handoff${all.length === 1 ? '' : 's'}, oldest first):\n\n`);
4286
+ for (const h of all) {
4287
+ const shortId = (h.session_id || '').slice(0, 8);
4288
+ const when = fmtDate(h.ended_at || h.started_at);
4289
+ const status = (h.status || 'unknown').padEnd(11);
4290
+ const goal = (h.goal || '(no goal)').slice(0, 70);
4291
+ process.stdout.write(` ${when} ${shortId} ${status} ${goal}\n`);
4292
+ }
4293
+ process.exit(0);
4294
+ } catch (err) {
4295
+ console.error('history command failed:', err.message);
4296
+ process.exit(1);
4297
+ }
4298
+ }
4299
+
4178
4300
  async function runDeleteCli() {
4179
4301
  if (!API_KEY) {
4180
4302
  console.error('Error: CF_MEMORY_API_KEY environment variable is required');
@@ -4367,7 +4489,7 @@ function runEnvCli() {
4367
4489
 
4368
4490
  function runCompletionCli() {
4369
4491
  const shell = process.argv[3] || 'bash';
4370
- const commands = ['resume', 'list', 'checkpoint', 'status', 'clean', 'export', 'import', 'delete', 'doctor', 'env', 'completion'];
4492
+ const commands = ['resume', 'list', 'history', 'checkpoint', 'status', 'clean', 'export', 'import', 'delete', 'doctor', 'env', 'completion'];
4371
4493
  const flags = ['--json', '-j', '--limit', '-n', '--md', '--all', '--force', '-f', '--version', '-v', '--help', '-h', '--diagnose'];
4372
4494
  if (shell === 'bash') {
4373
4495
  process.stdout.write(`# cf-memory-mcp bash completion
@@ -4501,6 +4623,14 @@ const PER_COMMAND_HELP = {
4501
4623
  Print all CF_MEMORY_* env vars the bridge reads, with their current
4502
4624
  values + descriptions. Useful for discovering knobs.
4503
4625
  --json, -j Emit JSON for scripts.`,
4626
+ history: `cf-memory-mcp history [--limit N] [--repo PATH] [--since ISO] [--json]
4627
+ Print all handoffs for the current cwd in chronological order (oldest
4628
+ first). Unlike 'list' (status-ranked, default 5), this is a flat
4629
+ timeline view.
4630
+ --limit N Max number of handoffs (default 25, max 50).
4631
+ --repo PATH Override the cwd's repo.
4632
+ --since ISO Lower bound on handoff timestamp.
4633
+ --json, -j Emit JSON.`,
4504
4634
  };
4505
4635
 
4506
4636
  function printPerCommandHelp(cmd) {
@@ -5090,6 +5220,11 @@ if (process.argv[2] === 'env') {
5090
5220
  return;
5091
5221
  }
5092
5222
 
5223
+ if (process.argv[2] === 'history') {
5224
+ runHistoryCli();
5225
+ return;
5226
+ }
5227
+
5093
5228
  if (process.argv.includes('--diagnose')) {
5094
5229
  (async () => {
5095
5230
  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.36.0",
3
+ "version": "3.37.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": {