cf-memory-mcp 3.11.0 → 3.12.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.
Files changed (2) hide show
  1. package/bin/cf-memory-mcp.js +142 -14
  2. package/package.json +1 -1
@@ -826,6 +826,20 @@ class CFMemoryMCP {
826
826
  }
827
827
  }
828
828
 
829
+ // After end_session without keep_open, drop the implicit-session
830
+ // cache so a subsequent end_session creates a fresh session.
831
+ if (message.method === 'tools/call' && message.params?.name === 'end_session') {
832
+ this.clearImplicitSessionIfFinalized(message, response);
833
+ }
834
+
835
+ // After explicit start_session, cache the returned session_id as
836
+ // the implicit session for this cwd. Lets a subsequent
837
+ // end_session({handoff:{...}}) without session_id resolve to
838
+ // the just-created one.
839
+ if (message.method === 'tools/call' && message.params?.name === 'start_session') {
840
+ this.cacheImplicitSessionFromResponse(response);
841
+ }
842
+
829
843
  // Send response to stdout
830
844
  process.stdout.write(JSON.stringify(response) + '\n');
831
845
 
@@ -2119,22 +2133,35 @@ class CFMemoryMCP {
2119
2133
  return;
2120
2134
  }
2121
2135
 
2122
- if (toolName === 'end_session' && args.handoff && typeof args.handoff === 'object') {
2123
- const meta = this.getRepoMetadata();
2124
- if (!args.handoff.repo_path && meta.repo_path) args.handoff.repo_path = meta.repo_path;
2125
- if (!args.handoff.branch && meta.branch) args.handoff.branch = meta.branch;
2126
- if (!args.handoff.project_id) {
2127
- const fake = { params: { name: 'retrieve_context', arguments: {} } };
2128
- await this.maybeFillProjectId(fake);
2129
- if (fake.params.arguments.project_id) {
2130
- args.handoff.project_id = fake.params.arguments.project_id;
2136
+ if (toolName === 'end_session') {
2137
+ // session_id auto-fill: if the caller didn't pass one, use
2138
+ // the implicit session for this cwd. Lets agents call
2139
+ // end_session({handoff:{...}}) without threading session_id
2140
+ // through every checkpoint.
2141
+ if (!args.session_id) {
2142
+ const implicit = await this.getOrCreateImplicitSession();
2143
+ if (implicit) {
2144
+ args.session_id = implicit;
2145
+ _mcpTrace('IMPLICIT_SESSION', `end_session using implicit session=${implicit}`);
2146
+ }
2147
+ }
2148
+ if (args.handoff && typeof args.handoff === 'object') {
2149
+ const meta = this.getRepoMetadata();
2150
+ if (!args.handoff.repo_path && meta.repo_path) args.handoff.repo_path = meta.repo_path;
2151
+ if (!args.handoff.branch && meta.branch) args.handoff.branch = meta.branch;
2152
+ if (!args.handoff.project_id) {
2153
+ const fake = { params: { name: 'retrieve_context', arguments: {} } };
2154
+ await this.maybeFillProjectId(fake);
2155
+ if (fake.params.arguments.project_id) {
2156
+ args.handoff.project_id = fake.params.arguments.project_id;
2157
+ }
2131
2158
  }
2159
+ // Auto-fill files_read from observed retrieve_context /
2160
+ // get_file_content / get_file_outline calls during this
2161
+ // bridge session, if the agent didn't supply them. The
2162
+ // agent's hand-written files_touched stays authoritative.
2163
+ this.autoFillHandoffFilesRead(args.handoff);
2132
2164
  }
2133
- // Auto-fill files_read from observed retrieve_context /
2134
- // get_file_content / get_file_outline calls during this
2135
- // bridge session, if the agent didn't supply them. The
2136
- // agent's hand-written files_touched stays authoritative.
2137
- this.autoFillHandoffFilesRead(args.handoff);
2138
2165
  return;
2139
2166
  }
2140
2167
  } catch (err) {
@@ -2142,6 +2169,107 @@ class CFMemoryMCP {
2142
2169
  }
2143
2170
  }
2144
2171
 
2172
+ /**
2173
+ * Return the implicit session_id for the current cwd, creating one via
2174
+ * start_session when no session is active. Lets agents skip explicit
2175
+ * session management — they can go straight to end_session({handoff})
2176
+ * for checkpoint or finalization.
2177
+ *
2178
+ * The cache is keyed by cwd so multiple bridge processes for different
2179
+ * repos don't share state. Sessions finalized via end_session WITHOUT
2180
+ * keep_open clear the implicit cache so the next call starts fresh.
2181
+ */
2182
+ async getOrCreateImplicitSession() {
2183
+ try {
2184
+ const cwd = process.env.CF_MEMORY_WATCH_PATH || process.cwd();
2185
+ if (!this._implicitSessionByCwd) this._implicitSessionByCwd = new Map();
2186
+ const cached = this._implicitSessionByCwd.get(cwd);
2187
+ if (cached) return cached;
2188
+
2189
+ // Lazily start a session, tagged with the same repo metadata as
2190
+ // start_session would auto-attach if called explicitly.
2191
+ const meta = this.getRepoMetadata();
2192
+ const startArgs = { context: 'main' };
2193
+ if (meta.repo_path) startArgs.repo_path = meta.repo_path;
2194
+ if (meta.branch) startArgs.branch = meta.branch;
2195
+ const fake = { params: { name: 'retrieve_context', arguments: {} } };
2196
+ await this.maybeFillProjectId(fake);
2197
+ if (fake.params.arguments.project_id) startArgs.project_id = fake.params.arguments.project_id;
2198
+
2199
+ const response = await this.makeRequest({
2200
+ jsonrpc: '2.0',
2201
+ id: `auto-session-${Date.now()}`,
2202
+ method: 'tools/call',
2203
+ params: { name: 'start_session', arguments: startArgs },
2204
+ });
2205
+ let sessionId = null;
2206
+ try {
2207
+ const payload = JSON.parse(response.result.content[0].text);
2208
+ sessionId = payload.session_id;
2209
+ } catch (_) { /* malformed response */ }
2210
+ if (sessionId) {
2211
+ this._implicitSessionByCwd.set(cwd, sessionId);
2212
+ _mcpTrace('IMPLICIT_SESSION', `created implicit session=${sessionId} for cwd=${cwd}`);
2213
+ }
2214
+ return sessionId;
2215
+ } catch (err) {
2216
+ this.logDebug(`getOrCreateImplicitSession failed: ${err && err.message}`);
2217
+ return null;
2218
+ }
2219
+ }
2220
+
2221
+ /**
2222
+ * After an explicit start_session response, cache the returned session_id
2223
+ * as the implicit session for the current cwd. This way the next
2224
+ * end_session call without a session_id picks up the right one
2225
+ * automatically.
2226
+ */
2227
+ cacheImplicitSessionFromResponse(response) {
2228
+ try {
2229
+ const text = response?.result?.content?.[0]?.text;
2230
+ if (!text) return;
2231
+ let parsed;
2232
+ try { parsed = JSON.parse(text); } catch (_) { return; }
2233
+ const sessionId = parsed?.session_id;
2234
+ if (!sessionId) return;
2235
+ const cwd = process.env.CF_MEMORY_WATCH_PATH || process.cwd();
2236
+ if (!this._implicitSessionByCwd) this._implicitSessionByCwd = new Map();
2237
+ this._implicitSessionByCwd.set(cwd, sessionId);
2238
+ _mcpTrace('IMPLICIT_SESSION', `cached implicit session=${sessionId} for cwd=${cwd}`);
2239
+ } catch (err) {
2240
+ this.logDebug(`cacheImplicitSessionFromResponse failed: ${err && err.message}`);
2241
+ }
2242
+ }
2243
+
2244
+ /**
2245
+ * Clear the implicit session for the current cwd after a non-keep_open
2246
+ * end_session call. Lets a subsequent checkpoint create a fresh
2247
+ * session instead of reusing a finalized one.
2248
+ */
2249
+ clearImplicitSessionIfFinalized(originalMessage, response) {
2250
+ try {
2251
+ if (originalMessage?.params?.name !== 'end_session') return;
2252
+ const args = originalMessage.params.arguments || {};
2253
+ if (args.keep_open) return; // checkpoint — keep cache
2254
+ // Parse the response to confirm the session was actually ended
2255
+ // (vs. an error). Don't clear if the server errored.
2256
+ const text = response?.result?.content?.[0]?.text;
2257
+ if (!text) return;
2258
+ try {
2259
+ const parsed = JSON.parse(text);
2260
+ if (parsed?.error || parsed?.kept_open) return;
2261
+ } catch (_) { return; }
2262
+
2263
+ const cwd = process.env.CF_MEMORY_WATCH_PATH || process.cwd();
2264
+ if (this._implicitSessionByCwd?.has(cwd)) {
2265
+ this._implicitSessionByCwd.delete(cwd);
2266
+ _mcpTrace('IMPLICIT_SESSION', `cleared implicit session for cwd=${cwd}`);
2267
+ }
2268
+ } catch (err) {
2269
+ this.logDebug(`clearImplicitSessionIfFinalized failed: ${err && err.message}`);
2270
+ }
2271
+ }
2272
+
2145
2273
  /**
2146
2274
  * Record file paths the agent touched via retrieve_context, get_file_content,
2147
2275
  * get_file_outline, and refresh_files. Used to seed end_session.handoff.files_read
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cf-memory-mcp",
3
- "version": "3.11.0",
3
+ "version": "3.12.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": {