@memlab/mcp-server 2.1.1 → 2.18.1
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 +83 -3
- package/dist/analysis-budget.d.ts +71 -0
- package/dist/analysis-budget.d.ts.map +1 -0
- package/dist/analysis-budget.js +118 -0
- package/dist/analysis-budget.js.map +1 -0
- package/dist/guardrail.d.ts +24 -0
- package/dist/guardrail.d.ts.map +1 -0
- package/dist/guardrail.js +65 -0
- package/dist/guardrail.js.map +1 -0
- package/dist/heap-state.d.ts +53 -1
- package/dist/heap-state.d.ts.map +1 -1
- package/dist/heap-state.js +153 -10
- package/dist/heap-state.js.map +1 -1
- package/dist/index.js +38 -1
- package/dist/index.js.map +1 -1
- package/dist/tools/array-group-by.d.ts +12 -0
- package/dist/tools/array-group-by.d.ts.map +1 -0
- package/dist/tools/array-group-by.js +191 -0
- package/dist/tools/array-group-by.js.map +1 -0
- package/dist/tools/auto-investigate.d.ts.map +1 -1
- package/dist/tools/auto-investigate.js +1056 -52
- package/dist/tools/auto-investigate.js.map +1 -1
- package/dist/tools/cache-analysis.d.ts.map +1 -1
- package/dist/tools/cache-analysis.js +438 -21
- package/dist/tools/cache-analysis.js.map +1 -1
- package/dist/tools/check-health.d.ts.map +1 -1
- package/dist/tools/check-health.js +261 -1
- package/dist/tools/check-health.js.map +1 -1
- package/dist/tools/class-histogram.d.ts.map +1 -1
- package/dist/tools/class-histogram.js +142 -33
- package/dist/tools/class-histogram.js.map +1 -1
- package/dist/tools/closure-inspection.d.ts.map +1 -1
- package/dist/tools/closure-inspection.js +238 -25
- package/dist/tools/closure-inspection.js.map +1 -1
- package/dist/tools/detached-dom.d.ts.map +1 -1
- package/dist/tools/detached-dom.js +25 -3
- package/dist/tools/detached-dom.js.map +1 -1
- package/dist/tools/dev-artifacts.d.ts +33 -0
- package/dist/tools/dev-artifacts.d.ts.map +1 -0
- package/dist/tools/dev-artifacts.js +165 -0
- package/dist/tools/dev-artifacts.js.map +1 -0
- package/dist/tools/diff-snapshots.d.ts.map +1 -1
- package/dist/tools/diff-snapshots.js +200 -48
- package/dist/tools/diff-snapshots.js.map +1 -1
- package/dist/tools/duplicated-strings.d.ts.map +1 -1
- package/dist/tools/duplicated-strings.js +74 -12
- package/dist/tools/duplicated-strings.js.map +1 -1
- package/dist/tools/eval.d.ts.map +1 -1
- package/dist/tools/eval.js +189 -10
- package/dist/tools/eval.js.map +1 -1
- package/dist/tools/event-listener-leaks.d.ts.map +1 -1
- package/dist/tools/event-listener-leaks.js +235 -13
- package/dist/tools/event-listener-leaks.js.map +1 -1
- package/dist/tools/event-registry.d.ts +12 -0
- package/dist/tools/event-registry.d.ts.map +1 -0
- package/dist/tools/event-registry.js +183 -0
- package/dist/tools/event-registry.js.map +1 -0
- package/dist/tools/find-by-property.d.ts.map +1 -1
- package/dist/tools/find-by-property.js +113 -31
- package/dist/tools/find-by-property.js.map +1 -1
- package/dist/tools/find-by-shape.d.ts +12 -0
- package/dist/tools/find-by-shape.d.ts.map +1 -0
- package/dist/tools/find-by-shape.js +178 -0
- package/dist/tools/find-by-shape.js.map +1 -0
- package/dist/tools/find-nodes-by-class.d.ts.map +1 -1
- package/dist/tools/find-nodes-by-class.js +50 -6
- package/dist/tools/find-nodes-by-class.js.map +1 -1
- package/dist/tools/for-each.d.ts.map +1 -1
- package/dist/tools/for-each.js +1 -1
- package/dist/tools/for-each.js.map +1 -1
- package/dist/tools/get-node.d.ts.map +1 -1
- package/dist/tools/get-node.js +6 -0
- package/dist/tools/get-node.js.map +1 -1
- package/dist/tools/get-references.d.ts.map +1 -1
- package/dist/tools/get-references.js +35 -6
- package/dist/tools/get-references.js.map +1 -1
- package/dist/tools/get-referrers.d.ts.map +1 -1
- package/dist/tools/get-referrers.js +35 -6
- package/dist/tools/get-referrers.js.map +1 -1
- package/dist/tools/get-string.d.ts +12 -0
- package/dist/tools/get-string.d.ts.map +1 -0
- package/dist/tools/get-string.js +223 -0
- package/dist/tools/get-string.js.map +1 -0
- package/dist/tools/get-value.d.ts +12 -0
- package/dist/tools/get-value.d.ts.map +1 -0
- package/dist/tools/get-value.js +152 -0
- package/dist/tools/get-value.js.map +1 -0
- package/dist/tools/global-variables.d.ts.map +1 -1
- package/dist/tools/global-variables.js +54 -34
- package/dist/tools/global-variables.js.map +1 -1
- package/dist/tools/growth-signals.d.ts +12 -0
- package/dist/tools/growth-signals.d.ts.map +1 -0
- package/dist/tools/growth-signals.js +211 -0
- package/dist/tools/growth-signals.js.map +1 -0
- package/dist/tools/intern-opportunities.d.ts +12 -0
- package/dist/tools/intern-opportunities.d.ts.map +1 -0
- package/dist/tools/intern-opportunities.js +870 -0
- package/dist/tools/intern-opportunities.js.map +1 -0
- package/dist/tools/largest-objects.d.ts.map +1 -1
- package/dist/tools/largest-objects.js +37 -14
- package/dist/tools/largest-objects.js.map +1 -1
- package/dist/tools/load-snapshot.d.ts +70 -0
- package/dist/tools/load-snapshot.d.ts.map +1 -1
- package/dist/tools/load-snapshot.js +446 -17
- package/dist/tools/load-snapshot.js.map +1 -1
- package/dist/tools/object-cost-breakdown.d.ts +12 -0
- package/dist/tools/object-cost-breakdown.d.ts.map +1 -0
- package/dist/tools/object-cost-breakdown.js +439 -0
- package/dist/tools/object-cost-breakdown.js.map +1 -0
- package/dist/tools/object-shape.d.ts.map +1 -1
- package/dist/tools/object-shape.js +109 -2
- package/dist/tools/object-shape.js.map +1 -1
- package/dist/tools/pinch-points.d.ts.map +1 -1
- package/dist/tools/pinch-points.js +9 -9
- package/dist/tools/pinch-points.js.map +1 -1
- package/dist/tools/property-distribution.d.ts +12 -0
- package/dist/tools/property-distribution.d.ts.map +1 -0
- package/dist/tools/property-distribution.js +168 -0
- package/dist/tools/property-distribution.js.map +1 -0
- package/dist/tools/quick-diagnosis.d.ts.map +1 -1
- package/dist/tools/quick-diagnosis.js +65 -20
- package/dist/tools/quick-diagnosis.js.map +1 -1
- package/dist/tools/retainer-summary.d.ts.map +1 -1
- package/dist/tools/retainer-summary.js +90 -5
- package/dist/tools/retainer-summary.js.map +1 -1
- package/dist/tools/retainer-trace.d.ts.map +1 -1
- package/dist/tools/retainer-trace.js +173 -43
- package/dist/tools/retainer-trace.js.map +1 -1
- package/dist/tools/search-nodes.d.ts.map +1 -1
- package/dist/tools/search-nodes.js +10 -3
- package/dist/tools/search-nodes.js.map +1 -1
- package/dist/tools/search-strings.d.ts +12 -0
- package/dist/tools/search-strings.d.ts.map +1 -0
- package/dist/tools/search-strings.js +182 -0
- package/dist/tools/search-strings.js.map +1 -0
- package/dist/tools/sequence-analysis.d.ts +12 -0
- package/dist/tools/sequence-analysis.d.ts.map +1 -0
- package/dist/tools/sequence-analysis.js +244 -0
- package/dist/tools/sequence-analysis.js.map +1 -0
- package/dist/tools/server-status.d.ts +12 -0
- package/dist/tools/server-status.d.ts.map +1 -0
- package/dist/tools/server-status.js +41 -0
- package/dist/tools/server-status.js.map +1 -0
- package/dist/tools/shape-histogram.d.ts.map +1 -1
- package/dist/tools/shape-histogram.js +61 -16
- package/dist/tools/shape-histogram.js.map +1 -1
- package/dist/tools/sliced-strings.d.ts.map +1 -1
- package/dist/tools/sliced-strings.js +38 -15
- package/dist/tools/sliced-strings.js.map +1 -1
- package/dist/tools/snapshot-header.d.ts +28 -0
- package/dist/tools/snapshot-header.d.ts.map +1 -0
- package/dist/tools/snapshot-header.js +111 -0
- package/dist/tools/snapshot-header.js.map +1 -0
- package/dist/tools/snapshot-summary.d.ts.map +1 -1
- package/dist/tools/snapshot-summary.js +55 -13
- package/dist/tools/snapshot-summary.js.map +1 -1
- package/dist/tools/snapshots.d.ts +12 -0
- package/dist/tools/snapshots.d.ts.map +1 -0
- package/dist/tools/snapshots.js +93 -0
- package/dist/tools/snapshots.js.map +1 -0
- package/dist/tools/stale-collections.d.ts.map +1 -1
- package/dist/tools/stale-collections.js +248 -7
- package/dist/tools/stale-collections.js.map +1 -1
- package/dist/tools/trace-dominators.d.ts.map +1 -1
- package/dist/tools/trace-dominators.js +28 -1
- package/dist/tools/trace-dominators.js.map +1 -1
- package/dist/tools/weakmap-entries.d.ts +12 -0
- package/dist/tools/weakmap-entries.d.ts.map +1 -0
- package/dist/tools/weakmap-entries.js +156 -0
- package/dist/tools/weakmap-entries.js.map +1 -0
- package/dist/utils.d.ts +139 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +448 -35
- package/dist/utils.js.map +1 -1
- package/package.json +3 -3
|
@@ -11,7 +11,7 @@ import memlabCore from '@memlab/core';
|
|
|
11
11
|
const { utils, NumericSet } = memlabCore;
|
|
12
12
|
import { z } from 'zod';
|
|
13
13
|
import { getSnapshot } from '../heap-state.js';
|
|
14
|
-
import { formatBytes, formatNumber, errorResult, toolResult } from '../utils.js';
|
|
14
|
+
import { formatBytes, formatNumber, errorResult, toolResult, suggestionsSuppressed, } from '../utils.js';
|
|
15
15
|
function getPropertyNames(node) {
|
|
16
16
|
const names = [];
|
|
17
17
|
for (const edge of node.references) {
|
|
@@ -25,7 +25,8 @@ function getPropertyNames(node) {
|
|
|
25
25
|
return names;
|
|
26
26
|
}
|
|
27
27
|
export function registerShapeHistogram(server) {
|
|
28
|
-
server.tool('memlab_shape_histogram', 'Group objects by their property structure (shape/hidden class). Objects with the same set of property names are grouped together, revealing distinct record types. Much more useful than class_histogram when most objects are generic "Object" — this tells you exactly what data structures exist and how much memory each shape consumes. Essential for data-heavy apps where millions of "Object" instances hide 3-5 distinct record shapes.'
|
|
28
|
+
server.tool('memlab_shape_histogram', 'Group objects by their property structure (shape/hidden class). Objects with the same set of property names are grouped together, revealing distinct record types. Much more useful than class_histogram when most objects are generic "Object" — this tells you exactly what data structures exist and how much memory each shape consumes. Essential for data-heavy apps where millions of "Object" instances hide 3-5 distinct record shapes. ' +
|
|
29
|
+
'By DEFAULT retained size per shape is reported as a cheap RANGE (lower = Σ self size, upper = Σ retained size) from a single O(N) pass; sorting/filtering by retained uses the upper bound. The exact dominator-deduped value needs a per-shape dominator walk that can stall/OOM the server on very large heaps — pass `exact_retained_size:true` ONLY when the user explicitly asks for it.', {
|
|
29
30
|
limit: z
|
|
30
31
|
.number()
|
|
31
32
|
.optional()
|
|
@@ -44,8 +45,24 @@ export function registerShapeHistogram(server) {
|
|
|
44
45
|
.string()
|
|
45
46
|
.optional()
|
|
46
47
|
.describe('Filter to objects with this constructor name (e.g., "Object"). If omitted, includes all object-type nodes.'),
|
|
47
|
-
|
|
48
|
+
sort_by: z
|
|
49
|
+
.enum(['retained_size', 'retained', 'count', 'self_size', 'self'])
|
|
50
|
+
.optional()
|
|
51
|
+
.default('retained_size')
|
|
52
|
+
.describe('Sort order: "retained_size" (default; alias "retained" — uses the upper-bound retained unless exact_retained_size is set), "count" (instance count — find accumulation patterns), or "self_size" (alias "self").'),
|
|
53
|
+
exact_retained_size: z
|
|
54
|
+
.boolean()
|
|
55
|
+
.optional()
|
|
56
|
+
.default(false)
|
|
57
|
+
.describe('Compute the EXACT dominator-deduped retained size per shape via aggregateDominatorMetrics. DEFAULT false: report a retained-size range instead (lower = Σ self size, upper = Σ raw retained) from the O(N) pass; sorting/filtering by retained uses the upper bound. Set true ONLY on explicit user request — the exact walk can be very slow or time out on huge heaps.'),
|
|
58
|
+
}, async ({ limit, min_count, min_retained_size, class_name, sort_by, exact_retained_size, }) => {
|
|
48
59
|
try {
|
|
60
|
+
// Normalize sort aliases so the vocabulary matches other tools.
|
|
61
|
+
const sort = sort_by === 'retained'
|
|
62
|
+
? 'retained_size'
|
|
63
|
+
: sort_by === 'self'
|
|
64
|
+
? 'self_size'
|
|
65
|
+
: sort_by;
|
|
49
66
|
const snapshot = getSnapshot();
|
|
50
67
|
const shapeMap = new Map();
|
|
51
68
|
snapshot.nodes.forEach(node => {
|
|
@@ -63,28 +80,47 @@ export function registerShapeHistogram(server) {
|
|
|
63
80
|
if (existing) {
|
|
64
81
|
existing.count++;
|
|
65
82
|
existing.totalSelfSize += node.self_size;
|
|
66
|
-
existing.
|
|
83
|
+
existing.rawRetained += node.retainedSize;
|
|
84
|
+
if (existing.nodeIds)
|
|
85
|
+
existing.nodeIds.add(node.id);
|
|
67
86
|
}
|
|
68
87
|
else {
|
|
69
88
|
shapeMap.set(key, {
|
|
70
89
|
properties: props,
|
|
71
90
|
count: 1,
|
|
72
91
|
totalSelfSize: node.self_size,
|
|
73
|
-
|
|
92
|
+
rawRetained: node.retainedSize,
|
|
93
|
+
// Per-node ids only needed for the opt-in exact dominator walk.
|
|
94
|
+
nodeIds: exact_retained_size ? new NumericSet([node.id]) : null,
|
|
74
95
|
exampleNodeId: node.id,
|
|
75
96
|
});
|
|
76
97
|
}
|
|
77
98
|
});
|
|
78
99
|
const filtered = [...shapeMap.values()].filter(g => g.count >= min_count);
|
|
100
|
+
// By DEFAULT skip the exact dominator walk (the part that stalls the
|
|
101
|
+
// server on huge heaps) and rank/filter by the upper-bound raw retained
|
|
102
|
+
// sum; only compute exact dominator-deduped retained when asked.
|
|
79
103
|
const withRetained = filtered.map(g => {
|
|
80
|
-
|
|
81
|
-
|
|
104
|
+
let exact = null;
|
|
105
|
+
if (exact_retained_size && g.nodeIds) {
|
|
106
|
+
exact = utils.aggregateDominatorMetrics(g.nodeIds, snapshot, () => true, (node) => node.retainedSize);
|
|
107
|
+
}
|
|
108
|
+
const retainedRank = exact ?? g.rawRetained;
|
|
109
|
+
return { ...g, exact, retainedRank };
|
|
82
110
|
});
|
|
83
111
|
let finalList = withRetained;
|
|
84
112
|
if (min_retained_size != null) {
|
|
85
|
-
finalList = finalList.filter(g => g.
|
|
113
|
+
finalList = finalList.filter(g => g.retainedRank >= min_retained_size);
|
|
114
|
+
}
|
|
115
|
+
if (sort === 'count') {
|
|
116
|
+
finalList.sort((a, b) => b.count - a.count);
|
|
117
|
+
}
|
|
118
|
+
else if (sort === 'self_size') {
|
|
119
|
+
finalList.sort((a, b) => b.totalSelfSize - a.totalSelfSize);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
finalList.sort((a, b) => b.retainedRank - a.retainedRank);
|
|
86
123
|
}
|
|
87
|
-
finalList.sort((a, b) => b.retainedSize - a.retainedSize);
|
|
88
124
|
finalList = finalList.slice(0, limit);
|
|
89
125
|
if (finalList.length === 0) {
|
|
90
126
|
return toolResult('No object shapes found matching the criteria. Try lowering min_count or removing class_name filter.');
|
|
@@ -99,15 +135,24 @@ export function registerShapeHistogram(server) {
|
|
|
99
135
|
const propsDisplay = g.properties.length <= 8
|
|
100
136
|
? `{${g.properties.join(', ')}}`
|
|
101
137
|
: `{${g.properties.slice(0, 6).join(', ')}, … +${g.properties.length - 6} more}`;
|
|
102
|
-
|
|
138
|
+
const retainedStr = exact_retained_size
|
|
139
|
+
? `${formatBytes(g.exact ?? 0)} retained, ${formatBytes(g.totalSelfSize)} self`
|
|
140
|
+
: `retained ∈ [${formatBytes(g.totalSelfSize)}, ≤ ${formatBytes(g.rawRetained)}]`;
|
|
141
|
+
lines.push(`${i + 1}. **${propsDisplay}** — ${formatNumber(g.count)} instances, ${retainedStr}`);
|
|
103
142
|
lines.push(` Example: @${g.exampleNodeId}`);
|
|
104
143
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
144
|
+
if (!exact_retained_size) {
|
|
145
|
+
lines.push('', "_Retained shown as a range `[lower, ≤ upper]`: lower = Σ self size (objects' own bytes), upper = Σ retained size (double-counts subtrees shared between instances on the dominator tree). The exact dominator-deduped value needs a per-shape dominator walk that can stall or time out on large heaps — pass `exact_retained_size:true` for it._");
|
|
146
|
+
}
|
|
147
|
+
if (!suggestionsSuppressed()) {
|
|
148
|
+
lines.push('', '**Suggested next steps:**');
|
|
149
|
+
const top = finalList[0];
|
|
150
|
+
lines.push(`- Inspect example: \`memlab_object_shape(${top.exampleNodeId})\``);
|
|
151
|
+
lines.push(`- Distribution of a property's values: \`memlab_property_distribution\` (cardinality + top values)`);
|
|
152
|
+
lines.push(`- Trace retention: \`memlab_retainer_trace(${top.exampleNodeId})\``);
|
|
153
|
+
if (finalList.length > 1) {
|
|
154
|
+
lines.push(`- Compare shapes: \`memlab_object_shape\` on examples from different shapes`);
|
|
155
|
+
}
|
|
111
156
|
}
|
|
112
157
|
return toolResult(lines.join('\n'));
|
|
113
158
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shape-histogram.js","sourceRoot":"","sources":["../../src/tools/shape-histogram.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,UAAU,MAAM,cAAc,CAAC;AACtC,MAAM,EAAC,KAAK,EAAE,UAAU,EAAC,GAAG,UAAU,CAAC;AACvC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAAC,WAAW,EAAC,MAAM,kBAAkB,CAAC;AAC7C,OAAO,
|
|
1
|
+
{"version":3,"file":"shape-histogram.js","sourceRoot":"","sources":["../../src/tools/shape-histogram.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sliced-strings.d.ts","sourceRoot":"","sources":["../../src/tools/sliced-strings.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"sliced-strings.d.ts","sourceRoot":"","sources":["../../src/tools/sliced-strings.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAqBvE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAuK7D"}
|
|
@@ -31,9 +31,12 @@ export function registerSlicedStrings(server) {
|
|
|
31
31
|
// Map from parent node ID -> stats
|
|
32
32
|
const parentMap = new Map();
|
|
33
33
|
snapshot.nodes.forEach(node => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
// V8 represents these as distinct node *types*, not as `string`
|
|
35
|
+
// nodes with a special name. A sliced string shares the backing
|
|
36
|
+
// store of a parent; a concatenated (cons) string references two
|
|
37
|
+
// halves via `first`/`second`.
|
|
38
|
+
const isSliced = node.type === 'sliced string';
|
|
39
|
+
const isConcat = node.type === 'concatenated string';
|
|
37
40
|
if (!isSliced && !isConcat)
|
|
38
41
|
return;
|
|
39
42
|
if (isSliced) {
|
|
@@ -44,17 +47,18 @@ export function registerSlicedStrings(server) {
|
|
|
44
47
|
concatCount++;
|
|
45
48
|
concatTotalSize += node.self_size;
|
|
46
49
|
}
|
|
47
|
-
//
|
|
48
|
-
//
|
|
50
|
+
// Only sliced strings keep a single large parent alive through the
|
|
51
|
+
// `parent` edge. (Cons strings split across two halves and rarely
|
|
52
|
+
// pin one oversized backing buffer, so they're counted but not
|
|
53
|
+
// attributed to a parent here.)
|
|
54
|
+
if (!isSliced)
|
|
55
|
+
return;
|
|
49
56
|
for (const edge of node.references) {
|
|
50
|
-
|
|
51
|
-
if (target.type !== 'string')
|
|
57
|
+
if (String(edge.name_or_index) !== 'parent')
|
|
52
58
|
continue;
|
|
53
|
-
|
|
54
|
-
if (target.
|
|
55
|
-
target.name === 'system / ConsString') {
|
|
59
|
+
const target = edge.toNode;
|
|
60
|
+
if (!target.isString)
|
|
56
61
|
continue;
|
|
57
|
-
}
|
|
58
62
|
const existing = parentMap.get(target.id);
|
|
59
63
|
if (existing) {
|
|
60
64
|
existing.sliced_ref_count++;
|
|
@@ -70,16 +74,32 @@ export function registerSlicedStrings(server) {
|
|
|
70
74
|
sliced_total_size: node.self_size,
|
|
71
75
|
});
|
|
72
76
|
}
|
|
77
|
+
break;
|
|
73
78
|
}
|
|
74
79
|
});
|
|
75
80
|
if (slicedCount === 0 && concatCount === 0) {
|
|
76
81
|
return toolResult('No sliced or concatenated strings found in the snapshot.');
|
|
77
82
|
}
|
|
78
|
-
|
|
83
|
+
const allParents = [...parentMap.values()];
|
|
84
|
+
const totalPinnedBytes = allParents.reduce((sum, p) => sum + p.self_size, 0);
|
|
85
|
+
// Don't go silent when `min_parent_size` filters everything out. On the
|
|
86
|
+
// common "many medium parents" case (each parent sub-MB but thousands of
|
|
87
|
+
// them, e.g. ~250 KB JSON records) the filter used to hide every row and
|
|
88
|
+
// the tool reported nothing actionable. Fall back to the largest parents
|
|
89
|
+
// with a note, and ALWAYS report the aggregate bytes pinned so the "lots
|
|
90
|
+
// of small parents" case is visible (Feedback round 4 §3a).
|
|
91
|
+
let parents = allParents;
|
|
92
|
+
let filteredNote = '';
|
|
79
93
|
if (min_parent_size != null) {
|
|
80
|
-
|
|
94
|
+
const filtered = allParents.filter(p => p.self_size >= min_parent_size);
|
|
95
|
+
if (filtered.length > 0) {
|
|
96
|
+
parents = filtered;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
filteredNote = ` (no parent ≥ ${formatBytes(min_parent_size)}; showing the largest instead)`;
|
|
100
|
+
}
|
|
81
101
|
}
|
|
82
|
-
parents.sort((a, b) => b.self_size - a.self_size);
|
|
102
|
+
parents = [...parents].sort((a, b) => b.self_size - a.self_size);
|
|
83
103
|
const topParents = parents.slice(0, limit);
|
|
84
104
|
const lines = [];
|
|
85
105
|
lines.push(`**Sliced strings:** ${formatNumber(slicedCount)} nodes (${formatBytes(slicedTotalSize)} self size)`);
|
|
@@ -87,6 +107,9 @@ export function registerSlicedStrings(server) {
|
|
|
87
107
|
lines.push(`**Concatenated strings:** ${formatNumber(concatCount)} nodes (${formatBytes(concatTotalSize)} self size)`);
|
|
88
108
|
}
|
|
89
109
|
lines.push(`**Unique parent strings referenced:** ${formatNumber(parentMap.size)}`);
|
|
110
|
+
if (allParents.length > 0) {
|
|
111
|
+
lines.push(`**Total parent bytes pinned by slices:** ${formatBytes(totalPinnedBytes)} across ${formatNumber(allParents.length)} parent string(s) — substrings keep these alive even when each individual parent is small.`);
|
|
112
|
+
}
|
|
90
113
|
lines.push('');
|
|
91
114
|
if (topParents.length > 0) {
|
|
92
115
|
const headers = [
|
|
@@ -106,7 +129,7 @@ export function registerSlicedStrings(server) {
|
|
|
106
129
|
formatNumber(p.sliced_ref_count),
|
|
107
130
|
formatBytes(p.sliced_total_size),
|
|
108
131
|
]);
|
|
109
|
-
lines.push(
|
|
132
|
+
lines.push(`### Top parent strings (keeping slices alive)${filteredNote}`);
|
|
110
133
|
lines.push('');
|
|
111
134
|
lines.push(markdownTable(headers, rows, rightCols));
|
|
112
135
|
const largeMB = topParents.filter(p => p.self_size >= 1024 * 1024);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sliced-strings.js","sourceRoot":"","sources":["../../src/tools/sliced-strings.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAAC,WAAW,EAAC,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EACL,WAAW,EACX,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,WAAW,
|
|
1
|
+
{"version":3,"file":"sliced-strings.js","sourceRoot":"","sources":["../../src/tools/sliced-strings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @format
|
|
8
|
+
* @oncall memory_lab
|
|
9
|
+
*/
|
|
10
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
11
|
+
/**
|
|
12
|
+
* memlab_snapshot_header — peek a snapshot's header WITHOUT the dominator pass.
|
|
13
|
+
*
|
|
14
|
+
* The expensive/risky part of memlab_load_snapshot is the dominator-tree
|
|
15
|
+
* computation (uninterruptible, super-linear, can wedge/OOM the server). This
|
|
16
|
+
* tool skips it entirely: it resolves + fetches the file (reusing any cached
|
|
17
|
+
* download, so a follow-up real load doesn't re-download), reads the node/edge
|
|
18
|
+
* counts and capture time from the file header, and reports whether the capture
|
|
19
|
+
* fits under the current auto-scaled load ceiling.
|
|
20
|
+
*
|
|
21
|
+
* The point is to end the "attempt load → refused on counts → hand-compute
|
|
22
|
+
* density → re-query Scuba for a smaller file → retry" loop that dominated a
|
|
23
|
+
* server-snapshot sweep (feedback round 7 §1/§2): peek a candidate, read its
|
|
24
|
+
* exact counts + this-app density + the estimated max-loadable file size, then
|
|
25
|
+
* pick the right capture in one step.
|
|
26
|
+
*/
|
|
27
|
+
export declare function registerSnapshotHeader(server: McpServer): void;
|
|
28
|
+
//# sourceMappingURL=snapshot-header.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-header.d.ts","sourceRoot":"","sources":["../../src/tools/snapshot-header.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAcvE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAwH9D"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @format
|
|
8
|
+
* @oncall memory_lab
|
|
9
|
+
*/
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
import { resolveSnapshotPath, peekSnapshotCounts, computeDefaultCeilings, estimateMaxLoadableMB, extractCaptureTime, DEFAULT_MANIFOLD_BUCKET, } from './load-snapshot.js';
|
|
14
|
+
import { formatBytes, formatNumber, errorResult, textResult } from '../utils.js';
|
|
15
|
+
/**
|
|
16
|
+
* memlab_snapshot_header — peek a snapshot's header WITHOUT the dominator pass.
|
|
17
|
+
*
|
|
18
|
+
* The expensive/risky part of memlab_load_snapshot is the dominator-tree
|
|
19
|
+
* computation (uninterruptible, super-linear, can wedge/OOM the server). This
|
|
20
|
+
* tool skips it entirely: it resolves + fetches the file (reusing any cached
|
|
21
|
+
* download, so a follow-up real load doesn't re-download), reads the node/edge
|
|
22
|
+
* counts and capture time from the file header, and reports whether the capture
|
|
23
|
+
* fits under the current auto-scaled load ceiling.
|
|
24
|
+
*
|
|
25
|
+
* The point is to end the "attempt load → refused on counts → hand-compute
|
|
26
|
+
* density → re-query Scuba for a smaller file → retry" loop that dominated a
|
|
27
|
+
* server-snapshot sweep (feedback round 7 §1/§2): peek a candidate, read its
|
|
28
|
+
* exact counts + this-app density + the estimated max-loadable file size, then
|
|
29
|
+
* pick the right capture in one step.
|
|
30
|
+
*/
|
|
31
|
+
export function registerSnapshotHeader(server) {
|
|
32
|
+
server.tool('memlab_snapshot_header', "Peek a .heapsnapshot's header (node/edge counts, capture time, file size) WITHOUT loading it — no dominator pass, so it can never wedge or OOM the server the way a full memlab_load_snapshot can. Reports whether the capture fits under the current auto-scaled load ceiling, this app's node/edge density, and — when it doesn't fit — the estimated largest same-app capture that WOULD fit, so you can select a loadable snapshot in one step instead of attempting an oversized load. Accepts a local absolute path, a manifold:// URL, or a bare snapshot filename (resolved against the nest_server_nodejs_heap_snapshots bucket and fetched automatically; the download is reused by a subsequent memlab_load_snapshot). Use this to triage which capture in a size band to load before committing to the expensive load.", {
|
|
33
|
+
file_path: z
|
|
34
|
+
.string()
|
|
35
|
+
.describe('A local absolute path to a .heapsnapshot file, a manifold:// URL (manifold://bucket/key), or a bare snapshot filename to fetch from the nest_server_nodejs_heap_snapshots bucket.'),
|
|
36
|
+
}, async ({ file_path }) => {
|
|
37
|
+
try {
|
|
38
|
+
let resolved;
|
|
39
|
+
let fetchedFrom = null;
|
|
40
|
+
try {
|
|
41
|
+
const r = resolveSnapshotPath(file_path);
|
|
42
|
+
resolved = r.localPath;
|
|
43
|
+
fetchedFrom = r.fetchedFrom;
|
|
44
|
+
}
|
|
45
|
+
catch (fetchErr) {
|
|
46
|
+
const msg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
|
|
47
|
+
return errorResult(new Error(`Failed to fetch "${file_path}" from Manifold: ${msg}\n` +
|
|
48
|
+
`Ensure the 'manifold' CLI is installed and you have access to the bucket, ` +
|
|
49
|
+
`or pass a local absolute path instead.`));
|
|
50
|
+
}
|
|
51
|
+
if (!fs.existsSync(resolved)) {
|
|
52
|
+
return errorResult(new Error(`File not found: ${resolved}` +
|
|
53
|
+
(file_path.includes('/')
|
|
54
|
+
? ''
|
|
55
|
+
: ` (also tried fetching "${file_path}" from the ${DEFAULT_MANIFOLD_BUCKET} bucket)`)));
|
|
56
|
+
}
|
|
57
|
+
const fileStat = fs.statSync(resolved);
|
|
58
|
+
const fileSizeMB = fileStat.size / (1024 * 1024);
|
|
59
|
+
const fileName = path.basename(fetchedFrom ?? file_path.replace(/^manifold:\/\//, ''));
|
|
60
|
+
const counts = peekSnapshotCounts(resolved);
|
|
61
|
+
const ceilings = computeDefaultCeilings();
|
|
62
|
+
const lines = [];
|
|
63
|
+
lines.push(`Snapshot header: ${fileName}`);
|
|
64
|
+
if (fetchedFrom)
|
|
65
|
+
lines.push(`Fetched from Manifold: ${fetchedFrom}`);
|
|
66
|
+
lines.push(`File size: ${formatBytes(fileStat.size)}`);
|
|
67
|
+
if (!counts) {
|
|
68
|
+
lines.push(`Node/edge counts: not found in the header prefix — cannot assess loadability without a full load.`);
|
|
69
|
+
return textResult(lines.join('\n'));
|
|
70
|
+
}
|
|
71
|
+
lines.push(`Nodes: ${formatNumber(counts.nodeCount)} Edges: ${formatNumber(counts.edgeCount)}`);
|
|
72
|
+
const captured = extractCaptureTime(fileName);
|
|
73
|
+
if (captured) {
|
|
74
|
+
const iso = captured.toISOString().slice(0, 10);
|
|
75
|
+
const ageDays = Math.max(0, Math.floor((Date.now() - captured.getTime()) / 86_400_000));
|
|
76
|
+
lines.push(`Captured ${iso} (${ageDays} day(s) ago) — if you find a candidate, verify the fix wasn't already deployed since then before writing one.`);
|
|
77
|
+
}
|
|
78
|
+
const heapNote = ceilings.heapLimitMB > 0
|
|
79
|
+
? ` (server heap ~${formatNumber(ceilings.heapLimitMB)} MB via --max-old-space-size)`
|
|
80
|
+
: '';
|
|
81
|
+
lines.push(`Current load ceiling: ${formatNumber(ceilings.maxNodes)} nodes / ${formatNumber(ceilings.maxEdges)} edges${heapNote}.`);
|
|
82
|
+
const nodesPerMB = Math.round(counts.nodeCount / fileSizeMB);
|
|
83
|
+
const edgesPerMB = Math.round(counts.edgeCount / fileSizeMB);
|
|
84
|
+
const fitsNodes = counts.nodeCount <= ceilings.maxNodes;
|
|
85
|
+
const fitsEdges = counts.edgeCount <= ceilings.maxEdges;
|
|
86
|
+
if (fitsNodes && fitsEdges) {
|
|
87
|
+
lines.push(`✓ Loadable — under the ceiling on both nodes and edges. Load it with memlab_load_snapshot({file_path: "${file_path}"}); the fetched file is cached so it won't re-download.`);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
const over = !fitsNodes && !fitsEdges
|
|
91
|
+
? 'nodes and edges'
|
|
92
|
+
: !fitsEdges
|
|
93
|
+
? 'edges'
|
|
94
|
+
: 'nodes';
|
|
95
|
+
const estMB = estimateMaxLoadableMB(counts, fileSizeMB, {
|
|
96
|
+
maxNodes: ceilings.maxNodes,
|
|
97
|
+
maxEdges: ceilings.maxEdges,
|
|
98
|
+
});
|
|
99
|
+
lines.push(`✗ Over the ceiling on ${over}. Density: ${formatNumber(nodesPerMB)} nodes/MB, ${formatNumber(edgesPerMB)} edges/MB.` +
|
|
100
|
+
(estMB != null
|
|
101
|
+
? ` For this app the ceiling corresponds to roughly a ≤${formatNumber(estMB)} MB capture — select a smaller snapshot below that size (then memlab_snapshot_header it to confirm), or force the load with memlab_load_snapshot({force: true}) accepting a long, uninterruptible dominator pass.`
|
|
102
|
+
: ` Select a smaller snapshot, or force the load with memlab_load_snapshot({force: true}).`));
|
|
103
|
+
}
|
|
104
|
+
return textResult(lines.join('\n'));
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
return errorResult(err);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=snapshot-header.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-header.js","sourceRoot":"","sources":["../../src/tools/snapshot-header.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot-summary.d.ts","sourceRoot":"","sources":["../../src/tools/snapshot-summary.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"snapshot-summary.d.ts","sourceRoot":"","sources":["../../src/tools/snapshot-summary.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAevE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAwL/D"}
|
|
@@ -9,10 +9,19 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import memlabCore from '@memlab/core';
|
|
11
11
|
const { utils, NumericSet } = memlabCore;
|
|
12
|
+
import { z } from 'zod';
|
|
12
13
|
import { getSnapshot, getFilePath } from '../heap-state.js';
|
|
13
|
-
import { formatBytes, formatNumber, markdownTable, errorResult, toolResult, } from '../utils.js';
|
|
14
|
+
import { formatBytes, formatNumber, markdownTable, detectAppName, errorResult, toolResult, } from '../utils.js';
|
|
14
15
|
export function registerSnapshotSummary(server) {
|
|
15
|
-
server.tool('memlab_snapshot_summary', 'Get an overview of the loaded heap snapshot: total nodes, edges, size, and
|
|
16
|
+
server.tool('memlab_snapshot_summary', 'Get an overview of the loaded heap snapshot: total nodes, edges, size, and per-node-type breakdown with count and self size. ' +
|
|
17
|
+
'By DEFAULT retained size per type is reported as a cheap RANGE (lower bound = Σ self size, upper bound = Σ retained size) computed in a single O(N) pass — the exact dominator-deduped retained size needs a per-type dominator walk that can stall/OOM the server on very large heaps. Pass `exact_retained_size:true` ONLY when the user explicitly asks for the exact figure. ' +
|
|
18
|
+
'Includes anomaly detection for high-count classes. Follow up with memlab_class_histogram for a per-class breakdown, or memlab_duplicated_strings to find string duplication.', {
|
|
19
|
+
exact_retained_size: z
|
|
20
|
+
.boolean()
|
|
21
|
+
.optional()
|
|
22
|
+
.default(false)
|
|
23
|
+
.describe('Compute the EXACT dominator-deduped retained size per node type via aggregateDominatorMetrics. DEFAULT false: report a range instead (lower = Σ self size, upper = Σ raw retained size), which is fast and safe on huge heaps. Set true ONLY on explicit user request — the exact walk can be very slow or time out on snapshots with millions of nodes and deep dominator chains.'),
|
|
24
|
+
}, async ({ exact_retained_size }) => {
|
|
16
25
|
try {
|
|
17
26
|
const snapshot = getSnapshot();
|
|
18
27
|
let nodeCount = 0;
|
|
@@ -24,45 +33,78 @@ export function registerSnapshotSummary(server) {
|
|
|
24
33
|
totalSelfSize += node.self_size;
|
|
25
34
|
let stats = typeStats.get(node.type);
|
|
26
35
|
if (!stats) {
|
|
27
|
-
stats = {
|
|
36
|
+
stats = {
|
|
37
|
+
count: 0,
|
|
38
|
+
self_size: 0,
|
|
39
|
+
raw_retained: 0,
|
|
40
|
+
// Only collect per-node ids for the exact dominator walk, which
|
|
41
|
+
// is opt-in — in the default range mode they would waste memory.
|
|
42
|
+
node_ids: exact_retained_size ? new NumericSet() : null,
|
|
43
|
+
};
|
|
28
44
|
typeStats.set(node.type, stats);
|
|
29
45
|
}
|
|
30
46
|
stats.count++;
|
|
31
47
|
stats.self_size += node.self_size;
|
|
32
|
-
stats.
|
|
48
|
+
stats.raw_retained += node.retainedSize;
|
|
49
|
+
if (stats.node_ids)
|
|
50
|
+
stats.node_ids.add(node.id);
|
|
33
51
|
});
|
|
34
52
|
snapshot.edges.forEach(() => {
|
|
35
53
|
edgeCount++;
|
|
36
54
|
});
|
|
37
|
-
//
|
|
38
|
-
// aggregation.
|
|
39
|
-
//
|
|
40
|
-
//
|
|
55
|
+
// Retained size per type. By DEFAULT we do NOT run the exact
|
|
56
|
+
// dominator-aware aggregation (utils.aggregateDominatorMetrics): on
|
|
57
|
+
// large heaps with deep dominator chains it is the operation that
|
|
58
|
+
// stalls/OOMs the server. Instead we report a cheap range computed in
|
|
59
|
+
// the O(N) pass above:
|
|
60
|
+
// - lower bound = Σ self size (the objects' own bytes; never
|
|
61
|
+
// double-counts, but excludes everything they retain)
|
|
62
|
+
// - upper bound = Σ retained size (raw; double-counts memory shared
|
|
63
|
+
// between instances on the dominator tree)
|
|
64
|
+
// The true dominator-deduped value lies in [lower, upper]. Pass
|
|
65
|
+
// exact_retained_size:true to compute the exact figure anyway.
|
|
41
66
|
const breakdown = [];
|
|
42
67
|
for (const [type, stats] of typeStats) {
|
|
43
|
-
|
|
68
|
+
let exact = null;
|
|
69
|
+
if (exact_retained_size && stats.node_ids) {
|
|
70
|
+
exact = utils.aggregateDominatorMetrics(stats.node_ids, snapshot, () => true, (node) => node.retainedSize);
|
|
71
|
+
}
|
|
44
72
|
breakdown.push({
|
|
45
73
|
type,
|
|
46
74
|
count: stats.count,
|
|
47
75
|
self_size: stats.self_size,
|
|
48
|
-
|
|
76
|
+
raw_retained: stats.raw_retained,
|
|
77
|
+
exact,
|
|
49
78
|
});
|
|
50
79
|
}
|
|
51
|
-
breakdown.sort((a, b) =>
|
|
80
|
+
breakdown.sort((a, b) => exact_retained_size
|
|
81
|
+
? (b.exact ?? 0) - (a.exact ?? 0)
|
|
82
|
+
: b.raw_retained - a.raw_retained);
|
|
83
|
+
const appName = detectAppName(snapshot);
|
|
52
84
|
const lines = [
|
|
53
85
|
`**File:** ${getFilePath()}`,
|
|
86
|
+
...(appName
|
|
87
|
+
? [`**Detected app:** \`${appName}\` (from bundle paths)`]
|
|
88
|
+
: []),
|
|
54
89
|
`**Nodes:** ${formatNumber(nodeCount)} | **Edges:** ${formatNumber(edgeCount)} | **Total Self Size:** ${formatBytes(totalSelfSize)}`,
|
|
55
90
|
'',
|
|
56
91
|
];
|
|
57
|
-
const headers =
|
|
92
|
+
const headers = exact_retained_size
|
|
93
|
+
? ['Type', 'Count', 'Self Size', 'Retained Size']
|
|
94
|
+
: ['Type', 'Count', 'Self (lower)', 'Retained ≤ (upper)'];
|
|
58
95
|
const rightCols = new Set([1, 2, 3]);
|
|
59
96
|
const rows = breakdown.map(b => [
|
|
60
97
|
b.type,
|
|
61
98
|
formatNumber(b.count),
|
|
62
99
|
formatBytes(b.self_size),
|
|
63
|
-
|
|
100
|
+
exact_retained_size
|
|
101
|
+
? formatBytes(b.exact ?? 0)
|
|
102
|
+
: `≤ ${formatBytes(b.raw_retained)}`,
|
|
64
103
|
]);
|
|
65
104
|
lines.push(markdownTable(headers, rows, rightCols));
|
|
105
|
+
if (!exact_retained_size) {
|
|
106
|
+
lines.push('', "_Retained size is shown as a **range** to avoid an expensive dominator walk. The true dominator-deduped retained size of each type lies between the **lower bound** (`Self` = the objects' own bytes) and the **upper bound** (`Retained ≤` = Σ of each instance's retained size, which double-counts memory shared between instances on the dominator tree). Computing the exact value needs a per-type dominator walk — slow and, on large heaps with deep PromiseReaction/Context chains, prone to stalling or timing out. Pass `exact_retained_size:true` to compute it anyway._");
|
|
107
|
+
}
|
|
66
108
|
// Anomaly detection: find classes with unusually high instance counts
|
|
67
109
|
const classMap = new Map();
|
|
68
110
|
snapshot.nodes.forEach(node => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot-summary.js","sourceRoot":"","sources":["../../src/tools/snapshot-summary.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,UAAU,MAAM,cAAc,CAAC;AAEtC,MAAM,EAAC,KAAK,EAAE,UAAU,EAAC,GAAG,UAAU,CAAC;AACvC,OAAO,EAAC,WAAW,EAAE,WAAW,EAAC,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EACL,WAAW,EACX,YAAY,EACZ,aAAa,EACb,WAAW,
|
|
1
|
+
{"version":3,"file":"snapshot-summary.js","sourceRoot":"","sources":["../../src/tools/snapshot-summary.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @format
|
|
8
|
+
* @oncall memory_lab
|
|
9
|
+
*/
|
|
10
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
11
|
+
export declare function registerSnapshots(server: McpServer): void;
|
|
12
|
+
//# sourceMappingURL=snapshots.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshots.d.ts","sourceRoot":"","sources":["../../src/tools/snapshots.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAYvE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAyGzD"}
|