cf-memory-mcp 3.8.11 → 3.9.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.
@@ -155,7 +155,8 @@ const TOOLS_LIST = [
155
155
  file_filter: { type: 'array', items: { type: 'string' }, description: 'Limit to files matching path substrings' },
156
156
  all_projects: { type: 'boolean', description: 'Search across ALL your indexed projects (overrides project_id). Useful for "find X in any of my repos".' },
157
157
  expand_context: { type: 'boolean', description: 'Include file_imports (the file\'s module/imports chunk) with each result. Default: true.' },
158
- exclude_docs: { type: 'boolean', description: 'Filter out markdown/docs from code queries. Default: true (auto-disabled if query mentions docs/readme/tutorial). Response includes docs_filtered_count when chunks were dropped — flip to false to surface them.' }
158
+ exclude_docs: { type: 'boolean', description: 'Filter out markdown/docs from code queries. Default: true (auto-disabled if query mentions docs/readme/tutorial). Response includes docs_filtered_count when chunks were dropped — flip to false to surface them.' },
159
+ auto_refresh: { type: 'boolean', description: 'When true, if results contain stale chunks the bridge transparently calls refresh_files + re-runs the query before returning. Removes the manual find_stale_files → refresh → re-query loop. Adds ~5-10s to the request when stale files exist. Set globally via CF_MEMORY_AUTO_REFRESH=true.' }
159
160
  },
160
161
  required: ['query']
161
162
  }
@@ -740,7 +741,7 @@ class CFMemoryMCP {
740
741
  await this.maybeFillProjectId(message);
741
742
  }
742
743
 
743
- const response = await this.makeRequest(message);
744
+ let response = await this.makeRequest(message);
744
745
  _mcpTrace('DISPATCH_DONE', `id=${message.id} method=${message.method} elapsed=${Date.now()-_t0}ms`);
745
746
 
746
747
  // Annotate retrieve_context results with local staleness when
@@ -750,6 +751,20 @@ class CFMemoryMCP {
750
751
  if (message.method === 'tools/call' &&
751
752
  message.params && message.params.name === 'retrieve_context') {
752
753
  this.maybeAnnotateStaleness(response, message.params.arguments);
754
+
755
+ // Auto-refresh path: when CF_MEMORY_AUTO_REFRESH=true OR the
756
+ // caller passes auto_refresh:true in arguments, and the
757
+ // response had stale chunks, transparently refresh those
758
+ // files and re-run the query. Removes the find-stale →
759
+ // refresh → re-query loop the codex review flagged as
760
+ // operationally brittle. Capped to avoid pathological cases.
761
+ const args = message.params.arguments || {};
762
+ const autoRefreshOptIn = args.auto_refresh === true ||
763
+ process.env.CF_MEMORY_AUTO_REFRESH === '1' ||
764
+ process.env.CF_MEMORY_AUTO_REFRESH === 'true';
765
+ if (autoRefreshOptIn) {
766
+ response = await this.maybeAutoRefreshAndRequery(response, message);
767
+ }
753
768
  }
754
769
 
755
770
  // Send response to stdout
@@ -1639,6 +1654,93 @@ class CFMemoryMCP {
1639
1654
  }
1640
1655
  }
1641
1656
 
