cf-memory-mcp 3.9.12 → 3.10.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 CHANGED
@@ -14,6 +14,7 @@ Cloudflare-hosted MCP server for semantic code indexing, retrieval, and assistan
14
14
  - **Self-healing freshness** - Results auto-flag stale chunks with a copy-pasteable `refresh_files` call; `refresh_stale` rebuilds the changed files in seconds; cache invalidates on write
15
15
  - **Self-debugging errors** - `retrieve_context` includes `empty_hint` on 0-result queries; `get_file_content` / `get_file_outline` return `{error, hint}` pointing to the next call instead of bare null; calling bridge-only tools server-side returns `bridge_required` install instructions
16
16
  - **Smart defaults** - Bridge auto-detects project_id from cwd, returns chunks pre-enriched with file imports + citation + source classification
17
+ - **Cross-chat resume** - `end_session({handoff:{goal, status, next_steps, code_anchors, ...}})` persists a structured ~600-1500 token resume packet; next chat calls `get_context_bootstrap({resume:true})` to pick up where the previous chat left off, with `match_confidence`, branch-mismatch downgrades, and stale-anchor markers + refresh hints. Bridge auto-fills `repo_path`/`branch`/`project_id` from cwd + git.
17
18
  - **Contextual embeddings** - Qwen3 8K-context model with Anthropic-style chunk headers
18
19
 
19
20
  The active runtime is at [src-simplified/index.ts](src-simplified/index.ts). It exposes direct MCP over HTTP and a local stdio bridge for clients that want `npx cf-memory-mcp`.
@@ -779,6 +779,19 @@ class CFMemoryMCP {
779
779
  await this.maybeFillProjectId(message);
780
780
  }
781
781
 
782
+ // For resume-context tools, auto-attach repo_path/branch/project_id
783
+ // from cwd + git when the caller didn't supply them. Makes
784
+ // get_context_bootstrap({resume:true}) zero-config and lets
785
+ // end_session({handoff:{goal,status,next_steps}}) work without
786
+ // the agent having to dig up the repo metadata itself.
787
+ if (message.method === 'tools/call' && message.params && (
788
+ message.params.name === 'get_context_bootstrap' ||
789
+ message.params.name === 'start_session' ||
790
+ message.params.name === 'end_session'
791
+ )) {
792
+ await this.maybeAttachResumeMetadata(message);
793
+ }
794
+
782
795
  let response = await this.makeRequest(message);
783
796
  _mcpTrace('DISPATCH_DONE', `id=${message.id} method=${message.method} elapsed=${Date.now()-_t0}ms`);
784
797
 
@@ -2024,6 +2037,98 @@ class CFMemoryMCP {
2024
2037
  }
2025
2038
  }
2026
2039
 
