DEV Community

Cover image for Your Node.js App Is Slow. Your AI Agent Can't Help - Until Now
Albert Alov
Albert Alov

Posted on

Your Node.js App Is Slow. Your AI Agent Can't Help - Until Now

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, ...]
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
  }
]
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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": []
}
Enter fullscreen mode Exit fullscreen mode

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:])
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Claude Desktop (~/.config/claude/claude_desktop_config.json):

{
  "mcpServers": {
    "v8-cpu-profile-decoder-mcp": {
      "command": "npx",
      "args": ["-y", "v8-cpu-profile-decoder-mcp"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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., validateSession calling hashPassword on 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:

Top comments (0)