cf-memory-mcp 3.20.0 → 3.22.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.
@@ -3580,12 +3580,17 @@ A portable MCP (Model Context Protocol) server for AI memory storage using Cloud
3580
3580
 
3581
3581
  Usage:
3582
3582
  npx cf-memory-mcp Start the MCP server
3583
- npx cf-memory-mcp resume Print the prior resume handoff for cwd (markdown)
3584
- npx cf-memory-mcp resume <id> Print a specific handoff by session_id prefix
3583
+ npx cf-memory-mcp resume [id] Print the prior resume handoff (markdown)
3584
+ npx cf-memory-mcp list List recent handoffs for cwd
3585
+ npx cf-memory-mcp checkpoint ["<goal>"] Snapshot current state (keep_open)
3585
3586
  npx cf-memory-mcp --version Show version
3586
3587
  npx cf-memory-mcp --help Show this help
3587
3588
  npx cf-memory-mcp --diagnose Test connectivity and report issues
3588
3589
 
3590
+ Subcommand flags:
3591
+ --json, -j Emit JSON instead of formatted text (resume/list/checkpoint)
3592
+ --limit N, -n N For 'list': max number of handoffs (default 5, max 50)
3593
+
3589
3594
  Environment Variables:
3590
3595
  CF_MEMORY_API_KEY=<key> Your CF Memory API key (required)
3591
3596
  CF_MEMORY_BASE_URL=<url> Override the default deployed worker
@@ -3598,6 +3603,25 @@ For more information, visit: https://github.com/johnlam90/cf-memory-mcp
3598
3603
  process.exit(0);
3599
3604
  }
3600
3605
 
3606
+ // Parse positional + flag args for CLI subcommands. Returns
3607
+ // { positional: string[], flags: { json: boolean, limit?: number } }.
3608
+ function parseCliArgs(rest) {
3609
+ const positional = [];
3610
+ const flags = { json: false };
3611
+ for (let i = 0; i < rest.length; i++) {
3612
+ const a = rest[i];
3613
+ if (a === '--json' || a === '-j') flags.json = true;
3614
+ else if (a === '--limit' || a === '-n') {
3615
+ const n = parseInt(rest[++i], 10);
3616
+ if (Number.isFinite(n) && n > 0) flags.limit = Math.min(n, 50);
3617
+ } else if (a.startsWith('--limit=')) {
3618
+ const n = parseInt(a.slice('--limit='.length), 10);
3619
+ if (Number.isFinite(n) && n > 0) flags.limit = Math.min(n, 50);
3620
+ } else positional.push(a);
3621
+ }
3622
+ return { positional, flags };
3623
+ }
3624
+
3601
3625
  // Read-only inspection command: print the prior resume handoff to stdout.
3602
3626
  // Lets a human see what state is captured without involving an MCP client.
