cf-memory-mcp 3.9.4 → 3.9.6

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
@@ -25,8 +25,8 @@ The active runtime is at [src-simplified/index.ts](src-simplified/index.ts). It
25
25
  - Code indexing with `index_project` and `index_github`
26
26
  - Retrieval with `retrieve_context` (3-lane hybrid + cross-encoder rerank, returns `file_imports` + `source_kind` + `citation`)
27
27
  - Code relationship graph with `get_related_code` (calls, imports, extends, implements)
28
- - Project exploration with `list_files`, `list_projects`, `get_stats`, `get_file_outline`, `get_file_content`
29
- - **Staleness handling**: `find_stale_files`, `refresh_files`, `refresh_stale`. Results auto-tag stale chunks with a `stale_refresh_hint` showing the exact call to fix them.
28
+ - Project exploration with `list_files`, `list_projects`, `get_stats`, `get_file_outline`, `get_file_content` (bridge reads local file byte-exact when resolvable; falls back to chunk-reconstruction with `reconstruction_warning` + `missing_line_ranges`)
29
+ - **Staleness handling**: `find_stale_files`, `refresh_files`, `refresh_stale`. Results auto-tag stale chunks with a `stale_refresh_hint` showing the exact call to fix them. Set `CF_MEMORY_AUTO_REFRESH=true` (or pass `auto_refresh:true`) to have the bridge transparently refresh + re-query before returning.
30
30
  - Assistant memory tools (`store_memory`, `retrieve_memories`, `get_context_bootstrap`, `delete_memory`)
31
31
  - Session tracking with `start_session`, `end_session`
32
32
  - Structured entity storage with `store_entity`
@@ -64,6 +64,7 @@ Useful environment variables:
64
64
  - `CF_MEMORY_TRACE=1` — verbose request/response logging to `/tmp/cf-memory-mcp.log` (rotates at 5MB)
65
65
  - `CF_MEMORY_PROGRESS=true` — stream per-file indexing progress via SSE to stderr during `index_project`
66
66
  - `CF_MEMORY_AUTO_WATCH=true` — watch the project directory and auto-reindex on file change
