cf-memory-mcp 3.19.0 → 3.21.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.
Files changed (2) hide show
  1. package/bin/cf-memory-mcp.js +223 -10
  2. package/package.json +1 -1
@@ -769,15 +769,25 @@ class CFMemoryMCP {
769
769
  // tools) recover prior chat state.
770
770
  if (message.method === 'resources/read') {
771
771
  const uri = message.params?.uri;
772
- if (uri === 'cfm://resume/current' || uri === 'cfm://resume/recent') {
772
+ const isCurrent = uri === 'cfm://resume/current';
773
+ const isRecent = uri === 'cfm://resume/recent';
774
+ // cfm://resume/session/<id-or-prefix>
775
+ const sessionMatch = typeof uri === 'string' && uri.startsWith('cfm://resume/session/')
776
+ ? uri.slice('cfm://resume/session/'.length)
777
+ : null;
778
+ if (isCurrent || isRecent || sessionMatch) {
773
779
  try {
774
780
  const meta = this.getRepoMetadata();
775
781
  const args = { resume: true };
776
- if (meta.repo_path) args.repo_path = meta.repo_path;
777
- if (meta.branch) args.branch = meta.branch;
778
- const fake = { params: { name: 'retrieve_context', arguments: {} } };
779
- await this.maybeFillProjectId(fake);
780
- if (fake.params.arguments.project_id) args.project_id = fake.params.arguments.project_id;
782
+ if (sessionMatch) {
783
+ args.session_id_hint = sessionMatch;
784
+ } else {
785
+ if (meta.repo_path) args.repo_path = meta.repo_path;
786
+ if (meta.branch) args.branch = meta.branch;
787
+ const fake = { params: { name: 'retrieve_context', arguments: {} } };
788
+ await this.maybeFillProjectId(fake);
789
+ if (fake.params.arguments.project_id) args.project_id = fake.params.arguments.project_id;
790
+ }
781
791
  const bootstrap = await this.makeRequest({
782
792
  jsonrpc: '2.0',
783
793
  id: `resource-read-${Date.now()}`,
@@ -787,7 +797,7 @@ class CFMemoryMCP {
787
797
  const text = bootstrap?.result?.content?.[0]?.text || '{}';
788
798
  // For cfm://resume/recent, surface only the recent_handoffs summaries.
789
799
  let payload = text;
790
- if (uri === 'cfm://resume/recent') {
800
+ if (isRecent) {
791
801
  try {
792
802
  const parsed = JSON.parse(text);
793
803
  payload = JSON.stringify({
@@ -818,19 +828,29 @@ class CFMemoryMCP {
818
828
  id: message.id,
819
829
  error: {
820
830
  code: -32602,
821
- message: `Unknown resource URI: ${uri}. Supported: cfm://resume/current, cfm://resume/recent`,
831
+ message: `Unknown resource URI: ${uri}. Supported: cfm://resume/current, cfm://resume/recent, cfm://resume/session/<id-or-prefix>`,
822
832
  },
823
833
  };
824
834
  process.stdout.write(JSON.stringify(response) + '\n');
825
835
  return;
826
836
  }
827
837
 
828
- // Handle resources/templates/list locally (we have none)
838
+ // Handle resources/templates/list advertise the parameterized
839
+ // per-session resume URI so clients can discover the template.
829
840
  if (message.method === 'resources/templates/list') {
830
841
  const response = {
831
842
  jsonrpc: '2.0',
832
843
  id: message.id,
833
- result: { resourceTemplates: [] }
844
+ result: {
845
+ resourceTemplates: [
846
+ {
847
+ uriTemplate: 'cfm://resume/session/{session_id}',
848
+ name: 'Specific session handoff',
849
+ description: 'Fetch a specific session\'s handoff by full UUID or short prefix (>=8 chars).',
850
+ mimeType: 'application/json',
851
+ },
852
+ ],
853
+ },
834
854
  };
835
855
  process.stdout.write(JSON.stringify(response) + '\n');
836
856
  return;
@@ -3560,6 +3580,11 @@ A portable MCP (Model Context Protocol) server for AI memory storage using Cloud
3560
3580
 
3561
3581
  Usage:
3562
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
3585
+ npx cf-memory-mcp list List recent handoffs for cwd
3586
+ npx cf-memory-mcp checkpoint Snapshot current state to a fresh handoff (keep session open)
3587
+ npx cf-memory-mcp checkpoint "<goal>" Same, with an explicit goal string
3563
3588
  npx cf-memory-mcp --version Show version
3564
3589
  npx cf-memory-mcp --help Show this help
3565
3590
  npx cf-memory-mcp --diagnose Test connectivity and report issues
@@ -3576,6 +3601,194 @@ For more information, visit: https://github.com/johnlam90/cf-memory-mcp
3576
3601
  process.exit(0);
3577
3602
  }
3578
3603
 
3604
+ // Read-only inspection command: print the prior resume handoff to stdout.
3605
+ // Lets a human see what state is captured without involving an MCP client.
3606
+ async function runResumeCli() {
3607
+ if (!API_KEY) {
3608
+ console.error('Error: CF_MEMORY_API_KEY environment variable is required');
3609
+ process.exit(1);
3610
+ }
3611
+ const server = new CFMemoryMCP();
3612
+ server.logDebug = () => {};
3613
+ server.logError = (...a) => process.stderr.write(a.join(' ') + '\n');
3614
+ try {
3615
+ const meta = server.getRepoMetadata();
3616
+ const args = { resume: true };
3617
+ if (meta.repo_path) args.repo_path = meta.repo_path;
3618
+ if (meta.branch) args.branch = meta.branch;
3619
+ // Optional positional session_id argument.
3620
+ const sessionArg = process.argv[3];
3621
+ if (sessionArg) args.session_id_hint = sessionArg;
3622
+ const fake = { params: { name: 'retrieve_context', arguments: {} } };
3623
+ await server.maybeFillProjectId(fake);
3624
+ if (fake.params.arguments.project_id) args.project_id = fake.params.arguments.project_id;
3625
+
3626
+ const response = await server.makeRequest({
3627
+ jsonrpc: '2.0',
3628
+ id: `cli-resume-${Date.now()}`,
3629
+ method: 'tools/call',
3630
+ params: { name: 'get_context_bootstrap', arguments: args },
3631
+ });
3632
+ const text = response?.result?.content?.[0]?.text;
3633
+ if (!text) {
3634
+ console.error('No response from server.');
3635
+ process.exit(2);
3636
+ }
3637
+ const payload = JSON.parse(text);
3638
+ if (payload.resume_handoff?.handoff) {
3639
+ if (payload.resume_prompt) {
3640
+ process.stdout.write(payload.resume_prompt + '\n');
3641
+ } else {
3642
+ process.stdout.write(JSON.stringify(payload.resume_handoff, null, 2) + '\n');
3643
+ }
3644
+ if (Array.isArray(payload.recent_handoffs) && payload.recent_handoffs.length > 1) {
3645
+ process.stdout.write('\n---\nOther recent handoffs:\n');
3646
+ for (const h of payload.recent_handoffs.slice(0, 4)) {
3647
+ const shortId = (h.session_id || '').slice(0, 8);
3648
+ process.stdout.write(` ${shortId} [${h.status || '?'}] ${h.goal || ''}\n`);
3649
+ }
3650
+ process.stdout.write(`(Pass any short id as: npx cf-memory-mcp resume <id>)\n`);
3651
+ }
3652
+ process.exit(0);
3653
+ } else {
3654
+ process.stdout.write((payload.empty_hint || 'No resume handoff available.') + '\n');
3655
+ process.exit(0);
3656
+ }
3657
+ } catch (err) {
3658
+ console.error('resume command failed:', err.message);
3659
+ process.exit(1);
3660
+ }
3661
+ }
3662
+
3663
+ async function runListCli() {
3664
+ if (!API_KEY) {
3665
+ console.error('Error: CF_MEMORY_API_KEY environment variable is required');
3666
+ process.exit(1);
3667
+ }
3668
+ const server = new CFMemoryMCP();
3669
+ server.logDebug = () => {};
3670
+ server.logError = (...a) => process.stderr.write(a.join(' ') + '\n');
3671
+ try {
3672
+ const meta = server.getRepoMetadata();
3673
+ const args = { resume: true };
3674
+ if (meta.repo_path) args.repo_path = meta.repo_path;
3675
+ if (meta.branch) args.branch = meta.branch;
3676
+ const fake = { params: { name: 'retrieve_context', arguments: {} } };
3677
+ await server.maybeFillProjectId(fake);
3678
+ if (fake.params.arguments.project_id) args.project_id = fake.params.arguments.project_id;
3679
+
3680
+ const response = await server.makeRequest({
3681
+ jsonrpc: '2.0',
3682
+ id: `cli-list-${Date.now()}`,
3683
+ method: 'tools/call',
3684
+ params: { name: 'get_context_bootstrap', arguments: args },
3685
+ });
3686
+ const text = response?.result?.content?.[0]?.text;
3687
+ const payload = JSON.parse(text || '{}');
3688
+ const list = Array.isArray(payload.recent_handoffs) ? payload.recent_handoffs : [];
3689
+ if (list.length === 0) {
3690
+ process.stdout.write((payload.empty_hint || 'No handoffs for this context.') + '\n');
3691
+ process.exit(0);
3692
+ }
3693
+ // Render a compact table to terminal.
3694
+ process.stdout.write(`Recent handoffs for ${meta.repo_path || 'this cwd'}:\n\n`);
3695
+ const fmtAge = (iso) => {
3696
+ if (!iso) return '?';
3697
+ const min = Math.round((Date.now() - new Date(iso).getTime()) / 60000);
3698
+ if (min < 60) return `${min}m`;
3699
+ if (min < 1440) return `${Math.round(min/60)}h`;
3700
+ return `${Math.round(min/1440)}d`;
3701
+ };
3702
+ for (const h of list) {
3703
+ const shortId = (h.session_id || '').slice(0, 8);
3704
+ const age = fmtAge(h.ended_at || h.started_at);
3705
+ const status = (h.status || 'unknown').padEnd(11);
3706
+ const branch = h.branch ? `[${h.branch}] ` : '';
3707
+ const goal = (h.goal || '(no goal)').slice(0, 80);
3708
+ process.stdout.write(` ${shortId} ${status} ${age.padStart(4)} ago ${branch}${goal}\n`);
3709
+ if (h.next_step) process.stdout.write(` next: ${h.next_step.slice(0, 80)}\n`);
3710
+ }
3711
+ process.stdout.write(`\n(Run "npx cf-memory-mcp resume <id>" to see a specific handoff)\n`);
3712
+ process.exit(0);
3713
+ } catch (err) {
3714
+ console.error('list command failed:', err.message);
3715
+ process.exit(1);
3716
+ }
3717
+ }
3718
+
3719
+ async function runCheckpointCli() {
3720
+ if (!API_KEY) {
3721
+ console.error('Error: CF_MEMORY_API_KEY environment variable is required');
3722
+ process.exit(1);
3723
+ }
3724
+ const server = new CFMemoryMCP();
3725
+ server.logDebug = () => {};
3726
+ server.logError = (...a) => process.stderr.write(a.join(' ') + '\n');
3727
+ try {
3728
+ // Optional positional goal argument: `cf-memory-mcp checkpoint "<goal text>"`
3729
+ const goalArg = process.argv.slice(3).join(' ').trim();
3730
+ const meta = server.getRepoMetadata();
3731
+ // Need a session to attach the handoff to. Use or create the
3732
+ // implicit session for this cwd (creates a new one if no implicit).
3733
+ const sessionId = await server.getOrCreateImplicitSession();
3734
+ if (!sessionId) {
3735
+ console.error('Could not create or find an implicit session for this cwd.');
3736
+ process.exit(1);
3737
+ }
3738
+ // Build handoff: if agent provided a goal, use it; otherwise
3739
+ // synthesize from bridge activity (which is likely sparse since
3740
+ // this is a fresh process).
3741
+ const handoff = server.synthesizeMinimalHandoff();
3742
+ if (goalArg) handoff.goal = goalArg;
3743
+ if (meta.repo_path) handoff.repo_path = meta.repo_path;
3744
+ if (meta.branch) handoff.branch = meta.branch;
3745
+ const fake = { params: { name: 'retrieve_context', arguments: {} } };
3746
+ await server.maybeFillProjectId(fake);
3747
+ if (fake.params.arguments.project_id) handoff.project_id = fake.params.arguments.project_id;
3748
+
3749
+ const response = await server.makeRequest({
3750
+ jsonrpc: '2.0',
3751
+ id: `cli-checkpoint-${Date.now()}`,
3752
+ method: 'tools/call',
3753
+ params: { name: 'end_session', arguments: {
3754
+ session_id: sessionId,
3755
+ keep_open: true,
3756
+ handoff,
3757
+ } },
3758
+ });
3759
+ const text = response?.result?.content?.[0]?.text;
3760
+ const payload = JSON.parse(text || '{}');
3761
+ if (payload.handoff_stored) {
3762
+ const shortId = sessionId.slice(0, 8);
3763
+ process.stdout.write(`Checkpoint saved (session ${shortId}).\n`);
3764
+ process.stdout.write(`Goal: ${handoff.goal}\n`);
3765
+ process.stdout.write(`To resume later: npx cf-memory-mcp resume ${shortId}\n`);
3766
+ process.exit(0);
3767
+ }
3768
+ process.stdout.write('Checkpoint did not store a handoff.\n');
3769
+ if (payload.error) process.stdout.write(`Error: ${payload.error}\n`);
3770
+ process.exit(2);
3771
+ } catch (err) {
3772
+ console.error('checkpoint command failed:', err.message);
3773
+ process.exit(1);
3774
+ }
3775
+ }
3776
+
3777
+ if (process.argv[2] === 'resume') {
3778
+ runResumeCli();
3779
+ return;
3780
+ }
3781
+
3782
+ if (process.argv[2] === 'list') {
3783
+ runListCli();
3784
+ return;
3785
+ }
3786
+
3787
+ if (process.argv[2] === 'checkpoint') {
3788
+ runCheckpointCli();
3789
+ return;
3790
+ }
3791
+
3579
3792
  if (process.argv.includes('--diagnose')) {
3580
3793
  (async () => {
3581
3794
  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.19.0",
3
+ "version": "3.21.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": {