3603
3627
  async function runResumeCli() {
@@ -3605,6 +3629,7 @@ async function runResumeCli() {
3605
3629
  console.error('Error: CF_MEMORY_API_KEY environment variable is required');
3606
3630
  process.exit(1);
3607
3631
  }
3632
+ const { positional, flags } = parseCliArgs(process.argv.slice(3));
3608
3633
  const server = new CFMemoryMCP();
3609
3634
  server.logDebug = () => {};
3610
3635
  server.logError = (...a) => process.stderr.write(a.join(' ') + '\n');
@@ -3614,7 +3639,7 @@ async function runResumeCli() {
3614
3639
  if (meta.repo_path) args.repo_path = meta.repo_path;
3615
3640
  if (meta.branch) args.branch = meta.branch;
3616
3641
  // Optional positional session_id argument.
3617
- const sessionArg = process.argv[3];
3642
+ const sessionArg = positional[0];
3618
3643
  if (sessionArg) args.session_id_hint = sessionArg;
3619
3644
  const fake = { params: { name: 'retrieve_context', arguments: {} } };
3620
3645
  await server.maybeFillProjectId(fake);
@@ -3632,6 +3657,12 @@ async function runResumeCli() {
3632
3657
  process.exit(2);
3633
3658
  }
3634
3659
  const payload = JSON.parse(text);
3660
+ // JSON output mode: emit the full payload as a single JSON object.
3661
+ // Scripts can pipe through jq to extract specific fields.
3662
+ if (flags.json) {
3663
+ process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
3664
+ process.exit(payload.resume_handoff ? 0 : 0);
3665
+ }
3635
3666
  if (payload.resume_handoff?.handoff) {
3636
3667
  if (payload.resume_prompt) {
3637
3668
  process.stdout.write(payload.resume_prompt + '\n');
@@ -3657,11 +3688,162 @@ async function runResumeCli() {
3657
3688
  }
3658
3689
  }
3659
3690
 
3691
+ async function runListCli() {
3692
+ if (!API_KEY) {
3693
+ console.error('Error: CF_MEMORY_API_KEY environment variable is required');
3694
+ process.exit(1);
3695
+ }
3696
+ const { flags } = parseCliArgs(process.argv.slice(3));
3697
+ const server = new CFMemoryMCP();
3698
+ server.logDebug = () => {};
3699
+ server.logError = (...a) => process.stderr.write(a.join(' ') + '\n');
3700
+ try {
3701
+ const meta = server.getRepoMetadata();
3702
+ const args = { resume: true };
3703
+ if (meta.repo_path) args.repo_path = meta.repo_path;
3704
+ if (meta.branch) args.branch = meta.branch;
3705
+ const fake = { params: { name: 'retrieve_context', arguments: {} } };
3706
+ await server.maybeFillProjectId(fake);
3707
+ if (fake.params.arguments.project_id) args.project_id = fake.params.arguments.project_id;
3708
+
3709
+ const response = await server.makeRequest({
3710
+ jsonrpc: '2.0',
3711
+ id: `cli-list-${Date.now()}`,
3712
+ method: 'tools/call',
3713
+ params: { name: 'get_context_bootstrap', arguments: args },
3714
+ });
3715
+ const text = response?.result?.content?.[0]?.text;
3716
+ const payload = JSON.parse(text || '{}');
3717
+ const allList = Array.isArray(payload.recent_handoffs) ? payload.recent_handoffs : [];
3718
+ const list = flags.limit ? allList.slice(0, flags.limit) : allList;
3719
+
3720
+ if (flags.json) {
3721
+ process.stdout.write(JSON.stringify({
3722
+ repo_path: meta.repo_path,
3723
+ branch: meta.branch,
3724
+ count: list.length,
3725
+ handoffs: list,
3726
+ empty_hint: list.length === 0 ? payload.empty_hint : undefined,
3727
+ }, null, 2) + '\n');
3728
+ process.exit(0);
3729
+ }
3730
+ if (list.length === 0) {
3731
+ process.stdout.write((payload.empty_hint || 'No handoffs for this context.') + '\n');
3732
+ process.exit(0);
3733
+ }
3734
+ // Render a compact table to terminal.
3735
+ process.stdout.write(`Recent handoffs for ${meta.repo_path || 'this cwd'}:\n\n`);
3736
+ const fmtAge = (iso) => {
3737
+ if (!iso) return '?';
3738
+ const min = Math.round((Date.now() - new Date(iso).getTime()) / 60000);
3739
+ if (min < 60) return `${min}m`;
3740
+ if (min < 1440) return `${Math.round(min/60)}h`;
3741
+ return `${Math.round(min/1440)}d`;
3742
+ };
3743
+ for (const h of list) {
3744
+ const shortId = (h.session_id || '').slice(0, 8);
3745
+ const age = fmtAge(h.ended_at || h.started_at);
3746
+ const status = (h.status || 'unknown').padEnd(11);
3747
+ const branch = h.branch ? `[${h.branch}] ` : '';
3748
+ const goal = (h.goal || '(no goal)').slice(0, 80);
3749
+ process.stdout.write(` ${shortId} ${status} ${age.padStart(4)} ago ${branch}${goal}\n`);
3750
+ if (h.next_step) process.stdout.write(` next: ${h.next_step.slice(0, 80)}\n`);
3751
+ }
3752
+ process.stdout.write(`\n(Run "npx cf-memory-mcp resume <id>" to see a specific handoff)\n`);
3753
+ process.exit(0);
3754
+ } catch (err) {
3755
+ console.error('list command failed:', err.message);
3756
+ process.exit(1);
3757
+ }
3758
+ }
3759
+
3760
+ async function runCheckpointCli() {
3761
+ if (!API_KEY) {
3762
+ console.error('Error: CF_MEMORY_API_KEY environment variable is required');
3763
+ process.exit(1);
3764
+ }
3765
+ const { positional, flags } = parseCliArgs(process.argv.slice(3));
3766
+ const server = new CFMemoryMCP();
3767
+ server.logDebug = () => {};
3768
+ server.logError = (...a) => process.stderr.write(a.join(' ') + '\n');
3769
+ try {
3770
+ // Optional positional goal argument: `cf-memory-mcp checkpoint "<goal text>"`
3771
+ const goalArg = positional.join(' ').trim();
3772
+ const meta = server.getRepoMetadata();
3773
+ // Need a session to attach the handoff to. Use or create the
3774
+ // implicit session for this cwd (creates a new one if no implicit).
3775
+ const sessionId = await server.getOrCreateImplicitSession();
3776
+ if (!sessionId) {
3777
+ console.error('Could not create or find an implicit session for this cwd.');
3778
+ process.exit(1);
3779
+ }
3780
+ // Build handoff: if agent provided a goal, use it; otherwise
3781
+ // synthesize from bridge activity (which is likely sparse since
3782
+ // this is a fresh process).
3783
+ const handoff = server.synthesizeMinimalHandoff();
3784
+ if (goalArg) handoff.goal = goalArg;
3785
+ if (meta.repo_path) handoff.repo_path = meta.repo_path;
3786
+ if (meta.branch) handoff.branch = meta.branch;
3787
+ const fake = { params: { name: 'retrieve_context', arguments: {} } };
3788
+ await server.maybeFillProjectId(fake);
3789
+ if (fake.params.arguments.project_id) handoff.project_id = fake.params.arguments.project_id;
3790
+
3791
+ const response = await server.makeRequest({
3792
+ jsonrpc: '2.0',
3793
+ id: `cli-checkpoint-${Date.now()}`,
3794
+ method: 'tools/call',
3795
+ params: { name: 'end_session', arguments: {
3796
+ session_id: sessionId,
3797
+ keep_open: true,
3798
+ handoff,
3799
+ } },
3800
+ });
3801
+ const text = response?.result?.content?.[0]?.text;
3802
+ const payload = JSON.parse(text || '{}');
3803
+ if (flags.json) {
3804
+ process.stdout.write(JSON.stringify({
3805
+ session_id: sessionId,
3806
+ short_id: sessionId.slice(0, 8),
3807
+ handoff_stored: !!payload.handoff_stored,
3808
+ kept_open: !!payload.kept_open,
3809
+ goal: handoff.goal,
3810
+ repo_path: handoff.repo_path,
3811
+ branch: handoff.branch,
3812
+ resume_command: `npx cf-memory-mcp resume ${sessionId.slice(0, 8)}`,
3813
+ }, null, 2) + '\n');
3814
+ process.exit(payload.handoff_stored ? 0 : 2);
3815
+ }
3816
+ if (payload.handoff_stored) {
3817
+ const shortId = sessionId.slice(0, 8);
3818
+ process.stdout.write(`Checkpoint saved (session ${shortId}).\n`);
3819
+ process.stdout.write(`Goal: ${handoff.goal}\n`);
3820
+ process.stdout.write(`To resume later: npx cf-memory-mcp resume ${shortId}\n`);
3821
+ process.exit(0);
3822
+ }
3823
+ process.stdout.write('Checkpoint did not store a handoff.\n');
3824
+ if (payload.error) process.stdout.write(`Error: ${payload.error}\n`);
3825
+ process.exit(2);
3826
+ } catch (err) {
3827
+ console.error('checkpoint command failed:', err.message);
3828
+ process.exit(1);
3829
+ }
3830
+ }
3831
+
3660
3832
  if (process.argv[2] === 'resume') {
3661
3833
  runResumeCli();
3662
3834
  return;
3663
3835
  }
3664
3836
 
3837
+ if (process.argv[2] === 'list') {
3838
+ runListCli();
3839
+ return;
3840
+ }
3841
+
3842
+ if (process.argv[2] === 'checkpoint') {
3843
+ runCheckpointCli();
3844
+ return;
3845
+ }
3846
+
3665
3847
  if (process.argv.includes('--diagnose')) {
3666
3848
  (async () => {
3667
3849
  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.20.0",
3
+ "version": "3.22.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": {