1657
+ /**
1658
+ * If the just-returned retrieve_context response had stale files,
1659
+ * refresh them via refresh_files, re-run the original query, and
1660
+ * return the fresh response. Opt-in via CF_MEMORY_AUTO_REFRESH or
1661
+ * per-call `auto_refresh:true`. Guards against infinite loops by
1662
+ * stripping auto_refresh from the re-query.
1663
+ */
1664
+ async maybeAutoRefreshAndRequery(response, originalMessage) {
1665
+ try {
1666
+ const text = response?.result?.content?.[0]?.text;
1667
+ if (!text) return response;
1668
+ let parsed;
1669
+ try { parsed = JSON.parse(text); } catch (_) { return response; }
1670
+ const hint = parsed?.stale_refresh_hint;
1671
+ const stalePaths = hint?.arguments?.file_paths;
1672
+ const projectId = hint?.arguments?.project_id;
1673
+ if (!stalePaths || stalePaths.length === 0 || !projectId) {
1674
+ return response; // Nothing stale, return as-is
1675
+ }
1676
+
1677
+ _mcpTrace('AUTO_REFRESH', `${stalePaths.length} stale files; refreshing then re-querying`);
1678
+
1679
+ // Build a refresh_files message and invoke it via our own handler
1680
+ const refreshMessage = {
1681
+ jsonrpc: '2.0',
1682
+ id: `auto-refresh-${Date.now()}`,
1683
+ method: 'tools/call',
1684
+ params: {
1685
+ name: 'refresh_files',
1686
+ arguments: {
1687
+ project_id: projectId,
1688
+ file_paths: stalePaths,
1689
+ },
1690
+ },
1691
+ };
1692
+ // handleRefreshFiles writes to stdout itself; we need its work
1693
+ // done but the output suppressed. Capture stdout for this call.
1694
+ const origWrite = process.stdout.write.bind(process.stdout);
1695
+ let captured = '';
1696
+ process.stdout.write = (chunk) => {
1697
+ captured += String(chunk);
1698
+ return true;
1699
+ };
1700
+ try {
1701
+ await this.handleRefreshFiles(refreshMessage);
1702
+ } finally {
1703
+ process.stdout.write = origWrite;
1704
+ }
1705
+ _mcpTrace('AUTO_REFRESH_DONE', `refreshed ${stalePaths.length} files; captured=${captured.length}b`);
1706
+
1707
+ // Re-run the original retrieve_context (without auto_refresh
1708
+ // to prevent loops). Server cache invalidates on write so the
1709
+ // re-query sees fresh data.
1710
+ const requeryArgs = { ...(originalMessage.params.arguments || {}) };
1711
+ delete requeryArgs.auto_refresh;
1712
+ const requery = {
1713
+ ...originalMessage,
1714
+ id: `${originalMessage.id}-refreshed`,
1715
+ params: { ...originalMessage.params, arguments: requeryArgs },
1716
+ };
1717
+ const fresh = await this.makeRequest(requery);
1718
+
1719
+ // Tag the fresh response so callers can see auto-refresh fired
1720
+ try {
1721
+ const freshText = fresh?.result?.content?.[0]?.text;
1722
+ if (freshText) {
1723
+ const freshParsed = JSON.parse(freshText);
1724
+ freshParsed.auto_refreshed = {
1725
+ files_refreshed: stalePaths.length,
1726
+ file_paths: stalePaths,
1727
+ };
1728
+ fresh.result.content[0].text = JSON.stringify(freshParsed);
1729
+ }
1730
+ } catch (_) { /* best-effort tag only */ }
1731
+
1732
+ // Re-annotate so the fresh response carries the staleness check
1733
+ // result (should now be empty)
1734
+ this.maybeAnnotateStaleness(fresh, requeryArgs);
1735
+ // Restore the original message id so the MCP client correlates correctly
1736
+ fresh.id = originalMessage.id;
1737
+ return fresh;
1738
+ } catch (err) {
1739
+ this.logDebug(`maybeAutoRefreshAndRequery failed: ${err && err.message}`);
1740
+ return response;
1741
+ }
1742
+ }
1743
+
1642
1744
  maybeAnnotateStaleness(response, args) {
1643
1745
  try {
1644
1746
  const text = response?.result?.content?.[0]?.text;
@@ -1991,6 +2093,45 @@ if (process.argv.includes('--diagnose')) {
1991
2093
  console.log(`FAIL: ${err.message}`);
1992
2094
  }
1993
2095
 
2096
+ // 5. Deep health (D1 + Vectorize + AI bindings)
2097
+ process.stdout.write('5. Deep health (D1/Vectorize/AI)... ');
2098
+ const deepStart = Date.now();
2099
+ try {
2100
+ const url = new URL(BASE_URL + '/health/deep');
2101
+ const result = await new Promise((resolve, reject) => {
2102
+ const req = https.request({
2103
+ hostname: url.hostname,
2104
+ port: url.port || 443,
2105
+ path: url.pathname,
2106
+ method: 'GET',
2107
+ headers: { 'X-API-Key': API_KEY },
2108
+ timeout: 15000,
2109
+ }, (res) => {
2110
+ let body = '';
2111
+ res.on('data', (c) => body += c);
2112
+ res.on('end', () => resolve({ status: res.statusCode, body }));
2113
+ });
2114
+ req.on('error', reject);
2115
+ req.on('timeout', () => reject(new Error('timeout')));
2116
+ req.end();
2117
+ });
2118
+ const elapsed = Date.now() - deepStart;
2119
+ const parsed = JSON.parse(result.body);
2120
+ const checks = parsed.checks || {};
2121
+ const summary = ['d1', 'vectorize', 'ai']
2122
+ .map(k => `${k}=${checks[k]?.ok ? 'ok' : 'FAIL'}`)
2123
+ .join(' ');
2124
+ console.log(`${parsed.status} (${elapsed}ms, ${summary})`);
2125
+ // Surface any sub-check errors
2126
+ for (const [k, v] of Object.entries(checks)) {
2127
+ if (v && typeof v === 'object' && 'ok' in v && !v.ok && v.error) {
2128
+ console.log(` ${k}: ${v.error}`);
2129
+ }
2130
+ }
2131
+ } catch (err) {
2132
+ console.log(`FAIL: ${err.message}`);
2133
+ }
2134
+
1994
2135
  console.log('\nDiagnostics complete.');
1995
2136
  process.exit(0);
1996
2137
  })().catch(err => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cf-memory-mcp",
3
- "version": "3.8.11",
3
+ "version": "3.9.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": {