Skip to content

Commit 007cd37

Browse files
JacksonGLmeta-codesync[bot]
authored andcommitted
feat(mcp-server): Add @memlab/mcp-server package
Summary: Add a new `memlab/mcp-server` package to the memlab monorepo that provides an MCP (Model Context Protocol) server for heap snapshot analysis. This gives AI coding assistants like Claude Code tools to explore JavaScript heap snapshots, find memory leaks, and identify optimization opportunities. The package includes 23 tools covering: - Snapshot loading and summarization - Object inspection and graph traversal (references, referrers, retainer traces) - Memory leak detection (detached DOM, stale collections, duplicated strings) - Advanced analysis (dominator trees, closure inspection, class histograms) - Programmable queries (eval, for-each, aggregate, search) The package is ESM-based (`"type": "module"`) with its own standalone tsconfig (not extending tsconfig.base.json which is CJS). It can be used via: - `npx memlab/mcp-server` (zero-install) - `npm install -g memlab/mcp-server` (global install) - Building from source Also updates the internal memlab Claude Code plugin's dependency versions from ^2.0.0 to ^2.0.1 to stay in sync. Differential Revision: D97688039 fbshipit-source-id: 78e088074430b27893e7e07fc8bdccc813a78c2e
1 parent 5e285cf commit 007cd37

80 files changed

Lines changed: 7084 additions & 3047 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"./packages/heap-analysis",
5353
"./packages/api",
5454
"./packages/cli",
55-
"./packages/memlab"
55+
"./packages/memlab",
56+
"./packages/mcp-server"
5657
]
5758
}

packages/core/src/lib/Config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,7 @@ export class MemLabConfig {
735735
}
736736

737737
get browser(): string {
738-
return this._browser || 'google-chrome';
738+
return this._browser || 'chrome';
739739
}
740740

