This document describes how to use the mcp-debugger with Large Language Models (LLMs) for step-through debugging, based on real testing conducted on 2025-06-11.
- Node.js 22+
- Python 3.7+ with debugpy (for Python debugging)
npm install -g @debugmcp/mcp-debuggergit clone https://github.com/debugmcp/mcp-debugger.git
cd mcp-debugger
pnpm install
npm run buildAdd the server to your MCP settings:
{
"mcpServers": {
"mcp-debugger": {
"command": "mcp-debugger",
"args": ["stdio"],
"disabled": false,
"autoApprove": ["create_debug_session", "set_breakpoint", "get_variables"]
}
}
}If running from a source checkout instead of a global install, use the CLI entrypoint:
{
"mcpServers": {
"mcp-debugger": {
"command": "node",
"args": ["C:/path/to/mcp-debugger/packages/mcp-debugger/dist/cli", "stdio"],
"disabled": false,
"autoApprove": ["create_debug_session", "set_breakpoint", "get_variables"]
}
}
}Here's a real example of debugging a Python script with a bug:
# swap_vars.py
# A simple script that swaps two variables, with an intentional bug for debugging.
def swap_variables(a, b):
print(f"Initial values: a = {a}, b = {b}")
# Intentionally buggy swap logic for demonstration
# Correct logic would use a temporary variable: temp = a; a = b; b = temp
# Or Python's tuple assignment: a, b = b, a
a = b # Bug: 'a' loses its original value here
b = a # Bug: 'b' gets the new value of 'a' (which is original 'b')
print(f"Swapped values: a = {a}, b = {b}")
return a, b
def main():
x = 10
y = 20
print("Starting variable swap demo...")
swapped_x, swapped_y = swap_variables(x, y)
# Verification
if swapped_x == 20 and swapped_y == 10:
print("Swap successful!")
else:
print(f"Swap NOT successful. Expected x=20, y=10 but got x={swapped_x}, y={swapped_y}")
if __name__ == "__main__":
main()// Tool: create_debug_session
// Request:
{
"language": "python",
"name": "Investigate Swap Bug"
}
// Response:
{
"success": true,
"sessionId": "a4d1acc8-84a8-44fe-a13e-28628c5b33c7",
"message": "Created python debug session: Investigate Swap Bug"
}Set a breakpoint where the bug occurs:
// Tool: set_breakpoint
// Request:
{
"sessionId": "a4d1acc8-84a8-44fe-a13e-28628c5b33c7",
"file": "swap_vars.py",
"line": 10
}
// Response:
{
"success": true,
"breakpointId": "28e06119-619e-43c0-b029-339cec2615df",
"file": "C:\\path\\to\\swap_vars.py",
"line": 10,
"verified": false,
"message": "Breakpoint set at C:\\path\\to\\swap_vars.py:10"
}// Tool: start_debugging
// Request:
{
"sessionId": "a4d1acc8-84a8-44fe-a13e-28628c5b33c7",
"scriptPath": "swap_vars.py"
}
// Response:
{
"success": true,
"state": "paused",
"message": "Debugging started for swap_vars.py. Current state: paused",
"data": {
"message": "Debugging started for swap_vars.py. Current state: paused",
"reason": "breakpoint"
}
}// Tool: get_stack_trace
// Request:
{
"sessionId": "a4d1acc8-84a8-44fe-a13e-28628c5b33c7"
}
// Response:
{
"success": true,
"stackFrames": [
{
"id": 3,
"name": "swap_variables",
"file": "C:\\path\\to\\swap_vars.py",
"line": 10,
"column": 1
},
{
"id": 4,
"name": "main",
"file": "C:\\path\\to\\swap_vars.py",
"line": 21,
"column": 1
},
{
"id": 2,
"name": "<module>",
"file": "C:\\path\\to\\swap_vars.py",
"line": 30,
"column": 1
}
],
"count": 3
}// Tool: get_scopes
// Request:
{
"sessionId": "a4d1acc8-84a8-44fe-a13e-28628c5b33c7",
"frameId": 3
}
// Response:
{
"success": true,
"scopes": [
{
"name": "Locals",
"variablesReference": 5,
"expensive": false,
"presentationHint": "locals",
"source": {}
},
{
"name": "Globals",
"variablesReference": 6,
"expensive": false,
"source": {}
}
]
}// Tool: get_variables
// Request:
{
"sessionId": "a4d1acc8-84a8-44fe-a13e-28628c5b33c7",
"scope": 5
}
// Response:
{
"success": true,
"variables": [
{"name": "a", "value": "10", "type": "int", "variablesReference": 0, "expandable": false},
{"name": "b", "value": "20", "type": "int", "variablesReference": 0, "expandable": false}
],
"count": 2,
"variablesReference": 5
}// Tool: step_over
// Request:
{
"sessionId": "a4d1acc8-84a8-44fe-a13e-28628c5b33c7"
}
// Response:
{
"success": true,
"state": "paused",
"message": "Stepped over"
}// Tool: get_variables
// Request:
{
"sessionId": "a4d1acc8-84a8-44fe-a13e-28628c5b33c7",
"scope": 5
}
// Response:
{
"success": true,
"variables": [
{"name": "a", "value": "20", "type": "int", "variablesReference": 0, "expandable": false},
{"name": "b", "value": "20", "type": "int", "variablesReference": 0, "expandable": false}
],
"count": 2,
"variablesReference": 5
}Now we can see the bug! After a = b, both variables have the value 20.
You can also evaluate arbitrary expressions in the current debug context:
// Tool: evaluate_expression
// Request:
{
"sessionId": "a4d1acc8-84a8-44fe-a13e-28628c5b33c7",
"expression": "a == b"
}
// Response:
{
"success": true,
"result": "True",
"type": "bool",
"variablesReference": 0,
"message": "Evaluated expression: a == b"
}// Tool: continue_execution
// Request:
{
"sessionId": "a4d1acc8-84a8-44fe-a13e-28628c5b33c7"
}
// Response:
{
"success": true,
"state": "running",
"message": "Continued execution"
}// Tool: close_debug_session
// Request:
{
"sessionId": "a4d1acc8-84a8-44fe-a13e-28628c5b33c7"
}
// Response:
{
"success": true,
"message": "Closed debug session: a4d1acc8-84a8-44fe-a13e-28628c5b33c7"
}- All session IDs are UUIDs in the format:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - Sessions can terminate unexpectedly, always check if a session exists before operations
- The
variablesReferencefromget_scopesis what you pass toget_variables - This is NOT the same as the frame ID from
get_stack_trace - Common mistake: Using frame ID instead of variablesReference
- Breakpoints initially show
"verified": falsebecause verification happens asynchronously by the debug adapter once the module is loaded (e.g., debugpy verifies after the script starts) - Avoid setting breakpoints on non-executable lines (comments, blank lines)
- Best lines for breakpoints: assignments, function calls, conditionals
- The server uses
SimpleFileCheckerfor both path validation and resolution. It returns aFileExistenceResultcontaining theeffectivePath(the resolved path actually used downstream). The server passes thiseffectivePathto SessionManager for all subsequent operations (breakpoints, launch, source context) - In container mode,
resolvePathForRuntime()rewrites paths to be under the workspace root (default/workspace/), thenSimpleFileCheckervalidates existence at that resolved location - In host mode,
SimpleFileCheckerrejects non-absolute resolved paths during preflight existence checks (relative paths may still pass through other code paths) - Use forward slashes (/) or escaped backslashes (\\) in JSON
{
"code": -32603,
"message": "MCP error -32603: Failed to continue execution: Managed session not found: {sessionId}"
}Solution: The session has terminated. Create a new session.
{
"code": -32602,
"message": "scope (variablesReference) parameter is required and must be a number"
}Solution: Use the variablesReference from get_scopes, not the frame ID.
All 28 tools are fully implemented, including:
-
restart_debugging: One call terminates the current debuggee (if any) and relaunches with the same configuration; breakpoints re-apply automatically and the output buffer starts fresh (read from
since: 0). Works while running, paused, or after the program exited; attach sessions are rejected with a clear error. -
list_breakpoints / remove_breakpoint / clear_breakpoints: Full breakpoint lifecycle management. Listing shows each breakpoint's verified state and adapter-assigned id; removal (by id, or file+line) and clearing take effect immediately while the program is running or paused, and still work after the program exits so breakpoints can be adjusted before a relaunch.
-
pause_execution: Sends a DAP pause request and waits briefly (up to ~5s) for the program to stop; on a fresh stop it returns the stop reason (
data.stopReason). If the program cannot stop within the grace window (e.g. blocked in native code), it returns success withpending: trueand the paused state is picked up asynchronously. The session normally must be in therunningstate, but calling pause on an already paused session succeeds as a no-op. -
get_output: Returns the debuggee's stdout/stderr/console output, buffered per launch from DAP output events. Cursor-based (
since/nextSince) for incremental polling; output stays readable after the program exits until the session is closed. The same data is exposed as a subscribable MCP resource (debug://sessions/{id}/output). -
evaluate_expression: Evaluates arbitrary expressions in the current debug context. When
frameIdis not specified, the server infers it by fetching the stack trace and using the topmost frame -- this works reliably only when a single frame exists or the top frame is the desired context. Callers should provideframeIdexplicitly when debugging code with multiple stack frames. Expressions with side effects are allowed (can modify program state). -
expose_session / unexpose_session: Opens a read-only DAP mirror endpoint (loopback-only, token-gated) so an IDE such as VS Code can attach to the live session and inspect the paused state — threads, stack, scopes, variables, evaluate — while execution control stays with the MCP session. See tool-reference.md for the VS Code
launch.jsonrecipe.
- Always create a session first - No debugging operations work without an active session
- Check the stack trace - Understand where you are in the code before inspecting variables
- Get scopes before variables - You need the variablesReference to inspect variables
- Handle errors gracefully - Sessions can terminate, files might not exist
- Use meaningful session names - Helps when debugging multiple scripts
Last updated: 2026-08-19 (v0.24.0) - All 28 tools including breakpoint management, logpoints, restart_debugging, and the DAP mirror; variable responses are size-guarded (truncation field, narrow with names: [...]) and get_stack_trace may annotate hiddenFrames