{
  "markdown": "# windbg-mcp\n\n[![CI](https://github.com/glslang/windbg-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/glslang/windbg-mcp/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/glslang/windbg-mcp?utm_source=oss&utm_medium=github&utm_campaign=glslang%2Fwindbg-mcp&labelColor=171717&color=FF570A&label=CodeRabbit+Reviews)](https://coderabbit.ai)\n[![Latest release](https://img.shields.io/badge/release-v0.16.0-blue)](https://github.com/glslang/windbg-mcp/releases/latest)\n[![Platform: Windows x64](https://img.shields.io/badge/platform-Windows%20x64-0078D6)](https://github.com/glslang/windbg-mcp/blob/main/docs/install.md#requirements)\n\nAn [MCP](https://modelcontextprotocol.io) server that exposes **WinDbg/DbgEng** to AI agents\n(Claude Code, Claude Desktop, Cursor, …) over stdio. It drives a live debugger engine for\n**user-mode**, **kernel-mode**, **crash-dump**, and **Time Travel Debugging (TTD)** workflows.\n\nThe low-level engine bindings live in [`dbgscope`](https://github.com/glslang/dbgscope)\n(`src/dbgeng.rs`); this crate adds process-per-session engine supervision and the `rmcp` tool\nsurface on top.\n\n## Documentation\n\nThis file is the map. Each topic is one document, and each document is the whole of that topic.\n\n| | |\n|---|---|\n| [Install and engine setup](docs/install.md) | Requirements, prebuilt binaries, Scoop, and the one-time WinDbg engine copy that TTD replay, `!analyze`, the driver tools and 32-bit .NET SOS need |\n| [Use with an MCP client](docs/mcp-clients.md) | Client config, running the server on another machine (`--listen`), the Claude Code plugin, the MCP registry |\n| [Architecture](docs/architecture.md) | Why a supervisor process and one engine worker per session, what each source file owns, and which MCP revisions are served |\n| [The tool surface](docs/tool-surface.md) | Serving fewer tools with `--tools`, what a typed operand may contain, and how the control-flow and TTD tools behave |\n| [Sessions and session handles](docs/sessions.md) | `session_id` routing, the four-session cap, `interrupt`, progress notifications, and recovering a parked attach |\n| [Kernel connection profiles](docs/kernel-profiles.md) | Keeping a KDNET debug key out of tool arguments and out of the client's transcript |\n| [Structured results](docs/structured-results.md) | Which tools answer with `structuredContent`, what each carries, and the error categories a caller can branch on |\n| [Transactional batches](docs/debug-batch.md) | `debug_batch`: a mutating sequence whose cleanup runs on every path, including a timeout or a disconnect |\n| [Walking a structure](docs/walk-memory.md) | `walk_memory`: lists, arrays and chains where an unreadable node is a row rather than the end of the walk |\n| [Session transcripts](docs/transcripts.md) | `WINDBG_MCP_TRANSCRIPT`: a JSONL record of every call, what is redacted, and rendering one as an asciicast |\n| [Limitations & notes](docs/limitations.md) | The honest edges — TTD is user-mode only, static reachability is best-effort, pool and heap walks need a stopped x64 target |\n| [Walkthroughs](docs/walkthroughs.md) | Worked sessions end to end: crash-dump triage, TTD, a Flare-On solve, driver IOCTL surfaces |\n| [The local-model eval](docs/local-model-eval.md) | A grid of model × tool surface × context window against a verified answer key: what a laptop-sized model can drive, and the two defects it found in this server |\n\nOperator and reference material: [remote listener](docs/remote-listener.md),\n[driving it with ollama](docs/local-model.md), [disassembler coordinates](docs/coordinates.md),\n[smoke test](docs/smoke-test.md), [token budget](docs/token-budget.md),\n[releasing](docs/releasing.md).\n\n## Quick start\n\nWindows x64, with `dbgeng.dll` from `System32` — enough for live user-mode, kernel and crash-dump\nwork. TTD `.run` replay, `!analyze` and the driver tools each need files that engine does not ship;\n[`docs/install.md`](docs/install.md) is the one-time copy.\n\nDownload a prebuilt `windbg-mcp-vX.Y.Z-windows-x64.zip` from a\n[release](https://github.com/glslang/windbg-mcp/releases), or build it:\n\n```pwsh\ncargo build --release\n```\n\nThen point a client at the binary:\n\n```jsonc\n// .mcp.json  (or claude_desktop_config.json under \"mcpServers\")\n{\n  \"mcpServers\": {\n    \"windbg\": {\n      \"command\": \"C:\\\\workspace\\\\windbg-mcp\\\\target\\\\release\\\\windbg-mcp.exe\"\n    }\n  }\n}\n```\n\nThe client and the model do not have to run where DbgEng does: `--listen <addr>` serves the same\ntools over HTTP, one bearer token per client, so a Mac can drive a Windows VM — and the model\nitself can be a local one. [Driving it](#driving-it--hosted-or-local-here-or-on-another-machine)\nbelow has the configurations.\n\n`cargo test` covers the unit tests plus an end-to-end smoke test that drives the built binary over\nstdio; run it after a dependency bump or an MCP spec revision — [`docs/smoke-test.md`](docs/smoke-test.md).\n\n## How it works\n\n**One debug session per process.** dbgeng.dll holds a single debuggee session per process, so this\nserver runs the MCP protocol in a **supervisor** and each open target in its own **engine worker**\nchild process. Two things follow: a session that cannot be unwound — a live-kernel attach waiting on\na guest that never dials in — costs a process rather than the server, and sessions are **concurrent**\n(triage a crash dump while a kernel attach is live, up to four at once).\n\nEvery tool that touches a target takes the `session_id` an opener returned, and that is what routes\nthe call. Omit it and the call goes to the current session. [`docs/architecture.md`](docs/architecture.md)\nhas the process model and the file-by-file breakdown; [`docs/sessions.md`](docs/sessions.md) has the\nhandle rules, the cap, and what to do when a session is stuck.\n\n**A 32-bit target gets a 32-bit worker.** An extension DLL is loaded into the debugger's own\nprocess, so .NET's SOS on a 32-bit target is reachable only from a 32-bit host — which a process\ncannot become after its image has loaded. So the release ships a second build of this same server\nat `x86\\windbg-mcp.exe`, and a 32-bit dump or a WoW64 `attach_process` is opened by that worker\ninstead of by a re-execution of the x64 one. A client cannot tell: one server, one handle, one tool\nsurface. Where that worker or its 32-bit engine is absent the target still opens on the x64 build —\nnative analysis of it works and always has — and says so in the opener's `limitation`.\n\n## Tools\n\nFifty-seven tools in eight `--tools` groups; the rows below split some of those groups by theme. The\n`--tools` column is the name that selects one — see\n[Serving fewer tools](docs/tool-surface.md#serving-fewer-tools---tools).\n\n| Group | `--tools` | Tools |\n|-------|-----------|-------|\n| Session | `session` | `open_dump`, `open_trace`, `attach_kernel_local`, `attach_kernel`, `attach_process`, `launch`, `interrupt`, `end_session`, `session_status` |\n| Server   | `session` | `server_log` — the server's own log: the supervisor's records, plus those of the sessions you opened, tagged with the session each belongs to |\n| State   | `inspect` | `current_location` (instruction pointer, execution context, and PE coordinate), `registers`, `read_memory`, `backtrace` (the stack as typed frames, each carrying `module`+`RVA` where the engine can place it, as well as its symbol), `modules` (`refresh: true` resynchronises the debugger's inventory with the target first — what a fresh kernel attach needs before \"not loaded\" means anything), `threads`, `disassemble` (instructions as records, each with its encoding and, where the engine can place it, its `RVA`), `dx`, `set_symbol_path` |\n| Crash   | `crash` | `crash_triage` — a bug check as fields: code and parameters, crashing process, the stack as `module+RVA`, and the faulting driver frame; `exception_triage` — the user-mode counterpart: the exception record decoded, what kind of fault it is, the thrown C++ object and the HRESULT it carries, and the stack walked from the crash context; `decode_error_reporting` — an HRESULT, NTSTATUS or Win32 error as fields, with the message the system's own tables give it |\n| Control | `exec` | `go`, `step_over`, `step_into`, `set_breakpoint`, `run_to_address` |\n| Async control | `exec` | `continue_async` (resume and return a handle), `wait_for_stop` (collect the stop; running out of the wait is a poll, not a failure), `break_in` |\n| Transaction | `batch` | `debug_batch` — an ordered sequence with assertions and a rollback the engine process runs on every path |\n| TTD nav | `ttd` | `step_back` (`t-`), `step_over_back` (`p-`), `reverse_go` (`g-`), `goto_position` (`!tt`) |\n| TTD analysis | `ttd` | `ttd_calls`, `ttd_memory`, `ttd_events`, `index_trace`, `record_trace` |\n| Driver IOCTL | `ioctl` | `decode_ioctl`, `driver_object`, `device_object`, `irp_stack`, `ioctl_trace`, `reachable_from_dispatch` |\n| Kernel pool | `allocator` | `pool_find_tag`, `pool_chunk`, `pool_census`, `pool_diagnostics` |\n| User Segment Heap | `allocator` | `heap_list`, `heap_allocations`, `heap_chunk`, `heap_census`, `heap_diagnostics` |\n| Structure walk | `allocator` | `walk_memory` |\n| Raw     | `inspect` | `execute` — run any debugger command, returns full text output |\n\nAll of them are served unless you say otherwise, and the definitions cost the model **84,506 bytes —\nabout 21k tokens — before it has asked anything** (measured 2026-09-07). `--tools\nsession,inspect,crash` cuts that to 32,322 B for twenty-three tools, and a `--listen` client can be\ngiven a narrower surface than the run's default. [`docs/tool-surface.md`](docs/tool-surface.md) has the arithmetic, the rule that `session`\nis always included, and what a typed operand may not contain.\n\nMost of the tools also answer with MCP `structuredContent`, so a program can read a field\ninstead of parsing prose, and a failure carries a stable category (`invalid_argument`, `debugger`,\n`timeout`, `stale_session`, …) rather than wording —\n[`docs/structured-results.md`](docs/structured-results.md).\n\n## Walkthroughs\n\nWorked sessions with the real outputs and the gotchas — the long form is in\n[`docs/walkthroughs.md`](docs/walkthroughs.md).\n\n- [Crash-dump triage](docs/crash-dump-walkthrough.md) — a `0x9F DRIVER_POWER_STATE_FAILURE` traced to\n  `nvlddmkm.sys`, and a `0x13A` in a driver with no PDB.\n- [TTD tour](docs/ttd-walkthrough.md) — opening a `.run`, forward/reverse navigation, and counting\n  `printf` calls with symbols.\n- [Flare-On 12 #8](docs/flareauthenticator-ttd-walkthrough.md) — a full TTD → Z3 solve of an\n  obfuscated Qt crackme.\n- [Driver IOCTL surface](docs/driver-ioctl-walkthrough.md) — recovering a dispatch switch on a live\n  KDNET kernel and deciding user-mode reachability.\n- [Explorer won't start](docs/explorer-crash-walkthrough.md) — the server debugging its own host:\n  a dead Windows shell traced through three faults to a malformed State Repository.\n- [Disassembler coordinates](docs/coordinates.md) — joining a `crash_triage` frame to a function in\n  an image fetched on another machine.\n\n## Driving it — hosted or local, here or on another machine\n\nAnything that speaks MCP can hold this server, and **DbgEng is the only part pinned to Windows**.\nSo the model may be a hosted one inside an editor, a local one in ollama, or an ollama cloud model,\nand it does not have to run on the machine being debugged.\n\n| What drives it | Where that runs | The server | Reached over |\n|---|---|---|---|\n| An MCP client — Claude Code, Cursor, Claude Desktop | the Windows machine | launched by the client | stdio |\n| An MCP client | a Mac, or any other machine | a Windows host, `--listen`, usually as a service | HTTP through an ssh forward |\n| A model in **ollama** | the Windows machine | the same machine, `--listen` on loopback | HTTP on loopback |\n| A model in **ollama** | a Mac | a Windows host, `--listen` as a service | HTTP through an ssh forward |\n| A model in **ollama's cloud** | wherever ollama runs | either of the above | unchanged — the tag is all that differs |\n\n**One machine needs no configuration at all**: the client launches the binary and talks to it over\nstdio, which is the Quick start above.\n\n**Two machines need a listener.** For a session you are driving anyway, run it in the foreground on\nthe Windows host — this works wherever the binary happens to live:\n\n```pwsh\n$env:WINDBG_MCP_LISTEN_TOKEN = \"<a long random string>\"   # this shell only\nwindbg-mcp.exe --listen 127.0.0.1:8765\n```\n\n**For anything longer-lived, install it as a service — from a protected directory.** The SCM stores\nthat exact path for a `LocalSystem` auto-start service, so whoever can write the directory, or drop\nan engine DLL beside the exe, gets their code run as SYSTEM at the next start.\n`--install-service` therefore refuses an exe outside `%ProgramFiles%`, `%ProgramFiles(x86)%` or\n`%SystemRoot%` — which a downloaded zip, a Scoop shim or a `target\\release` build all are — so move\nthe **whole** deployment first: the exe, the engine DLLs beside it, and `x86\\`. Then, elevated:\n\n```pwsh\n$env:WINDBG_MCP_LISTEN_TOKEN = \"<a long random string>\"   # this shell only\n& \"$env:ProgramFiles\\windbg-mcp\\windbg-mcp.exe\" --install-service --listen 127.0.0.1:8765\nStart-Service windbg-mcp          # --install-service only *registers* it\n```\n\nOn a machine that is entirely yours, `--allow-unprotected-path` says so out loud and installs in\nplace; that is a development install, not a deployment.\n\nEither way, from the machine you are actually working on, for as long as you want the link:\n\n```console\nssh -N -L 8765:127.0.0.1:8765 <windows-host>\n```\n\nThe listener **refuses to start without a token**, because it serves `execute`, `launch` and\n`debug_batch` — an open port here is arbitrary code on the host holding your kernel debugger. Bind\nloopback and forward over ssh rather than exposing it. Each client authenticates as itself, and can\nbe served a narrower `--tools` surface than the run's default, which is how a local model and an\neditor share one listener without sharing sessions.\n[`docs/remote-listener.md`](docs/remote-listener.md) is the operator's reference.\n\n**Pointing ollama at it is the client's job, not this server's.** An MCP client that drives an\nollama model holds the listener exactly as an editor does — nothing here has to be installed, and\nthis server never learns which kind of model answered. ollama ships integrations for a number of\nthose clients; `ollama launch` lists them, and `ollama launch claude --model <tag>` is one. A local\ntag and an ollama **cloud** tag are the same route, differing only in the model name.\n\n[`docs/local-model.md`](docs/local-model.md) is the runbook: the arrangements, choosing a model that\ncan actually run, which of two clocks a quiet model loses its sessions to, and what a cloud tag can no longer\ntell you about its own run.\n\n## Whether a local model copes — the benchmark\n\nThe tool surface is paid on every turn, so *\"can a model that runs on a laptop actually drive\nthis?\"* is a question about **this server** as much as about the model. The repo ships the\nbenchmark rather than the assertion: `tools/local_model_eval.py` runs a grid of model × tool\nsurface × context window, `tools/bench_listener.ps1` serves all three surfaces from one listener as\nthree separately-budgeted clients, and the answer key is read off the checked-in crash dumps with\nthis server's own tools before any model sees them. Claude is in the grid as the control, not as a\ncompetitor. [`docs/local-model-eval.md`](docs/local-model-eval.md) is the write-up.\n\nRunning the grid yourself needs a **development environment rather than a release**: the release\nzip is `windbg-mcp.exe`, the `x86\\` worker and `LICENSE`, so `tools/` comes from a checkout, and the\ndriver and grader are Python 3. Nothing above this section needs either — driving the server with a\nlocal model is a client's job, and the driver here exists to *measure* that, one task list at a\ntime, with no interactive mode.\n[`agent-sandbox-vm`](https://github.com/glslang/agent-sandbox-vm) is the Hyper-V / Parallels VM\nsetup this project is developed and benchmarked in.\n\n- **The context window was not the binding constraint** — on that bench's runtime, which is the\n  qualifier that matters. A 17,300-token surface answered all six tasks at a *served* 8,192-token\n  window, multi-turn ones carrying 10,000 characters of tool output included. The arithmetic that\n  predicted otherwise is in `docs/local-model.md`, and it was wrong; ask `ollama ps` what your own\n  runtime serves rather than generalising either result.\n- **Cutting 51 tools to 11 costs two of six answers** — the surface that grid measured, before\n  the three asynchronous-execution tools. Most facts here are reachable by more than\n  one route, so a narrow surface keeps the ones that matter.\n- **It measures this server before it measures the model.** Every off-surface tool call the first\n  grid recorded was a name this server had advertised and would then refuse — in the `instructions`\n  sent at connect time, and in the descriptions of the tools it *does* serve. Both are narrowed per\n  client now, and re-running the narrow cells took those calls from 17 to 6, with every\n  server-taught name at zero.\n\n[**The Tool Surface Grid**](https://claude.ai/code/artifact/aad9956d-47f3-450a-a436-1d2b29939a39)\nis the visual write-up — 33 cells, five models, the three axes, and what the two fixes it produced\ndid and did not buy.\n\n## Limitations\n\nThe full list, with what each one means for a workflow, is in\n[`docs/limitations.md`](docs/limitations.md). The four that catch people first:\n\n- **TTD is user-mode only** — a Microsoft limitation, so a kernel target cannot be time-travelled.\n- **One command at a time per session.** Sessions run concurrently, but each is one engine running\n  operations serially: await each result before sending the next call against that session.\n- **Symbol *names* need setup on the debugger host** — `msdia140.dll` beside the binary, a symbol\n  path, and (for TTD) a reload at a stopped position. Without them, address-based queries still work.\n- **The pool and heap walkers need a stopped x64 target.** They decode x64 allocator structures, so\n  a 32-bit target has no `heap_*` tools whichever worker holds it — SOS's own `!dumpheap`/`!eeheap`\n  are the managed equivalent, and reaching those is what the 32-bit worker and its `x86\\` engine\n  payload ([`docs/install.md`](docs/install.md)) are for.\n",
  "bytes": 18555,
  "sha": "0b31b4b5292e64a3c4a352c22392b6f75a5e6a4bfb6d386add21ce6eb625e550",
  "repo_slug": "glslang/windbg-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_glslang_windbg_mcp_413090d7/readme"
}