741741
set isHeadfulBrowser(isHeadful: boolean) {

packages/core/src/lib/Constant.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const constants = {
5555

5656
Object.assign(constants.supportedBrowsers, {
5757
chromium: 'chrome',
58-
chrome: 'google-chrome',
58+
chrome: 'chrome',
5959
});
6060

6161
export type Constants = typeof constants;

packages/lens/dist/memlens.lib.bundle.js

Lines changed: 864 additions & 864 deletions
Large diffs are not rendered by default.

packages/lens/dist/memlens.lib.bundle.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/lens/dist/memlens.run.bundle.js

Lines changed: 2043 additions & 2043 deletions
Large diffs are not rendered by default.

packages/lens/dist/memlens.run.bundle.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/mcp-server/README.md

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
# @memlab/mcp-server
2+
3+
An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that wraps [MemLab](https://facebook.github.io/memlab/)'s heap analysis APIs, giving AI coding assistants (Claude Code, Cursor, etc.) interactive tools to explore JavaScript heap snapshots, find memory leaks, and identify optimization opportunities.
4+
5+
## Quick Start
6+
7+
### Option 1: npx (no install)
8+
9+
Add to your Claude Code MCP config (`~/.claude.json` or `.mcp.json`):
10+
11+
```json
12+
{
13+
"mcpServers": {
14+
"memlab": {
15+
"type": "stdio",
16+
"command": "npx",
17+
"args": ["@memlab/mcp-server"]
18+
}
19+
}
20+
}
21+
```
22+
23+
### Option 2: Global install
24+
25+
```bash
26+
npm install -g @memlab/mcp-server
27+
```
28+
29+
Then configure:
30+
31+
```json
32+
{
33+
"mcpServers": {
34+
"memlab": {
35+
"type": "stdio",
36+
"command": "memlab-mcp"
37+
}
38+
}
39+
}
40+
```
41+
42+
### Option 3: From source
43+
44+
```bash
45+
git clone https://github.com/facebook/memlab.git
46+
cd memlab
47+
npm install
48+
npm run build
49+
```
50+
51+
Then configure:
52+
53+
```json
54+
{
55+
"mcpServers": {
56+
"memlab": {
57+
"type": "stdio",
58+
"command": "node",
59+
"args": ["/path/to/memlab/packages/mcp/dist/index.js"]
60+
}
61+
}
62+
}
63+
```
64+
65+
## How It Works
66+
67+
The server holds a loaded `IHeapSnapshot` in memory across tool calls (loading is expensive for large heaps). Only one snapshot can be loaded at a time. All tools are read-only — they analyze the heap but never modify it.
68+
69+
## Getting a Heap Snapshot
70+
71+
### Chrome DevTools
72+
73+
1. Open DevTools (F12) > Memory tab
74+
2. Select "Heap snapshot" and click "Take snapshot"
75+
3. Right-click the snapshot in the left panel > "Save..."
76+
4. Save the `.heapsnapshot` file
77+
78+
### Node.js
79+
80+
```js
81+
const v8 = require('v8');
82+
const snapshot = v8.writeHeapSnapshot();
83+
console.log(`Heap snapshot written to ${snapshot}`);
84+
```
85+
86+
## Tools Reference
87+
88+
### `memlab_load_snapshot`
89+
90+
Load and parse a `.heapsnapshot` file. Builds indexes, computes the dominator tree, and calculates retained sizes.
91+
92+
```
93+
Input: { file_path: "/path/to/snapshot.heapsnapshot" }
94+
Output: { status, file_path, node_count, edge_count, total_size }
95+
```
96+
97+
### `memlab_snapshot_summary`
98+
99+
Overview stats: total nodes/edges, total size, breakdown by node type with dominator-aware aggregate retained sizes.
100+
101+
### `memlab_largest_objects`
102+
103+
Top N objects by retained size, filtering out internal/meta nodes.
104+
105+
```
106+
Input: { limit?: 20 }
107+
```
108+
109+
### `memlab_get_node`
110+
111+
Look up a single node by numeric ID with full details (size, type, detachment status, dominator, location, string value).
112+
113+
```
114+
Input: { node_id: 12345 }
115+
```
116+
117+
### `memlab_find_nodes_by_class`
118+
119+
Find all objects with a given constructor/class name, sorted by retained size.
120+
121+
```
122+
Input: { class_name: "FiberNode", limit?: 20 }
123+
```
124+
125+
### `memlab_get_references`
126+
127+
Outgoing edges from a node (what it points to), sorted by target retained size.
128+
129+
```
130+
Input: { node_id: 12345, limit?: 30 }
131+
```
132+
133+
### `memlab_get_referrers`
134+
135+
Incoming edges to a node (what points to it), sorted by source retained size.
136+
137+
```
138+
Input: { node_id: 12345, limit?: 30 }
139+
```
140+
141+
### `memlab_retainer_trace`
142+
143+
Shortest path from a GC root to a node. Shows why the object is retained in memory.
144+
145+
```
146+
Input: { node_id: 12345 }
147+
```
148+
149+
### `memlab_detached_dom`
150+
151+
Find detached DOM elements still retained in memory (common memory leak source). Supports count-only and ids-only modes for large result sets.
152+
153+
```
154+
Input: { output_mode?: "full"|"count"|"ids", limit?: 20 }
155+
```
156+
157+
### `memlab_duplicated_strings`
158+
159+
Find duplicated string instances ranked by total retained size.
160+
161+
```
162+
Input: { limit?: 15 }
163+
```
164+
165+
### `memlab_stale_collections`
166+
167+
Find Map/Set/Array collections holding detached DOM or unmounted Fiber nodes.
168+
169+
```
170+
Input: { limit?: 15 }
171+
```
172+
173+
### `memlab_global_variables`
174+
175+
Non-built-in global variables on the Window object, sorted by retained size.
176+
177+
```
178+
Input: { limit?: 20 }
179+
```
180+
181+
### `memlab_search_nodes`
182+
183+
General-purpose search combining filters: name pattern (regex), node type, size thresholds, detachment status.
184+
185+
```
186+
Input: { name_pattern?: "Regex", type?: "object", min_retained_size?: 1000000, limit?: 20 }
187+
```
188+
189+
### `memlab_get_property`
190+
191+
Look up a specific property of a node by name and return the target node with full details.
192+
193+
```
194+
Input: { node_id: 12345, property_name: "stateNode" }
195+
```
196+
197+
### `memlab_object_shape`
198+
199+
Show all named properties of an object with target types and sizes.
200+
201+
```
202+
Input: { node_id: 12345, include_internal?: false, limit?: 50 }
203+
```
204+
205+
### `memlab_class_histogram`
206+
207+
Instance count and total retained size per constructor name, sorted by aggregate retained size (dominator-aware). The Chrome DevTools "Summary" view equivalent.
208+
209+
```
210+
Input: { limit?: 30, min_count?: 1, node_type?: "object" }
211+
```
212+
213+
### `memlab_dominator_subtree`
214+
215+
Show objects dominated by a given node — what would be freed if it were garbage collected.
216+
217+
```
218+
Input: { node_id: 12345, limit?: 20 }
219+
```
220+
221+
### `memlab_closure_inspection`
222+
223+
Inspect a closure's captured variables, source location, and scope context. Critical for diagnosing closure-based memory leaks.
224+
225+
```
226+
Input: { node_id: 12345 }
227+
```
228+
229+
### `memlab_find_by_property`
230+
231+
Find all objects that have a specific property name. Useful for React internals (`__reactFiber$`), custom markers, or framework-specific patterns.
232+
233+
```
234+
Input: { property_name: "__reactFiber$", limit?: 20 }
235+
```
236+
237+
### `memlab_aggregate`
238+
239+
Aggregate heap nodes by type, name, or name prefix. Returns grouped statistics with dominator-aware retained sizes (no double-counting).
240+
241+
```
242+
Input: { group_by: "type"|"name"|"name_prefix", name_pattern?: "...", limit?: 30 }
243+
```
244+
245+
### `memlab_reports`
246+
247+
Run curated memory analysis reports. Use `"list"` to see available reports, pick one by name, or use `"full_analysis"` to run all reports for comprehensive triage.
248+
249+
```
250+
Input: { report: "list"|"full_analysis"|"detached_dom"|"duplicated_strings"|..., limit?: 10 }
251+
```
252+
253+
### `memlab_eval`
254+
255+
Execute arbitrary JavaScript against the loaded heap snapshot in a sandboxed VM. Has access to `snapshot`, `utils`, and `helpers` but no filesystem/network access.
256+
257+
```
258+
Input: { code: "...", timeout_ms?: 30000 }
259+
```
260+
261+
### `memlab_for_each`
262+
263+
Structured map/filter/reduce over all heap nodes with code predicates.
264+
265+
```
266+
Input: { filter_code: "node.type === 'closure'", map_code?: "...", reduce_code?: "...", limit?: 100 }
267+
```
268+
269+
## Example Workflow
270+
271+
A typical memory investigation:
272+
273+
1. **Load the snapshot**: "Load the heap snapshot at /tmp/my-app.heapsnapshot"
274+
2. **Get an overview**: "Show me a summary of the heap"
275+
3. **Find the biggest objects**: "What are the largest objects by retained size?"
276+
4. **Investigate a specific object**: "Show me the retainer trace for node 48231"
277+
5. **Check for common leak patterns**:
278+
- "Are there any detached DOM nodes?"
279+
- "Show me duplicated strings"
280+
- "Are any collections holding stale objects?"
281+
6. **Drill into references**: "What does node 48231 reference?"
282+
283+
## License
284+
285+
MIT
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/usr/bin/env node
2+
3+
import '../dist/index.js';

packages/mcp-server/package.json

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"name": "@memlab/mcp-server",
3+
"version": "2.0.1",
4+
"license": "MIT",
5+
"description": "MCP server for MemLab heap snapshot analysis — gives AI coding assistants tools to explore JavaScript heap snapshots, find memory leaks, and identify optimization opportunities",
6+
"author": "Liang Gong <lgong@meta.com>",
7+
"contributors": [],
8+
"keywords": [
9+
"mcp",
10+
"memlab",
11+
"memory",
12+
"leak",
13+
"heap",
14+
"snapshot",
15+
"analysis",
16+
"claude",
17+
"ai"
18+
],
19+
"type": "module",
20+
"main": "dist/index.js",
21+
"types": "dist/index.d.ts",
22+
"bin": {
23+
"memlab-mcp": "./bin/memlab-mcp.js"
24+
},
25+
"files": [
26+
"dist",
27+
"bin",
28+
"LICENSE"
29+
],
30+
"engines": {
31+
"node": ">= 18.0.0"
32+
},
33+
"dependencies": {
34+
"@memlab/core": "^2.0.1",
35+
"@memlab/heap-analysis": "^2.0.1",
36+
"@modelcontextprotocol/sdk": "~1.12.1",
37+
"zod": "~3.23.8"
38+
},
39+
"devDependencies": {
40+
"@types/node": "^22.15.3",
41+
"typescript": "^5.8.3"
42+
},
43+
"repository": {
44+
"type": "git",
45+
"url": "git+https://github.com/facebook/memlab.git",
46+
"directory": "packages/mcp-server"
47+
},
48+
"scripts": {
49+
"build-pkg": "tsc",
50+
"test-pkg": "echo 'no tests yet'",
51+
"publish-patch": "npm publish",
52+
"clean-pkg": "rm -rf ./dist && rm -rf ./node_modules && rm -f ./tsconfig.tsbuildinfo"
53+
},
54+
"bugs": {
55+
"url": "https://github.com/facebook/memlab/issues"
56+
},
57+
"homepage": "https://github.com/facebook/memlab#readme"
58+
}

0 commit comments

Comments
 (0)