@memlab/mcp-server 2.0.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.
Files changed (107) hide show
  1. package/README.md +285 -0
  2. package/bin/memlab-mcp.js +3 -0
  3. package/dist/heap-state.d.ts +14 -0
  4. package/dist/heap-state.d.ts.map +1 -0
  5. package/dist/heap-state.js +25 -0
  6. package/dist/heap-state.js.map +1 -0
  7. package/dist/index.d.ts +12 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +71 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/tools/aggregate-nodes.d.ts +12 -0
  12. package/dist/tools/aggregate-nodes.d.ts.map +1 -0
  13. package/dist/tools/aggregate-nodes.js +136 -0
  14. package/dist/tools/aggregate-nodes.js.map +1 -0
  15. package/dist/tools/class-histogram.d.ts +12 -0
  16. package/dist/tools/class-histogram.d.ts.map +1 -0
  17. package/dist/tools/class-histogram.js +94 -0
  18. package/dist/tools/class-histogram.js.map +1 -0
  19. package/dist/tools/closure-inspection.d.ts +12 -0
  20. package/dist/tools/closure-inspection.d.ts.map +1 -0
  21. package/dist/tools/closure-inspection.js +107 -0
  22. package/dist/tools/closure-inspection.js.map +1 -0
  23. package/dist/tools/detached-dom.d.ts +12 -0
  24. package/dist/tools/detached-dom.d.ts.map +1 -0
  25. package/dist/tools/detached-dom.js +53 -0
  26. package/dist/tools/detached-dom.js.map +1 -0
  27. package/dist/tools/dominator-subtree.d.ts +12 -0
  28. package/dist/tools/dominator-subtree.d.ts.map +1 -0
  29. package/dist/tools/dominator-subtree.js +77 -0
  30. package/dist/tools/dominator-subtree.js.map +1 -0
  31. package/dist/tools/duplicated-strings.d.ts +12 -0
  32. package/dist/tools/duplicated-strings.d.ts.map +1 -0
  33. package/dist/tools/duplicated-strings.js +78 -0
  34. package/dist/tools/duplicated-strings.js.map +1 -0
  35. package/dist/tools/eval.d.ts +12 -0
  36. package/dist/tools/eval.d.ts.map +1 -0
  37. package/dist/tools/eval.js +119 -0
  38. package/dist/tools/eval.js.map +1 -0
  39. package/dist/tools/find-by-property.d.ts +12 -0
  40. package/dist/tools/find-by-property.d.ts.map +1 -0
  41. package/dist/tools/find-by-property.js +77 -0
  42. package/dist/tools/find-by-property.js.map +1 -0
  43. package/dist/tools/find-nodes-by-class.d.ts +12 -0
  44. package/dist/tools/find-nodes-by-class.d.ts.map +1 -0
  45. package/dist/tools/find-nodes-by-class.js +38 -0
  46. package/dist/tools/find-nodes-by-class.js.map +1 -0
  47. package/dist/tools/for-each.d.ts +12 -0
  48. package/dist/tools/for-each.d.ts.map +1 -0
  49. package/dist/tools/for-each.js +185 -0
  50. package/dist/tools/for-each.js.map +1 -0
  51. package/dist/tools/get-node.d.ts +12 -0
  52. package/dist/tools/get-node.d.ts.map +1 -0
  53. package/dist/tools/get-node.js +51 -0
  54. package/dist/tools/get-node.js.map +1 -0
  55. package/dist/tools/get-property.d.ts +12 -0
  56. package/dist/tools/get-property.d.ts.map +1 -0
  57. package/dist/tools/get-property.js +88 -0
  58. package/dist/tools/get-property.js.map +1 -0
  59. package/dist/tools/get-references.d.ts +12 -0
  60. package/dist/tools/get-references.d.ts.map +1 -0
  61. package/dist/tools/get-references.js +61 -0
  62. package/dist/tools/get-references.js.map +1 -0
  63. package/dist/tools/get-referrers.d.ts +12 -0
  64. package/dist/tools/get-referrers.d.ts.map +1 -0
  65. package/dist/tools/get-referrers.js +61 -0
  66. package/dist/tools/get-referrers.js.map +1 -0
  67. package/dist/tools/global-variables.d.ts +12 -0
  68. package/dist/tools/global-variables.d.ts.map +1 -0
  69. package/dist/tools/global-variables.js +331 -0
  70. package/dist/tools/global-variables.js.map +1 -0
  71. package/dist/tools/largest-objects.d.ts +12 -0
  72. package/dist/tools/largest-objects.d.ts.map +1 -0
  73. package/dist/tools/largest-objects.js +32 -0
  74. package/dist/tools/largest-objects.js.map +1 -0
  75. package/dist/tools/load-snapshot.d.ts +12 -0
  76. package/dist/tools/load-snapshot.d.ts.map +1 -0
  77. package/dist/tools/load-snapshot.js +39 -0
  78. package/dist/tools/load-snapshot.js.map +1 -0
  79. package/dist/tools/object-shape.d.ts +12 -0
  80. package/dist/tools/object-shape.d.ts.map +1 -0
  81. package/dist/tools/object-shape.js +86 -0
  82. package/dist/tools/object-shape.js.map +1 -0
  83. package/dist/tools/reports.d.ts +12 -0
  84. package/dist/tools/reports.d.ts.map +1 -0
  85. package/dist/tools/reports.js +540 -0
  86. package/dist/tools/reports.js.map +1 -0
  87. package/dist/tools/retainer-trace.d.ts +12 -0
  88. package/dist/tools/retainer-trace.d.ts.map +1 -0
  89. package/dist/tools/retainer-trace.js +68 -0
  90. package/dist/tools/retainer-trace.js.map +1 -0
  91. package/dist/tools/search-nodes.d.ts +12 -0
  92. package/dist/tools/search-nodes.d.ts.map +1 -0
  93. package/dist/tools/search-nodes.js +85 -0
  94. package/dist/tools/search-nodes.js.map +1 -0
  95. package/dist/tools/snapshot-summary.d.ts +12 -0
  96. package/dist/tools/snapshot-summary.d.ts.map +1 -0
  97. package/dist/tools/snapshot-summary.js +73 -0
  98. package/dist/tools/snapshot-summary.js.map +1 -0
  99. package/dist/tools/stale-collections.d.ts +12 -0
  100. package/dist/tools/stale-collections.d.ts.map +1 -0
  101. package/dist/tools/stale-collections.js +97 -0
  102. package/dist/tools/stale-collections.js.map +1 -0
  103. package/dist/utils.d.ts +82 -0
  104. package/dist/utils.d.ts.map +1 -0
  105. package/dist/utils.js +220 -0
  106. package/dist/utils.js.map +1 -0
  107. package/package.json +58 -0
