You've been here before.
The API is dragging. P99 latency is climbing. Someone says "just profile it" — so you run node --cpu-prof and get a file like CPU.20260516.154355.cpuprofile.
You open it. It's 28MB of this:
{
"nodes": [
{ "id": 1482, "callFrame": { "functionName": "processRequest", "scriptId": "94", "url": "file:///app/dist/server.js", "lineNumber": 847, "columnNumber": 12 }, "hitCount": 3241, "children": [1483, 1490, 1501] },
{ "id": 1483, "callFrame": { "functionName": "parseBody", "scriptId": "94", "url": "file:///app/dist/middleware.js", "lineNumber": 23, "columnNumber": 4 }, "hitCount": 0, "children": [1484] },
...
],
"samples": [1482, 1483, 1482, 1490, 1501, 1482, 1483, ...],
"timeDeltas": [0, 118, 97, 124, 89, 112, ...]
}
Thousands of nodes. Millions of samples. Raw memory addresses. Microsecond tick intervals.
You paste it into Claude. The context window collapses. It hallucinates an answer. You're back to square one.
That's the problem this MCP solves.
Why AI Agents Are Blind to CPU Profiles
A .cpuprofile isn't just "a big file." It's a specific format that requires real computation to be useful:
-
Exclusive time (self time) = how long a function ran on its own, without counting callees =
hitCount × avg(timeDeltas) - Inclusive time = self time + all descendant inclusive times = requires a recursive DFS over the call tree
- Hot path = which chain of callers led to the bottleneck = requires building a reverse parent map
An LLM can't do any of this. It can't execute algorithms. It can't traverse a tree across 50,000 nodes. Even if you somehow got the file into context, it would just pattern-match on function names and guess.
The agent needs a bridge — something that runs the math locally and hands back a 10-line summary.
Introducing v8-cpu-profile-decoder-mcp
An MCP server that decodes V8 CPU profiles into token-efficient bottleneck summaries for AI agents.
npx v8-cpu-profile-decoder-mcp
Three tools. Each one answers a specific question the agent would otherwise be blind to.
Tool 1: extract_hottest_functions
"Which function is burning the most CPU?"
{
"profile_path": "/app/profiles/CPU.20260516.cpuprofile",
"top_n": 5,
"min_self_percent": 1.0
}
Response:
[
{
"rank": 1,
"functionName": "hashPassword",
"url": "file:///app/dist/auth/crypto.js",
"lineNumber": 42,
"selfTimeMs": 1842.5,
"totalTimeMs": 1842.5,
"selfPercent": 61.32,
"hitCount": 3241
},
{
"rank": 2,
"functionName": "parseJsonBody",
"url": "file:///app/dist/middleware/body.js",
"lineNumber": 18,
"selfTimeMs": 412.1,
"selfPercent": 13.71,
"hitCount": 724
}
]
61% of all CPU time in one function. The agent now knows exactly where to look — without ever reading the raw profile.
V8 internals ((program), (garbage collector), (idle)) are filtered out by default. You get user code only.
Tool 2: analyze_call_tree_path
"What's calling my slow function, and how often?"
{
"profile_path": "/app/profiles/CPU.20260516.cpuprofile",
"function_name": "hashPassword",
"top_callers": 3
}
Response:
{
"targetFunction": "hashPassword",
"matchedNodes": 2,
"totalSelfTimeMs": 1842.5,
"totalPercent": 61.32,
"callers": [
{
"functionName": "loginHandler",
"url": "file:///app/dist/routes/auth.js",
"lineNumber": 94,
"sampleCount": 2180,
"attributedTimeMs": 1235.4
},
{
"functionName": "validateSession",
"url": "file:///app/dist/middleware/auth.js",
"lineNumber": 31,
"sampleCount": 1061,
"attributedTimeMs": 607.1
}
]
}
Now the agent knows the full picture: hashPassword is slow, loginHandler is responsible for 67% of those calls, and validateSession is calling it unnecessarily on every request.
Function name matching is partial and case-insensitive — you don't need to know the exact internal name.
Tool 3: correlate_source_code
"Which TypeScript file and line is the bottleneck actually from?"
After TypeScript compilation, your profile shows dist/auth/crypto.js:42. That's not where you write code. This tool reads the .js.map source maps and resolves back to the original TypeScript:
{
"resolved": [
{
"rank": 1,
"generatedUrl": "file:///app/dist/auth/crypto.js",
"generatedLine": 42,
"source": {
"originalFile": "src/auth/crypto.ts",
"originalLine": 38,
"originalColumn": 2,
"originalFunction": "hashPassword"
},
"selfTimeMs": 1842.5,
"selfPercent": 61.32
}
],
"sourcemapErrors": []
}
The agent can now open src/auth/crypto.ts at line 38 and fix the actual source — not a compiled artifact.
Falls back gracefully to compiled JS locations if no .map file is found.
How the Algorithm Works
Three steps happen locally before anything reaches the agent:
1. Build the node map
Parse the nodes array into Map<id, node> for O(1) lookup.
2. Compute exclusive time
selfTimeMs = hitCount × avg(timeDeltas[1:])
Note: timeDeltas[0] is always 0 per V8 spec (offset from startTime), so we skip it.
3. Compute inclusive time via DFS with memoization
inclusiveTime(node) = selfTime(node) + Σ inclusiveTime(child)
Cached to avoid recomputation on shared subtrees.
The raw profile might have 50,000 nodes and 200,000 samples. What comes back to the agent is 10 ranked objects. That's the token compression that makes this useful.
Setup
npm install -g v8-cpu-profile-decoder-mcp
Claude Desktop (~/.config/claude/claude_desktop_config.json):
{
"mcpServers": {
"v8-cpu-profile-decoder-mcp": {
"command": "npx",
"args": ["-y", "v8-cpu-profile-decoder-mcp"]
}
}
}
Generate a profile:
node --cpu-prof --cpu-prof-dir ./profiles your-script.js
# or for a running server: kill -USR1 <pid> (with --cpu-prof flag)
Then ask your agent:
"Profile is at
./profiles/CPU.cpuprofile— find the bottleneck and suggest a fix"
What the Agent Can Do With This
With a decoded profile in context, an AI agent can:
- Identify the hot function and read its source code
- Trace who's calling it and how many times
- Suggest memoization, caching, or algorithmic improvements
- Spot unexpected callers (e.g.,
validateSessioncallinghashPasswordon every request) - Generate a PR with the fix
Without this MCP, it's guessing from static code. With it, it's working from measured runtime data.
Links
More in this series:
- playwright-trace-decoder-mcp — same pattern for Playwright CI failures
- playwright-spatial-layout-mcp — geometric vision for browser layouts
Top comments (0)