cf-memory-mcp 3.9.0 → 3.9.2

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.
@@ -809,25 +809,31 @@ class CFMemoryMCP {
809
809
  * a full re-index, just refresh the affected files.
810
810
  */
811
811
  async handleRefreshFiles(message) {
812
- const args = (message.params && message.params.arguments) || {};
812
+ const payload = await this.refreshFilesCore(message.params?.arguments || {});
813
+ process.stdout.write(JSON.stringify({
814
+ jsonrpc: '2.0',
815
+ id: message.id,
816
+ result: { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] },
817
+ }) + '\n');
818
+ }
819
+
820
+ /**
821
+ * Core refresh-files logic: reads local files, uploads them, returns a
822
+ * payload. Extracted so the auto-refresh path can call it without
823
+ * needing to capture stdout (the previous approach of swapping
824
+ * process.stdout.write was racy against other concurrent messages).
825
+ */
826
+ async refreshFilesCore(args) {
813
827
  const projectIdOrName = args.project_id;
814
828
  const filePaths = Array.isArray(args.file_paths) ? args.file_paths : [];
815
829
  const projectRoot = args.project_root ? path.resolve(args.project_root)
816
830
  : (process.env.CF_MEMORY_WATCH_PATH || process.cwd());
817
831
 
818
- const respond = (payload) => {
819
- process.stdout.write(JSON.stringify({
820
- jsonrpc: '2.0',
821
- id: message.id,
822
- result: { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] },
823
- }) + '\n');
824
- };
825
-
826
832
  if (!projectIdOrName) {
827
- return respond({ error: 'project_id is required' });
833
+ return { error: 'project_id is required' };
828
834
  }
829
835
  if (filePaths.length === 0) {
830
- return respond({ error: 'file_paths (string[]) is required' });
836
+ return { error: 'file_paths (string[]) is required' };
831
837
  }
832
838
 
833
839
  // Resolve to project ID via list_projects if a name was given.
@@ -872,26 +878,26 @@ class CFMemoryMCP {
872
878
  }
873
879
 
874
880
  if (files.length === 0) {
875
- return respond({
881
+ return {
876
882
  project_id: projectId,
877
883
  files_refreshed: 0,
878
884
  skipped,
879
885
  hint: `All ${filePaths.length} file path(s) failed to read. Check that paths are relative to project_root (${projectRoot}). If the project was indexed from a different directory, pass project_root=<that directory>.`,
880
- });
886
+ };
881
887
  }
882
888
 
883
889
  const uploadResult = await this.uploadFileBatch(projectId, files);
884
890
  const refreshed = (uploadResult && typeof uploadResult.files_indexed === 'number') ? uploadResult.files_indexed : 0;
885
891
  const chunks = (uploadResult && typeof uploadResult.chunks_created === 'number') ? uploadResult.chunks_created : 0;
886
892
 
887
- return respond({
893
+ return {
888
894
  project_id: projectId,
889
895
  files_attempted: files.length,
890
896
  files_refreshed: refreshed,
891
897
  chunks_created: chunks,
892
898
  skipped,
893
899
  errors: uploadResult && Array.isArray(uploadResult.errors) ? uploadResult.errors : undefined,
894
- });
900
+ };
895
901
  }
896
902
 
897
903
  /**
@@ -1656,10 +1662,15 @@ class CFMemoryMCP {
1656
1662
 
1657
1663
  /**
1658
1664
  * If the just-returned retrieve_context response had stale files,
1659
- * refresh them via refresh_files, re-run the original query, and
1665
+ * refresh them via refreshFilesCore, re-run the original query, and
1660
1666
  * return the fresh response. Opt-in via CF_MEMORY_AUTO_REFRESH or
1661
1667
  * per-call `auto_refresh:true`. Guards against infinite loops by
1662
1668
  * stripping auto_refresh from the re-query.
1669
+ *
1670
+ * Uses refreshFilesCore (returns data directly) rather than
1671
+ * handleRefreshFiles (writes to stdout). Avoids globally overriding
1672
+ * process.stdout.write, which was racy against any other MCP message
1673
+ * being handled concurrently in the same isolate.
1663
1674
  */
1664
1675
  async maybeAutoRefreshAndRequery(response, originalMessage) {
1665
1676
  try {
@@ -1676,33 +1687,27 @@ class CFMemoryMCP {
1676
1687
 
1677
1688
  _mcpTrace('AUTO_REFRESH', `${stalePaths.length} stale files; refreshing then re-querying`);
1678
1689
 
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;
1690
+ // Call core directly no stdout override needed.
1691
+ const refreshResult = await this.refreshFilesCore({
1692
+ project_id: projectId,
1693
+ file_paths: stalePaths,
1694
+ });
1695
+ _mcpTrace('AUTO_REFRESH_DONE', `refreshed=${refreshResult.files_refreshed || 0}`);
1696
+
1697
+ // If refresh itself errored or refreshed 0 files, return the
1698
+ // original response with a clear note rather than re-querying
1699
+ // and pretending the refresh succeeded.
1700
+ if (refreshResult.error || (refreshResult.files_refreshed || 0) === 0) {
1701
+ try {
1702
+ parsed.auto_refresh_failed = {
1703
+ attempted: stalePaths.length,
1704
+ error: refreshResult.error || 'refresh ran but indexed 0 files (paths may be unreadable or project_root mismatched)',
1705
+ skipped: refreshResult.skipped,
1706
+ };
1707
+ response.result.content[0].text = JSON.stringify(parsed);
1708
+ } catch (_) { /* best-effort tag */ }
1709
+ return response;
1704
1710
  }
1705
- _mcpTrace('AUTO_REFRESH_DONE', `refreshed ${stalePaths.length} files; captured=${captured.length}b`);
1706
1711
 
1707
1712
  // Re-run the original retrieve_context (without auto_refresh
1708
1713
  // to prevent loops). Server cache invalidates on write so the
@@ -1722,7 +1727,8 @@ class CFMemoryMCP {
1722
1727
  if (freshText) {
1723
1728
  const freshParsed = JSON.parse(freshText);
1724
1729
  freshParsed.auto_refreshed = {
1725
- files_refreshed: stalePaths.length,
1730
+ files_refreshed: refreshResult.files_refreshed,
1731
+ chunks_created: refreshResult.chunks_created || 0,
1726
1732
  file_paths: stalePaths,
1727
1733
  };
1728
1734
  fresh.result.content[0].text = JSON.stringify(freshParsed);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cf-memory-mcp",
3
- "version": "3.9.0",
3
+ "version": "3.9.2",
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": {