@@ -0,0 +1,94 @@
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 { z } from 'zod';
11
+ import memlabCore from '@memlab/core';
12
+ const { utils } = memlabCore;
13
+ import { getSnapshot } from '../heap-state.js';
14
+ import { errorResult, textResult, formatBytes, formatNumber, markdownTable, } from '../utils.js';
15
+ export function registerClassHistogram(server) {
16
+ server.tool('memlab_class_histogram', 'Show instance count and total self size per constructor/class name, sorted by aggregate retained size (dominator-aware, no double-counting). Useful for identifying which types of objects dominate memory.', {
17
+ limit: z
18
+ .number()
19
+ .optional()
20
+ .default(30)
21
+ .describe('Maximum number of classes to return (default 30)'),
22
+ min_count: z
23
+ .number()
24
+ .optional()
25
+ .default(1)
26
+ .describe('Minimum instance count to include (default 1)'),
27
+ node_type: z
28
+ .string()
29
+ .optional()
30
+ .describe('Filter by node type (e.g., "object", "closure", "string"). If omitted, all types are included.'),
31
+ }, async ({ limit, min_count, node_type }) => {
32
+ try {
33
+ const snapshot = getSnapshot();
34
+ const classMap = new Map();
35
+ snapshot.nodes.forEach(node => {
36
+ if (node_type && node.type !== node_type)
37
+ return;
38
+ // Skip internal meta nodes
39
+ if (node.id <= 3)
40
+ return;
41
+ const key = `${node.type}::${node.name}`;
42
+ const entry = classMap.get(key);
43
+ if (entry) {
44
+ entry.count++;
45
+ entry.total_self_size += node.self_size;
46
+ entry.node_ids.add(node.id);
47
+ }
48
+ else {
49
+ classMap.set(key, {
50
+ count: 1,
51
+ total_self_size: node.self_size,
52
+ node_ids: new Set([node.id]),
53
+ type: node.type,
54
+ });
55
+ }
56
+ });
57
+ // Filter by min_count first, then compute retained sizes only for
58
+ // classes that pass the filter to avoid expensive dominator walks
59
+ // on classes we'll discard anyway.
60
+ const filtered = [...classMap.entries()].filter(([, v]) => v.count >= min_count);
61
+ // Compute dominator-aware aggregate retained size per class.
62
+ const withRetained = filtered.map(([key, v]) => {
63
+ const retainedSize = utils.aggregateDominatorMetrics(v.node_ids, snapshot, () => true, (node) => node.retainedSize);
64
+ return { key, ...v, retained_size: retainedSize };
65
+ });
66
+ const sorted = withRetained
67
+ .sort((a, b) => b.retained_size - a.retained_size)
68
+ .slice(0, limit);
69
+ const headers = [
70
+ 'Class',
71
+ 'Type',
72
+ 'Count',
73
+ 'Self Size',
74
+ 'Retained Size',
75
+ ];
76
+ const rightCols = new Set([2, 3, 4]);
77
+ const rows = sorted.map(v => {
78
+ const name = v.key.split('::').slice(1).join('::');
79
+ return [
80
+ name,
81
+ v.type,
82
+ formatNumber(v.count),
83
+ formatBytes(v.total_self_size),
84
+ formatBytes(v.retained_size),
85
+ ];
86
+ });
87
+ return textResult(`Class histogram (${formatNumber(classMap.size)} total classes, showing ${rows.length})\n\n${markdownTable(headers, rows, rightCols)}`);
88
+ }
89
+ catch (err) {
90
+ return errorResult(err);
91
+ }
92
+ });
93
+ }
94
+ //# sourceMappingURL=class-histogram.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"class-histogram.js","sourceRoot":"","sources":["../../src/tools/class-histogram.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 registerClosureInspection(server: McpServer): void;
12
+ //# sourceMappingURL=closure-inspection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"closure-inspection.d.ts","sourceRoot":"","sources":["../../src/tools/closure-inspection.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAavE,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA2HjE"}
@@ -0,0 +1,107 @@
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 { z } from 'zod';
11
+ import { getSnapshot } from '../heap-state.js';
12
+ import { errorResult, textResult, formatBytes, formatNumber, formatNodeInline, markdownTable, } from '../utils.js';
13
+ export function registerClosureInspection(server) {
14
+ server.tool('memlab_closure_inspection', 'Inspect a closure (function) node to show its captured variables from the enclosing scope. Shows the function name, source location, and all context-bound variables with their types and sizes. Critical for diagnosing closure-based memory leaks where a function unintentionally retains large objects.', {
15
+ node_id: z
16
+ .number()
17
+ .describe('The numeric ID of the closure/function node'),
18
+ }, async ({ node_id }) => {
19
+ try {
20
+ const snapshot = getSnapshot();
21
+ const node = snapshot.getNodeById(node_id);
22
+ if (!node) {
23
+ return errorResult(`Node with id ${node_id} not found`);
24
+ }
25
+ if (node.type !== 'closure') {
26
+ return textResult(`Node @${node_id} is not a closure (type is "${node.type}"). This tool is designed for closure nodes.`);
27
+ }
28
+ // Captured variables come via "context" type edges
29
+ const capturedVars = node.references
30
+ .filter(edge => edge.type === 'context')
31
+ .sort((a, b) => b.toNode.retainedSize - a.toNode.retainedSize)
32
+ .map(edge => {
33
+ const target = edge.toNode;
34
+ const v = {
35
+ variable_name: String(edge.name_or_index),
36
+ target_type: target.type,
37
+ target_name: target.name,
38
+ target_id: target.id,
39
+ self_size: target.self_size,
40
+ retained_size: target.retainedSize,
41
+ retained_size_formatted: formatBytes(target.retainedSize),
42
+ };
43
+ if (target.isString) {
44
+ const strNode = target.toStringNode();
45
+ if (strNode) {
46
+ const val = strNode.stringValue;
47
+ v.string_value =
48
+ val.length > 200 ? val.slice(0, 200) + '...' : val;
49
+ }
50
+ }
51
+ return v;
52
+ });
53
+ // Also look for the "shared" edge which points to SharedFunctionInfo
54
+ // containing the function's source position
55
+ const sharedEdge = node.references.find(e => String(e.name_or_index) === 'shared' && e.type === 'internal');
56
+ // Get the context chain — the scope object this closure closes over
57
+ const contextEdge = node.references.find(e => String(e.name_or_index) === 'context' && e.type === 'internal');
58
+ const location = node.location
59
+ ? {
60
+ script_id: node.location.script_id,
61
+ line: node.location.line,
62
+ column: node.location.column,
63
+ }
64
+ : null;
65
+ const totalCapturedRetained = capturedVars.reduce((sum, v) => sum + v.retained_size, 0);
66
+ const lines = [
67
+ `**Closure:** ${formatNodeInline(node.id, node.name, node.type)}`,
68
+ `**Self Size:** ${formatBytes(node.self_size)} | **Retained Size:** ${formatBytes(node.retainedSize)}`,
69
+ ];
70
+ if (location) {
71
+ lines.push(`**Location:** script ${location.script_id}, line ${location.line}, col ${location.column}`);
72
+ }
73
+ if (contextEdge) {
74
+ const ctx = contextEdge.toNode;
75
+ lines.push(`**Scope Context:** ${formatNodeInline(ctx.id, ctx.name, ctx.type)}`);
76
+ }
77
+ if (sharedEdge) {
78
+ lines.push(`**Shared Function Info:** @${sharedEdge.toNode.id} ${sharedEdge.toNode.name}`);
79
+ }
80
+ lines.push(`**Captured Variables:** ${formatNumber(capturedVars.length)}, total retained ${formatBytes(totalCapturedRetained)}`);
81
+ lines.push('');
82
+ if (capturedVars.length > 0) {
83
+ const headers = ['Variable', 'Target', 'Target Type', 'Retained'];
84
+ const rightCols = new Set([3]);
85
+ const rows = capturedVars.map(v => {
86
+ let targetLabel = `@${v.target_id} ${v.target_name}`;
87
+ if (v.string_value != null) {
88
+ const sv = v.string_value;
89
+ targetLabel = `@${v.target_id} "${sv.length > 60 ? sv.slice(0, 60) + '...' : sv}"`;
90
+ }
91
+ return [
92
+ v.variable_name,
93
+ targetLabel,
94
+ v.target_type,
95
+ formatBytes(v.retained_size),
96
+ ];
97
+ });
98
+ lines.push(markdownTable(headers, rows, rightCols));
99
+ }
100
+ return textResult(lines.join('\n'));
101
+ }
102
+ catch (err) {
103
+ return errorResult(err);
104
+ }
105
+ });
106
+ }
107
+ //# sourceMappingURL=closure-inspection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"closure-inspection.js","sourceRoot":"","sources":["../../src/tools/closure-inspection.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 registerDetachedDom(server: McpServer): void;
12
+ //# sourceMappingURL=detached-dom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detached-dom.d.ts","sourceRoot":"","sources":["../../src/tools/detached-dom.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAkBvE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA2C3D"}
@@ -0,0 +1,53 @@
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 { z } from 'zod';
11
+ import { getSnapshot } from '../heap-state.js';
12
+ import { queryNodes, formatQueryNodesResult, errorResult, textResult, } from '../utils.js';
13
+ function isDetachedDOMNode(node) {
14
+ if (node.id <= 3)
15
+ return false;
16
+ if (node.is_detached)
17
+ return true;
18
+ return node.name.startsWith('Detached ');
19
+ }
20
+ export function registerDetachedDom(server) {
21
+ server.tool('memlab_detached_dom', 'Find detached DOM elements still retained in memory. These are common sources of memory leaks — DOM nodes removed from the document but kept alive by JavaScript references. Supports count-only and ids-only modes for large result sets.', {
22
+ output_mode: z
23
+ .enum(['full', 'count', 'ids'])
24
+ .optional()
25
+ .default('full')
26
+ .describe('Output verbosity: "full" returns node summaries (default), "count" returns only the total count, "ids" returns only node IDs'),
27
+ offset: z
28
+ .number()
29
+ .optional()
30
+ .default(0)
31
+ .describe('Skip the first N results (for pagination)'),
32
+ limit: z
33
+ .number()
34
+ .optional()
35
+ .default(20)
36
+ .describe('Maximum number of results (default 20, up to 10000 for ids mode)'),
37
+ }, async ({ output_mode, offset, limit }) => {
38
+ try {
39
+ const snapshot = getSnapshot();
40
+ const effectiveLimit = output_mode === 'ids' ? Math.min(limit, 10000) : Math.min(limit, 500);
41
+ const result = queryNodes(snapshot, isDetachedDOMNode, {
42
+ limit: effectiveLimit,
43
+ offset,
44
+ outputMode: output_mode,
45
+ });
46
+ return textResult(formatQueryNodesResult(result, offset));
47
+ }
48
+ catch (err) {
49
+ return errorResult(err);
50
+ }
51
+ });
52
+ }
53
+ //# sourceMappingURL=detached-dom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detached-dom.js","sourceRoot":"","sources":["../../src/tools/detached-dom.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAAC,WAAW,EAAC,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EACL,UAAU,EACV,sBAAsB,EACtB,WAAW,EACX,UAAU,GACX,MAAM,aAAa,CAAC;AAGrB,SAAS,iBAAiB,CAAC,IAAe;IACxC,IAAI,IAAI,CAAC,EAAE,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/B,IAAI,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IAClC,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,4OAA4O,EAC5O;QACE,WAAW,EAAE,CAAC;aACX,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;aAC9B,QAAQ,EAAE;aACV,OAAO,CAAC,MAAM,CAAC;aACf,QAAQ,CACP,8HAA8H,CAC/H;QACH,MAAM,EAAE,CAAC;aACN,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,OAAO,CAAC,CAAC,CAAC;aACV,QAAQ,CAAC,2CAA2C,CAAC;QACxD,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,OAAO,CAAC,EAAE,CAAC;aACX,QAAQ,CACP,kEAAkE,CACnE;KACJ,EACD,KAAK,EAAE,EAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAC,EAAE,EAAE;QACrC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;YAC/B,MAAM,cAAc,GAClB,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YAExE,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,EAAE,iBAAiB,EAAE;gBACrD,KAAK,EAAE,cAAc;gBACrB,MAAM;gBACN,UAAU,EAAE,WAAyB;aACtC,CAAC,CAAC;YAEH,OAAO,UAAU,CAAC,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QAC5D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -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 registerDominatorSubtree(server: McpServer): void;
12
+ //# sourceMappingURL=dominator-subtree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dominator-subtree.d.ts","sourceRoot":"","sources":["../../src/tools/dominator-subtree.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAevE,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAuEhE"}
@@ -0,0 +1,77 @@
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 { z } from 'zod';
11
+ import { getSnapshot } from '../heap-state.js';
12
+ import { serializeNodeSummary, isNodeWorthInspecting, formatNodeSummaryTable, formatNodeInline, errorResult, textResult, formatBytes, formatNumber, } from '../utils.js';
13
+ export function registerDominatorSubtree(server) {
14
+ server.tool('memlab_dominator_subtree', 'Show the direct children in the dominator tree for a given node — i.e., objects whose retained size is exclusively attributed to this node. These are the objects that would be freed if this node were garbage collected. Useful for understanding what composes a large retained size.', {
15
+ node_id: z.number().describe('The numeric ID of the heap node'),
16
+ limit: z
17
+ .number()
18
+ .optional()
19
+ .default(20)
20
+ .describe('Maximum number of dominated children to return (default 20)'),
21
+ include_internal: z
22
+ .boolean()
23
+ .optional()
24
+ .default(false)
25
+ .describe('Include internal/meta nodes (default false)'),
26
+ }, async ({ node_id, limit, include_internal }) => {
27
+ try {
28
+ const snapshot = getSnapshot();
29
+ const targetNode = snapshot.getNodeById(node_id);
30
+ if (!targetNode) {
31
+ return errorResult(`Node with id ${node_id} not found`);
32
+ }
33
+ // Find all nodes directly dominated by this node
34
+ const dominated = [];
35
+ let totalDominated = 0;
36
+ snapshot.nodes.forEach(node => {
37
+ if (node.dominatorNode?.id === node_id && node.id !== node_id) {
38
+ totalDominated++;
39
+ if (!include_internal && !isNodeWorthInspecting(node))
40
+ return;
41
+ // Insertion sort to keep top N by retained size
42
+ const size = node.retainedSize;
43
+ let inserted = false;
44
+ for (let i = 0; i < dominated.length; i++) {
45
+ if (size > dominated[i].retainedSize) {
46
+ dominated.splice(i, 0, node);
47
+ inserted = true;
48
+ break;
49
+ }
50
+ }
51
+ if (!inserted) {
52
+ dominated.push(node);
53
+ }
54
+ if (dominated.length > limit) {
55
+ dominated.length = limit;
56
+ }
57
+ }
58
+ });
59
+ const summaries = dominated.map(serializeNodeSummary);
60
+ const lines = [
61
+ `**${formatNodeInline(targetNode.id, targetNode.name, targetNode.type)}** — retained ${formatBytes(targetNode.retainedSize)}, ${formatNumber(totalDominated)} dominated nodes (showing ${summaries.length})`,
62
+ '',
63
+ ];
64
+ if (summaries.length > 0) {
65
+ lines.push(formatNodeSummaryTable(summaries));
66
+ }
67
+ else {
68
+ lines.push('No dominated nodes found.');
69
+ }
70
+ return textResult(lines.join('\n'));
71
+ }
72
+ catch (err) {
73
+ return errorResult(err);
74
+ }
75
+ });
76
+ }
77
+ //# sourceMappingURL=dominator-subtree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dominator-subtree.js","sourceRoot":"","sources":["../../src/tools/dominator-subtree.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 registerDuplicatedStrings(server: McpServer): void;
12
+ //# sourceMappingURL=duplicated-strings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duplicated-strings.d.ts","sourceRoot":"","sources":["../../src/tools/duplicated-strings.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAKvE,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA4EjE"}
@@ -0,0 +1,78 @@
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 { z } from 'zod';
11
+ import { getSnapshot } from '../heap-state.js';
12
+ import { formatBytes, errorResult, textResult } from '../utils.js';
13
+ export function registerDuplicatedStrings(server) {
14
+ server.tool('memlab_duplicated_strings', 'Find duplicated string instances in the heap. Shows strings that appear multiple times, ranked by total retained size — a common source of memory waste.', {
15
+ limit: z
16
+ .number()
17
+ .optional()
18
+ .default(15)
19
+ .describe('Maximum number of results (default 15)'),
20
+ }, async ({ limit }) => {
21
+ try {
22
+ const snapshot = getSnapshot();
23
+ // Build frequency map: string value -> { count, totalSize, exampleIds }
24
+ const stringMap = new Map();
25
+ snapshot.nodes.forEach(node => {
26
+ if (node.type !== 'string')
27
+ return;
28
+ // Skip sliced strings (they share backing storage)
29
+ if (node.name === 'system / SlicedString')
30
+ return;
31
+ const strNode = node.toStringNode();
32
+ if (!strNode)
33
+ return;
34
+ const value = strNode.stringValue;
35
+ const entry = stringMap.get(value);
36
+ if (entry) {
37
+ entry.count++;
38
+ entry.total_size += node.retainedSize;
39
+ if (entry.example_node_ids.length < 3) {
40
+ entry.example_node_ids.push(node.id);
41
+ }
42
+ }
43
+ else {
44
+ stringMap.set(value, {
45
+ count: 1,
46
+ total_size: node.retainedSize,
47
+ example_node_ids: [node.id],
48
+ });
49
+ }
50
+ });
51
+ // Filter to only duplicated strings (count > 1) and rank by total size
52
+ const duplicated = Array.from(stringMap.entries())
53
+ .filter(([, stats]) => stats.count > 1)
54
+ .sort((a, b) => b[1].total_size - a[1].total_size)
55
+ .slice(0, limit)
56
+ .map(([value, stats]) => ({
57
+ value: value.length > 200 ? value.substring(0, 200) + '...' : value,
58
+ count: stats.count,
59
+ total_size: stats.total_size,
60
+ total_size_formatted: formatBytes(stats.total_size),
61
+ example_node_ids: stats.example_node_ids,
62
+ }));
63
+ if (duplicated.length === 0) {
64
+ return textResult('No duplicated strings found.');
65
+ }
66
+ const lines = duplicated.map((d, i) => {
67
+ const val = d.value.length > 80 ? d.value.slice(0, 80) + '...' : d.value;
68
+ const nodeIds = d.example_node_ids.map(id => `@${id}`).join(', ');
69
+ return `${i + 1}. "${val}" x ${d.count} copies, ${d.total_size_formatted} total (nodes: ${nodeIds})`;
70
+ });
71
+ return textResult(`Duplicated strings (${duplicated.length} entries):\n\n${lines.join('\n')}`);
72
+ }
73
+ catch (err) {
74
+ return errorResult(err);
75
+ }
76
+ });
77
+ }
78
+ //# sourceMappingURL=duplicated-strings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duplicated-strings.js","sourceRoot":"","sources":["../../src/tools/duplicated-strings.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 registerEval(server: McpServer): void;
12
+ //# sourceMappingURL=eval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eval.d.ts","sourceRoot":"","sources":["../../src/tools/eval.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AA0BvE,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAwHpD"}