cf-memory-mcp 3.21.0 → 3.23.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 +210 -16
- package/package.json +1 -1
package/bin/cf-memory-mcp.js
CHANGED
|
@@ -216,7 +216,8 @@ const TOOLS_LIST = [
|
|
|
216
216
|
session_id_hint: { type: 'string', description: 'Resume a specific session by id (overrides repo/branch matching). Full UUID or short prefix (>=8 chars). Useful after seeing recent_handoffs.' },
|
|
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
|
-
since: { type: 'string', description: 'Lower bound on handoff timestamp (ISO 8601). Scope resume to a time window.' }
|
|
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
221
|
}
|
|
221
222
|
}
|
|
222
223
|
},
|
|
@@ -695,7 +696,11 @@ class CFMemoryMCP {
|
|
|
695
696
|
// cfm://resume/current and recent handoffs at
|
|
696
697
|
// cfm://resume/recent for clients that prefer the
|
|
697
698
|
// resources protocol over tool calls.
|
|
698
|
-
resources: {}
|
|
699
|
+
resources: {},
|
|
700
|
+
// Prompts capability exposes /resume and /list_threads
|
|
701
|
+
// for clients (Claude Desktop, etc.) that surface
|
|
702
|
+
// prompts as slash-commands in their UI.
|
|
703
|
+
prompts: {}
|
|
699
704
|
},
|
|
700
705
|
serverInfo: {
|
|
701
706
|
name: 'cf-memory-mcp-simplified',
|
|
@@ -856,17 +861,147 @@ class CFMemoryMCP {
|
|
|
856
861
|
return;
|
|
857
862
|
}
|
|
858
863
|
|
|
859
|
-
// Handle prompts/list
|
|
864
|
+
// Handle prompts/list — advertise the resume + threads prompts.
|
|
865
|
+
// These let MCP clients (Claude Desktop, etc.) show a slash-
|
|
866
|
+
// command UI like /resume that injects the prior handoff into
|
|
867
|
+
// the next message without an explicit tool call.
|
|
860
868
|
if (message.method === 'prompts/list') {
|
|
861
869
|
const response = {
|
|
862
870
|
jsonrpc: '2.0',
|
|
863
871
|
id: message.id,
|
|
864
|
-
result: {
|
|
872
|
+
result: {
|
|
873
|
+
prompts: [
|
|
874
|
+
{
|
|
875
|
+
name: 'resume',
|
|
876
|
+
description: 'Insert the prior resume handoff for this repo into the chat so the agent can pick up where the last session left off.',
|
|
877
|
+
arguments: [
|
|
878
|
+
{ name: 'session_id', description: 'Optional: resume a specific session (full UUID or short prefix >=8 chars).', required: false },
|
|
879
|
+
],
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
name: 'list_threads',
|
|
883
|
+
description: 'Insert a list of recent handoffs for this repo so the user can pick a specific thread to resume.',
|
|
884
|
+
arguments: [
|
|
885
|
+
{ name: 'status', description: 'Optional: filter by status (in_progress, blocked, completed, abandoned).', required: false },
|
|
886
|
+
],
|
|
887
|
+
},
|
|
888
|
+
],
|
|
889
|
+
},
|
|
865
890
|
};
|
|
866
891
|
process.stdout.write(JSON.stringify(response) + '\n');
|
|
867
892
|
return;
|
|
868
893
|
}
|
|
869
894
|
|
|
895
|
+
// Handle prompts/get — render the requested prompt with current
|
|
896
|
+
// resume state baked in.
|
|
897
|
+
if (message.method === 'prompts/get') {
|
|
898
|
+
const promptName = message.params?.name;
|
|
899
|
+
const promptArgs = message.params?.arguments || {};
|
|
900
|
+
try {
|
|
901
|
+
if (promptName === 'resume') {
|
|
902
|
+
const meta = this.getRepoMetadata();
|
|
903
|
+
const args = { resume: true };
|
|
904
|
+
if (meta.repo_path) args.repo_path = meta.repo_path;
|
|
905
|
+
if (meta.branch) args.branch = meta.branch;
|
|
906
|
+
if (promptArgs.session_id) args.session_id_hint = promptArgs.session_id;
|
|
907
|
+
const fake = { params: { name: 'retrieve_context', arguments: {} } };
|
|
908
|
+
await this.maybeFillProjectId(fake);
|
|
909
|
+
if (fake.params.arguments.project_id) args.project_id = fake.params.arguments.project_id;
|
|
910
|
+
const bootstrap = await this.makeRequest({
|
|
911
|
+
jsonrpc: '2.0',
|
|
912
|
+
id: `prompt-resume-${Date.now()}`,
|
|
913
|
+
method: 'tools/call',
|
|
914
|
+
params: { name: 'get_context_bootstrap', arguments: args },
|
|
915
|
+
});
|
|
916
|
+
const text = bootstrap?.result?.content?.[0]?.text || '{}';
|
|
917
|
+
const payload = JSON.parse(text);
|
|
918
|
+
const body = payload.resume_prompt
|
|
919
|
+
|| (payload.empty_hint || 'No prior resume handoff for this context.');
|
|
920
|
+
const response = {
|
|
921
|
+
jsonrpc: '2.0',
|
|
922
|
+
id: message.id,
|
|
923
|
+
result: {
|
|
924
|
+
description: `Resume context for ${meta.repo_path || 'this cwd'}`,
|
|
925
|
+
messages: [
|
|
926
|
+
{ role: 'user', content: { type: 'text', text: body } },
|
|
927
|
+
],
|
|
928
|
+
},
|
|
929
|
+
};
|
|
930
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
if (promptName === 'list_threads') {
|
|
934
|
+
const meta = this.getRepoMetadata();
|
|
935
|
+
const args = { resume: true };
|
|
936
|
+
if (meta.repo_path) args.repo_path = meta.repo_path;
|
|
937
|
+
if (meta.branch) args.branch = meta.branch;
|
|
938
|
+
if (promptArgs.status) args.status_filter = promptArgs.status;
|
|
939
|
+
const fake = { params: { name: 'retrieve_context', arguments: {} } };
|
|
940
|
+
await this.maybeFillProjectId(fake);
|
|
941
|
+
if (fake.params.arguments.project_id) args.project_id = fake.params.arguments.project_id;
|
|
942
|
+
const bootstrap = await this.makeRequest({
|
|
943
|
+
jsonrpc: '2.0',
|
|
944
|
+
id: `prompt-list-${Date.now()}`,
|
|
945
|
+
method: 'tools/call',
|
|
946
|
+
params: { name: 'get_context_bootstrap', arguments: args },
|
|
947
|
+
});
|
|
948
|
+
const text = bootstrap?.result?.content?.[0]?.text || '{}';
|
|
949
|
+
const payload = JSON.parse(text);
|
|
950
|
+
const list = Array.isArray(payload.recent_handoffs) ? payload.recent_handoffs : [];
|
|
951
|
+
const fmtAge = (iso) => {
|
|
952
|
+
if (!iso) return '?';
|
|
953
|
+
const min = Math.round((Date.now() - new Date(iso).getTime()) / 60000);
|
|
954
|
+
if (min < 60) return `${min}m`;
|
|
955
|
+
if (min < 1440) return `${Math.round(min/60)}h`;
|
|
956
|
+
return `${Math.round(min/1440)}d`;
|
|
957
|
+
};
|
|
958
|
+
const lines = ['## Recent handoffs for this repo', ''];
|
|
959
|
+
if (list.length === 0) {
|
|
960
|
+
lines.push(payload.empty_hint || '(no handoffs)');
|
|
961
|
+
} else {
|
|
962
|
+
for (const h of list) {
|
|
963
|
+
const shortId = (h.session_id || '').slice(0, 8);
|
|
964
|
+
const age = fmtAge(h.ended_at || h.started_at);
|
|
965
|
+
lines.push(`- \`${shortId}\` [${h.status || '?'}] ${age} ago — ${h.goal || '(no goal)'}`);
|
|
966
|
+
if (h.next_step) lines.push(` next: ${h.next_step}`);
|
|
967
|
+
}
|
|
968
|
+
lines.push('', 'Pick one via `/resume <id>` or call `get_context_bootstrap({resume:true, session_id_hint:"<id>"})`.');
|
|
969
|
+
}
|
|
970
|
+
const response = {
|
|
971
|
+
jsonrpc: '2.0',
|
|
972
|
+
id: message.id,
|
|
973
|
+
result: {
|
|
974
|
+
description: `Recent handoffs for ${meta.repo_path || 'this cwd'}`,
|
|
975
|
+
messages: [
|
|
976
|
+
{ role: 'user', content: { type: 'text', text: lines.join('\n') } },
|
|
977
|
+
],
|
|
978
|
+
},
|
|
979
|
+
};
|
|
980
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
// Unknown prompt name
|
|
984
|
+
const response = {
|
|
985
|
+
jsonrpc: '2.0',
|
|
986
|
+
id: message.id,
|
|
987
|
+
error: {
|
|
988
|
+
code: -32602,
|
|
989
|
+
message: `Unknown prompt: ${promptName}. Supported: resume, list_threads`,
|
|
990
|
+
},
|
|
991
|
+
};
|
|
992
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
993
|
+
return;
|
|
994
|
+
} catch (err) {
|
|
995
|
+
const response = {
|
|
996
|
+
jsonrpc: '2.0',
|
|
997
|
+
id: message.id,
|
|
998
|
+
error: { code: -32603, message: `Failed to render prompt: ${err && err.message}` },
|
|
999
|
+
};
|
|
1000
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
870
1005
|
// Handle ping locally
|
|
871
1006
|
if (message.method === 'ping') {
|
|
872
1007
|
const response = {
|
|
@@ -3580,15 +3715,17 @@ A portable MCP (Model Context Protocol) server for AI memory storage using Cloud
|
|
|
3580
3715
|
|
|
3581
3716
|
Usage:
|
|
3582
3717
|
npx cf-memory-mcp Start the MCP server
|
|
3583
|
-
npx cf-memory-mcp resume
|
|
3584
|
-
npx cf-memory-mcp resume <id> Print a specific handoff by session_id prefix
|
|
3718
|
+
npx cf-memory-mcp resume [id] Print the prior resume handoff (markdown)
|
|
3585
3719
|
npx cf-memory-mcp list List recent handoffs for cwd
|
|
3586
|
-
npx cf-memory-mcp checkpoint
|
|
3587
|
-
npx cf-memory-mcp checkpoint "<goal>" Same, with an explicit goal string
|
|
3720
|
+
npx cf-memory-mcp checkpoint ["<goal>"] Snapshot current state (keep_open)
|
|
3588
3721
|
npx cf-memory-mcp --version Show version
|
|
3589
3722
|
npx cf-memory-mcp --help Show this help
|
|
3590
3723
|
npx cf-memory-mcp --diagnose Test connectivity and report issues
|
|
3591
3724
|
|
|
3725
|
+
Subcommand flags:
|
|
3726
|
+
--json, -j Emit JSON instead of formatted text (resume/list/checkpoint)
|
|
3727
|
+
--limit N, -n N For 'list': max number of handoffs (default 5, max 50)
|
|
3728
|
+
|
|
3592
3729
|
Environment Variables:
|
|
3593
3730
|
CF_MEMORY_API_KEY=<key> Your CF Memory API key (required)
|
|
3594
3731
|
CF_MEMORY_BASE_URL=<url> Override the default deployed worker
|
|
@@ -3601,6 +3738,25 @@ For more information, visit: https://github.com/johnlam90/cf-memory-mcp
|
|
|
3601
3738
|
process.exit(0);
|
|
3602
3739
|
}
|
|
3603
3740
|
|
|
3741
|
+
// Parse positional + flag args for CLI subcommands. Returns
|
|
3742
|
+
// { positional: string[], flags: { json: boolean, limit?: number } }.
|
|
3743
|
+
function parseCliArgs(rest) {
|
|
3744
|
+
const positional = [];
|
|
3745
|
+
const flags = { json: false };
|
|
3746
|
+
for (let i = 0; i < rest.length; i++) {
|
|
3747
|
+
const a = rest[i];
|
|
3748
|
+
if (a === '--json' || a === '-j') flags.json = true;
|
|
3749
|
+
else if (a === '--limit' || a === '-n') {
|
|
3750
|
+
const n = parseInt(rest[++i], 10);
|
|
3751
|
+
if (Number.isFinite(n) && n > 0) flags.limit = Math.min(n, 50);
|
|
3752
|
+
} else if (a.startsWith('--limit=')) {
|
|
3753
|
+
const n = parseInt(a.slice('--limit='.length), 10);
|
|
3754
|
+
if (Number.isFinite(n) && n > 0) flags.limit = Math.min(n, 50);
|
|
3755
|
+
} else positional.push(a);
|
|
3756
|
+
}
|
|
3757
|
+
return { positional, flags };
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3604
3760
|
// Read-only inspection command: print the prior resume handoff to stdout.
|
|
3605
3761
|
// Lets a human see what state is captured without involving an MCP client.
|
|
3606
3762
|
async function runResumeCli() {
|
|
@@ -3608,6 +3764,7 @@ async function runResumeCli() {
|
|
|
3608
3764
|
console.error('Error: CF_MEMORY_API_KEY environment variable is required');
|
|
3609
3765
|
process.exit(1);
|
|
3610
3766
|
}
|
|
3767
|
+
const { positional, flags } = parseCliArgs(process.argv.slice(3));
|
|
3611
3768
|
const server = new CFMemoryMCP();
|
|
3612
3769
|
server.logDebug = () => {};
|
|
3613
3770
|
server.logError = (...a) => process.stderr.write(a.join(' ') + '\n');
|
|
@@ -3617,7 +3774,7 @@ async function runResumeCli() {
|
|
|
3617
3774
|
if (meta.repo_path) args.repo_path = meta.repo_path;
|
|
3618
3775
|
if (meta.branch) args.branch = meta.branch;
|
|
3619
3776
|
// Optional positional session_id argument.
|
|
3620
|
-
const sessionArg =
|
|
3777
|
+
const sessionArg = positional[0];
|
|
3621
3778
|
if (sessionArg) args.session_id_hint = sessionArg;
|
|
3622
3779
|
const fake = { params: { name: 'retrieve_context', arguments: {} } };
|
|
3623
3780
|
await server.maybeFillProjectId(fake);
|
|
@@ -3635,7 +3792,15 @@ async function runResumeCli() {
|
|
|
3635
3792
|
process.exit(2);
|
|
3636
3793
|
}
|
|
3637
3794
|
const payload = JSON.parse(text);
|
|
3638
|
-
|
|
3795
|
+
// Exit codes:
|
|
3796
|
+
// 0 — handoff found and printed
|
|
3797
|
+
// 3 — no handoff found (lets scripts branch: `if ! cf-memory-mcp resume; then ...`)
|
|
3798
|
+
const found = !!payload.resume_handoff?.handoff;
|
|
3799
|
+
if (flags.json) {
|
|
3800
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
3801
|
+
process.exit(found ? 0 : 3);
|
|
3802
|
+
}
|
|
3803
|
+
if (found) {
|
|
3639
3804
|
if (payload.resume_prompt) {
|
|
3640
3805
|
process.stdout.write(payload.resume_prompt + '\n');
|
|
3641
3806
|
} else {
|
|
@@ -3651,8 +3816,10 @@ async function runResumeCli() {
|
|
|
3651
3816
|
}
|
|
3652
3817
|
process.exit(0);
|
|
3653
3818
|
} else {
|
|
3654
|
-
|
|
3655
|
-
|
|
3819
|
+
// No handoff: print empty_hint to stderr so it doesn't pollute
|
|
3820
|
+
// stdout for scripts that pipe the output.
|
|
3821
|
+
process.stderr.write((payload.empty_hint || 'No resume handoff available.') + '\n');
|
|
3822
|
+
process.exit(3);
|
|
3656
3823
|
}
|
|
3657
3824
|
} catch (err) {
|
|
3658
3825
|
console.error('resume command failed:', err.message);
|
|
@@ -3665,6 +3832,7 @@ async function runListCli() {
|
|
|
3665
3832
|
console.error('Error: CF_MEMORY_API_KEY environment variable is required');
|
|
3666
3833
|
process.exit(1);
|
|
3667
3834
|
}
|
|
3835
|
+
const { flags } = parseCliArgs(process.argv.slice(3));
|
|
3668
3836
|
const server = new CFMemoryMCP();
|
|
3669
3837
|
server.logDebug = () => {};
|
|
3670
3838
|
server.logError = (...a) => process.stderr.write(a.join(' ') + '\n');
|
|
@@ -3685,10 +3853,22 @@ async function runListCli() {
|
|
|
3685
3853
|
});
|
|
3686
3854
|
const text = response?.result?.content?.[0]?.text;
|
|
3687
3855
|
const payload = JSON.parse(text || '{}');
|
|
3688
|
-
const
|
|
3856
|
+
const allList = Array.isArray(payload.recent_handoffs) ? payload.recent_handoffs : [];
|
|
3857
|
+
const list = flags.limit ? allList.slice(0, flags.limit) : allList;
|
|
3858
|
+
|
|
3859
|
+
if (flags.json) {
|
|
3860
|
+
process.stdout.write(JSON.stringify({
|
|
3861
|
+
repo_path: meta.repo_path,
|
|
3862
|
+
branch: meta.branch,
|
|
3863
|
+
count: list.length,
|
|
3864
|
+
handoffs: list,
|
|
3865
|
+
empty_hint: list.length === 0 ? payload.empty_hint : undefined,
|
|
3866
|
+
}, null, 2) + '\n');
|
|
3867
|
+
process.exit(list.length === 0 ? 3 : 0);
|
|
3868
|
+
}
|
|
3689
3869
|
if (list.length === 0) {
|
|
3690
|
-
process.
|
|
3691
|
-
process.exit(
|
|
3870
|
+
process.stderr.write((payload.empty_hint || 'No handoffs for this context.') + '\n');
|
|
3871
|
+
process.exit(3);
|
|
3692
3872
|
}
|
|
3693
3873
|
// Render a compact table to terminal.
|
|
3694
3874
|
process.stdout.write(`Recent handoffs for ${meta.repo_path || 'this cwd'}:\n\n`);
|
|
@@ -3721,12 +3901,13 @@ async function runCheckpointCli() {
|
|
|
3721
3901
|
console.error('Error: CF_MEMORY_API_KEY environment variable is required');
|
|
3722
3902
|
process.exit(1);
|
|
3723
3903
|
}
|
|
3904
|
+
const { positional, flags } = parseCliArgs(process.argv.slice(3));
|
|
3724
3905
|
const server = new CFMemoryMCP();
|
|
3725
3906
|
server.logDebug = () => {};
|
|
3726
3907
|
server.logError = (...a) => process.stderr.write(a.join(' ') + '\n');
|
|
3727
3908
|
try {
|
|
3728
3909
|
// Optional positional goal argument: `cf-memory-mcp checkpoint "<goal text>"`
|
|
3729
|
-
const goalArg =
|
|
3910
|
+
const goalArg = positional.join(' ').trim();
|
|
3730
3911
|
const meta = server.getRepoMetadata();
|
|
3731
3912
|
// Need a session to attach the handoff to. Use or create the
|
|
3732
3913
|
// implicit session for this cwd (creates a new one if no implicit).
|
|
@@ -3758,6 +3939,19 @@ async function runCheckpointCli() {
|
|
|
3758
3939
|
});
|
|
3759
3940
|
const text = response?.result?.content?.[0]?.text;
|
|
3760
3941
|
const payload = JSON.parse(text || '{}');
|
|
3942
|
+
if (flags.json) {
|
|
3943
|
+
process.stdout.write(JSON.stringify({
|
|
3944
|
+
session_id: sessionId,
|
|
3945
|
+
short_id: sessionId.slice(0, 8),
|
|
3946
|
+
handoff_stored: !!payload.handoff_stored,
|
|
3947
|
+
kept_open: !!payload.kept_open,
|
|
3948
|
+
goal: handoff.goal,
|
|
3949
|
+
repo_path: handoff.repo_path,
|
|
3950
|
+
branch: handoff.branch,
|
|
3951
|
+
resume_command: `npx cf-memory-mcp resume ${sessionId.slice(0, 8)}`,
|
|
3952
|
+
}, null, 2) + '\n');
|
|
3953
|
+
process.exit(payload.handoff_stored ? 0 : 2);
|
|
3954
|
+
}
|
|
3761
3955
|
if (payload.handoff_stored) {
|
|
3762
3956
|
const shortId = sessionId.slice(0, 8);
|
|
3763
3957
|
process.stdout.write(`Checkpoint saved (session ${shortId}).\n`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cf-memory-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.23.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": {
|