You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(cmp-server): Improve MCP server cache/growth/duplicate-string detectors
Summary:
Three detector improvements to the MemLab MCP server, based on recurring gaps hit while analyzing real Node.js server heap snapshots. Source of truth is edited in `www/scripts/webspeed/memory_lab/packages/mcp-server/src/` and synced to the internal Claude plugin copy under `fbcode/claude-templates/components/plugins/memlab/memlab-server/` via `sync-mcp-to-plugin.sh`.
**1. Recognize the `{<array>, timestamp/loadedAt, map}` TTL-cache shape (`cache-analysis`).** The object-cache detector already matched `hasDataProp && hasTimestampProp`, but its `DATA_PROPS` set lacked `entries` and its `TIMESTAMP_PROPS` set lacked `loadedAt`, so the two most common in-process Nest cache shapes — `{entries, timestamp, map}` and `{items, loadedAt}` — were classified as "plain collection" instead of "cache-like." Added `entries`/`records`/`nodes`/`list`/`cache` to the data-property set and `loadedAt`/`loaded_at`/`cached_at`/`fetchedAt`/`fetched_at`/`lastFetched`/`lastLoaded`/`lastUpdated` to the timestamp set.
**2. Stop flagging load-once TTL caches as unbounded growth (`growth-signals`).** A large `Array` held by a `{…, loadedAt/timestamp}` wrapper is a point-in-time snapshot refreshed wholesale, not append-only growth, yet it was reported as an "append-only candidate." Added a shared `hasFreshnessTimestampSibling()` helper and skip such arrays, reporting a count of how many were excluded so the heuristic stays transparent.
**3. Detect cached failure payloads (`duplicated-strings`).** Added `looksLikeFailurePayload()` and a callout when heavily-duplicated strings look like cached error/failure responses (JSON-ish payloads carrying markers such as `UNKNOWN_FAILURE`, `"status":"…FAILURE"`, or a non-null `"error"`). These are both wasted memory and a strong signal that an upstream dependency is failing and its failures are being cached — linking a memory finding to a likely correctness bug.
New shared helpers (`FRESHNESS_TIMESTAMP_PROPS`, `hasFreshnessTimestampSibling`, `looksLikeFailurePayload`) live in `utils.ts`. All three changes are additive and backward-compatible; package/plugin versions bumped accordingly (mcp-server 2.1.2→2.2.0, internal server 2.0.6→2.1.0, plugin 1.7.0→1.8.0).
Reviewed By: boujeepossum
Differential Revision: D107701087
fbshipit-source-id: f6270b7cd9f0892758d0f53e59bdb1a75e519329
@@ -118,6 +119,10 @@ export function registerGrowthSignals(server: McpServer): void {
118
119
constmeta=getSnapshotMetadata();
119
120
consttotalSize=meta?.totalSize??0;
120
121
constcandidates: GrowthCandidate[]=[];
122
+
// Count of large arrays skipped because they are the data side of a
123
+
// load-once `{entries, loadedAt}`-style TTL cache (a point-in-time
124
+
// snapshot, not append-only growth). Feedback round 4 §C.
125
+
letexcludedLoadOnce=0;
121
126
122
127
constinsert=(c: GrowthCandidate)=>{
123
128
leti=0;
@@ -164,6 +169,13 @@ export function registerGrowthSignals(server: McpServer): void {
164
169
}elseif(node.name==='Array'){
165
170
constcount=node.edge_count;
166
171
if(count<min_entries)return;
172
+
// A large array held by a `{..., loadedAt/timestamp}` cache wrapper
173
+
// is a load-once snapshot refreshed wholesale, not append-only
174
+
// growth — don't flag it as a growth signal. Feedback round 4 §C.
175
+
if(hasFreshnessTimestampSibling(node)){
176
+
excludedLoadOnce++;
177
+
return;
178
+
}
167
179
// Heuristic: large dense arrays are append-only growth candidates.
168
180
insert({
169
181
nodeId: node.id,
@@ -176,9 +188,14 @@ export function registerGrowthSignals(server: McpServer): void {
176
188
}
177
189
});
178
190
191
+
constloadOnceNote=
192
+
excludedLoadOnce>0
193
+
? ` (${formatNumber(excludedLoadOnce)} large array(s) held by a \`{…, loadedAt/timestamp}\` cache wrapper were excluded as load-once snapshots, not append-only growth)`
194
+
: '';
195
+
179
196
if(candidates.length===0){
180
197
returntoolResult(
181
-
`No growth signals found (no timestamp/sequentially-keyed collections or large arrays >= ${formatNumber(min_entries)} entries and >= ${formatBytes(min_retained_size)}). This is a single-snapshot heuristic — capture a second snapshot later and use memlab_diff_snapshots to confirm actual growth.`,
198
+
`No growth signals found (no timestamp/sequentially-keyed collections or large arrays >= ${formatNumber(min_entries)} entries and >= ${formatBytes(min_retained_size)})${loadOnceNote}. This is a single-snapshot heuristic — capture a second snapshot later and use memlab_diff_snapshots to confirm actual growth.`,
182
199
);
183
200
}
184
201
@@ -209,6 +226,7 @@ export function registerGrowthSignals(server: McpServer): void {
'_Heuristic only — timestamp/sequential keys and large dense arrays *suggest* append-only growth but do not prove it. Confirm by capturing a later snapshot and running `memlab_diff_snapshots`, or trace one with `memlab_retainer_trace` to see what keeps it alive._',
0 commit comments