67
+ - `CF_MEMORY_AUTO_REFRESH=true` — when retrieve_context returns stale chunks, transparently refresh the changed files and re-query before returning (new in v3.9.0). Removes the find_stale → refresh → re-query loop.
67
68
  - `CF_MEMORY_WATCH_PATH=/path/to/project` — explicit project root (defaults to bridge's `cwd`)
68
69
  - `CF_MEMORY_PROJECT_NAME=my-project` — display name when auto-watch indexes the project
69
70
 
@@ -1801,22 +1801,45 @@ class CFMemoryMCP {
1801
1801
  projectRoot = process.env.CF_MEMORY_WATCH_PATH || process.cwd();
1802
1802
  }
1803
1803
 
1804
- // Try exact path first, then walk up substring matches under root
1805
- const candidates = [
1806
- path.resolve(projectRoot, filePath),
1807
- path.resolve(filePath),
1808
- ];
1809
- let resolvedFull = null;
1810
- for (const c of candidates) {
1811
- try {
1812
- const stat = fs.statSync(c);
1813
- if (stat.isFile()) {
1814
- resolvedFull = c;
1815
- break;
1816
- }
1817
- } catch (_) { /* try next */ }
1804
+ // Resolve the file_path against the project root and HARD-CONFINE
1805
+ // the result to be inside the project root. Without this check the
1806
+ // tool became a general filesystem reader — `file_path: "/etc/hosts"`
1807
+ // or `file_path: "../../../etc/passwd"` would both serve those
1808
+ // files because path.resolve treats absolute file_paths as-is and
1809
+ // doesn't enforce ancestry. This is a security finding from codex
1810
+ // review; fix before reading anything off disk.
1811
+ //
1812
+ // Also dereference symlinks via fs.realpathSync — without this,
1813
+ // an attacker could create a symlink inside the project root
1814
+ // pointing at /etc/passwd and the relative-path check would
1815
+ // still pass (the symlink itself is in-root). realpath gives
1816
+ // the underlying target which we then check the same way.
1817
+ const normalizedRoot = (() => {
1818
+ try { return fs.realpathSync(path.resolve(projectRoot)); }
1819
+ catch (_) { return path.resolve(projectRoot); }
1820
+ })();
1821
+ const candidateFull = path.resolve(normalizedRoot, filePath);
1822
+ let resolvedFull;
1823
+ try {
1824
+ resolvedFull = fs.realpathSync(candidateFull);
1825
+ } catch (_) {
1826
+ return false; // doesn't exist or unreadable
1827
+ }
1828
+ // Path must be strictly under the (real) project root. `relative`
1829
+ // returns an empty string for the root itself and a `..`-prefixed
1830
+ // string for anything outside. Use the realpath of both sides so
1831
+ // symlinks can't smuggle the target out of the root.
1832
+ const relative = path.relative(normalizedRoot, resolvedFull);
1833
+ if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
1834
+ _mcpTrace('GFC_LOCAL_REJECT_ESCAPE', `${filePath} -> ${resolvedFull} escapes root ${normalizedRoot}`);
1835
+ return false;
1836
+ }
1837
+ try {
1838
+ const probe = fs.statSync(resolvedFull);
1839
+ if (!probe.isFile()) return false;
1840
+ } catch (_) {
1841
+ return false;
1818
1842
  }
1819
- if (!resolvedFull) return false;
1820
1843
 
1821
1844
  // Guard against blowing memory on huge files (binary blobs, big
1822
1845
  // SQL dumps, etc). 10MB is plenty for any source file the
@@ -1849,7 +1872,11 @@ class CFMemoryMCP {
1849
1872
  const language = extToLang[ext] || null;
1850
1873
 
1851
1874
  const payload = {
1852
- file_path: path.relative(projectRoot, resolvedFull) || path.basename(resolvedFull),
1875
+ // Use normalizedRoot (the realpath'd root) to match resolvedFull
1876
+ // (also realpath'd). Using the original projectRoot here gave
1877
+ // weird relative paths on macOS where /tmp is a symlink to
1878
+ // /private/tmp.
1879
+ file_path: path.relative(normalizedRoot, resolvedFull) || path.basename(resolvedFull),
1853
1880
  language,
1854
1881
  total_lines: totalLines,
1855
1882
  size_bytes: stat.size,
@@ -2274,23 +2301,30 @@ if (process.argv.includes('--diagnose')) {
2274
2301
  return;
2275
2302
  }
2276
2303
 
2277
- // Check API key before starting server
2278
- if (!API_KEY) {
2279
- console.error('Error: CF_MEMORY_API_KEY environment variable is required');
2280
- console.error('');
2281
- console.error('Please set your API key:');
2282
- console.error(' export CF_MEMORY_API_KEY="your-api-key-here"');
2283
- console.error('');
2284
- console.error('Or run with:');
2285
- console.error(' CF_MEMORY_API_KEY="your-api-key-here" npx cf-memory-mcp');
2286
- console.error('');
2287
- console.error(`Target server: ${BASE_URL}`);
2288
- process.exit(1);
2304
+ // Only run the entry-point side effects when invoked directly (not when
2305
+ // require()'d from a test or downstream tool). This guard lets the test
2306
+ // suite import CFMemoryMCP without triggering the API-key check and
2307
+ // starting the stdio listener.
2308
+ if (require.main === module) {
2309
+ if (!API_KEY) {
2310
+ console.error('Error: CF_MEMORY_API_KEY environment variable is required');
2311
+ console.error('');
2312
+ console.error('Please set your API key:');
2313
+ console.error(' export CF_MEMORY_API_KEY="your-api-key-here"');
2314
+ console.error('');
2315
+ console.error('Or run with:');
2316
+ console.error(' CF_MEMORY_API_KEY="your-api-key-here" npx cf-memory-mcp');
2317
+ console.error('');
2318
+ console.error(`Target server: ${BASE_URL}`);
2319
+ process.exit(1);
2320
+ }
2321
+
2322
+ const server = new CFMemoryMCP();
2323
+ server.start().catch(error => {
2324
+ console.error('Failed to start CF Memory MCP server:', error.message);
2325
+ process.exit(1);
2326
+ });
2289
2327
  }
2290
2328
 
2291
- // Start the MCP server
2292
- const server = new CFMemoryMCP();
2293
- server.start().catch(error => {
2294
- console.error('Failed to start CF Memory MCP server:', error.message);
2295
- process.exit(1);
2296
- });
2329
+ // Export for tests/downstream tools
2330
+ module.exports = { CFMemoryMCP };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cf-memory-mcp",
3
- "version": "3.9.4",
3
+ "version": "3.9.6",
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": {