15 Commits

Author SHA1 Message Date
Sven Scharmentke b2f8b36f7a fix(cdb): open a remote session whose target is running (#145) 2026-10-10 17:51:32 +02:00
Sven Scharmentke 4cce8b1d65 build: add a standalone mcp-windbg.exe build (#143)
Freezes the server into a single self-contained `mcp-windbg.exe` with
PyInstaller,
for users who want nothing to do with a Python toolchain. `cdb.exe`
stays a
prerequisite.

**What the build does.** `scripts/Build-Exe.ps1` builds it, `-Verify`
runs the
scenarios against it, and `MCP_WINDBG_SERVER_EXE` re-points the e2e
harness at a
built binary so the whole suite runs with the same assertions and a
different
launcher. A new `exe` job does both on every PR.

**Signed release asset.** The exe is signed and attached to each release
with a
`SHA256SUMS` taken from the signed binary, since signing rewrites the PE
and a
digest from before it would match nothing anyone can download. The
signature is
verified before the asset is attached.

**Identity.** Version resource generated from `pyproject.toml`, plus an
icon and a
`Comments` field stating the binary is signed by Liasoft GmbH, which
sponsors the
certificate. Windows shows the certificate subject as the verified
publisher, so
without that note the author and the signer read as two unrelated names.

**Why signing uses a self-hosted runner.** The signing service requires
one. Two
rules keep that safe on a public repository, and both must hold if the
job is ever
changed: it stays out of reach of `pull_request`, `pull_request_target`
and
`issue_comment`, which would run a fork's code on it, and it never
checks out the
repository. It declares `permissions: {}`, takes its endpoint from a
secret, and
waits for approval in a `signing` environment.

**Verified.** 29 scenarios pass against the binary (2 kernel skips, no
target).
Signing exercised end to end with a rehearsal run, which builds, signs
and
attaches while publishing nothing: `osslsigncode verify` reports
`Succeeded`,
signer `O=Liasoft GmbH`, RFC3161 countersigned.

**Not yet exercised.** Nothing launches the signed binary, so a
post-sign smoke
step is worth adding.

Out of scope: winget.
2026-10-10 16:47:04 +02:00
Sven Scharmentke a376c7c4ce feat: open kernel crash dumps with open_kd_dump (#120)
Adds open_kd_dump, which opens a kernel crash dump with kd.exe -z as a kd session.

Closes #111
2026-10-01 01:31:17 +02:00
Ramana Reddy Polaka f998e614c9 feat: run init commands on every new session (#121)
## What this changes

Adds a way to run debugger commands automatically when a session opens,
for example to `.load`
a debugger extension.

- `--init-command COMMAND` (repeatable), or `MCP_WINDBG_INIT_COMMANDS`
with one command per line
when no flag is given. These run on every `open_*` tool (dump, remote,
kernel), right after the
session opens and before triage. An extension loaded with `.load` is
therefore available to
  `!analyze -v` and every later command.
- `--kernel-init-command COMMAND` (repeatable), or
`MCP_WINDBG_KERNEL_INIT_COMMANDS`, for commands
that only apply to kernel targets. They run after the shared commands,
on `open_kd_session`
and on dumps whose header is a kernel dump (`PAGEDUMP`/`PAGEDU64`), even
when opened with
`open_cdb_dump`. User-mode dumps (`MDMP`) and `open_cdb_remote` skip
them.
- Output from both lists leads the open result under `###
Initialization`, each command echoed
  as `> command`.
- Docs: new "Init commands" section and two option rows in
`docs/reference/cli.md`; CHANGELOG
  entry under Unreleased.

## Why

In-house and third-party debugger extensions (an OS-version helper,
driver-specific dumpers,
`.loadby sos clr` for .NET) have to be loaded by hand in every session,
and the model does not
know to do it. There was no hook for this: the server reads no
environment configuration, and
`additional_args` is not exposed on the CLI.

Some of these commands only make sense on a kernel target. For example,
reading the Windows
edition and LCU from the kernel just prints an error on a user-mode
minidump, which carries no
such data. So kernel-only commands get their own list. The server
decides by dump header rather
than by which tool opened the dump, because kernel dumps are often
opened with `open_cdb_dump`.

## How it was verified

Through the real MCP server over stdio (WinDbg 1.2606.22001.0),
configured as it would be in an
MCP client's `env` block:
`MCP_WINDBG_INIT_COMMANDS=".load <ext>.dll"`,
`MCP_WINDBG_KERNEL_INIT_COMMANDS="!winver"`.

| Dump | Initialization output |
|---|---|
| 66 GB complete kernel dump (`MEMORY.DMP`, `PAGEDU64`), via
`open_cdb_dump` | `.load` then `!winver`, which printed `Windows:
Windows 10 Enterprise ... build 28020, LCU 10.0.28000.2818` |
| `DemoCrash1.exe.7088.dmp` (user-mode, `MDMP`) | `.load` only;
`!winver` skipped |

On the kernel dump, the extension stayed loaded: `!winver` via
`run_cdb_command` later in the same
session worked. Section order was Initialization, then Crash
Information, then Crash Analysis.

Tests:
- New `init_commands.yaml` scenario against real cdb: shared commands
run before
Crash Information, and a `--kernel-init-command` is absent on a
user-mode dump.
- New hermetic tests in `test_init_commands.py`:
  - shared commands run before triage on all three `open_*` tools
  - no `Initialization` section when nothing is configured
- kernel-only commands run on `open_kd_session` and on
`PAGEDU64`/`PAGEDUMP` dumps, and not on
    `MDMP` dumps or `open_cdb_remote`
  - an unreadable dump is treated as user mode
- both environment variables are parsed one command per line, and CLI
flags take precedence
- Full suite: `uv run pytest src/mcp_windbg/tests/` gives 147 passed, 2
skipped (the live
kernel-target scenarios; no `MCP_WINDBG_KERNEL_CONNECTION` target was
configured).
- `pwsh scripts/Format-Docs.ps1 -Check` and `mkdocs build --strict` are
clean.

Note: if #120 (`open_kd_dump`) merges first, its handler needs the same
one-line call,
`results.extend(_init_sections(session, _init_for(kernel=True),
effective))`. I can rebase
to add it.

---------

Co-authored-by: Sven Scharmentke <sven.scharmentke@liasoft.de>
2026-10-01 00:02:45 +02:00
Oscar c8e2355696 fix(debugger): preserve output on command timeout (#114)
Fixes #112.

## What this changes

- Preserve the debugger lines already read when a marked CDB command
times out.
- Bound timeout diagnostics to 2,000 lines and 64 KiB, with a truncation
note.
- Document the preserved output in the timeout troubleshooting guide and
changelog.

## Why

The reader kept output in a private buffer until the completion marker
arrived. A runaway
SOS command such as `!clrstack` could therefore print useful frames and
then lose all of
them when the timeout path fired. The timeout error now includes that
partial output and
also exposes it on `DebuggerError.partial_output`; marker
synchronization and the existing
manual break-in warning are unchanged.

## How it was verified

- The new timeout regression fails on `35b6cdb` and passes with the fix;
the same test
  asserts the preserved line and the bounded diagnostic behavior.
- `test_debug_session.py`: 66 passed.
- Hermetic suite: 103 passed, 12 deselected; full suite: 103 passed, 12
skipped because
  this host has no CDB/KD or Git LFS debugger dumps.
- `uv lock --check`, MCP schema validation, docs formatter,
`compileall`, `uv build`,
  `twine check`, and `git diff --check` passed.

Co-authored-by: Oscar <319719771+Oscar-Williams@users.noreply.github.com>
Co-authored-by: Sven Scharmentke <sven.scharmentke@liasoft.de>
2026-09-29 00:39:47 +02:00
Sven Scharmentke 7c55ac325f feat(plugin): package skills and agents as separate plugins (#106)
Separate the MCP server, four debugging skills, and crash-analyst agent into independently installable plugins. Document installation and migration, support existing MCP connections, and validate every marketplace version.
2026-09-10 01:24:35 +02:00
Sven Scharmentke d3ddd90588 feat(plugin): name the marketplace entry by how it ships (#97)
Installing becomes:

```
/plugin marketplace add svnscha/mcp-windbg
/plugin install mcp-windbg-uvx@mcp-windbg
```

This frees the plain `mcp-windbg` name for a self-contained build and
makes room for `mcp-windbg-pipx` beside it. Doing it **before** 1.2.0
rather than after means there is no rename migration to run: the old
name was never published.

## The syntax is plugin@marketplace, not the reverse

Worth stating since it drove the shape here. `claude plugin install
--help`:

> use plugin@marketplace for specific marketplace

So `mcp-windbg@uvx` would mean "plugin `mcp-windbg` from a marketplace
called `uvx`" and would need three separate marketplaces. The variant
has to live in the plugin name, which is what this does.

## The two names do different jobs

`plugin.json`s `name` deliberately stays `mcp-windbg`. I verified the
split by installing a plugin whose two names differed on purpose:

| Name | Governs | Observed |
| :--- | :------ | :------- |
| marketplace entry | install identity, plugin cache directory |
`cache/probe-mp/mcp-windbg-uvx/` |
| `plugin.json` `name` | skill and MCP tool scoping |
`plugin:mcp-windbg:mcp-windbg` |

and a headless session confirmed the skills:

```
/mcp-windbg:analyze-dump
/mcp-windbg:debug-remote
/mcp-windbg:kernel-debug
/mcp-windbg:windbg-doctor
```

That is the good outcome. Every future variant keeps **identical skill
names, identical tool names and one set of documentation** - only the
install command differs. Renaming `plugin.json` instead would have
renamed every skill and every tool with it, and split the docs three
ways.

The distinction is recorded in CLAUDE.md, because nothing about the two
fields makes it obvious.

## Also verified

Installed from the real marketplace under the new name:

```
Source: mcp-windbg-uvx@mcp-windbg
Skills (4)  analyze-dump, debug-remote, kernel-debug, windbg-doctor
Agents (1)  crash-analyst
MCP servers (1)  mcp-windbg
```

`claude plugin validate` passes, docs typography clean, and every
occurrence of the old install command across the README, plugin README,
getting-started, clients, plugin reference and CHANGELOG is updated.
Test state removed afterwards.

## Note on sequencing

The open release PR (#91) predates this, so its 1.2.0 CHANGELOG entry
still shows the old command. Once this merges I will delete the release
branch and let release-please rebuild it, which is the documented way to
refresh a release PR - `sync-lockfile` now re-does the lockfile commit
on its own.
2026-08-27 22:09:51 +02:00
Sven Scharmentke 90457306fe feat: ship a Claude Code plugin and host a marketplace for it (#88)
Installing mcp-windbg into Claude Code meant a `pip install` plus a
hand-written server registration with an env var. It becomes two lines:

```
/plugin marketplace add svnscha/mcp-windbg
/plugin install mcp-windbg@mcp-windbg
```

This repo hosts the marketplace itself, so there is no second repository
to keep in step.

## Why there is no install step at all

The plugin launches the server with `uvx`, pinned to the matching
release, so uv fetches it from PyPI on first use. That is what makes the
two lines *complete* rather than the last two of five. `server.json`
already declared `runtimeHint: uvx`, so this is how the package
presented itself anyway.

## What ships

- The ten tools, through the plugin's `.mcp.json`.
- **`/mcp-windbg:analyze-dump`** - crash-dump triage that separates fact
from inference and calls out missing symbols rather than reading meaning
into `module+0x1234`.
- **`/mcp-windbg:debug-remote`** - attaching to a user-mode debug
server, and the part that is easy to get wrong by hand: saying plainly
whether the target was left running or frozen.
- **`/mcp-windbg:kernel-debug`** - driving a live kernel target,
including the `g` / `wait_for_break` / `send_ctrl_break` handling from
1.1.0, and closing with `resume: true` so nobody leaves a machine
halted.
- **`/mcp-windbg:windbg-doctor`** - the first thing to run when nothing
works: checks platform, CDB, uv, the `uvx` round trip, and symbols, then
reports what to fix rather than one failure at a time.
- **`crash-analyst`** agent - hand it a dump and a question and it
investigates on its own, returning a verdict, the evidence behind it,
what it ruled out, and what to do next. Its `tools:` list uses the
scoped `mcp__plugin_mcp-windbg_mcp-windbg__*` names.
- A [reference
page](https://github.com/svnscha/mcp-windbg/blob/feat/claude-plugin/docs/reference/plugin.md)
in the docs site, plus plugin sections in the README, getting-started,
and client configuration.

Always-on cost is about 400 tokens; a skill or the agent costs more only
when it fires.

## Symbols are configured, without overriding yours

The plugin sets `_NT_SYMBOL_PATH` as a **default**, not a value:

```json
"_NT_SYMBOL_PATH": "${_NT_SYMBOL_PATH:-SRV*C:\Symbols*https://msdl.microsoft.com/download/symbols}"
```

Claude Code's `.mcp.json` expansion supports `${VAR:-default}`, so an
existing value wins and a symbol path someone tuned themselves is never
overwritten. Without this the out-of-the-box experience is a stack of
`module+0x1234` offsets, which is exactly the case where triage is
guesswork.

## One thing that cannot be declared

**Windows-only.** There is no manifest field for a platform constraint,
so installing on macOS or Linux loads the plugin and the server then
fails to find a debugger. Stated in the plugin README, the marketplace
description, and the reference page; `windbg-doctor` catches it at the
first check and stops there.

## Version drift, closed

release-please now bumps the plugin manifest, the marketplace entry, and
the `uvx` pin along with `pyproject.toml` and `server.json`, and
`check-version-consistency.ps1` checks all three. That is the guard that
a silently-broken `extra-files` entry does not ship - the exact failure
caught by hand during the 1.1.0 release.

The `uvx` pin survives the bump because release-please's `generic-json`
updater does a substring replace against its version regex rather than
overwriting the whole value, so `mcp-windbg@1.1.0` becomes
`mcp-windbg@1.2.0` and not a bare `1.2.0`.

## A bug fixed on the way

`check-version-consistency.ps1` looped with `foreach ($error in
$errors)`. `$error` is a **read-only PowerShell automatic variable**, so
the first time it had a mismatch to report it threw `Cannot overwrite
variable Error` instead of naming the files. The build still failed, so
this was never dangerous - just useless exactly when you needed it.
Verified both ways now: it names the mismatching file, and still exits
1.

## Dependencies

Floors raised to current releases with the major caps unchanged: `mcp`
2.1.1, `starlette` 1.6.0, `uvicorn` 0.52.4, and `pymdown-extensions`
11.0.2 for the docs build. `uv.lock` is regenerated; it had been
stranded at 1.0.1 since the 1.1.0 release, because release-please has no
updater for it. A `uv lock --check` step in CI would catch that drift
and is worth a follow-up.

## Verification

- `claude plugin validate` passes for both the marketplace manifest and
the plugin manifest.
- Full suite green against the upgraded dependencies: 99 passed, 2
skipped (the kernel scenarios, no target attached). Live CDB scenarios
ran.
- `validate-server-schema.py` passes; `uv lock --check` reports the lock
in sync.
- The `.mcp.json` snippet on the reference page is asserted to parse and
to equal the real file, after it shipped with a single-backslash
`C:\Symbols` that did neither.
- The earlier end-to-end check (`claude plugin marketplace add` ->
`install` -> `details`, and a headless session seeing the tools through
`uvx` with nothing pre-installed) was run when the plugin had two skills
and no agent, so it predates the current surface.

## Note

You already have a `svnscha` marketplace pointing at
`svnscha/claude-plugins`. This adds a second, repo-local one. If you
would rather have the plugin listed there instead, say so - the plugin
directory moves as-is and only the marketplace manifest changes.
2026-08-27 20:08:07 +02:00
Sven Scharmentke 30c7dd9d9f Fix kernel go/break-in handling, add wait_for_break (#80)
Fixes #71, #72, #73. Supersedes #74.

@TempAccountNull diagnosed all three bugs correctly. This keeps every
fix and the credit, but replaces the patch - it had a defect that would
have broken stepping - and adds one more bug found by backtesting
against a real kernel target.

## The three reported bugs

- **Named pipe / COM sessions always timed out** (#71). `kd` announces a
pipe or serial link with `Kernel Debugger connection established`, not
the KDNET `Connected to target ...`. Only the latter was matched, so
`open_kd_session` sat waiting for a banner that was never coming on a
target that had in fact attached.
- **`g` re-froze the target it had just released** (#72). Go-class
commands hand the CPU back and the debugger stops reading stdin, so the
marker protocol's `.echo` sat unread, the command hit its timeout, and
the cancel-on-timeout CTRL+BREAK halted the machine again.
- **Any command after `g` did the same** (#73), through the same path -
the session had no concept of "the target is running".

## Why #74 is not merged as-is

Its `_GO_COMMANDS` set includes `p`, `t`, `pa`, `ta`. Those are *step*
commands: they execute one instruction or line and come straight back to
the prompt. Treating them as fire-and-forget swallows their output,
returns `"Target resumed."` instead, and leaves the session believing
the target is running - so the next command fires a spurious CTRL+BREAK.
Stepping would have become unusable. The step family keeps the marker
round-trip here, with a test pinning it.

## The fourth bug, found by backtesting

`_resume_target` wrote `g` and returned immediately. Until `kd` reads
it, `kd` is still at its prompt with the resume unread - and a
CTRL+BREAK arriving in that window is answered by the prompt rather than
reaching the target. The break is silently lost and the caller then
waits out a full `wait_for_break` timeout on a machine nothing will
stop.

That is exactly the sequence a tool caller writes, because
`run_kd_command("g")` now returns instantly. Measured on the live KDNET
target:

| gap after `g` | result |
| :--- | :--- |
| 0.00s | **break lost** (every attempt) |
| 0.05s | landed in 0.04s |
| 0.25s / 1s / 2s | landed |

The resume is now probed with a marker the debugger can only answer from
a prompt. Silence means it stopped reading stdin - the target is away
and the resume can no longer swallow a break. An answer means the target
never left, which is a bonus fix: a `gu` that returns in microseconds,
or a breakpoint hit at once, now reports what it printed instead of
falsely claiming the target is running.

## Also in here

- `wait_for_break` - block until a target stops (bugcheck, breakpoint,
manual break) and return what it printed. Asks the debugger rather than
trusting a flag, so it is right about targets this server never resumed.
If the wait expires the target is left **running**.
- Compound forms: `bp nt!X; g` runs the breakpoint first and returns its
result, so a typo'd symbol surfaces as `Couldn't resolve error` rather
than a 300s wait for a break that can never come. A `;` inside a quoted
command string is left alone.
- Qualified forms `~0 g` / `~*g` are recognized.
- One operation at a time per session; closing a session ends a wait
parked on it.

## Verification

Against the real kernel target (Windows 11 26100, KDNET):

- `scenarios/kernel_resume_and_wait.yaml` - new, covers resume, auto
break-in, and the zero-gap break. **Verified to fail without the fix**
with exactly the lost-break symptom, and to pass with it.
- `scenarios/kernel_session.yaml` - still passes.
- Machine confirmed left running afterwards, with no breakpoints behind.

Hermetic and user-mode:

- 89 hermetic tests pass (was 29 on `main`).
- 10 `live` user-mode scenarios pass, including the new
`resume_and_wait.yaml` against a real `cdb.exe`.
- Coverage 91% under CI's conditions, against the 88% gate.
- `mkdocs build --strict` clean; `docs/reference/tools.md` documents the
resume semantics.

**One limitation worth stating:** #71 is the one fix I could not
exercise end to end - this box has a KDNET target, not a pipe one. It is
covered hermetically for both banner wordings, and I did capture `Kernel
Debugger connection established.` verbatim from real `kd` output during
a KDNET reconnect, so the string is genuine. That it appears *instead
of* the KDNET wording on a pipe transport rests on @TempAccountNull's
report and their verified before/after.

---------

Co-authored-by: TempAccountNull <TempAccountNull@users.noreply.github.com>
2026-08-22 00:55:39 +02:00
Sven Scharmentke 2ebbe0041b Release 1.0.0: kernel debugging, session ids, and the cdb/kd split
Closes #62. Closes #47.

Kernel debugging never worked: every remote connection was launched with -remote, which is user-mode only, so a -k target could never connect. Fixing it properly meant kd.exe and a tool surface that can tell the two engines apart.

Breaking: every *_windbg_* tool is renamed, and sessions are addressed by an opaque session_id instead of by dump path or connection string. See CHANGELOG.md.
2026-07-16 20:43:47 +02:00
Igor Costa 3bf2c0be13 docs: add Autohand Code MCP setup (#63)
## Summary

Add an Autohand Code command beside the existing Claude Code
registration. The note explains that the server inherits
`_NT_SYMBOL_PATH` from the environment that launches Autohand.

## Verification

- `git diff --check`
- `uv run pytest` cannot collect on macOS because the Windows-only
server imports `winreg`; Windows CI remains the appropriate runtime
check

---------

Co-authored-by: Sven Scharmentke <24257042+svnscha@users.noreply.github.com>
2026-07-15 20:04:58 +02:00
Sven Scharmentke 0c01c283ed docs: add Claude Code client guide and simplify README config
Document `claude mcp add` for Claude Code (both uvx and python -m) in
docs/reference/clients.md, mirroring the project-scoped setup in ~/.claude.json.
Trim the README's duplicated filter-script and HTTP/VS Code blocks down to the
two common setups (VS Code, Claude Code) plus a uvx note, linking the docs for
the rest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 00:42:01 +02:00
Sven Scharmentke 28444b4f86 docs: user-focused guide overhaul (HTTP, redaction, WER, expectations) (#58)
## Summary

A user-focused pass over the MkDocs guide: close the operational gaps
and tighten every page so
each capability is discoverable as a goal the reader recognizes, and the
limits are stated.

(Supersedes #57, which GitHub auto-closed when its base branch
`test/e2e-framework` was deleted
on the merge of #56. This branch is rebased onto `main` and contains
only the docs changes.)

## New use-case pages

- **Debug from another machine** (`scenarios/http-service.md`) - run the
server over the
streamable-http transport on a Windows host and drive it from your
laptop or a shared
debugging host. Server-side paths, binding/firewall, and a clear
**no-authentication** warning.
- **Redact sensitive data** (`scenarios/redaction.md`) - scrub
secrets/PII from tool output with
a `--filter-script` before it reaches a cloud model. Real script, client
wiring, hook contract.

## Reworked pages

- **Triage** - "Capture crashes automatically" with WER `LocalDumps`
(the registry path behind
  the no-directory `list_windbg_dumps` fallback).
- **Crash dump** - use the `dump-triage` prompt for a structured report.
- **Remote target** - attach-by-PID is unsupported (connect to a
`-server`).
- **Tools** - sessions are concurrent. **cli/clients** HTTP no-auth
note. **Index** scope note,
**getting-started** pointers, new **troubleshooting** entries for HTTP
and filter scripts, and
  cross-links throughout.

## Verification

- `python -m mkdocs build --strict` - clean; all new links and anchors
resolve.
- `pwsh scripts/Format-Docs.ps1 -Check` - no em/en dashes, no emoji.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 23:21:09 +02:00
Sven Scharmentke 642422caab test: rebuild the suite as a declarative end-to-end framework with subprocess coverage (#56)
## Summary

Rebuilds the test suite as a declarative end-to-end framework. The
harness hosts a real `python -m mcp_windbg` server (stdio and
streamable-http) and drives it with a real MCP `ClientSession`. The only
faked component is the LLM: each `tests/scenarios/*.yaml` scripts the
tool calls a model would otherwise choose. Real server, real transport,
real `cdb.exe`, real dump.

Adding a test means writing YAML, not Python. See
`src/mcp_windbg/tests/e2e/README.md`.

## What is exercised (27 scenarios)

- **Local dump:** real `cdb.exe -z` against the LFS
`DemoCrash1.exe.7088.dmp` (analyze, run commands, session lifecycle,
symbol-path reuse + combine, `--cdb-path`, `--no-dump-dir-symbols`).
- **Remote:** a real local `cdb .server tcp:` connected via `-remote`
(connect/inspect/close, break-in via `send_ctrl_break`, invalid-endpoint
error).
- **Transports:** stdio and streamable-http, both over the real MCP
protocol.
- **Hermetic (no debugger):** tools contract, list dumps, validation +
unknown-tool/-prompt + non-existent-dump errors, the empty-path branch,
the `dump-triage` prompt, and the full range of filter-script behaviors
(redact in/out, input-only, None / non-string returns, raising /
zero-arg / non-callable / missing hooks).

## Skipping vs. failing

- Missing `cdb.exe` -> the `live`/`remote` scenarios `pytest.skip` (so
`-m "not live"` runs anywhere; 17 hermetic scenarios).
- Missing Git LFS dump -> **hard fail** (`git lfs pull`). The dumps are
mandatory; a half-set-up checkout is a loud error, not silent green.

This fixes the old inconsistency where the remote tests *failed* rather
than skipped on a CDB-less machine.

## Coverage: 91%, enforced

The server runs in a subprocess, so a naive `pytest --cov` measured the
parent and reported `server.py` at a misleading 20%. The harness now
launches the server under `coverage run --parallel-mode`
(`MCP_WINDBG_COVERAGE`), measuring where dispatch actually runs. CI
enforces `coverage report --fail-under=90`.

| Module | naive (parent) | subprocess |
| --- | --- | --- |
| `server.py` | 20% | 90% |
| `cdb_session.py` | 16% | 87% |
| `filter_script.py` | 21% | 100% |
| `__init__.py` (CLI) | 14% | 100% |
| **total** | **19%** | **91%** |

Code that genuinely cannot be line-measured end-to-end is excluded with
documented `# pragma: no cover` / `exclude_also`: the streamable-http
transport (hard-killed on Windows teardown - asyncio's Proactor loop
does not deliver the shutdown signal, so coverage cannot flush; still
verified behaviorally by the HTTP scenario), the atexit cleanup,
debug-only output and low-level IO error handlers, and guards the MCP
layer enforces before they could run.

## Cleanup

- Removed dead code found while raising coverage:
`server.execute_common_analysis_commands`, `CDBSession.get_session_id`,
`prompts.get_available_prompts` (no callers).
- Documented the previously-undocumented `dump-triage` MCP prompt
(`docs/reference/prompts.md`, nav links).
- Corrected the CLAUDE.md testing description (the old "skip cleanly /
unit coverage" claim was false).
- Removed `test_cdb.py`, `test_cli.py`, `test_filter_script.py`,
`test_remote_debugging.py`.
- Adds `pyyaml` and `coverage` as dev dependencies.

## Verification

- `uv run pytest src/mcp_windbg/tests/ -v` - 27 passed (live +
hermetic).
- `uv run pytest src/mcp_windbg/tests/ -m "not live"` - 17 passed, 10
deselected.
- Coverage flow above; `coverage report --fail-under=90` passes at 91%.
- `Format-Docs.ps1 -Check` and `mkdocs build --strict` clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 23:13:31 +02:00
Sven Scharmentke c3c3c7fa57 docs: onboard MkDocs site and migrate the wiki (#50)
## Summary

Onboards a MkDocs (Material) documentation site, mirroring the
dap-dbgeng setup, and migrates the GitHub wiki into it (trimmed to the
essentials: getting started, use cases, and a lookup reference).

## What's included

**Site infrastructure**
- `mkdocs.yml` - Material theme, VS Code blue palette, light/dark
toggle, the same markdown extensions and nav structure as dap-dbgeng.
- `requirements-docs.txt` - pinned `mkdocs` / `mkdocs-material` /
`pymdown-extensions`.
- `docs/assets/` - `extra.css` (brand color + mermaid styling) and the
self-contained `mermaid.js` loader.

**Pipeline**
- `.github/workflows/pages.yml` - builds with `mkdocs build --strict`
and deploys to GitHub Pages on pushes to `main` that touch docs.

**Content (migrated + trimmed from the wiki)**
- `index.md`, `getting-started.md`
- Use cases: `scenarios/` - analyze a crash dump, debug a remote target,
triage multiple dumps
- Reference: `reference/` - command-line options, tools, client
configuration
- `troubleshooting.md`

**Agent / authoring setup**
- Migrated `AGENTS.md` -> `CLAUDE.md` (concise, dap-dbgeng style).
- Added `.claude/rules/markdown.md` and
`.claude/rules/documentation.md`, plus `scripts/Format-Docs.ps1` to
enforce the typography rules (plain hyphens, no emojis).

**Housekeeping**
- README documentation links now point at the new site.
- `.gitignore` ignores `/site/`.

## Verification

- `mkdocs build --strict` - clean, all internal links resolve.
- `scripts/Format-Docs.ps1 -Check` - passes repo-wide (also fixed a
stray dash + emoji in CHANGELOG).

## One manual step

GitHub Pages is not enabled on the repo yet. After merge, set **Settings
-> Pages -> Build and deployment -> Source = GitHub Actions** (one time)
so `pages.yml` can publish to `https://svnscha.github.io/mcp-windbg/`.
Happy to enable it via API if you'd prefer.

## Notes / decisions

- Use-case pages chosen to match the product: crash dump, remote target,
triage. The wiki's separate per-client install pages are consolidated
into `reference/clients.md`.
- The wiki repo (`mcp-windbg.wiki`) is left untouched; we can archive or
redirect it once this is live.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 04:38:12 +02:00