Development¶
Run mcp-windbg from a local checkout so an MCP client uses your working tree, with source
edits picked up live. This is the setup for working on the server itself.
Editable install¶
An editable install links the package to your checkout instead of copying it, so changes to the source (and switching branches) take effect the next time the server starts. Install into your user site with pip:
This puts mcp-windbg.exe in your user scripts directory (for example
%APPDATA%\Python\Python314\Scripts). That directory is often not on PATH, which is fine:
the client configs below launch the module with a full Python path instead, so PATH does
not matter.
Confirm the install points at your checkout:
It should print the path inside your clone (...\mcp-windbg\src\mcp_windbg\__init__.py).
uv alternative
uv sync --dev already installs the project editable into .venv, so uv run mcp-windbg
runs your checkout without a separate install. Use that for running the tests; use the
--user install above when you want an MCP client to launch the dev build directly.
Point an MCP client at the dev build¶
Use a distinct server name (for example mcp-windbg-dev) so it does not clash with a released
install, and launch the module with your full Python path so the editable install resolves
regardless of PATH. Add --verbose to see the debugger commands the server runs.
Edit %APPDATA%\Claude\claude_desktop_config.json:
Replace C:\Python314\python.exe with the interpreter you ran pip install --user against
(python -c "import sys; print(sys.executable)"). Restart the MCP client after editing source
or switching branches so it relaunches the server on the new code.
Build a wheel¶
To produce the distributable artifacts (a wheel and an sdist under dist/), for example to
test packaging before a release:
This is a built artifact, not an editable install: reinstall it to pick up later source changes. For day-to-day development, prefer the editable install above.
Tests¶
uv run pytest src/mcp_windbg/tests/ -v # full suite (needs cdb.exe for live cases)
uv run pytest src/mcp_windbg/tests/ -v -m "not live" # hermetic subset, no debugger needed
uv run pytest src/mcp_windbg/tests/ -v -m kernel # kernel scenarios, needs a real target
Markers gate what needs hardware: live needs cdb.exe, remote starts a local CDB server,
and kernel needs kd.exe plus a target machine. Whatever is unavailable skips rather than
fails, so the hermetic subset stays green anywhere.
The scenarios/*.yaml files are not a separate runner: each becomes one parametrized case,
addressed by its name: field rather than its filename.
uv run pytest "src/mcp_windbg/tests/test_scenarios.py::test_scenario[Kernel session against a real target]" -v
Kernel target¶
Kernel scenarios attach to a real target, so they need its -k connection string in
MCP_WINDBG_KERNEL_CONNECTION. Unset, they skip (as they do on CI, which has no target):
$env:MCP_WINDBG_KERNEL_CONNECTION = "net:port=50005,key=1.2.3.4"
uv run pytest src/mcp_windbg/tests/ -m kernel -v
The hermetic tests in tests/test_kd_session.py drive a fake kd.exe instead. They are the
floor CI can reach, not proof the feature works: run the kernel marker against a real target
before shipping a kernel change.
VS Code¶
.vscode/settings.json is set up for discovery already. Two things there are load-bearing:
python.testing.pytestArgsstays empty. Pytest then falls back totestpathsinpyproject.toml(src/mcp_windbg/tests). A path argument overridestestpaths, so discovery and the command line silently disagree about what the suite is.python.envFileandpython.terminal.useEnvFilepoint at.envand apply it, since VS Code does not inherit your shell. Both are needed:envFilealone does not reach the test run, which goes through a terminal. Create the file to run the kernel scenarios from the Test Explorer. It is gitignored, as it holds your KDNET key:
Coverage¶
The code runs in two processes and both must be measured, or the number lies. The scenarios
drive a server subprocess (MCP_WINDBG_COVERAGE launches it under coverage run), while the
hermetic tests run in the pytest process itself, so pytest needs starting under coverage too:
uv run coverage erase
$env:MCP_WINDBG_COVERAGE = "1"
uv run coverage run -m pytest src/mcp_windbg/tests/ # measures pytest AND the server
$env:MCP_WINDBG_COVERAGE = $null
uv run coverage combine # merge the per-process .coverage.* files
uv run coverage report # or: uv run coverage html -> htmlcov/
Plain pytest reports every hermetic test's code as never executed, which is why the Test
Explorer's coverage numbers are not trustworthy. CI gates on --fail-under=88; leave
MCP_WINDBG_KERNEL_CONNECTION unset to reproduce its number, set it for the honest local one.
What the scenarios alone cover¶
To ask what the end-to-end suite reaches on its own, run only the scenarios and drop the
coverage run -m wrapper: they exercise the server subprocess, and the pytest process is just
the client driving it, so measuring the parent adds nothing.
uv run coverage erase
$env:MCP_WINDBG_COVERAGE = "1"
uv run pytest src/mcp_windbg/tests/test_scenarios.py
$env:MCP_WINDBG_COVERAGE = $null
uv run coverage combine
uv run coverage html -d htmlcov-e2e # browsable, line by line -> htmlcov-e2e/index.html
Any marker or path narrows it, and --include trims the report to the modules you care about.
This answers "what does this one scenario actually touch?" in seconds:
uv run pytest src/mcp_windbg/tests/ -m kernel
uv run coverage combine
uv run coverage report --include="*kd_session*,*debug_session*"
Always start with coverage erase
combine folds new data into an existing .coverage, so skipping the erase silently
unions this run with the last one, exactly when you are trying to isolate a subset. Note
that erase also deletes everything matching .coverage.*, including a snapshot you named
with that prefix. To keep data files around, point COVERAGE_FILE somewhere outside that
glob.
See the end-to-end scenario guide for the test format.