node-runtime
# node-runtime-mcp
An MCP server that gives Claude Code a debugger for a live Node.js process. Instead of reasoning from static source back to what *probably* happened, Claude can set a breakpoint, run to it, read the locals, evaluate an expression in that frame, step, and read what the process printed — the same loop a developer runs in DevTools, exposed as tools.
It speaks the V8 Inspector protocol directly (the same one Chrome DevTools and VS Code use), so it works with any Node ≥ 18 script, CommonJS or ESM, with no instrumentation added to your code.
## Tools
| Tool | What it does |
|---|---|
| `launch` | Start `node <script> [args]` under the debugger. Paused at line 1 while breakpoints are applied, then runs (or stays paused with `stop_on_entry`). |
| `attach` | Attach to a process already started with `node --inspect` / `--inspect-brk` (default `127.0.0.1:9229`). |
| `disconnect` | Kill a launched process, or detach from an attached one. |
| `status` | What's attached, paused/running, current stack. |
| `set_breakpoint` | `file:line`, optional JS condition. Resolves lazily if the file hasn't loaded yet. |
| `remove_breakpoint`, `list_breakpoints` | Manage breakpoints; hit counts included. Breakpoints survive relaunches. |
| `set_exception_breakpoints` | `none` / `uncaught` (default) / `all`. |
| `continue`, `step_over`, `step_into`, `step_out` | Each returns the next pause location, or `running` after `wait_ms`. |
| `pause` | Interrupt a running process. |
| `wait_for_pause` | Block until the next breakpoint/exception — e.g. after you fire an HTTP request at the app. |
| `get_stack` | Full call stack with frame indices. |
| `get_scopes` | Every variable visible in a frame: local, closure, block, module, `this`. |
| `evaluate` | Any JS expression, in a paused frame or (while running) in the global scope with `require` available. Side effects allowed. |
| `get_properties` | Expand an object by the `⟨ref …⟩` shown in `get_scopes` / `evaluate` output. |
| `get_output` | stdout, stderr, console.* and uncaught exceptions, with seq numbers and grep. |
| `list_scripts` | Source files the process has loaded (handy for finding the right breakpoint path). |
Line numbers are 1-based everywhere. Paths resolve against the launch `cwd`.
## Install
Requires Node 18+ and git. The plugin installs its own npm dependencies and builds itself the first time it starts (one-time, ~20 s), so there is no manual build step.
### As a Claude Code plugin
Through the `keith-tools` marketplace, which also carries [jec-index](https://github.com/keiththornton/jec-index) and [deliberate](https://github.com/keiththornton/deliberate):
```bash
claude plugin marketplace add keiththornton/claude-plugins
claude plugin install node-runtime@keith-tools --scope user # or --scope project
```
Or straight from this repository, which is its own single-plugin marketplace:
```bash
claude plugin marketplace add keiththornton/node-runtime-mcp
claude plugin install node-runtime@node-runtime --scope user
```
Update later with `claude plugin update node-runtime@keith-tools`.
### Manual setup (plain MCP server)
```bash
git clone https://github.com/keiththornton/node-runtime-mcp.git ~/Code/node-runtime-mcp
claude mcp add --scope user node-runtime -- bash ~/Code/node-runtime-mcp/bin/launch.sh
```
Drop `--scope user` to register it for the current project only, or commit a `.mcp.json` in the project:
```json
{
"mcpServers": {
"node-runtime": {
"command": "bash",
"args": ["/absolute/path/to/node-runtime-mcp/bin/launch.sh"]
}
}
}
```
`bin/launch.sh` runs `npm ci` (which builds `dist/`) if either is missing, then starts `node dist/index.js`. For development: `npm install && npm run build && npm test` (the tests debug the programs in `examples/`).
The server's working directory is where Claude Code is running, so relative script and breakpoint paths resolve against your project.
## What a session looks like
> *"The `/users/:id` endpoint returns the wrong total for VIP carts. Find out why."*
Claude, with the tools:
1. `launch` `src/server.js` with `env: {PORT: "3001"}`.
2. `set_breakpoint` `src/pricing.js:24` with condition `cart.tier === "vip"`.
3. Fires a request with its own HTTP tool, then `wait_for_pause`.
4. `get_scopes` → sees `sub = 160, discount = 0.1, tax = undefined`.
5. `step_over` twice, `evaluate` `tax` → `11.52`. The discount is applied before tax rather than after.
6. `continue`, `get_output` to confirm the printed total, `disconnect`, then edits the code.
## Notes and limitations
- **TypeScript / source maps.** v1 does not translate source maps: breakpoints and stack frames use the file and line numbers V8 actually executes. For pre-compiled output, set breakpoints in the emitted `.js`. For in-process runners (Node's own `--experimental-strip-types`, `tsx`, `ts-node`), pass the flags via `node_args` (e.g. `["--experimental-strip-types"]` or `["--import", "tsx"]`); the script URL stays the `.ts` path and you set breakpoints there. Node's type stripping preserves line numbers exactly (tested in `test/ts_smoke.mjs`); `tsx`/esbuild mostly do, but it isn't guaranteed. Proper source-map translation is the obvious next feature.
- **Idle processes.** `pause` on a process that is waiting on I/O has nothing to pause; it lands at the next JS execution, which may be a Node-internal timer. The tool says so when every frame is internal.
- **One session at a time.** Launching or attaching tears down the previous session. Breakpoints are remembered across sessions so relaunch → hit the same breakpoint works.
- **Node internals are ignore-listed** (`node:*`) so steps and pauses land in your code. Add your own patterns with the `NODE_RUNTIME_MCP_BLACKBOX` env var (a regex), e.g. `node_modules`.
- **Worker threads** have their own inspector targets and aren't covered.
- **Debugging the protocol.** Set `NODE_RUNTIME_MCP_DEBUG=1` to trace every inspector message on stderr.
## Roadmap
- Source-map aware breakpoints and stack frames.
- Logpoints (print an expression at a line without pausing).
- HTTP request/response tap and structured log tail.
- Python adapter via debugpy behind the same tool surface.
## Layout
```
src/inspector.ts WebSocket client for the Inspector protocol
src/session.ts launch/attach, breakpoints, execution control, inspection
src/format.ts renders RemoteObjects into short readable strings
src/index.ts MCP server: tool definitions
examples/ small programs the tests debug
test/ end-to-end tests driven through the MCP client SDK
```
TDQS
Scored across 20 tools
Each tool targets a distinct debugger action: session lifecycle (launch/attach/disconnect/status), inspection (get_stack/get_scopes/get_properties/get_output/evaluate), breakpoints (set/remove/list/set_exception_breakpoints), and execution control (continue/pause/step_over/step_into/step_out/wait_for_pause). Overlaps are minimal and handled by clear scoping (e.g. evaluate vs get_properties, continue vs wait_for_pause). An agent can reliably pick the right tool.
Naming is largely readable and predictable, with verb_noun forms for most tools (get_stack, set_breakpoint, remove_breakpoint, list_breakpoints, step_over, wait_for_pause). Deviations are single-action verbs (launch, attach, disconnect, continue, pause, evaluate, status), which are conventional in debugger APIs but technically mixed with the verb_noun pattern.
At 20 tools this is on the heavier side for a typical MCP server, but the Node.js debugging domain genuinely requires lifecycle, inspection, breakpoint, and stepping primitives. Each tool appears to earn its place, with no obvious redundant entries, so the count is reasonable though slightly over the ideal 3-15 range.
Core debugger workflows are well covered: launch/attach, session teardown, breakpoint management including conditional and exception breakpoints, stack/scope/property inspection, evaluation, output capture, and full stepping controls. Minor gaps exist, such as no explicit tool to read a loaded script's source (list_scripts only enumerates paths) and no bulk breakpoint removal, but these are workable.