cf-memory-mcp 3.22.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 +152 -13
- 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 = {
|
|
@@ -3657,13 +3792,15 @@ async function runResumeCli() {
|
|
|
3657
3792
|
process.exit(2);
|
|
3658
3793
|
}
|
|
3659
3794
|
const payload = JSON.parse(text);
|
|
3660
|
-
//
|
|
3661
|
-
//
|
|
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;
|
|
3662
3799
|
if (flags.json) {
|
|
3663
3800
|
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
3664
|
-
process.exit(
|
|
3801
|
+
process.exit(found ? 0 : 3);
|
|
3665
3802
|
}
|
|
3666
|
-
if (
|
|
3803
|
+
if (found) {
|
|
3667
3804
|
if (payload.resume_prompt) {
|
|
3668
3805
|
process.stdout.write(payload.resume_prompt + '\n');
|
|
3669
3806
|
} else {
|
|
@@ -3679,8 +3816,10 @@ async function runResumeCli() {
|
|
|
3679
3816
|
}
|
|
3680
3817
|
process.exit(0);
|
|
3681
3818
|
} else {
|
|
3682
|
-
|
|
3683
|
-
|
|
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);
|
|
3684
3823
|
}
|
|
3685
3824
|
} catch (err) {
|
|
3686
3825
|
console.error('resume command failed:', err.message);
|
|
@@ -3725,11 +3864,11 @@ async function runListCli() {
|
|
|
3725
3864
|
handoffs: list,
|
|
3726
3865
|
empty_hint: list.length === 0 ? payload.empty_hint : undefined,
|
|
3727
3866
|
}, null, 2) + '\n');
|
|
3728
|
-
process.exit(0);
|
|
3867
|
+
process.exit(list.length === 0 ? 3 : 0);
|
|
3729
3868
|
}
|
|
3730
3869
|
if (list.length === 0) {
|
|
3731
|
-
process.
|
|
3732
|
-
process.exit(
|
|
3870
|
+
process.stderr.write((payload.empty_hint || 'No handoffs for this context.') + '\n');
|
|
3871
|
+
process.exit(3);
|
|
3733
3872
|
}
|
|
3734
3873
|
// Render a compact table to terminal.
|
|
3735
3874
|
process.stdout.write(`Recent handoffs for ${meta.repo_path || 'this cwd'}:\n\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": {
|