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.
- package/bin/cf-memory-mcp.js +143 -2
- package/package.json +1 -1
package/bin/cf-memory-mcp.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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": {
|