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.
- package/bin/cf-memory-mcp.js +223 -10
- package/package.json +1 -1
package/bin/cf-memory-mcp.js
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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 (
|
|
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
|
|
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: {
|
|
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.
|
|
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": {
|