A local MCP server that lets an agent (e.g. Claude) debug .NET applications:
launch a program, set breakpoints, step, inspect variables / call stacks /
threads, and enumerate all in-flight async Tasks — everything a developer looks
at while debugging — deterministically, headless, with no IDE.
Style is the same as the Azure DevOps / Azure MCP servers: run it locally, point your agent at it, and ask in natural language:
"Launch project X, set a breakpoint where the bug is, run it, read the variables and the async tasks, and tell me why ABC happens."
The agent decides where to break and what to inspect (it reads your source with its own tools); ClrVoyant gives it the deterministic debugger primitives.
A single .NET process orchestrates two engines behind one IDE-neutral tool contract:
- netcoredbg (bundled, MIT) over the Debug Adapter Protocol — control + live introspection (breakpoints, step, threads, call stack, variables, evaluate).
- ClrMD — reads the heap at a stop to
enumerate all
Tasks and reconstruct the async await/continuation graph (the "Tasks window" equivalent, which DAP cannot provide). On Windows via a PSS snapshot; on Linux via a passive read of the (netcoredbg-held) process — both validated to coexist with netcoredbg holding the process.
See docs/architecture.md for the design as built, and docs/ for the full documentation (tool reference, POD/remote debugging, security model, ADRs).
Concrete scenarios it delivers today (Windows or Linux, .NET 8/9/10):
- Debug an app you launch — start a program, break, step, read variables / call stack / threads. The base loop. (tools)
- Attach to a running process — discover the PID (
list_processes, filters to .NET) and attach; for long-running or externally-started processes. - Debug a unit test in one step —
debug_test(project[, testName])launches the test host suspended, attaches, and breaks in your test/production code. - Understand stuck
asynccode — enumerate every in-flightTaskwith its state and the await/continuation graph (list_tasks,get_async_graph). This is the "Tasks window" that DAP-only debuggers structurally can't provide. - Debug several processes at once — multi-session, with
wait_for_any_stopto orchestrate stops that arrive asynchronously; optional child-process auto-attach for parents that spawn workers. - Debug inside a container / Kubernetes POD — run it as an authenticated HTTP sidecar co-located with the target (PDBs required); only the agent is remote, the engine stays local to the process. (remote debugging)
- Supported architectures:
win-x64,linux-x64,linux-arm64. The engine debugs locally, so the tool runs at the same architecture as the target — an arm64 tool for an arm64 process. (Nowin-arm64or macOS: netcoredbg ships no such build.) Each architecture is exercised by its own CI leg, including anarm64runner on real hardware — see ADR-0011. - .NET SDK 8 / 9 / 10 (the target apps you debug must be .NET 8+; .NET Framework is not supported)
The cleanest way is as a .NET global tool — you get a clrvoyant command on
your PATH, so client config never points at a build folder. netcoredbg is fetched
once (pinned + SHA-256 verified) on first run.
# From a published package (once it's on NuGet):
dotnet tool install -g ClrVoyant
# Or from source right now:
dotnet pack src/ClrVoyant.Server -c Release -o ./nupkg
dotnet tool install -g ClrVoyant --add-source ./nupkgThen point your MCP client at the command (.mcp.json / Claude config; VS Code's
mcp.json uses the key servers):
{
"mcpServers": {
"clrvoyant": { "command": "clrvoyant" }
}
}The install scripts do build + register in one step:
./scripts/install.ps1 -Client cursor # claude-code | claude-desktop | cursor | windsurf | vscode
./scripts/install.ps1 # default: just prints the snippet./scripts/install.sh --client cursor # bash equivalent (uses jq to merge)Prefer not to install a tool? Build and point at the binary directly:
dotnet build ClrVoyant.slnx -c Release # bundles netcoredbg next to the serverand set "command" to the built ClrVoyant.Server executable. To use your own
netcoredbg, set CLRVOYANT_NETCOREDBG; to forbid the first-run download (air-gapped),
set CLRVOYANT_NO_FETCH=1 and pre-provide the engine.
For debugging a .NET app in Kubernetes, run ClrVoyant as a sidecar over HTTP — see deploy/ and docs/remote-debugging-pod.md.
Tip: have the agent call get_debug_instructions first — it returns a short
playbook of the tools and their ordering.
- stdio (default) — the agent spawns the server locally; one client per process.
- HTTP — set
CLRVOYANT_TRANSPORT=httpfor a Streamable-HTTP server a remote agent can reach (e.g. shipped inside a POD next to the app). HTTP requiresCLRVOYANT_AUTH_TOKEN(a bearer token) and bindsCLRVOYANT_HTTP_URL(defaulthttp://0.0.0.0:3001); it fails closed without a token, because a debug server that can launch processes andevaluatecode is an RCE surface.
For debugging a .NET app running in Kubernetes, run ClrVoyant as a sidecar: see deploy/ (Dockerfile + sidecar manifest + the security model).
Onboarding
get_debug_instructions()— a short playbook (typical loop, tool ordering, gotchas); call it first
Sessions (multi-session: every tool takes an optional sessionId; omit for
the active one)
debug_launch(program, args?, cwd?, stopAtEntry?)— launch a built.dlldebug_attach(pid)— attach to a running process (e.g. a child process)list_processes(dotnetOnly?)— list processes (pid, parent, name, isDotNet) to find a target to attach (e.g. the app process in a shared-PID-namespace POD)debug_test(testProject, testName?, configuration?)— rundotnet testwith the host suspended and attach in one step (theVSTEST_HOST_DEBUGrecipe, automated)restart_debugging(sessionId?)— relaunch a launched session in place: same id, same args, breakpoints preserved (not valid for attached sessions)set_auto_attach(enabled)— auto-attach .NET child processes of debugged processes (off by default; matched by parent PID, no suspend-at-startup)debug_stop(sessionId?),debug_status(sessionId?),list_sessions()wait_for_any_stop(timeoutMs?)— block until any session breaks (key for several processes running asynchronously)
Breakpoints & execution (wait-based: continue/step_* block until the next
stop and return the new location)
set_breakpoint(file, line?, content?, condition?, hitCondition?, logMessage?)— prefercontent(the source text of the line) over a rawline: it survives line-number drift;linethen only disambiguates duplicate matchesremove_breakpoint(bpId),clear_all_breakpoints(),list_breakpoints(),set_exception_breakpoints(filters)continue(threadId?, timeoutMs?),step_over/step_into/step_out,pause()
Introspection (when stopped)
get_threads(),get_callstack(threadId?, ...)get_scopes(frameId),get_variables(variablesReference)evaluate(expression, frameId?, context?)—⚠️ executes code in the debuggee (side effects); preferget_variablesfor plain inspectionget_exception_info(threadId?)
Async / Tasks (ClrMD, when stopped)
list_tasks(status?)— all Tasks with status and async methodget_task(address)get_async_graph()— await/continuation graphget_async_callstack(address)— logical async chain from a Task
debug_launchthe app →set_breakpointat the suspect linecontinue→ returnsstopped at File.cs:NNget_callstack→get_scopes→get_variablesto read statelist_tasks/get_async_graphto see what async work is pending/stuckstep_over/continueto narrow it down → diagnose
Tests run in a testhost process. Use debug_test(testProject, testName?) — it
runs dotnet test with VSTEST_HOST_DEBUG=1 (so the host suspends and announces
its PID), parses that PID and attaches in one step; set breakpoints right after it
returns and execution resumes into them. The dotnet test driver is killed when
you debug_stop the session. Build the tests in Debug for symbols and locals.
Under the hood this automates the manual recipe (kept here for reference):
$env:VSTEST_HOST_DEBUG=1; dotnet test <project> -c Debug prints Process Id: NNNN
and waits; debug_attach(NNNN) releases it via Debugger.IsAttached.
There are several MCP debuggers. They make different trade-offs — this is about what you get and what you give up, not "we're best". Pick by what you debug.
| If you want… | Consider | What you give up |
|---|---|---|
Deep .NET debugging — async/Task state, headless, remote/POD |
ClrVoyant (this) | breadth: it is .NET 8+ only, and headless (no visual IDE) |
| Many languages via one DAP server (Python, JS, Go, Rust, Java, .NET…) | debugmcp/mcp-debugger | no async/Task/heap view — DAP can't enumerate Tasks, so .NET async state is invisible |
| Debugging inside VS Code with a human watching | microsoft/DebugMCP | requires VS Code running (not truly headless); bounded by the VS Code Debug API; no async Tasks |
| Python/Node/Java/browser, no IDE | bastiencb/claude-mcp-debugger | no .NET; no async/heap inspection |
The one thing ClrVoyant has that DAP-based tools structurally cannot: a "Tasks
window" — enumerate every in-flight Task and its await/continuation graph (via
ClrMD), the thing you actually need to debug a stuck async method. The price is
focus: it does .NET only, deeply, instead of many languages shallowly.
Fuller breakdown with sources: docs/comparison.md.
- Child-process auto-attach (
set_auto_attach) discovers children by parent PID without suspending them, so a child's very first startup instants may run before the debugger attaches. Suspend-at-startup (tier 3) is not yet implemented. evaluateruns code in the target — treat as dangerous.pauserequires a thread known from a prior stop (netcoredbg does not enumerate threads of a freely-running process).- Targets must be .NET 8+ (netcoredbg does not debug .NET Framework).
dotnet test tests/ClrVoyant.Tests --settings coverlet.runsettingsUnit tests exercise the real logic — the wait-based execution model, the
breakpoint store, content-based breakpoint resolution, and Task status/async-method
decoding — with a fake engine; integration tests drive a real netcoredbg + ClrMD
against SampleApp (launch → breakpoint → inspect → enumerate Tasks). Line coverage
is high as a by-product, but the point is the behaviours above, not the number. See
docs/testing.md.
src/ClrVoyant.Core models, IDebugEngine, Session, SessionManager
src/ClrVoyant.Dap DAP client + DapEngine (netcoredbg)
src/ClrVoyant.Inspection ClrMD TaskInspector (Tasks + async graph)
src/ClrVoyant.Server MCP stdio host + tools
tools/netcoredbg bundled debug engine (fetched at build)
samples/SampleApp a target app for tests
spike/ the de-risking proof-of-concept
scripts/mcp-driver.ps1 stdio test harness
ClrVoyant stands on:
- netcoredbg (Samsung, MIT) — the .NET debug engine, driven over DAP.
- ClrMD (Microsoft, MIT) — heap/Task introspection, the basis of the "Tasks window".
- The Debug Adapter Protocol (Microsoft) and the Model Context Protocol (Anthropic) and its C# SDK.
Prior art that shaped the design space: microsoft/DebugMCP and debugmcp/mcp-debugger. See THIRD-PARTY-NOTICES.md for licenses.
MIT © Matteo Panzacchi