cf-memory-mcp 3.31.0 → 3.33.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/README.md +26 -0
- package/bin/cf-memory-mcp.js +153 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,32 @@ The active runtime is at [src-simplified/index.ts](src-simplified/index.ts). It
|
|
|
35
35
|
- Coordination and progress with Durable Objects
|
|
36
36
|
- Diagnostics: `health_check` tool + `npx cf-memory-mcp --diagnose` + `CF_MEMORY_TRACE=1`
|
|
37
37
|
|
|
38
|
+
## Resume Context (cross-chat handoff)
|
|
39
|
+
|
|
40
|
+
Beyond code retrieval, cf-memory-mcp persists structured "handoff" packets so one chat can hand work off to the next. The next agent calls `get_context_bootstrap({resume:true})` (or the CLI `cf-memory-mcp resume`) and gets back the goal, status, next steps, code anchors with staleness markers, files touched, and a pre-rendered markdown prompt — without re-exploring the repo.
|
|
41
|
+
|
|
42
|
+
**Measured impact** (`scripts/benchmark-cold-vs-warm.ts`): warm resume is **82-85% faster** with **3-4 fewer tool calls** than cold-start exploration, and the warm path returns the exact next_action the cold agent would have eventually figured out.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
$ npx cf-memory-mcp resume # print prior handoff for cwd
|
|
46
|
+
$ npx cf-memory-mcp list # all recent handoffs (with status counts)
|
|
47
|
+
$ npx cf-memory-mcp checkpoint # snapshot current state
|
|
48
|
+
$ npx cf-memory-mcp status # bridge + server health for cwd
|
|
49
|
+
$ npx cf-memory-mcp doctor # diagnose common setup issues
|
|
50
|
+
$ npx cf-memory-mcp export <id> # backup a handoff
|
|
51
|
+
$ npx cf-memory-mcp import <file> # cross-machine sync
|
|
52
|
+
$ npx cf-memory-mcp delete <id> # remove a session
|
|
53
|
+
$ cfm resume # short alias
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Resume context also surfaces via:
|
|
57
|
+
|
|
58
|
+
- **MCP tools**: `start_session`, `end_session` (with structured `handoff`), `get_context_bootstrap` (with `resume`, `session_id_hint`, `goal_contains`, `since`, `status_filter`, `max_age_minutes`)
|
|
59
|
+
- **MCP resources**: `cfm://resume/current`, `cfm://resume/recent`, `cfm://resume/session/<id>`
|
|
60
|
+
- **MCP prompts**: `/resume`, `/list_threads` (for clients with slash-command UI)
|
|
61
|
+
|
|
62
|
+
See [docs/RESUME_CONTEXT_PLAN.md](docs/RESUME_CONTEXT_PLAN.md) for design rationale and [agents.md](agents.md) for the full agent-facing reference.
|
|
63
|
+
|
|
38
64
|
## Active Infra
|
|
39
65
|
|
|
40
66
|
- Workers
|
package/bin/cf-memory-mcp.js
CHANGED
|
@@ -411,13 +411,16 @@ const TOOLS_LIST = [
|
|
|
411
411
|
},
|
|
412
412
|
{
|
|
413
413
|
name: 'delete_session',
|
|
414
|
-
description: 'Delete
|
|
414
|
+
description: 'Delete session row(s) and any session_summary memories tied to them. Accepts single-id or bulk filters. Different from end_session — this physically removes rows.',
|
|
415
415
|
inputSchema: {
|
|
416
416
|
type: 'object',
|
|
417
417
|
properties: {
|
|
418
|
-
session_id: { type: 'string', description: 'Full session UUID to delete.' }
|
|
419
|
-
|
|
420
|
-
|
|
418
|
+
session_id: { type: 'string', description: 'Full session UUID to delete (not a prefix).' },
|
|
419
|
+
status: { description: 'Status filter: single string or array of statuses (in_progress, blocked, completed, abandoned).' },
|
|
420
|
+
older_than_days: { type: 'number', description: 'Delete sessions older than N days.' },
|
|
421
|
+
repo_path: { type: 'string', description: 'Restrict to a specific repo_path.' },
|
|
422
|
+
dry_run: { type: 'boolean', description: 'When true, preview what would be deleted without actually deleting.' }
|
|
423
|
+
}
|
|
421
424
|
}
|
|
422
425
|
}
|
|
423
426
|
];
|
|
@@ -3850,7 +3853,7 @@ For more information, visit: https://github.com/johnlam90/cf-memory-mcp
|
|
|
3850
3853
|
}
|
|
3851
3854
|
|
|
3852
3855
|
// Parse positional + flag args for CLI subcommands. Returns
|
|
3853
|
-
// { positional: string[], flags: { json, limit, md_path } }.
|
|
3856
|
+
// { positional: string[], flags: { json, limit, md_path, older_than_days, status, repo_path, yes } }.
|
|
3854
3857
|
function parseCliArgs(rest) {
|
|
3855
3858
|
const positional = [];
|
|
3856
3859
|
const flags = { json: false };
|
|
@@ -3864,10 +3867,40 @@ function parseCliArgs(rest) {
|
|
|
3864
3867
|
const n = parseInt(a.slice('--limit='.length), 10);
|
|
3865
3868
|
if (Number.isFinite(n) && n > 0) flags.limit = Math.min(n, 50);
|
|
3866
3869
|
} else if (a === '--md') {
|
|
3867
|
-
// Next arg is path; supports `--md=path` too.
|
|
3868
3870
|
flags.md_path = rest[++i];
|
|
3869
3871
|
} else if (a.startsWith('--md=')) {
|
|
3870
3872
|
flags.md_path = a.slice('--md='.length);
|
|
3873
|
+
} else if (a === '--older-than') {
|
|
3874
|
+
// Accept "7d" / "30d" / "12h" / raw number (days).
|
|
3875
|
+
const raw = rest[++i] || '';
|
|
3876
|
+
const m = raw.match(/^(\d+)\s*(d|h|m)?$/);
|
|
3877
|
+
if (m) {
|
|
3878
|
+
const n = parseInt(m[1], 10);
|
|
3879
|
+
const unit = m[2] || 'd';
|
|
3880
|
+
flags.older_than_days = unit === 'd' ? n : unit === 'h' ? n / 24 : n / 1440;
|
|
3881
|
+
}
|
|
3882
|
+
} else if (a.startsWith('--older-than=')) {
|
|
3883
|
+
const raw = a.slice('--older-than='.length);
|
|
3884
|
+
const m = raw.match(/^(\d+)\s*(d|h|m)?$/);
|
|
3885
|
+
if (m) {
|
|
3886
|
+
const n = parseInt(m[1], 10);
|
|
3887
|
+
const unit = m[2] || 'd';
|
|
3888
|
+
flags.older_than_days = unit === 'd' ? n : unit === 'h' ? n / 24 : n / 1440;
|
|
3889
|
+
}
|
|
3890
|
+
} else if (a === '--status') {
|
|
3891
|
+
flags.status = rest[++i];
|
|
3892
|
+
} else if (a.startsWith('--status=')) {
|
|
3893
|
+
flags.status = a.slice('--status='.length);
|
|
3894
|
+
} else if (a === '--repo') {
|
|
3895
|
+
flags.repo_path = rest[++i];
|
|
3896
|
+
} else if (a.startsWith('--repo=')) {
|
|
3897
|
+
flags.repo_path = a.slice('--repo='.length);
|
|
3898
|
+
} else if (a === '--since') {
|
|
3899
|
+
flags.since = rest[++i];
|
|
3900
|
+
} else if (a.startsWith('--since=')) {
|
|
3901
|
+
flags.since = a.slice('--since='.length);
|
|
3902
|
+
} else if (a === '--yes' || a === '-y') {
|
|
3903
|
+
flags.yes = true;
|
|
3871
3904
|
} else positional.push(a);
|
|
3872
3905
|
}
|
|
3873
3906
|
return { positional, flags };
|
|
@@ -3978,9 +4011,19 @@ async function runListCli() {
|
|
|
3978
4011
|
const args = { resume: true };
|
|
3979
4012
|
if (meta.repo_path) args.repo_path = meta.repo_path;
|
|
3980
4013
|
if (meta.branch) args.branch = meta.branch;
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
if (
|
|
4014
|
+
// Pass-through server-side filters when set on the CLI.
|
|
4015
|
+
if (flags.status) args.status_filter = flags.status;
|
|
4016
|
+
if (flags.since) args.since = flags.since;
|
|
4017
|
+
if (flags.repo_path) {
|
|
4018
|
+
// --repo overrides cwd → skip cwd's project_id too. Otherwise
|
|
4019
|
+
// tier-1 project_id_exact would short-circuit the lookup and
|
|
4020
|
+
// return cwd's handoff regardless of repo_path.
|
|
4021
|
+
args.repo_path = flags.repo_path;
|
|
4022
|
+
} else {
|
|
4023
|
+
const fake = { params: { name: 'retrieve_context', arguments: {} } };
|
|
4024
|
+
await server.maybeFillProjectId(fake);
|
|
4025
|
+
if (fake.params.arguments.project_id) args.project_id = fake.params.arguments.project_id;
|
|
4026
|
+
}
|
|
3984
4027
|
|
|
3985
4028
|
const response = await server.makeRequest({
|
|
3986
4029
|
jsonrpc: '2.0',
|
|
@@ -3997,8 +4040,10 @@ async function runListCli() {
|
|
|
3997
4040
|
|
|
3998
4041
|
if (flags.json) {
|
|
3999
4042
|
process.stdout.write(JSON.stringify({
|
|
4000
|
-
repo_path: meta.repo_path,
|
|
4001
|
-
branch: meta.branch,
|
|
4043
|
+
repo_path: args.repo_path || meta.repo_path,
|
|
4044
|
+
branch: args.branch || meta.branch,
|
|
4045
|
+
status_filter: flags.status,
|
|
4046
|
+
since: flags.since,
|
|
4002
4047
|
count: list.length,
|
|
4003
4048
|
handoffs: list,
|
|
4004
4049
|
empty_hint: list.length === 0 ? payload.empty_hint : undefined,
|
|
@@ -4009,8 +4054,20 @@ async function runListCli() {
|
|
|
4009
4054
|
process.stderr.write((payload.empty_hint || 'No handoffs for this context.') + '\n');
|
|
4010
4055
|
process.exit(3);
|
|
4011
4056
|
}
|
|
4012
|
-
//
|
|
4013
|
-
|
|
4057
|
+
// Status counts header: scan the visible list to give an at-a-glance
|
|
4058
|
+
// summary before the per-row details.
|
|
4059
|
+
const counts = {};
|
|
4060
|
+
for (const h of allList) {
|
|
4061
|
+
const s = h.status || 'unknown';
|
|
4062
|
+
counts[s] = (counts[s] || 0) + 1;
|
|
4063
|
+
}
|
|
4064
|
+
const countSummary = Object.entries(counts)
|
|
4065
|
+
.map(([s, n]) => `${n} ${s}`)
|
|
4066
|
+
.join(', ');
|
|
4067
|
+
// Render a compact table to terminal. Show the actual filter
|
|
4068
|
+
// applied (--repo override, or cwd default).
|
|
4069
|
+
const displayRepo = args.repo_path || 'this cwd';
|
|
4070
|
+
process.stdout.write(`Recent handoffs for ${displayRepo} (${countSummary}):\n\n`);
|
|
4014
4071
|
const fmtAge = (iso) => {
|
|
4015
4072
|
if (!iso) return '?';
|
|
4016
4073
|
const min = Math.round((Date.now() - new Date(iso).getTime()) / 60000);
|
|
@@ -4042,15 +4099,79 @@ async function runDeleteCli() {
|
|
|
4042
4099
|
}
|
|
4043
4100
|
const { positional, flags } = parseCliArgs(process.argv.slice(3));
|
|
4044
4101
|
const idArg = positional[0];
|
|
4045
|
-
|
|
4046
|
-
|
|
4102
|
+
const bulkMode = !idArg && (flags.older_than_days !== undefined || flags.status || flags.repo_path);
|
|
4103
|
+
if (!idArg && !bulkMode) {
|
|
4104
|
+
console.error('Usage:');
|
|
4105
|
+
console.error(' cf-memory-mcp delete <session-id-or-prefix> # single delete');
|
|
4106
|
+
console.error(' cf-memory-mcp delete --older-than 30d # bulk delete by age');
|
|
4107
|
+
console.error(' cf-memory-mcp delete --status abandoned # bulk by status');
|
|
4108
|
+
console.error(' cf-memory-mcp delete --repo /path/to/repo # bulk by repo');
|
|
4109
|
+
console.error(' Combine filters; use --yes to confirm >5 deletions.');
|
|
4047
4110
|
process.exit(1);
|
|
4048
4111
|
}
|
|
4049
4112
|
const server = new CFMemoryMCP();
|
|
4050
4113
|
server.logDebug = () => {};
|
|
4051
4114
|
try {
|
|
4052
|
-
|
|
4053
|
-
|
|
4115
|
+
if (bulkMode) {
|
|
4116
|
+
const args = {};
|
|
4117
|
+
if (flags.status) args.status = flags.status;
|
|
4118
|
+
if (flags.older_than_days !== undefined) args.older_than_days = flags.older_than_days;
|
|
4119
|
+
if (flags.repo_path) args.repo_path = flags.repo_path;
|
|
4120
|
+
|
|
4121
|
+
// Dry-run preview when --yes is missing. The server returns
|
|
4122
|
+
// {deleted:false, deleted_count, deleted_session_ids[]} so the
|
|
4123
|
+
// user can see what WOULD be deleted before committing.
|
|
4124
|
+
if (!flags.yes) {
|
|
4125
|
+
const previewRes = await server.makeRequest({
|
|
4126
|
+
jsonrpc: '2.0', id: `cli-delete-preview-${Date.now()}`,
|
|
4127
|
+
method: 'tools/call',
|
|
4128
|
+
params: { name: 'delete_session', arguments: { ...args, dry_run: true } },
|
|
4129
|
+
});
|
|
4130
|
+
const previewText = previewRes?.result?.content?.[0]?.text;
|
|
4131
|
+
const preview = JSON.parse(previewText || '{}');
|
|
4132
|
+
if (flags.json) {
|
|
4133
|
+
process.stdout.write(JSON.stringify({ ...preview, would_delete: true }, null, 2) + '\n');
|
|
4134
|
+
process.exit(2);
|
|
4135
|
+
}
|
|
4136
|
+
if (preview.deleted_count === 0) {
|
|
4137
|
+
process.stderr.write(`No sessions match the filters. Nothing to delete.\n`);
|
|
4138
|
+
process.exit(3);
|
|
4139
|
+
}
|
|
4140
|
+
process.stderr.write(`Would delete ${preview.deleted_count} session${preview.deleted_count === 1 ? '' : 's'}:\n`);
|
|
4141
|
+
const previewIds = preview.deleted_session_ids || [];
|
|
4142
|
+
for (const id of previewIds.slice(0, 10)) {
|
|
4143
|
+
process.stderr.write(` ${id.slice(0, 8)}\n`);
|
|
4144
|
+
}
|
|
4145
|
+
if (previewIds.length > 10) {
|
|
4146
|
+
process.stderr.write(` ... and ${preview.deleted_count - 10} more\n`);
|
|
4147
|
+
}
|
|
4148
|
+
process.stderr.write(`\nRe-run with --yes to confirm:\n`);
|
|
4149
|
+
process.stderr.write(` cf-memory-mcp delete ${process.argv.slice(3).join(' ')} --yes\n`);
|
|
4150
|
+
process.exit(2);
|
|
4151
|
+
}
|
|
4152
|
+
|
|
4153
|
+
const deleteRes = await server.makeRequest({
|
|
4154
|
+
jsonrpc: '2.0', id: `cli-delete-bulk-${Date.now()}`,
|
|
4155
|
+
method: 'tools/call',
|
|
4156
|
+
params: { name: 'delete_session', arguments: args },
|
|
4157
|
+
});
|
|
4158
|
+
const deleteText = deleteRes?.result?.content?.[0]?.text;
|
|
4159
|
+
const payload = JSON.parse(deleteText || '{}');
|
|
4160
|
+
if (flags.json) {
|
|
4161
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
4162
|
+
process.exit(payload.deleted ? 0 : 3);
|
|
4163
|
+
}
|
|
4164
|
+
if (payload.deleted) {
|
|
4165
|
+
process.stdout.write(`Deleted ${payload.deleted_count} session${payload.deleted_count === 1 ? '' : 's'} (and ${payload.cleaned_memories || 0} associated memories).\n`);
|
|
4166
|
+
process.exit(0);
|
|
4167
|
+
} else {
|
|
4168
|
+
process.stderr.write(`No sessions matched the filters.\n`);
|
|
4169
|
+
process.exit(3);
|
|
4170
|
+
}
|
|
4171
|
+
return;
|
|
4172
|
+
}
|
|
4173
|
+
|
|
4174
|
+
// Single-delete path (existing).
|
|
4054
4175
|
let fullId = idArg;
|
|
4055
4176
|
if (idArg.length < 36) {
|
|
4056
4177
|
const resolveRes = await server.makeRequest({
|
|
@@ -4207,8 +4328,12 @@ const PER_COMMAND_HELP = {
|
|
|
4207
4328
|
--md <path> Write the markdown to a file instead of stdout.
|
|
4208
4329
|
--json, -j Emit the full bootstrap payload as JSON for scripts.
|
|
4209
4330
|
Exit codes: 0 = handoff found, 3 = no handoff found.`,
|
|
4210
|
-
list: `cf-memory-mcp list [--limit N] [--json]
|
|
4211
|
-
List recent handoffs for the current cwd, status-ranked.
|
|
4331
|
+
list: `cf-memory-mcp list [--status S] [--since ISO] [--repo PATH] [--limit N] [--json]
|
|
4332
|
+
List recent handoffs for the current cwd, status-ranked. Header shows
|
|
4333
|
+
a status-count summary.
|
|
4334
|
+
--status S Restrict to a given status (in_progress, completed, ...).
|
|
4335
|
+
--since ISO Lower bound on handoff timestamp (ISO 8601).
|
|
4336
|
+
--repo PATH Override the cwd's repo (peek at another repo).
|
|
4212
4337
|
--limit N, -n N Max number of entries (default 5, max 50).
|
|
4213
4338
|
--json, -j Emit a JSON array for scripts.`,
|
|
4214
4339
|
checkpoint: `cf-memory-mcp checkpoint ["<goal>"] [--force] [--json]
|
|
@@ -4245,11 +4370,15 @@ const PER_COMMAND_HELP = {
|
|
|
4245
4370
|
completion: `cf-memory-mcp completion [bash|zsh|fish]
|
|
4246
4371
|
Output shell completion script. Pipe to your shell's completion dir.`,
|
|
4247
4372
|
delete: `cf-memory-mcp delete <session-id-or-prefix> [--json]
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
--
|
|
4252
|
-
|
|
4373
|
+
cf-memory-mcp delete [--status STATUS] [--older-than 30d] [--repo PATH] --yes [--json]
|
|
4374
|
+
Delete session row(s) and any session_summary memories tied to them.
|
|
4375
|
+
<session-id> Single delete: full UUID or short prefix (>=8 chars).
|
|
4376
|
+
--status STATUS Bulk filter by status (abandoned, completed, ...).
|
|
4377
|
+
--older-than 30d Bulk filter by age (suffixes: d, h, m).
|
|
4378
|
+
--repo PATH Bulk filter by repo_path.
|
|
4379
|
+
--yes, -y Required for bulk delete to confirm intent.
|
|
4380
|
+
--json, -j Emit JSON.
|
|
4381
|
+
Exit codes: 0 = deleted, 2 = bulk missing --yes, 3 = nothing matched.`,
|
|
4253
4382
|
env: `cf-memory-mcp env [--json]
|
|
4254
4383
|
Print all CF_MEMORY_* env vars the bridge reads, with their current
|
|
4255
4384
|
values + descriptions. Useful for discovering knobs.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cf-memory-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.33.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": {
|