cf-memory-mcp 3.12.0 → 3.13.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.
@@ -843,6 +843,13 @@ class CFMemoryMCP {
843
843
  // Send response to stdout
844
844
  process.stdout.write(JSON.stringify(response) + '\n');
845
845
 
846
+ // Safety net: every N tool calls, if there's an active implicit
847
+ // session AND tracked activity, fire a background auto-checkpoint
848
+ // with a synthesized handoff. Catches agents that forget to call
849
+ // end_session before a crash or timeout. Fire-and-forget so it
850
+ // doesn't slow down the real response.
851
+ this.maybeAutoCheckpoint();
852
+
846
853
  } catch (error) {
847
854
  this.logError('Error handling message:', error);
848
855
  _mcpTrace('ERROR', `${error.message} elapsed=${Date.now()-_t0}ms`);
@@ -2302,11 +2309,85 @@ class CFMemoryMCP {
2302
2309
  } else if (toolName === 'refresh_files' && Array.isArray(args.file_paths)) {
2303
2310
  for (const p of args.file_paths) noteFile(p, 'refresh_files');
2304
2311
  }
2312
+
2313
+ // Remember the most recent retrieve_context query so auto-checkpoints
2314
+ // have something concrete to put in the synthesized handoff.goal.
2315
+ if (toolName === 'retrieve_context' && typeof args.query === 'string' && args.query.trim()) {
2316
+ this._lastRetrieveQuery = args.query.trim();
2317
+ }
2318
+
2319
+ // Tool-call counter for auto-checkpoint safety net. Counts all
2320
+ // tool/call messages; auto-checkpoint fires every N calls.
2321
+ this._toolCallCount = (this._toolCallCount || 0) + 1;
2305
2322
  } catch (err) {
2306
2323
  this.logDebug(`trackToolActivity failed: ${err && err.message}`);
2307
2324
  }
2308
2325
  }
2309
2326
 
2327
+ /**
2328
+ * Fire-and-forget auto-checkpoint. Every N tool calls, if there's an
2329
+ * active implicit session AND there's been meaningful activity, send a
2330
+ * background end_session({keep_open:true}) with a synthesized handoff.
2331
+ * This is the safety net for agents that forget (or crash) before
2332
+ * calling end_session manually.
2333
+ *
2334
+ * Behavior:
2335
+ * - Only fires when an implicit session exists (no surprise creation)
2336
+ * - Synthesizes handoff.goal from the last retrieve_context query
2337
+ * - Pulls files_read from activity tracker
2338
+ * - keep_open=true so the user can still finalize properly
2339
+ * - Failures are silent — never blocks the real tool call
2340
+ */
2341
+ async maybeAutoCheckpoint() {
2342
+ try {
2343
+ const intervalEnv = parseInt(process.env.CF_MEMORY_CHECKPOINT_EVERY || '25', 10);
2344
+ const interval = Number.isFinite(intervalEnv) && intervalEnv > 0 ? intervalEnv : 25;
2345
+ const opt = process.env.CF_MEMORY_AUTO_CHECKPOINT;
2346
+ // Opt-out by env. Default is on. Set to 0/false/off to disable.
2347
+ const enabled = !(opt === '0' || opt === 'false' || opt === 'off');
2348
+ if (!enabled) return;
2349
+ const count = this._toolCallCount || 0;
2350
+ if (count === 0 || count % interval !== 0) return;
2351
+ const cwd = process.env.CF_MEMORY_WATCH_PATH || process.cwd();
2352
+ const sessionId = this._implicitSessionByCwd?.get(cwd);
2353
+ if (!sessionId) return; // no implicit session, nothing to checkpoint
2354
+ // Require at least one tracked file so we don't spam empty handoffs.
2355
+ if (!this._activityFiles || this._activityFiles.size === 0) return;
2356
+
2357
+ // Synthesize handoff.
2358
+ const meta = this.getRepoMetadata();
2359
+ const fake = { params: { name: 'retrieve_context', arguments: {} } };
2360
+ await this.maybeFillProjectId(fake);
2361
+ const handoff = {
2362
+ goal: this._lastRetrieveQuery
2363
+ ? `Working on: ${this._lastRetrieveQuery.slice(0, 120)}`
2364
+ : `Auto-checkpoint at ${count} tool calls`,
2365
+ status: 'in_progress',
2366
+ };
2367
+ if (meta.repo_path) handoff.repo_path = meta.repo_path;
2368
+ if (meta.branch) handoff.branch = meta.branch;
2369
+ if (fake.params.arguments.project_id) handoff.project_id = fake.params.arguments.project_id;
2370
+ this.autoFillHandoffFilesRead(handoff);
2371
+
2372
+ // Fire-and-forget — don't block the real response on this.
2373
+ // Awaiting would serialise auto-checkpoint behind the main
2374
+ // request path. Failures are logged at debug only.
2375
+ this.makeRequest({
2376
+ jsonrpc: '2.0',
2377
+ id: `auto-checkpoint-${Date.now()}`,
2378
+ method: 'tools/call',
2379
+ params: { name: 'end_session', arguments: {
2380
+ session_id: sessionId,
2381
+ keep_open: true,
2382
+ handoff,
2383
+ } },
2384
+ }).catch(err => this.logDebug(`auto-checkpoint failed: ${err && err.message}`));
2385
+ _mcpTrace('AUTO_CHECKPOINT', `session=${sessionId} after ${count} calls files=${this._activityFiles.size}`);
2386
+ } catch (err) {
2387
+ this.logDebug(`maybeAutoCheckpoint failed: ${err && err.message}`);
2388
+ }
2389
+ }
2390
+
2310
2391
  /**
2311
2392
  * Record file paths from a retrieve_context response. Called after the
2312
2393
  * server responds so we capture what was actually returned (not just
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cf-memory-mcp",
3
- "version": "3.12.0",
3
+ "version": "3.13.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": {