--- name: mcp-debugger description: Use when investigating a bug, failing test, or unexpected runtime behavior and the mcp-debugger MCP server is available — drives real step-through debuggers (breakpoints, stack traces, variable inspection, expression evaluation) for Python, JavaScript/TypeScript, Ruby, Rust, Go, Java, .NET/C#, C/C++, and COBOL, locally or attached to remote processes. --- # Debugging with mcp-debugger mcp-debugger exposes real language debuggers as MCP tools. Prefer it over print-debugging whenever you would otherwise need more than one edit-run cycle to see program state: a breakpoint plus `evaluate_expression` answers in one run what printf answers in three. ## When to reach for the debugger - A test fails and the assertion message doesn't explain *why* the value is wrong. - Control flow surprises you (a branch that "can't happen", a loop that exits early). - State mutates somewhere between two known-good points and you need to bisect. - The bug lives in code you can't easily edit (third-party package, compiled artifact). - You need ground truth about runtime types/values instead of inferring them from source. Do NOT reach for it when a single glance at the code or one log line would answer the question — session setup costs a few seconds and the target must be runnable. ## The golden path (launch) ```text 1. create_debug_session {language: "python"} -> sessionId 2. set_breakpoint {sessionId, file: "", statement: ""} (or line: N + expectedContent) 3. start_debugging {sessionId, scriptPath: ""} -> paused at the breakpoint, or pending: true -> wait_for_stop {sessionId} 4. get_stack_trace {sessionId} -> frames (use frame.id, never assume 0) 5. get_scopes {sessionId, frameId: } -> scope variablesReference 6. get_variables {sessionId, scope: } ... or get_local_variables {sessionId} for the common case 7. evaluate_expression {sessionId, expression: "x + y"} 8. step_over / step_into / step_out / continue_execution wait_for_stop {sessionId} -> blocks until the next stop or the exit (continue_execution does not wait) 9. get_output {sessionId} -> captured debuggee stdout/stderr 10. close_debug_session {sessionId} -> ALWAYS, even on failure ``` Rules that prevent 90% of failed sessions: - **Absolute paths only** for `file` and `scriptPath` (relative paths are rejected in host mode). - **Use real frame IDs.** Take `id` from `get_stack_trace` frames; it is adapter-assigned and is not 0-indexed. - **Expand variable containers.** If a variable entry carries a `variablesReference`, call `get_variables` again with that reference to see children (Python's "special variables", object fields, array elements). - **Respect session state.** Stepping, evaluation, and variable reads require `PAUSED`. After `continue_execution` the session is `RUNNING`; after a step or breakpoint hit it returns to `PAUSED` with a persisted stop reason telling you why it stopped (`breakpoint`, `step`, `entry`, `exception`, ...). `continue_execution` returns at once — call `wait_for_stop {sessionId}` to block until the program stops again or ends; it answers with the stop (`lastStop`, `location`) or the exit code. - **A `start_debugging` that answers `running` with `pending: true` is not stuck.** The launch holds its answer only briefly — about a second — for the program's first stop, so a breakpoint reached as the program starts is answered in the one call, and anything later is collected with `wait_for_stop {sessionId}`, which blocks until the program stops or ends. Nothing was cancelled: breakpoints stay armed for as long as the program runs, and the message says what is armed. For a server, this is the workflow: set the breakpoint in the handler, `start_debugging` (answers `pending`), send the request, `wait_for_stop`. The same goes for any step, pause or attach that answers `pending: true`. `wait_for_stop` takes a `timeout` in ms (default 30000); when it runs out it answers `pending: true` again — just call it again. - **Breakpoints may verify late.** Some adapters (debugpy, JDI) report breakpoints unverified until the module/class loads; that is normal, not an error. - **`` placeholders are masking, not program state.** Credential-shaped values and values of sensitive variable names (`password`, `api_key`, ...) are masked by default in variable/evaluate/output results; a `redaction` field reports what was hidden. The real value is intact in the debuggee — don't "fix" it, and don't retry the read. The user can disable masking by restarting the server with `DEBUG_MCP_NO_REDACT=1`. - **If `get_variables` demands `names`, the server is in least-privilege mode** (`DEBUG_MCP_VARIABLE_ACCESS=explicit`): pass the exact variable names you need (`names: ["user", "total"]`; case-sensitive, misses reported in `notFound`) instead of dumping the scope. `evaluate_expression` still works for targeted reads. - **Always `close_debug_session`** when done — it tears down the debuggee process tree. ## Root-cause discipline 1. State a hypothesis about where reality diverges from expectation *before* setting breakpoints. 2. Set at most two breakpoints: last-known-good and first-known-bad. Run, inspect, halve the interval. Bisection beats stepping line-by-line from the top. Move the window mid-session with `remove_breakpoint` / `clear_breakpoints`; `list_breakpoints` shows what is currently set (with verified state and adapter ids). - Prefer `statement: ""` over line numbers: it matches like an Edit-tool `old_string` (whole line or a distinctive substring — whitespace-trimmed, trailing comments ignored, exact matches win), only lands on a line containing your text (inexact or multi-candidate matches are flagged in the response `warning`), lists every occurrence on ambiguity (add `nearLine` to pick one), and re-resolves across `restart_debugging` after you edit the file. When you do address by line, pass `expectedContent: ""` (trailing comments ignored) so a stale or off-by-one line number fails immediately with the actual nearby lines. A response saying `requested line N, bound to line M` means the adapter moved the breakpoint — trust the bound line. - `function: "name"` breaks on entry to a symbol with no file or line at all — names survive edits best. Supported by Python/Go/Rust/.NET/Java/JavaScript, C/C++ and COBOL (paragraph, section or PROGRAM-ID names) — not Ruby, which rejects it up front (Java accepts bare `method`, `Class.method`, or fully-qualified names and binds every concrete overload; JavaScript names are dotted runtime paths like `obj.method` bound to the current function value — main-module function declarations bind at launch, functions in lazily-loaded modules bind at the next pause). 3. When pausing is too disruptive (hot loops, live or attached processes), use a **logpoint**: `set_breakpoint` with `logMessage: "x={x}"` streams interpolated values into `get_output` without stopping the program (Python/JS/Go/Rust, C/C++ and COBOL, where `{WS-NAME}` is a COBOL data reference; Java, .NET and Ruby reject it with a clear error). 4. At each pause, record what you *learned* (variable values, actual control flow), not just where you are. 5. When the diverging line is found, inspect every input to that line before concluding — the bug is usually an operand, not the operator. 6. Fix, then `restart_debugging {sessionId}` — one call relaunches with the same configuration and re-applies every breakpoint (the output buffer resets; read `get_output` from `since: 0`). Confirm the observed state changed as predicted. Works even after the program exited; attach sessions are rejected (detach and re-attach instead). ## Program output `get_output {sessionId}` returns buffered debuggee stdout/stderr with a cursor: pass the returned `nextSince` back as `since` to read only new output. Each session also exposes the transcript as MCP resource `debug://sessions/{id}/output` with subscription support. Caveat: Ruby **attach** sessions capture no stdout (launch sessions stream it) — when attached to Ruby, verify behavior via `evaluate_expression`/breakpoints or have the program write a file. ## Attach instead of launch For an already-running process (including remote machines, containers, and Kubernetes pods via port-forward): ```text attach_to_process {sessionId, host: "localhost", port: 5678, sourcePaths: [""], adapterConfig: {...}} ``` - **Attach pauses the target by default** (omitting `stopOnEntry` means `true` — the opposite of `start_debugging`). Pass `stopOnEntry: false` for a live service you must not freeze. A response with `pending: true` means the pause lands when the target next runs code (`wait_for_stop` blocks until it does); `continue_execution` releases it. - **Python**: target ran `python -m debugpy --listen : ...`; to address breakpoints by local-checkout path, map it onto the debuggee tree with `adapterConfig: {pathMappings: [{localRoot: "", remoteRoot: "/app"}]}` - **Ruby**: target ran `rdbg --open --port ...` (works through `kubectl port-forward`); `localfsMap: "/app:"` maps paths - **Java**: target JVM has `-agentlib:jdwp=transport=dt_socket,server=y,address=*:`; breakpoints in not-yet-loaded classes are deferred automatically, and a fully-qualified class name as `file` needs no source files at all - **C/C++ (and other native)**: attach by PID instead of port — `attach_to_process {sessionId, processId: , adapterConfig: {program: ""}}`; in a Kubernetes ephemeral debug container use `processId: 1` with `program: "/proc/1/root/"` (on Linux, mind `kernel.yama.ptrace_scope`) - **COBOL**: attach by PID like C/C++, plus `adapterConfig: {sources: ["", …], dialect: "ibm"}` (the manifest is regenerated by a translate-only `cobc -C`; pass the options the binary was built with) or `adapterConfig: {manifestDirs: [""]}` (an earlier source launch's `.debug-mcp/cobol///`, needs no cobc) so variables are COBOL-shaped; without either only the engine's C view is available. A job paused inside libcob still shows its program's data division (scopes from the nearest COBOL frame up the stack) Breakpoint paths on attach are sent **verbatim** and resolved against the **target's** filesystem (host-side existence checks are skipped). Without a mapping, use debuggee-side paths — `get_stack_trace` shows the paths the target uses — or address by symbol (`{function: "name"}`), which needs no paths. `adapterConfig` keys the adapter cannot forward into its attach request are named in the response's `warning`. Direct-connect attaches (debugpy, rdbg) need no local language toolchain — the debug engine runs inside the target. `list_supported_languages` reports per-mode availability (`modes.launch` / `modes.attach`) with reasons. `detach_from_process` leaves the target running; `close_debug_session` after detach cleans up the session. For Kubernetes pods, don't re-derive attach configs: the [Kubernetes recipe](https://github.com/debugmcp/mcp-debugger/blob/main/docs/kubernetes.md#breakpoints-and-paths-on-attach-read-this-once) (pattern decision table, path rules) and the per-language [attach presets](https://github.com/debugmcp/mcp-debugger/blob/main/examples/kubernetes/attach-presets.md) have verified copy-paste calls. ## IDE mirror (let a human look around) When a human wants to inspect your live session in their IDE — CI flake parked at the failing state, a long-running attach session that hit an anomaly — expose it: ```text expose_session {sessionId} -> {host: "127.0.0.1", port, token} ``` Relay the endpoint with a ready-to-paste VS Code config: `{"name": "Mirror", "type": "", "request": "attach", "debugServer": , "mirrorToken": ""}`. Their IDE attaches read-only and lands directly on the paused frame: stacks, scopes, variables, and evaluate all work; stepping, continuing, and breakpoint changes are rejected — execution control stays with you. `unexpose_session {sessionId}` disconnects IDE clients and closes the endpoint (it also closes on session close/restart/exit). Loopback-only; the token is required and should be treated as sensitive. ## Crash diagnosis - Launch sessions pause at uncaught exceptions **by default** (`breakOnExceptions: "uncaught"`) with the stack and locals live instead of losing the session — pass `"none"` to opt out, or `"all"` to also stop on caught raises (language-dependent). Ruby is the exception: rdbg has no uncaught-only filter, so Ruby crashes still run to termination unless you pass `"all"`. Attach sessions apply no default — pass the mode explicitly. - On an exception stop, `lastStop.description`/`lastStop.text` carry the exception class and message; where the adapter supports it (Python, JS, Java, .NET), `lastStop.exceptionInfo` adds `exceptionId`, `breakMode`, and details (it lands a moment after the pause — re-query if absent). After termination, `exitCode` in `list_debug_sessions` distinguishes a crash (non-zero) from a clean exit. ## Current limitations (be honest with yourself) - `pause_execution` support varies by adapter; prefer breakpoints over pausing a free-running program. - Variable responses are size-guarded (values truncated past ~1KB, capped variable counts/response size, all env-tunable); a `truncation` field says what was cut and suggests narrowing with `names: [...]` or a targeted `evaluate_expression`. ## Language specifics Read the matching reference before your first session in a language — each has load-bearing quirks: | Language | Reference | Headline quirk | |---|---|---| | Python | references/python.md | expand "special variables" containers; late breakpoint verification | | JavaScript/TS | references/javascript.md | child-session architecture; internals filtered from stacks | | Ruby | references/ruby.md | stops at load only with stopOnEntry (rdbg continues by itself otherwise); attach captures no stdout | | Rust | references/rust.md | GNU toolchain on Windows; scriptPath = source file, adapter finds Cargo project | | Go | references/go.md | Delve native DAP; optimized-binary locals warning | | Java | references/java.md | javac -g required; FQCN breakpoints; redefine_classes hot-swap | | .NET/C# | references/dotnet.md | scriptPath = compiled .dll; Portable PDB required | | C/C++ | references/cpp.md | scriptPath = binary (-gdwarf-4 -O0) or lone .c/.cpp (auto-compiled); attach by PID; MinGW/DWARF on Windows | | COBOL | references/cobol.md | scriptPath = .cob/.cbl source (auto-compiled with GnuCOBOL) or prebuilt exe; dapLaunchArgs {dialect, format, copybookDirs, runtimeChecks, stdinFile}; COBOL-shaped variables; breakpoints in copybooks; paragraph function breakpoints; `{WS-NAME}` logpoints; PERFORM-aware step_over/step_out |