cf-memory-mcp 3.36.0 → 3.38.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 +179 -4
- package/package.json +1 -1
package/bin/cf-memory-mcp.js
CHANGED
|
@@ -66,6 +66,32 @@ const STREAMABLE_HTTP_URL = `${BASE_URL}/mcp`;
|
|
|
66
66
|
const LEGACY_SERVER_URL = `${BASE_URL}/mcp/message`;
|
|
67
67
|
const PROGRESS_SSE_URL = `${BASE_URL}/api/indexing/progress`;
|
|
68
68
|
const PACKAGE_VERSION = require('../package.json').version;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Fetch the latest version of cf-memory-mcp from the npm registry.
|
|
72
|
+
* Returns the latest version string, or null on timeout/error.
|
|
73
|
+
* Used by `doctor` to flag outdated installs. Bounded by timeoutMs
|
|
74
|
+
* so an unreachable registry doesn't hang the doctor command.
|
|
75
|
+
*/
|
|
76
|
+
function checkLatestNpmVersion(currentVersion, timeoutMs = 1500) {
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
const req = https.get('https://registry.npmjs.org/cf-memory-mcp/latest', {
|
|
79
|
+
timeout: timeoutMs,
|
|
80
|
+
headers: { 'Accept': 'application/json', 'User-Agent': `cf-memory-mcp/${currentVersion}` },
|
|
81
|
+
}, (res) => {
|
|
82
|
+
let body = '';
|
|
83
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
84
|
+
res.on('end', () => {
|
|
85
|
+
try {
|
|
86
|
+
const parsed = JSON.parse(body);
|
|
87
|
+
resolve(parsed.version || null);
|
|
88
|
+
} catch (_) { resolve(null); }
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
req.on('error', () => resolve(null));
|
|
92
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
93
|
+
});
|
|
94
|
+
}
|
|
69
95
|
// Default per-request timeout. Batch uploads use BATCH_TIMEOUT_MS below.
|
|
70
96
|
const TIMEOUT_MS = 60000;
|
|
71
97
|
// Batch uploads can take longer because the worker processes each file
|
|
@@ -217,7 +243,8 @@ const TOOLS_LIST = [
|
|
|
217
243
|
max_age_minutes: { type: 'number', description: 'Hard cutoff: hide handoffs older than this many minutes. Without it, older handoffs surface with reduced confidence.' },
|
|
218
244
|
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
245
|
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"]).' }
|
|
246
|
+
status_filter: { description: 'Restrict matches to handoffs with this status. Single string or array of strings (e.g., ["in_progress","blocked"]).' },
|
|
247
|
+
recent_handoff_limit: { type: 'number', description: 'Override the default recent_handoffs cap (5). Range 1-50.' }
|
|
221
248
|
}
|
|
222
249
|
}
|
|
223
250
|
},
|
|
@@ -3806,7 +3833,7 @@ if (process.argv.includes('--version') || process.argv.includes('-v')) {
|
|
|
3806
3833
|
|
|
3807
3834
|
// Global --help only when no subcommand is present. With a subcommand, fall
|
|
3808
3835
|
// through to the per-command help dispatch below.
|
|
3809
|
-
const SUBCOMMANDS = new Set(['resume', 'list', 'checkpoint', 'status', 'clean', 'export', 'import', 'doctor', 'completion', 'delete', 'env']);
|
|
3836
|
+
const SUBCOMMANDS = new Set(['resume', 'list', 'checkpoint', 'status', 'clean', 'export', 'import', 'doctor', 'completion', 'delete', 'env', 'history']);
|
|
3810
3837
|
const hasSubcommand = process.argv[2] && SUBCOMMANDS.has(process.argv[2]);
|
|
3811
3838
|
|
|
3812
3839
|
if (!hasSubcommand && (process.argv.includes('--help') || process.argv.includes('-h'))) {
|
|
@@ -3818,7 +3845,8 @@ A portable MCP (Model Context Protocol) server for AI memory storage using Cloud
|
|
|
3818
3845
|
Usage:
|
|
3819
3846
|
npx cf-memory-mcp Start the MCP server
|
|
3820
3847
|
npx cf-memory-mcp resume [id] Print the prior resume handoff (markdown)
|
|
3821
|
-
npx cf-memory-mcp list List recent handoffs for cwd
|
|
3848
|
+
npx cf-memory-mcp list List recent handoffs for cwd (status-ranked)
|
|
3849
|
+
npx cf-memory-mcp history List all handoffs for cwd, chronological
|
|
3822
3850
|
npx cf-memory-mcp checkpoint ["<goal>"] Snapshot current state (keep_open)
|
|
3823
3851
|
npx cf-memory-mcp status Show bridge state + server resume availability
|
|
3824
3852
|
npx cf-memory-mcp clean Delete local disk cache for current cwd
|
|
@@ -3882,6 +3910,8 @@ function parseCliArgs(rest) {
|
|
|
3882
3910
|
flags.decisions_only = true;
|
|
3883
3911
|
} else if (a === '--blockers-only') {
|
|
3884
3912
|
flags.blockers_only = true;
|
|
3913
|
+
} else if (a === '--chain') {
|
|
3914
|
+
flags.chain = true;
|
|
3885
3915
|
} else if (a === '--older-than') {
|
|
3886
3916
|
// Accept "7d" / "30d" / "12h" / raw number (days).
|
|
3887
3917
|
const raw = rest[++i] || '';
|
|
@@ -4035,6 +4065,59 @@ async function runResumeCli() {
|
|
|
4035
4065
|
process.exit(0);
|
|
4036
4066
|
}
|
|
4037
4067
|
|
|
4068
|
+
// --chain: walk parent_session_id back through the thread history.
|
|
4069
|
+
// Bounded at 20 iterations to avoid an infinite loop on malformed
|
|
4070
|
+
// data. Prints each handoff as "<id> [age] status — goal".
|
|
4071
|
+
if (flags.chain) {
|
|
4072
|
+
if (!found) {
|
|
4073
|
+
process.stderr.write('No resume handoff available to walk back from.\n');
|
|
4074
|
+
process.exit(3);
|
|
4075
|
+
}
|
|
4076
|
+
const fmtAge = (iso) => {
|
|
4077
|
+
if (!iso) return '?';
|
|
4078
|
+
const min = Math.round((Date.now() - new Date(iso).getTime()) / 60000);
|
|
4079
|
+
if (min < 60) return `${min}m`;
|
|
4080
|
+
if (min < 1440) return `${Math.round(min/60)}h`;
|
|
4081
|
+
return `${Math.round(min/1440)}d`;
|
|
4082
|
+
};
|
|
4083
|
+
const chain = [];
|
|
4084
|
+
let cur = payload.resume_handoff;
|
|
4085
|
+
chain.push(cur);
|
|
4086
|
+
for (let i = 0; i < 20; i++) {
|
|
4087
|
+
const parentId = cur?.handoff?.parent_session_id;
|
|
4088
|
+
if (!parentId) break;
|
|
4089
|
+
// Fetch parent
|
|
4090
|
+
const parentRes = await server.makeRequest({
|
|
4091
|
+
jsonrpc: '2.0', id: `cli-chain-${i}-${Date.now()}`,
|
|
4092
|
+
method: 'tools/call',
|
|
4093
|
+
params: { name: 'get_context_bootstrap', arguments: { resume: true, session_id_hint: parentId } },
|
|
4094
|
+
});
|
|
4095
|
+
const parentText = parentRes?.result?.content?.[0]?.text;
|
|
4096
|
+
const parentPayload = JSON.parse(parentText || '{}');
|
|
4097
|
+
if (!parentPayload.resume_handoff) break;
|
|
4098
|
+
chain.push(parentPayload.resume_handoff);
|
|
4099
|
+
cur = parentPayload.resume_handoff;
|
|
4100
|
+
}
|
|
4101
|
+
if (flags.json) {
|
|
4102
|
+
process.stdout.write(JSON.stringify({ chain }, null, 2) + '\n');
|
|
4103
|
+
process.exit(0);
|
|
4104
|
+
}
|
|
4105
|
+
process.stdout.write(`Thread chain (newest first, ${chain.length} session${chain.length === 1 ? '' : 's'}):\n\n`);
|
|
4106
|
+
for (let i = 0; i < chain.length; i++) {
|
|
4107
|
+
const e = chain[i];
|
|
4108
|
+
const shortId = (e.session_id || '').slice(0, 8);
|
|
4109
|
+
const age = fmtAge(e.ended_at || e.started_at);
|
|
4110
|
+
const status = e.handoff?.status || '?';
|
|
4111
|
+
const goal = e.handoff?.goal || '(no goal)';
|
|
4112
|
+
const prefix = i === 0 ? '→' : ' ';
|
|
4113
|
+
process.stdout.write(`${prefix} ${shortId} ${status.padEnd(11)} ${age.padStart(4)} ago ${goal}\n`);
|
|
4114
|
+
}
|
|
4115
|
+
if (chain[chain.length-1]?.handoff?.parent_session_id) {
|
|
4116
|
+
process.stdout.write(`\n(chain truncated at 20 — increase with multiple --chain calls)\n`);
|
|
4117
|
+
}
|
|
4118
|
+
process.exit(0);
|
|
4119
|
+
}
|
|
4120
|
+
|
|
4038
4121
|
// --md <path>: write the rendered markdown to a file instead of
|
|
4039
4122
|
// (or in addition to) stdout. Useful for piping to a markdown
|
|
4040
4123
|
// viewer or saving to the project.
|
|
@@ -4175,6 +4258,71 @@ async function runListCli() {
|
|
|
4175
4258
|
}
|
|
4176
4259
|
}
|
|
4177
4260
|
|
|
4261
|
+
async function runHistoryCli() {
|
|
4262
|
+
if (!API_KEY) {
|
|
4263
|
+
console.error('Error: CF_MEMORY_API_KEY environment variable is required');
|
|
4264
|
+
process.exit(1);
|
|
4265
|
+
}
|
|
4266
|
+
const { flags } = parseCliArgs(process.argv.slice(3));
|
|
4267
|
+
const limit = flags.limit || 25;
|
|
4268
|
+
const server = new CFMemoryMCP();
|
|
4269
|
+
server.logDebug = () => {};
|
|
4270
|
+
try {
|
|
4271
|
+
const meta = server.getRepoMetadata();
|
|
4272
|
+
const args = { resume: true, recent_handoff_limit: limit };
|
|
4273
|
+
if (meta.repo_path) args.repo_path = meta.repo_path;
|
|
4274
|
+
if (meta.branch) args.branch = meta.branch;
|
|
4275
|
+
if (flags.repo_path) args.repo_path = flags.repo_path;
|
|
4276
|
+
if (flags.since) args.since = flags.since;
|
|
4277
|
+
if (!flags.repo_path) {
|
|
4278
|
+
const fake = { params: { name: 'retrieve_context', arguments: {} } };
|
|
4279
|
+
await server.maybeFillProjectId(fake);
|
|
4280
|
+
if (fake.params.arguments.project_id) args.project_id = fake.params.arguments.project_id;
|
|
4281
|
+
}
|
|
4282
|
+
|
|
4283
|
+
const response = await server.makeRequest({
|
|
4284
|
+
jsonrpc: '2.0',
|
|
4285
|
+
id: `cli-history-${Date.now()}`,
|
|
4286
|
+
method: 'tools/call',
|
|
4287
|
+
params: { name: 'get_context_bootstrap', arguments: args },
|
|
4288
|
+
});
|
|
4289
|
+
const text = response?.result?.content?.[0]?.text;
|
|
4290
|
+
const payload = JSON.parse(text || '{}');
|
|
4291
|
+
const all = (payload.recent_handoffs || []).slice();
|
|
4292
|
+
// Chronological order (oldest first).
|
|
4293
|
+
all.sort((a, b) => {
|
|
4294
|
+
const ta = new Date(a.ended_at || a.started_at).getTime();
|
|
4295
|
+
const tb = new Date(b.ended_at || b.started_at).getTime();
|
|
4296
|
+
return ta - tb;
|
|
4297
|
+
});
|
|
4298
|
+
if (flags.json) {
|
|
4299
|
+
process.stdout.write(JSON.stringify({
|
|
4300
|
+
repo_path: args.repo_path,
|
|
4301
|
+
count: all.length,
|
|
4302
|
+
handoffs: all,
|
|
4303
|
+
}, null, 2) + '\n');
|
|
4304
|
+
process.exit(all.length === 0 ? 3 : 0);
|
|
4305
|
+
}
|
|
4306
|
+
if (all.length === 0) {
|
|
4307
|
+
process.stderr.write((payload.empty_hint || 'No handoffs for this context.') + '\n');
|
|
4308
|
+
process.exit(3);
|
|
4309
|
+
}
|
|
4310
|
+
const fmtDate = (iso) => iso ? new Date(iso).toISOString().slice(0, 16).replace('T', ' ') : '?';
|
|
4311
|
+
process.stdout.write(`History for ${args.repo_path || 'this cwd'} (${all.length} handoff${all.length === 1 ? '' : 's'}, oldest first):\n\n`);
|
|
4312
|
+
for (const h of all) {
|
|
4313
|
+
const shortId = (h.session_id || '').slice(0, 8);
|
|
4314
|
+
const when = fmtDate(h.ended_at || h.started_at);
|
|
4315
|
+
const status = (h.status || 'unknown').padEnd(11);
|
|
4316
|
+
const goal = (h.goal || '(no goal)').slice(0, 70);
|
|
4317
|
+
process.stdout.write(` ${when} ${shortId} ${status} ${goal}\n`);
|
|
4318
|
+
}
|
|
4319
|
+
process.exit(0);
|
|
4320
|
+
} catch (err) {
|
|
4321
|
+
console.error('history command failed:', err.message);
|
|
4322
|
+
process.exit(1);
|
|
4323
|
+
}
|
|
4324
|
+
}
|
|
4325
|
+
|
|
4178
4326
|
async function runDeleteCli() {
|
|
4179
4327
|
if (!API_KEY) {
|
|
4180
4328
|
console.error('Error: CF_MEMORY_API_KEY environment variable is required');
|
|
@@ -4367,7 +4515,7 @@ function runEnvCli() {
|
|
|
4367
4515
|
|
|
4368
4516
|
function runCompletionCli() {
|
|
4369
4517
|
const shell = process.argv[3] || 'bash';
|
|
4370
|
-
const commands = ['resume', 'list', 'checkpoint', 'status', 'clean', 'export', 'import', 'delete', 'doctor', 'env', 'completion'];
|
|
4518
|
+
const commands = ['resume', 'list', 'history', 'checkpoint', 'status', 'clean', 'export', 'import', 'delete', 'doctor', 'env', 'completion'];
|
|
4371
4519
|
const flags = ['--json', '-j', '--limit', '-n', '--md', '--all', '--force', '-f', '--version', '-v', '--help', '-h', '--diagnose'];
|
|
4372
4520
|
if (shell === 'bash') {
|
|
4373
4521
|
process.stdout.write(`# cf-memory-mcp bash completion
|
|
@@ -4501,6 +4649,14 @@ const PER_COMMAND_HELP = {
|
|
|
4501
4649
|
Print all CF_MEMORY_* env vars the bridge reads, with their current
|
|
4502
4650
|
values + descriptions. Useful for discovering knobs.
|
|
4503
4651
|
--json, -j Emit JSON for scripts.`,
|
|
4652
|
+
history: `cf-memory-mcp history [--limit N] [--repo PATH] [--since ISO] [--json]
|
|
4653
|
+
Print all handoffs for the current cwd in chronological order (oldest
|
|
4654
|
+
first). Unlike 'list' (status-ranked, default 5), this is a flat
|
|
4655
|
+
timeline view.
|
|
4656
|
+
--limit N Max number of handoffs (default 25, max 50).
|
|
4657
|
+
--repo PATH Override the cwd's repo.
|
|
4658
|
+
--since ISO Lower bound on handoff timestamp.
|
|
4659
|
+
--json, -j Emit JSON.`,
|
|
4504
4660
|
};
|
|
4505
4661
|
|
|
4506
4662
|
function printPerCommandHelp(cmd) {
|
|
@@ -4518,6 +4674,20 @@ async function runDoctorCli() {
|
|
|
4518
4674
|
const checks = [];
|
|
4519
4675
|
const add = (label, ok, detail, fix) => checks.push({ label, ok, detail, ...(fix && !ok ? { fix } : {}) });
|
|
4520
4676
|
|
|
4677
|
+
// 0. Installed vs latest npm version. Best-effort: skipped on
|
|
4678
|
+
// network failure or when registry is unreachable. Doesn't add to
|
|
4679
|
+
// exit-code failures so doctor still passes if you're just behind.
|
|
4680
|
+
try {
|
|
4681
|
+
const latest = await checkLatestNpmVersion(PACKAGE_VERSION, 1500);
|
|
4682
|
+
if (latest && latest !== PACKAGE_VERSION) {
|
|
4683
|
+
add(`npm version current`, false,
|
|
4684
|
+
`installed v${PACKAGE_VERSION}, latest v${latest}`,
|
|
4685
|
+
`npm install -g cf-memory-mcp@latest # or: npx cf-memory-mcp@${latest}`);
|
|
4686
|
+
} else if (latest) {
|
|
4687
|
+
add(`npm version current`, true, `v${PACKAGE_VERSION} (latest)`);
|
|
4688
|
+
}
|
|
4689
|
+
} catch (_) { /* network failure or registry timeout — skip */ }
|
|
4690
|
+
|
|
4521
4691
|
// 1. API key set?
|
|
4522
4692
|
add('CF_MEMORY_API_KEY set', !!API_KEY,
|
|
4523
4693
|
API_KEY ? '(redacted)' : 'unset — most commands will fail',
|
|
@@ -5090,6 +5260,11 @@ if (process.argv[2] === 'env') {
|
|
|
5090
5260
|
return;
|
|
5091
5261
|
}
|
|
5092
5262
|
|
|
5263
|
+
if (process.argv[2] === 'history') {
|
|
5264
|
+
runHistoryCli();
|
|
5265
|
+
return;
|
|
5266
|
+
}
|
|
5267
|
+
|
|
5093
5268
|
if (process.argv.includes('--diagnose')) {
|
|
5094
5269
|
(async () => {
|
|
5095
5270
|
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.38.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": {
|