cf-memory-mcp 3.9.3 → 3.9.5

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,25 +1801,42 @@ 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
+ const normalizedRoot = path.resolve(projectRoot);
1812
+ const resolvedFull = path.resolve(normalizedRoot, filePath);
1813
+ // Path must be strictly under the project root. `relative` returns
1814
+ // an empty string for the root itself and a `..`-prefixed string
1815
+ // for anything outside.
1816
+ const relative = path.relative(normalizedRoot, resolvedFull);
1817
+ if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
1818
+ _mcpTrace('GFC_LOCAL_REJECT_ESCAPE', `${filePath} -> ${resolvedFull} escapes root ${normalizedRoot}`);
1819
+ return false;
1820
+ }
1821
+ try {
1822
+ const probe = fs.statSync(resolvedFull);
1823
+ if (!probe.isFile()) return false;
1824
+ } catch (_) {
1825
+ return false;
1818
1826
  }
1819
- if (!resolvedFull) return false;
1820
1827
 
1821
- const fullContent = fs.readFileSync(resolvedFull, 'utf8');
1828
+ // Guard against blowing memory on huge files (binary blobs, big
1829
+ // SQL dumps, etc). 10MB is plenty for any source file the
1830
+ // chunker would handle. Above that, fall through to the
1831
+ // server's chunk-reconstruction path which is already truncated.
1822
1832
  const stat = fs.statSync(resolvedFull);
1833
+ const MAX_LOCAL_READ_BYTES = 10 * 1024 * 1024;
1834
+ if (stat.size > MAX_LOCAL_READ_BYTES) {
1835
+ _mcpTrace('GFC_LOCAL_SKIP', `file too large (${stat.size}b > ${MAX_LOCAL_READ_BYTES}b): ${resolvedFull}`);
1836
+ return false;
1837
+ }
1838
+
1839
+ const fullContent = fs.readFileSync(resolvedFull, 'utf8');
1823
1840
  const truncated = fullContent.length > maxChars;
1824
1841
  const content = truncated ? fullContent.slice(0, maxChars) + '\n... [truncated]' : fullContent;
1825
1842
  const totalLines = fullContent.split('\n').length;
@@ -2264,23 +2281,30 @@ if (process.argv.includes('--diagnose')) {
2264
2281
  return;
2265
2282
  }
2266
2283
 
2267
- // Check API key before starting server
2268
- if (!API_KEY) {
2269
- console.error('Error: CF_MEMORY_API_KEY environment variable is required');
2270
- console.error('');
2271
- console.error('Please set your API key:');
2272
- console.error(' export CF_MEMORY_API_KEY="your-api-key-here"');
2273
- console.error('');
2274
- console.error('Or run with:');
2275
- console.error(' CF_MEMORY_API_KEY="your-api-key-here" npx cf-memory-mcp');
2276
- console.error('');
2277
- console.error(`Target server: ${BASE_URL}`);
2278
- process.exit(1);
2284
+ // Only run the entry-point side effects when invoked directly (not when
2285
+ // require()'d from a test or downstream tool). This guard lets the test
2286
+ // suite import CFMemoryMCP without triggering the API-key check and
2287
+ // starting the stdio listener.
2288
+ if (require.main === module) {
2289
+ if (!API_KEY) {
2290
+ console.error('Error: CF_MEMORY_API_KEY environment variable is required');
2291
+ console.error('');
2292
+ console.error('Please set your API key:');
2293
+ console.error(' export CF_MEMORY_API_KEY="your-api-key-here"');
2294
+ console.error('');
2295
+ console.error('Or run with:');
2296
+ console.error(' CF_MEMORY_API_KEY="your-api-key-here" npx cf-memory-mcp');
2297
+ console.error('');
2298
+ console.error(`Target server: ${BASE_URL}`);
2299
+ process.exit(1);
2300
+ }
2301
+
2302
+ const server = new CFMemoryMCP();
2303
+ server.start().catch(error => {
2304
+ console.error('Failed to start CF Memory MCP server:', error.message);
2305
+ process.exit(1);
2306
+ });
2279
2307
  }
2280
2308
 
2281
- // Start the MCP server
2282
- const server = new CFMemoryMCP();
2283
- server.start().catch(error => {
2284
- console.error('Failed to start CF Memory MCP server:', error.message);
2285
- process.exit(1);
2286
- });
2309
+ // Export for tests/downstream tools
2310
+ module.exports = { CFMemoryMCP };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cf-memory-mcp",
3
- "version": "3.9.3",
3
+ "version": "3.9.5",
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": {