An MCP server that integrates netcoredbg to provide debugging capabilities for .NET applications, including breakpoint management, execution control, stack inspection, and diagnostics.
Strong tool definitions with consistent naming, good descriptions, and well-structured schemas. All 27 tools are explicitly registered with [McpServerTool] attributes and have descriptions via [Description]. Parameters are typed and annotated. However, output schemas are not explicitly documented in code, LLM must infer structure from method signatures. Error handling relies on generic try-catch with ToolResults.Err(ex), which may not provide actionable recovery guidance. No tool annotations (readOnlyHint/destructiveHint) despite presence of READ_ONLY and WRITE/DESTRUCTIVE operations, these should be declared in schema metadata.
List all currently set breakpoints (line, function, and exception filters).
Remove a breakpoint by its id (line or function).
Set a line breakpoint in a source file. Supports conditional, hit-count, and logpoint (logMessage) breakpoints.
Set a data/watch breakpoint: break when a specific variable changes (or is read). Requires a variablesReference + name from variables_get. May not be supported by all adapters.
Set the active exception breakpoint filters. Pass [] to clear. Common netcoredbg filters: "all", "user-unhandled".
Set a breakpoint on a function by symbol name (e.g. "Namespace.Class.Method").
Output schemas not explicitly documented in code. While parameters are well-typed via C# attributes and descriptions, the return structures (e.g., what fields breakpoint_set returns, what exception_autopsy includes) must be inferred from method signatures and async Task<string> return statements. JSON Schema for responses is not visible.
Tool annotations (readOnlyHint, destructiveHint, idempotentHint) are not declared in schemas. The source metadata shows Risk tags (READ_ONLY, WRITE, DESTRUCTIVE) but these are not propagated as MCP toolAnnotations in the protocol output. LLM cannot determine which tools are safe to retry or have side effects without parsing descriptions.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | B | 72 | 2026-07-28+ | v2 |
Block until the debuggee hits a breakpoint, completes a step, or otherwise stops. Returns the stop info plus a full one-shot snapshot: the topmost stack frame, a source snippet around the stop, top-frame locals, and a peek of recent debuggee stdout/stderr (non-destructive — process_read_output still drains the full buffer). Designed so an agent in a step-inspect loop doesn't need separate inspect / read-output round trips.
Attach the debugger to an already-running .NET process by PID.
Resume execution of the debuggee. Defaults to the last-stopped thread.
Disconnect the debugger and terminate the debuggee.
Launch a .NET program under the debugger. Returns the resulting session state.
Pause the debuggee. Requires a thread id; defaults to the last-stopped thread if known.
Return the current debug session state, including process id and last stop info.
Single-step the debuggee. Kind: "in" (step into), "over" (step over), "out" (step out). Pass `waitTimeoutSeconds` > 0 to also block on the next stop and return the same one-shot snapshot as `breakpoint_wait` (top frame, snippet, top-frame locals, recent debuggee output). Without it the call returns immediately after issuing the step — today's behavior.
Self-diagnostic. Reports the host platform, RID, resolved netcoredbg path (or that it's missing), where the binary came from (bundled / env var / PATH), and the netcoredbg version it reports. Call this first when something looks wrong, instead of starting a real debug session to find out.
Evaluate a C# expression in the context of a stack frame. Returns the result as a string plus a variablesReference if the result is a compound value.
Full exception context in one call: exception type + inner-exception chain + top stack frames + top frame's locals + source snippet around the throw + a peek of recent debuggee stdout/stderr (non-destructive — process_read_output still drains the full buffer). Call this when state.lastStop.reason == "exception".
Diagnose why an application appears stuck. Auto-pauses if running, lists all threads, fetches the top frames of each, and classifies each thread's blocking pattern (Monitor / WaitHandle / Semaphore / Task / Thread.Join / Thread.Sleep / async await). Leaves the session paused so you can inspect further.
Drain buffered output (stdout/stderr) from the debuggee since the previous call. Returns the lines collected and removes them from the buffer. Filtering by category drains only that category — lines in other categories, and matches beyond maxLines, stay buffered for a later call.
In one call: full stack + locals at every frame + a pre-rendered ASCII tree showing caller → callee with arrows. Use this instead of stacktrace_get + variables_get per frame when you want to see the whole picture at once.
Get the call stack of a thread. By default async state-machine frames are flattened back to their original method names (e.g. UserService.<GetAsync>d__3.MoveNext → UserService.GetAsync) and BCL async infrastructure frames are hidden. Pass raw=true for the unmodified DAP frames.
List all threads in the debuggee.
Read events captured since trace_start, plus a pre-rendered ASCII timeline with → for method entries and ⚠ for exceptions. Does NOT clear the buffer — call trace_stop when done.
Begin tracing a set of methods. Each named method gets a server-side trace breakpoint that captures the call (top stack + locals) and auto-continues — the request flows through at near-normal speed and your debug state is unaffected. If includeExceptions=true, unhandled exceptions are also captured. Use trace_get to read the captured events and trace_stop to remove the trace. One trace active at a time.
Stop the active trace and remove its breakpoints. Captured events are discarded — call trace_get first if you want to keep them.
Get variables for a stack frame. Defaults to the topmost frame of the last-stopped thread. Recursively expands compound values up to `depth` levels and truncates each level at `maxChildren`.
Set the value of a variable or any lvalue expression (e.g. "userId" or "user.Name"). The agent can use this to test fixes by mutating state mid-run.
Error handling is generic: try-catch blocks return ToolResults.Err(ex) without categorizing errors as retryable, user-fixable, or fatal. LLM receives exception stack traces rather than actionable recovery guidance (e.g., 'Process not found. Ensure debug_launch succeeded before calling debug_attach').
Sparse parameter descriptions for some tools. threads_list and debug_continue have minimal descriptions ('List all threads in the debuggee', 'Resume execution of the debuggee') without guidance on when to use them or what they return. Parameters like 'threadId' with defaulting behavior need clearer explanation of fallback logic.
No explicit pagination or limits documented for tools that return collections. hang_analyze's thread list, trace_get's event buffer, stacktrace_get's frame results could grow unbounded. Descriptions should specify caps (e.g., 'Returns max 50 threads') and offer pagination parameters.
Some parameter descriptions lack concrete constraints. debug_step's 'waitTimeoutSeconds' says 'Seconds to wait' but does not specify range (0 - 300? 0 - 3600?). trace_start's 'maxFramesPerEvent' defaults to 10 but omits upper bound. Parameter descriptions should include min/max or enum values.
debug_disconnect is marked DESTRUCTIVE but has no confirmation or dry-run mechanism. Agents could inadvertently terminate a debug session. Consider requiring a confirmation parameter or issuing a warning in the description that this cannot be undone.