2040
+ /**
2041
+ * Detect the current repo path and git branch once per process. Used by
2042
+ * the resume-context plumbing on start_session / end_session /
2043
+ * get_context_bootstrap so callers don't need to pass them manually.
2044
+ * Returns { repo_path, branch } where either may be undefined when
2045
+ * detection fails.
2046
+ */
2047
+ getRepoMetadata() {
2048
+ if (this._repoMetaCache) return this._repoMetaCache;
2049
+ const result = {};
2050
+ try {
2051
+ const root = process.env.CF_MEMORY_WATCH_PATH || process.cwd();
2052
+ if (root && fs.existsSync(root)) {
2053
+ result.repo_path = path.resolve(root);
2054
+ }
2055
+ } catch (_) { /* leave repo_path undefined */ }
2056
+ try {
2057
+ const { execSync } = require('child_process');
2058
+ // symbolic-ref works on empty repos (no commits yet) where
2059
+ // rev-parse --abbrev-ref HEAD fails. Detached HEAD still throws,
2060
+ // which is the right behavior — we want the branch name, not "HEAD".
2061
+ const branch = execSync('git symbolic-ref --short HEAD', {
2062
+ cwd: result.repo_path || process.cwd(),
2063
+ encoding: 'utf8',
2064
+ stdio: ['ignore', 'pipe', 'ignore'],
2065
+ timeout: 500,
2066
+ }).trim();
2067
+ if (branch) result.branch = branch;
2068
+ } catch (_) { /* not a git repo, detached HEAD, or no branch — leave undefined */ }
2069
+ this._repoMetaCache = result;
2070
+ _mcpTrace('REPO_META', `repo_path=${result.repo_path||'?'} branch=${result.branch||'?'}`);
2071
+ return result;
2072
+ }
2073
+
2074
+ /**
2075
+ * Attach repo_path / project_id / branch to start_session,
2076
+ * end_session.handoff and get_context_bootstrap calls when the caller
2077
+ * left them blank. This makes resume work zero-config: just call
2078
+ * get_context_bootstrap({resume:true}) and the bridge fills in the rest.
2079
+ *
2080
+ * The server still treats these as optional, so any of the auto-fill
2081
+ * lookups failing (no git, no indexed project) is safe — the call
2082
+ * proceeds with whatever metadata was already present.
2083
+ */
2084
+ async maybeAttachResumeMetadata(message) {
2085
+ try {
2086
+ if (message.method !== 'tools/call' || !message.params) return;
2087
+ const toolName = message.params.name;
2088
+ const args = message.params.arguments || (message.params.arguments = {});
2089
+
2090
+ // get_context_bootstrap with resume:true wants repo_path + branch
2091
+ // for matching. We do not force resume:true — that's a caller
2092
+ // choice — but we fill the metadata so resume becomes useful
2093
+ // when the caller does opt in.
2094
+ if (toolName === 'get_context_bootstrap') {
2095
+ const meta = this.getRepoMetadata();
2096
+ if (!args.repo_path && meta.repo_path) args.repo_path = meta.repo_path;
2097
+ if (!args.branch && meta.branch) args.branch = meta.branch;
2098
+ if (!args.project_id) {
2099
+ await this.maybeFillProjectId({ params: { name: 'retrieve_context', arguments: args } });
2100
+ }
2101
+ return;
2102
+ }
2103
+
2104
+ if (toolName === 'start_session') {
2105
+ const meta = this.getRepoMetadata();
2106
+ if (!args.repo_path && meta.repo_path) args.repo_path = meta.repo_path;
2107
+ if (!args.branch && meta.branch) args.branch = meta.branch;
2108
+ if (!args.project_id) {
2109
+ await this.maybeFillProjectId({ params: { name: 'retrieve_context', arguments: args } });
2110
+ }
2111
+ return;
2112
+ }
2113
+
2114
+ if (toolName === 'end_session' && args.handoff && typeof args.handoff === 'object') {
2115
+ const meta = this.getRepoMetadata();
2116
+ if (!args.handoff.repo_path && meta.repo_path) args.handoff.repo_path = meta.repo_path;
2117
+ if (!args.handoff.branch && meta.branch) args.handoff.branch = meta.branch;
2118
+ if (!args.handoff.project_id) {
2119
+ const fake = { params: { name: 'retrieve_context', arguments: {} } };
2120
+ await this.maybeFillProjectId(fake);
2121
+ if (fake.params.arguments.project_id) {
2122
+ args.handoff.project_id = fake.params.arguments.project_id;
2123
+ }
2124
+ }
2125
+ return;
2126
+ }
2127
+ } catch (err) {
2128
+ this.logDebug(`maybeAttachResumeMetadata failed: ${err && err.message}`);
2129
+ }
2130
+ }
2131
+
2027
2132
  /**
2028
2133
  * If the just-returned retrieve_context response had stale files,
2029
2134
  * refresh them via refreshFilesCore, re-run the original query, and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cf-memory-mcp",
3
- "version": "3.9.12",
3
+ "version": "3.10.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": {