Content
# jdwp-mcp
**Java debugging for LLMs via JDWP and Model Context Protocol**
An MCP server that enables Claude Code and other LLM tools to debug Java
applications through the Java Debug Wire Protocol (JDWP). Attach to running
JVMs, set breakpoints, inspect variables, and step through code—all through
natural language.
## Features
- **Remote Debugging**: Connect to any JVM started with JDWP enabled
- **Breakpoint Management**: Set/list/clear by class+line — with optional **hit-count** (stop on the
Nth hit) and **thread filters**, or set by **method name** (first line)
- **Stack Inspection**: Stack frames with typed local variables and resolved **source lines**
- **Execution Control**: **Step over/into/out**, continue, pause
- **Expression Evaluation**: `localVar`/`this` with `.field` and `.method(args)` chains, resolving
overloads by arity and walking the superclass chain. Args: int, long, boolean, null, "string"
- **Value Rendering**: Strings, typed objects (best-effort `toString()`), and **array contents**
- **Set Values**: write a local variable in a suspended frame
- **Thread Management**: tools default to the last thread that hit a breakpoint
- **Structured Events**: `get_last_event` emits a machine-readable `[event]` line (thread, class.method:line)
- **Safety**: a `panic` tool (clear all + resume) and a **watchdog** that auto-resumes a long-suspended
VM (`JDWP_WATCHDOG_SECS`, default 120) so a forgotten breakpoint can't freeze a shared instance
> This fork implements `debug.evaluate` and `debug.step_*` (stubs upstream) plus the safety,
> structured-event, array, set-value, and breakpoint-modifier features above.
## Quick Start
### 1. Start your Java app with JDWP enabled
```bash
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar myapp.jar
```
### 2. Build the MCP server
```bash
cargo build --release
```
### 3. Configure Claude Code
The easiest way to enable the MCP server for your project:
```bash
# From your Java project directory
claude mcp add --scope project jdwp /path/to/jdwp-mcp/target/release/jdwp-mcp
```
Adjust the path to match where you cloned this repository. The `--scope project` flag makes the debugger available only in your current Java project.
**Alternative**: Manual configuration via `.mcp.json`:
```json
{
"mcpServers": {
"jdwp": {
"command": "/path/to/jdwp-mcp/target/release/jdwp-mcp"
}
}
}
```
### 4. Debug with natural language
```
> Attach to the JVM at localhost:5005
> Set a breakpoint at com.example.HelloController line 65
> When it hits, show me the stack and the value of requestCount
```
## Available Tools
| Tool | Description |
|------|-------------|
| `debug.attach` | Connect to a JVM via JDWP |
| `debug.set_breakpoint` | Set a breakpoint by class+line, or by method name; optional `hit_count` and thread filter |
| `debug.list_breakpoints` | List active breakpoints |
| `debug.clear_breakpoint` | Remove a breakpoint |
| `debug.continue` | Resume execution |
| `debug.step_over` | Step over current line (defaults to last-hit thread) |
| `debug.step_into` | Step into a method call |
| `debug.step_out` | Step out of the current method |
| `debug.get_stack` | Stack frames with typed variables and source lines |
| `debug.evaluate` | Evaluate `var`/`this` + `.field` / `.method(args)` chains in a frame |
| `debug.set_value` | Set a local variable in a suspended frame |
| `debug.get_last_event` | Last event, with a machine-readable `[event]` line (thread, class.method:line) |
| `debug.list_threads` | List all threads |
| `debug.pause` | Pause execution (suspend all threads) |
| `debug.panic` | Safety: clear all breakpoints and resume all threads |
| `debug.disconnect` | End the debug session |
Most tools take `thread_id` as an optional hex string (e.g. `"0x2"`); when omitted they default to
the last thread that hit a breakpoint. A watchdog auto-resumes a VM left suspended for too long
(`JDWP_WATCHDOG_SECS`, default 120).
## Example: Debugging with kubectl port-forward
For Kubernetes-deployed Java apps:
```bash
# Forward JDWP port from pod
kubectl port-forward pod/my-app-pod 5005:5005
```
Then in Claude Code:
```
> Attach to localhost:5005
> Set a breakpoint in the processRequest method
```
## Architecture
```
Claude Code → MCP Server → JDWP Client → TCP Socket → JVM
↓
Summarization &
Context Filtering
```
The MCP server handles:
- **Protocol Translation**: MCP JSON-RPC ↔ JDWP binary protocol
- **Smart Summarization**: Truncates large objects, limits depth
- **State Management**: Tracks breakpoints, threads, sessions
## Development
### Project Structure
```
jdwp-mcp/
├── jdwp-client/ # JDWP protocol implementation
│ ├── connection.rs # TCP + handshake
│ ├── protocol.rs # Packet encoding/decoding
│ ├── commands.rs # JDWP command constants
│ ├── types.rs # JDWP type definitions
│ └── events.rs # Event handling
├── mcp-server/ # MCP server
│ ├── main.rs # Stdio transport
│ ├── protocol.rs # MCP JSON-RPC
│ ├── handlers.rs # Request routing
│ ├── tools.rs # Tool definitions
│ └── session.rs # Debug session state
└── examples/ # Usage examples
```
### Testing
Use the companion [java-example-for-k8s](../java-example-for-k8s) as a test target:
```bash
cd ../java-example-for-k8s
mvn clean package
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 \
-jar target/probe-demo-0.0.1-SNAPSHOT.jar
```
Then test MCP tools against this running app.
### Building
```bash
# Debug build
cargo build
# Release build
cargo build --release
# Run tests
cargo test
```
## Status
✅ **Functionally complete** — 16 debug tools, integrated and validated against a live JVM.
### Implemented
- [x] JDWP protocol (handshake, packets, encoding/decoding)
- [x] MCP server with 16 debug tools (stdio transport)
- [x] VirtualMachine commands (Version, IDSizes, AllThreads, Suspend/Resume, CreateString)
- [x] ClassesBySignature, ReferenceType.Methods/Fields/Signature, ClassType.Superclass
- [x] Method.LineTable / VariableTable
- [x] EventRequest.Set/Clear/ClearAllBreakpoints — breakpoints with location, **count**, and **thread** modifiers
- [x] ThreadReference.Frames, StackFrame.GetValues/SetValues/ThisObject
- [x] ObjectReference.ReferenceType/GetValues/**InvokeMethod**, ArrayReference.Length/GetValues, StringReference.Value
- [x] **Event loop** for async breakpoint/step notifications
- [x] **Stepping** (step over/into/out)
- [x] **Expression evaluation** — `var`/`this` + `.field` / `.method(args)` chains, type-aware overload resolution, superclass walk
- [x] **String and object dereferencing**, array contents, best-effort `toString()`, source-line resolution
- [x] **Conditional breakpoints** — `condition` evaluated in the hit frame (`expr OP literal` or boolean chains); auto-resumes when false
- [x] **Multiple concurrent sessions** — `debug.attach` returns a `session_id`; tools take an optional `session_id` (defaults to current)
- [x] **Argument literals** in `evaluate` / conditions: int, long (`123L`), boolean, null, `"string"`
- [x] **Safety**: `panic` + idle watchdog auto-resume
- [x] Architecture independence (big-endian protocol; Intel & ARM)
## References
- [JDWP Specification](https://docs.oracle.com/javase/8/docs/platform/jpda/jdwp/jdwp-protocol.html)
- [Model Context Protocol](https://modelcontextprotocol.io/)
- [Claude Code MCP Documentation](https://docs.claude.com/claude-code)
## License
MIT