This guide explains how to correctly use the MCP Debugger tools when testing debugging functionality across all supported languages (Python, Ruby, JavaScript, Rust, Go, Java, .NET/C#, and C/C++).
What to expect:
- The debugger stops at your breakpoints in user code
- Variables are accessible when stopped at user breakpoints
How it works:
- The multi-session architecture properly routes evaluate commands to the active debugging context
- You can immediately evaluate expressions when stopped at breakpoints
- When
stopOnEntryis false (the default), the debugger auto-continues past entry breakpoints so execution advances to user code automatically
What to expect:
- Variables appear in a hierarchical structure
- You may see "special variables" as a container that needs to be expanded
How to access variables:
- Call
get_variableswith the scope ID (e.g.,scope: 3for Locals) - If you get
{"name":"special variables","variablesReference":5}, this is a container - Call
get_variablesagain withscope: 5(the variablesReference) to expand it - This will reveal the actual variables (
a,b, etc.)
// File: test.js
function compute(a, b) {
const product = a * b; // Line 3 - set breakpoint here
return product;
}
compute(5, 10);Testing sequence:
# 1. Create session
session_id = create_debug_session(language="javascript")
# 2. Set breakpoint
set_breakpoint(sessionId=session_id, file="/path/to/test.js", line=3)
# 3. Start debugging
start_debugging(sessionId=session_id, scriptPath="/path/to/test.js")
# Should stop at line 3 if the breakpoint is hit; if the debugger stops at
# a Node.js internal frame first, use continue_execution to advance to user code.
# 4. Get stack trace
get_stack_trace(sessionId=session_id)
# Should show test.js in the stack, not Node.js internals
# 5. Evaluate expressions
evaluate_expression(sessionId=session_id, expression="a") # Returns: "5"
evaluate_expression(sessionId=session_id, expression="b") # Returns: "10"
evaluate_expression(sessionId=session_id, expression="typeof compute") # Returns: "function"
# 6. Step over
step_over(sessionId=session_id)
# Now at line 4
# 7. Evaluate product
evaluate_expression(sessionId=session_id, expression="product") # Returns: "50"# File: test.py
def main():
a = 1 # Line 2
b = 2 # Line 3 - set breakpoint here
c = a + b # Line 4
return c
if __name__ == "__main__":
main()Testing sequence:
# 1. Create session
session_id = create_debug_session(language="python")
# 2. Set breakpoint
set_breakpoint(sessionId=session_id, file="/path/to/test.py", line=3)
# Note: Python breakpoints initially report as unverified; they are
# verified asynchronously by debugpy once the module is loaded.
# 3. Start debugging
start_debugging(sessionId=session_id, scriptPath="/path/to/test.py")
# Stops at line 3
# 4. Get scopes (use the actual frame ID from get_stack_trace, not a hardcoded value)
stack = get_stack_trace(sessionId=session_id)
frame_id = stack["stackFrames"][0]["id"] # Use the top frame's actual ID
scopes = get_scopes(sessionId=session_id, frameId=frame_id)
# Returns: [{"name":"Locals","variablesReference":3}, {"name":"Globals","variablesReference":4}]
# 5. Get variables (first level)
vars = get_variables(sessionId=session_id, scope=3)
# May return: {"name":"special variables","variablesReference":5}
# 6. Expand special variables (if needed)
if vars.get("variablesReference"):
actual_vars = get_variables(sessionId=session_id, scope=vars["variablesReference"])
# Now returns: [{"name":"a","value":"1"}, {"name":"b","value":"2"}, ...]
# 7. Evaluate expressions
evaluate_expression(sessionId=session_id, expression="a") # Returns: "1"
evaluate_expression(sessionId=session_id, expression="a + b") # Returns: "3"
# Note: The evaluate_expression tool uses the 'variables' context by default,
# which is intended for watch-style evaluation. Whether state-mutating
# expressions (e.g., "x = 5") work depends entirely on the debug adapter;
# debugpy may or may not allow mutations in this context.Solution: Use continue_execution to move past internal frames. Stack trace filtering hides internal frames by default for supported languages.
Solution: This is normal hierarchical organization. Use the variablesReference to expand:
# Step 1: Get initial scope
vars = get_variables(scope=3) # Returns special variables container
# Step 2: Expand using variablesReference
if "variablesReference" in vars:
actual_vars = get_variables(scope=vars["variablesReference"])Possible causes:
- Wrong scope: Ensure you're evaluating in the correct frame context
- Not yet defined: Variable hasn't been executed yet - step to after its assignment
- Out of scope: Variable is in a different function or scope
Solution:
- Use
get_stack_trace()to see current location - Step over assignment lines before evaluating variables
- Check the current frame ID and use it in evaluate_expression
-
Always check session state before operations:
- Must be
PAUSEDfor: evaluate, step operations, get variables - For
set_breakpoint: session must not beTERMINATED(breakpoints can be set in any non-terminated state, including before debugging starts)
- Must be
-
Use absolute paths for file references to avoid ambiguity
-
Wait for proper state after operations:
- After
start_debugging: Wait forPAUSEDstate if breakpoint set - After
continue_execution: Session becomesRUNNING - After
step_*: Wait forPAUSEDstate
- After
-
Handle variable hierarchies in Python:
- Always check for
variablesReferencein responses - Recursively expand containers to access nested variables
- Always check for
-
Frame context matters:
- If
evaluate_expressionfails, check you're using the correct frame - Use
get_stack_traceto find the right frame ID -- use theidfield from the stack frame object, not the array index - The top frame (first element in the
stackFramesarray) is usually what you want, but itsidis assigned by the debug adapter and is NOT necessarily 0
- If
- Session created successfully
- Breakpoints set and verified
- Debugging starts without timeout
- Stops at user breakpoints (not internals)
- Stack trace shows user code
- Variables are accessible (after expanding containers if needed)
- Expressions evaluate correctly
- Step operations work as expected
- Continue execution resumes properly
- Session closes cleanly
Prerequisites: Rust toolchain (rustc, cargo) installed. CodeLLDB debug adapter is vendored via the build:adapter script (run pnpm -w -F @debugmcp/codelldb-common run build:adapter), not during pnpm install.
Testing sequence:
# 1. Create session
session_id = create_debug_session(language="rust")
# 2. Set breakpoint (use absolute path to the source file)
set_breakpoint(sessionId=session_id, file="/path/to/src/main.rs", line=5)
# 3. Start debugging (scriptPath is the source file; the adapter resolves the
# enclosing Cargo project and may build/locate the binary before debugging)
start_debugging(sessionId=session_id, scriptPath="/path/to/src/main.rs")
# 4. Get stack trace and use actual frame IDs
stack = get_stack_trace(sessionId=session_id)
frame_id = stack["stackFrames"][0]["id"]
# 5. Inspect variables
get_local_variables(sessionId=session_id)Prerequisites: Ruby 2.7+ installed. rdbg must be available through the standard debug gem.
Testing sequence:
# 1. Create session
session_id = create_debug_session(language="ruby")
# 2. Set breakpoint
set_breakpoint(sessionId=session_id, file="/path/to/app.rb", line=12)
# 3. Start debugging
start_debugging(sessionId=session_id, scriptPath="/path/to/app.rb")
# 4. Inspect variables
get_local_variables(sessionId=session_id)Prerequisites: Go 1.18+ installed. Delve debugger must be installed: go install github.com/go-delve/delve/cmd/dlv@latest.
Testing sequence:
# 1. Create session
session_id = create_debug_session(language="go")
# 2. Set breakpoint
set_breakpoint(sessionId=session_id, file="/path/to/main.go", line=10)
# 3. Start debugging
start_debugging(sessionId=session_id, scriptPath="/path/to/main.go")
# 4. Inspect variables
get_local_variables(sessionId=session_id)Prerequisites: JDK 21+ installed. Uses JDI bridge -- the adapter attempts to locate pre-compiled bridge classes and may compile them on demand at command-build time if not found.
Key notes:
- Compile target code with
javac -gfor full variable inspection - For breakpoints, you can use a fully-qualified class name (e.g.,
"com.example.MyClass") instead of a file path - Use
dapLaunchArgsto passmainClassandclasspath
Testing sequence:
# 1. Create session
session_id = create_debug_session(language="java")
# 2. Set breakpoint (using FQCN)
set_breakpoint(sessionId=session_id, file="com.example.Main", line=10)
# 3. Start debugging with adapter-specific config
start_debugging(
sessionId=session_id,
scriptPath="/path/to/Main.java",
dapLaunchArgs={"mainClass": "com.example.Main", "classpath": "/path/to/classes"}
)
# 4. Inspect variables
get_local_variables(sessionId=session_id)Prerequisites: netcoredbg must be installed (set NETCOREDBG_PATH or add to PATH). A .NET SDK is needed to compile your target application.
Key notes:
- PDB symbols must be in Portable format (compile with
/debug:portable) - Uses TCP-to-stdio bridge on all platforms
Testing sequence:
# 1. Create session
session_id = create_debug_session(language="dotnet")
# 2. Set breakpoint
set_breakpoint(sessionId=session_id, file="/path/to/Program.cs", line=10)
# 3. Start debugging (pass compiled target, not source file)
start_debugging(
sessionId=session_id,
scriptPath="/path/to/bin/Debug/net8.0/YourApp.dll",
dapLaunchArgs={"program": "/path/to/bin/Debug/net8.0/YourApp.dll"}
)
# 4. Inspect variables
get_local_variables(sessionId=session_id)The MCP Debugger is fully functional for Python, Ruby, JavaScript, Rust, Go, Java, and .NET/C#. The key insights are:
- JavaScript: Stack trace filtering hides internal frames; may need
continue_executionif initially stopped at internals - Python: Use variablesReference to expand variable containers
- Ruby: Supports launch and attach flows through
rdbg; use Bundler mode for Rails and RSpec-style entrypoints - Rust: CodeLLDB adapter is vendored; the GNU toolchain is required for reliable debugging -- MSVC-built binaries may produce errors with CodeLLDB. Set
RUST_MSVC_BEHAVIORenv var to control MSVC handling - Go: Uses Delve's native DAP support
- Java: Use FQCN for breakpoints, pass
mainClass/classpathviadapLaunchArgs - .NET: Requires netcoredbg; uses TCP-to-stdio bridge
- All languages: Use actual frame IDs from
get_stack_trace(not hardcoded 0), and ensure proper state and context for operations
Following this guide will help you successfully test and use all debugging features without encountering the previously reported issues.