jonmmease/jons-mcp-rust-debug
If you are the rightful owner of jons-mcp-rust-debug and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to dayong@mcphub.com.
A Model Context Protocol (MCP) server that provides Rust debugging capabilities through LLDB Python API integration.
Jon's MCP Rust Debug Server
A Model Context Protocol (MCP) server that provides Rust debugging capabilities through LLDB Python API integration.
Overview
The MCP Rust Debug Server implements the Model Context Protocol to expose Rust debugger functionality using LLDB's Python API. This provides direct, reliable debugging control through LLDB's native Python bindings.
The server enables MCP clients to control debugging of Rust binaries, tests, and examples through a standardized interface.
Architecture
This implementation uses LLDB's Python API for direct debugging control:
- Each debug session uses lldb.SBDebugger for direct API access
- No subprocess or PTY communication needed
- Event-driven architecture with LLDB's listener/event system
- Sessions are managed independently, allowing multiple concurrent debugging sessions
- Direct access to debugging objects (breakpoints, variables, frames)
How It Works
The server uses the lldb-python package to provide direct access to LLDB's debugging capabilities through its Python API:
-
Session Creation: When you start debugging, the server:
- Builds your Rust target using
cargo build - Creates an LLDB debugger instance (
lldb.SBDebugger) - Loads the compiled binary as a target
- Sets up an event listener for asynchronous debugging events
- Builds your Rust target using
-
Event-Driven Control: A background thread monitors LLDB events:
- Process state changes (running, stopped, exited)
- Breakpoint hits
- Thread state updates
- The event handler updates session state in real-time
-
Direct API Access: All debugging operations use LLDB's structured API:
- Breakpoints:
target.BreakpointCreateByLocation() - Execution:
process.Continue(),thread.StepOver() - Inspection:
frame.EvaluateExpression(),value.GetSummary() - No text parsing or command interpretation needed
- Breakpoints:
-
Multi-threaded Debugging: The server properly handles multi-threaded programs:
- Automatically selects the thread that hit a breakpoint
- Allows manual thread switching
- Maintains correct context for variable inspection
-
Clean Shutdown: Sessions can be stopped gracefully:
- Process termination handled in background thread
- Resources cleaned up without blocking
- Event threads exit cleanly
Features
- LLDB Python API: Direct, reliable debugging through LLDB's native Python bindings
- Cargo Integration: Automatically builds targets before debugging
- Session Management: Create and manage multiple debugging sessions concurrently
- Breakpoint Control: Set, remove, and list breakpoints with conditions
- Watchpoint Support: Monitor memory locations and variables for changes with read/write watchpoints
- Execution Control: Run, step, next, finish, and continue-to-line commands with clear action feedback
- Stack Navigation: Navigate up and down the call stack, view backtraces
- Variable Inspection: Examine variables with direct API access, evaluate expressions, list locals
- Variable Modification: Set variables to new values during debugging sessions
- Array Inspection: Print array and slice contents with automatic type detection
- Source Code Display: View source code around current execution point
- Rust Panic Handling: Automatically sets breakpoints on rust_panic
- Test Debugging: Special support for debugging Rust tests with test summary
- Event-Driven: Uses LLDB's event system for responsive debugging
- Session State Tracking: Track execution state and stop reasons
Installation
Prerequisites
- Rust toolchain (cargo, rustc)
- Python 3.10-3.12 (required by lldb-python package)
Using uv (recommended)
# Clone the repository
git clone https://github.com/jonmmease/jons-mcp-rust-debug.git
cd jons-mcp-rust-debug
# Run the server
uv run jons-mcp-rust-debug
Direct from GitHub
# Run directly from GitHub
uvx --from git+https://github.com/jonmmease/jons-mcp-rust-debug jons-mcp-rust-debug
Adding to Claude Code as MCP Server
To use this with Claude Code, add it using the CLI:
claude mcp add jons-mcp-rust-debug uvx -- --from git+https://github.com/jonmmease/jons-mcp-rust-debug jons-mcp-rust-debug
The server will be available in Claude Code for debugging Rust applications.
Pagination Support
Many tools support pagination to handle large outputs efficiently. Pagination works with character-based limit and offset parameters:
limit: Maximum number of characters to returnoffset: Starting character position (0-based)
Tools with pagination support return a pagination object containing:
total_chars: Total characters availableoffset: Current offsetlimit: Limit usedhas_more: Whether more content is available
Supported tools:
backtrace- Stack traces can be very longrun- Program output can be extensivelist_source- Source files can be largeprint_variable- Complex data structures may have long representationsprint_array- Array contents can be very largeprint_slice- Slice contents can be very largelist_locals- Many local variables can produce long outputcheck_debug_info- Debug information can be very detailed
Example usage:
# Get first 1000 characters of backtrace
result = await backtrace(session_id="session_1", char_limit=1000, char_offset=0)
# Get next page if more content available
if result["pagination"]["has_more"]:
next_result = await backtrace(session_id="session_1", char_limit=1000, char_offset=1000)
Configuration
Create a rustdebugconfig.json file in your project root:
{
"environment": {
"RUST_BACKTRACE": "1"
}
}
All fields are optional:
cargo_path: Path to cargo executable (default: auto-detect)working_directory: Working directory for debugging (default: directory containing the config file)environment: Additional environment variablescargo_args: Additional cargo build arguments (e.g.,["--release"])
MCP Tools
Session Management
start_debug
Initialize a debugging session.
Args:
target_type: "binary", "test", or "example" (required)
target: Name of the specific target (optional for single binary)
args: Command line arguments (optional)
For tests: the test filter, e.g., ["test_name", "--exact"]
IMPORTANT: Use full module path for tests in a module: ["tests::test_name", "--exact"]
Run `cargo test --test <file> -- --list` to find the correct filter
cargo_flags: Additional cargo build flags (optional)
Example: ["--no-default-features", "--features", "test-only"]
env: Environment variables for the build process (optional)
Example: {"RUST_TEST_THREADS": "1", "CARGO_BUILD_TARGET": "x86_64-unknown-linux-gnu"}
package: Package name for workspace projects (optional)
When specified, the crate directory is automatically resolved from cargo metadata
Example: "my-crate"
root_directory: Absolute path to the Rust project root (optional)
- Single crate: The project directory (where Cargo.toml is)
- Workspace: The workspace root. Use with `package` to specify which crate
Returns:
session_id: Unique session identifier
status: "started" or error message
debugger: The debugger being used (lldb-api)
stop_debug
Terminate an active debugging session.
Args:
session_id: The session identifier
Returns:
status: "stopped" or error message
list_sessions
List all active debugging sessions.
Returns:
sessions: Array of session objects with details
Breakpoint Management
set_breakpoint
Set a breakpoint at specified location.
Args:
session_id: The session identifier
file: Source file path (optional if function specified)
line: Line number (optional if function specified)
function: Function name (optional if file/line specified)
condition: Conditional expression (optional)
temporary: One-time breakpoint (default: false)
Returns:
breakpoint_id: Unique breakpoint identifier
location: Resolved location
status: "set" or error message
remove_breakpoint
Remove a breakpoint.
Args:
session_id: The session identifier
breakpoint_id: Breakpoint identifier
Returns:
status: "removed" or error message
list_breakpoints
List all breakpoints.
Args:
session_id: The session identifier
Returns:
breakpoints: Array of breakpoint objects
Watchpoint Management
set_watchpoint
Set a watchpoint on a variable or memory address to pause execution when the value changes.
Args:
session_id: The session identifier
expression: Variable name or memory address to watch
watch_type: Type of watchpoint - "write", "read", or "read_write" (default: "write")
size: Size in bytes to watch (optional, auto-detected from expression)
condition: Conditional expression for the watchpoint (optional)
Returns:
watchpoint_id: Unique watchpoint identifier
address: Memory address being watched (hex format)
size: Size in bytes being monitored
watch_type: Type of watchpoint set
expression: Expression being watched
status: "set" or error message
list_watchpoints
List all watchpoints in the session.
Args:
session_id: The session identifier
Returns:
watchpoints: Array of watchpoint objects with details:
- id: Watchpoint identifier
- address: Memory address (hex format)
- size: Size in bytes
- watch_type: "write", "read", or "read_write"
- expression: Original expression
- condition: Conditional expression (if set)
- enabled: Whether watchpoint is active
- hit_count: Number of times triggered
remove_watchpoint
Remove a watchpoint.
Args:
session_id: The session identifier
watchpoint_id: Watchpoint identifier
Returns:
status: "removed" or error message
Execution Control
run
Start or continue program execution.
Args:
session_id: The session identifier
limit: Max characters to return (optional, for pagination)
offset: Starting character position (optional, for pagination)
Returns:
status: Current execution state
stop_reason: "breakpoint", "signal", "exited", etc.
stopped_at: Location where execution stopped
output: Debugger output
pagination: Pagination info (if limit specified)
step
Step into function calls (execute next line).
Args:
session_id: The session identifier
Returns:
location: New execution position
output: Debugger output
next
Step over function calls (execute next line in current function).
Args:
session_id: The session identifier
Returns:
location: New execution position
output: Debugger output
finish
Continue until current function returns.
Args:
session_id: The session identifier
Returns:
output: Debugger output
return_value: Value being returned (if available)
continue_to_line
Continue execution until the specified line is hit.
Args:
session_id: The session identifier
file: Source file path (relative or absolute)
line: Target line number
timeout_ms: Optional timeout in milliseconds
Returns:
status: Current execution state
stop_reason: Why execution stopped
stopped_at: Location where execution stopped
current_location: Current file:line position
target_location: The requested file:line
reached_target: Boolean indicating if the target was reached
file: File path where stopped
line: Line number where stopped
function: Function name where stopped
Stack Navigation
backtrace
Get current stack trace.
Args:
session_id: The session identifier
frame_limit: Maximum frames to return (optional)
char_limit: Max characters to return (optional, for pagination)
char_offset: Starting character position (optional, for pagination)
Returns:
frames: Array of stack frames
raw_output: Raw debugger output (paginated if limit specified)
pagination: Pagination info (if char_limit specified)
up
Move up in the stack (to caller).
Args:
session_id: The session identifier
count: Number of frames to move (default: 1)
Returns:
frame: New current frame information
output: Debugger output
down
Move down in the stack.
Args:
session_id: The session identifier
count: Number of frames to move (default: 1)
Returns:
frame: New current frame information
output: Debugger output
Inspection
list_source
Show source code around current or specified position.
Args:
session_id: The session identifier
line: Center line (optional, defaults to current)
count: Number of lines to show (default: 10)
Returns:
source: Source code
current_line: Currently executing line
print_variable
Print the value of a variable or expression.
For complex types (Result, DataFrame, etc.), use depth to limit output.
Args:
session_id: The session identifier
expression: Variable name or expression
limit: Max characters to return (optional, for pagination)
offset: Starting character position (optional, for pagination)
depth: Max nesting depth (default: 2). Use 1 for top-level only,
higher values for deeply nested structures.
Returns:
value: String representation
type: Type information
expression: The evaluated expression
depth: The depth used
pagination: Pagination info for value and type
set_variable
Set a variable to a new value during debugging.
Args:
session_id: The session identifier
variable: Variable name or path (e.g., "x", "config.timeout")
value: New value as string (e.g., "42", "true", "3.14")
frame_index: Stack frame index (default: 0 = current frame)
Returns:
status: "success" or "error"
variable: Variable name
old_value: Previous value
new_value: New value after assignment
type: Variable type
error: Error message if modification failed
list_locals
List all local variables in current scope.
Args:
session_id: The session identifier
Returns:
locals: Local variables with values
evaluate
Evaluate a Rust expression in the current context.
Args:
session_id: The session identifier
expression: Rust expression
Returns:
result: Evaluation result
error: Error message if evaluation failed
expression: The evaluated expression
print_array
Print elements of an array, slice, Vec, or pointer range.
Usage patterns:
- Vec/slice:
my_vec.as_ptr()with count - Fixed array:
&my_array[0]with count - Raw pointer:
my_ptrwith count - Custom struct:
my_struct.data_ptrwith count
Args:
session_id: The session identifier
expression: Pointer expression (e.g., "my_vec.as_ptr()", "&arr[0]", "data_ptr")
count: Number of elements to print (required)
start: Starting index (default: 0)
element_type: Optional type for pointer casting (e.g., "i32")
limit: Max characters to return (optional, for pagination)
offset: Starting character position (optional, for pagination)
Returns:
output: Formatted array elements
expression: The evaluated expression
start: Starting index used
count: Number of elements printed
element_type: Type used (if specified)
pagination: Pagination info (if limit specified)
get_test_summary
Get test-specific information (test sessions only).
Args:
session_id: The session identifier
Returns:
session_type: "test"
target: Test target name
state: Current debugger state
last_stop_reason: Why execution stopped
test_functions: List of test functions in backtrace
panic_info: Details about panic if stopped on assertion
test_results: Summary of passed/failed/ignored tests
get_enum_info
Get enum variant information for better understanding of discriminant values.
Args:
session_id: The session identifier
type_name: The enum type name (e.g., "Option<i32>", "MyEnum")
Returns:
type_name: The requested type name
variants: Dictionary mapping discriminant values to variant names
raw_output: Raw debugger output
session_diagnostics
Get detailed diagnostic information about the debugging session.
Args:
session_id: The session identifier
Returns:
session_id: The session ID
debugger_type: Type of debugger (gdb/lldb/rust-gdb/rust-lldb)
state: Current state (idle/running/paused/finished/error)
has_started: Whether the program has been run
last_stop_reason: Why execution stopped
current_location: Current file:line if stopped
breakpoints: Number of breakpoints set
process_alive: Whether debugger process is running
thread_info: Thread information
frame_info: Current frame information
program_status: Program execution status
is_stopped: Whether actually stopped
actual_stop_reason: Real stop reason from debugger
context_tests: Results of context accessibility tests
check_debug_info
Check debug symbol and source mapping information.
Args:
session_id: The session identifier
Returns:
debug_info: Dictionary containing:
- images: Loaded binary images (LLDB)
- source_map: Source mapping settings (LLDB)
- source_info: Current source information (LLDB)
- loaded_files: Loaded files (GDB)
- sources: Available source files (GDB)
Platform Support
- macOS: Best support with lldb-python package
- Linux: Good support with lldb-python package
- Windows: Limited support - LLDB availability varies
Requirements
- Rust toolchain installed (cargo, rustc)
- Python 3.10-3.12
- lldb-python package (automatically installed)
Usage Examples
Basic Binary Debugging
# Start debugging a simple binary
await start_debug(
target_type="binary",
target="myapp",
root_directory="/path/to/my-project"
)
Debugging with Custom Features
# Debug with specific features enabled
await start_debug(
target_type="test",
target="integration_test",
cargo_flags=["--no-default-features", "--features", "test-only"],
env={"RUST_TEST_THREADS": "1"},
root_directory="/path/to/my-project"
)
Workspace-Specific Debugging
# Debug a specific crate in a workspace
# The tool automatically resolves the crate directory from cargo metadata
await start_debug(
target_type="binary",
target="server",
package="backend-crate",
cargo_flags=["--release"],
env={"RUST_LOG": "debug"},
root_directory="/path/to/workspace"
)
Cross-Platform Debugging
# Debug for a specific target platform
await start_debug(
target_type="binary",
target="myapp",
env={"CARGO_BUILD_TARGET": "x86_64-unknown-linux-musl"},
root_directory="/path/to/my-project"
)
Debugging Tests Guide
This section covers common patterns and gotchas when debugging Rust tests.
Single Crate vs Workspace
For single crate projects, just specify root_directory:
start_debug(target_type="test", target="my_test",
root_directory="/path/to/my-project")
For workspace projects, specify both root_directory (workspace root) and package:
start_debug(target_type="test", target="my_test",
package="my-crate",
root_directory="/path/to/workspace")
The tool automatically resolves the crate's directory using cargo metadata.
Test Target Naming
Integration tests (in tests/ directory):
target: the test file name without.rsextensionargs: filter path starts from within the test file
// tests/test_data.rs
// Test directly in file:
#[test]
fn my_test() { }
// → args: ["my_test", "--exact"]
// Test inside mod tests {}:
mod tests {
#[test]
fn my_test() { }
}
// → args: ["tests::my_test", "--exact"]
Lib tests (in src/ with #[cfg(test)]):
target: leave empty (builds all lib tests)args: filter path includes the source module name
// src/lyon_utils.rs
mod tests {
#[test]
fn test_multi_polygon() { }
}
// → args: ["lyon_utils::tests::test_multi_polygon", "--exact"]
// src/foo/bar.rs
mod tests {
#[test]
fn test_something() { }
}
// → args: ["foo::bar::tests::test_something", "--exact"]
This is the key difference: integration test filters start from within the file, while lib test filters include the full module path from the crate root.
To find the correct filter, run:
# For integration tests
cargo test --test <test_file> -- --list
# For lib tests
cargo test --lib -- --list
Breakpoint File Paths
Use the filename only for breakpoints, not the full path:
# Recommended - filename only
set_breakpoint(session_id, file="test_data.rs", line=117)
# Also works
set_breakpoint(session_id, file="tests/test_data.rs", line=117)
# May cause issues - avoid absolute paths
set_breakpoint(session_id, file="/full/absolute/path/to/test_data.rs", line=117)
Async Test Support
Async tests using #[tokio::test], #[async_std::test], etc. are fully supported. The debugger automatically handles breakpoints on spawned threads.
#[tokio::test]
async fn test_async_operation() {
let result = some_async_fn().await; // ← breakpoint here works
}
Note on async locals: At certain points (like loop starts or await boundaries),
list_locals may show the future wrapper (body) rather than your variables.
This is how Rust async works - variables live inside the future's state machine.
Step forward or use print_variable with specific variable names to see values.
Complete Example: Debugging an Integration Test
Single crate:
# 1. Start session
result = await start_debug(
target_type="test",
target="integration",
args=["tests::test_foo", "--exact"],
root_directory="/path/to/my-crate"
)
session_id = result["session_id"]
# 2. Set breakpoint (filename only)
await set_breakpoint(session_id, file="integration.rs", line=25)
# 3. Run to breakpoint
await run(session_id) # → stops at breakpoint
Workspace:
# Specify package - tool automatically resolves crate directory
result = await start_debug(
target_type="test",
target="integration",
package="my-crate",
args=["tests::test_foo", "--exact"],
root_directory="/path/to/workspace"
)
Technical Implementation
LLDB Python API Benefits
The server leverages LLDB's Python API (via the lldb-python package) which provides several advantages:
- Type-safe bindings: Direct access to LLDB objects without text parsing
- Reliable operation: No issues with command echoing or output capture
- Better performance: No subprocess overhead or PTY emulation
- Rich functionality: Full access to LLDB's debugging capabilities
- Professional-grade: Same approach used by tools like CodeLLDB
Limitations
- Requires LLDB with Python bindings (no GDB support)
- Windows support is limited due to LLDB availability
- Some LLDB features may not be fully exposed through the API
Troubleshooting
Common Issues
- "LLDB Python module not found":
- Run:
uv pip install lldb-python --prerelease=allow - Ensure you're using Python 3.10-3.12
- Run:
- Build failures: Ensure cargo can build your project normally
- Permission denied on macOS: LLDB may require developer tools or code signing
- String literals show extra text:
&strmay display adjacent literals (e.g., "seattlestocksbarley...") because string literals are stored contiguously in the binary. This is normal - focus on the start of the string.
Debugging the Debugger
If you're experiencing issues with the debugger not working as expected:
-
Use session_diagnostics: Run this tool first to understand the current state
result = await session_diagnostics(session_id) print(result["is_stopped"]) # Check if actually stopped print(result["actual_stop_reason"]) # See real stop reason print(result["context_tests"]) # Check what's accessible -
Variables showing as "unknown":
- Ensure the program is actually stopped at a breakpoint (not just set)
- Run the program first with the
runcommand - Check
session_diagnosticsto verify the debugger state
-
Empty enum info:
- Try with just the base type name without generics (e.g., "Option" instead of "Option
") - Some complex generic types may not be fully supported
- Try with just the base type name without generics (e.g., "Option" instead of "Option
-
Minimal source listing:
- Make sure debug symbols are included in your build
- Try specifying an explicit line number
- Check that source files are accessible from the working directory
Development
# Install development dependencies
uv pip install -e ".[dev]"
# Format code
uv run black src/
# Lint code
uv run ruff check src/
Testing
The test suite needs to be rewritten for the LLDB Python API implementation. The previous tests were written for the subprocess-based approach and are no longer applicable.
License
MIT