# Java Debugging with Debug MCP Server

The Debug MCP Server provides Java debugging through a JDI bridge (`JdiDapServer.java`) — a single Java file that implements DAP over TCP using JDI (`com.sun.jdi.*`) directly. JDI ships with every JDK, so there are zero external dependencies.

## Architecture

```
MCP Client → MCP Server → ProxyManager → TCP → JdiDapServer (JVM)
                                                    ↓
                                              JDI (com.sun.jdi.*)
                                                    ↓
                                              Target JVM (via JDWP)
```

JdiDapServer is a ~2600-line Java program that:
- Accepts DAP requests over TCP (Content-Length framed JSON)
- Uses JDI to launch or attach to a target JVM
- Handles deferred breakpoints via `ClassPrepareRequest` for classes not yet loaded
- Maps JDI events (breakpoints, steps, thread events) to DAP events
- Compiles with `javac --release 21` (no external dependencies)

## Prerequisites

1. **JDK 21+ recommended** (installed from [adoptium.net](https://adoptium.net/) or your OS package manager). The adapter factory emits a warning for JDK versions below 21 but does not block execution; lower versions may work in practice.
2. **`java` on your PATH** (or `JAVA_HOME` set) for running the JDI bridge; **`javac`** is additionally needed to compile the bridge on first use and to compile your target Java sources with debug info

Verify your installation:
```bash
java -version    # Should show JDK 21+
javac -version   # Should show matching version
```

### Compilation Requirements

**You must compile target code with `javac -g`** (full debug info). Without `-g`, javac omits the `LocalVariableTable` from `.class` files, and the debugger will return empty variable lists even when stopped at a breakpoint.

```bash
# Correct: includes LocalVariableTable for variable inspection
javac -g MyProgram.java

# Wrong: variables will be empty in the debugger
javac MyProgram.java
```

If you use a build tool:
- **Gradle**: Debug info is included by default (`-g` is the default for `compileJava`)
- **Maven**: Debug info is included by default (`maven-compiler-plugin` uses `-g` by default)

## Debugging Modes

### Launch Mode

JDI bridge spawns the JVM and connects via JDI. The adapter derives `mainClass` from the `program` field in the launch configuration and transparently forwards `classpath`, `sourcePath`, `cwd`, `env`, and `args`.

```
use_mcp_tool(
  tool_name="start_debugging",
  arguments={
    "sessionId": "your-session-id",
    "scriptPath": "/path/to/MyProgram.java",
    "dapLaunchArgs": {
      "mainClass": "MyProgram",
      "classpath": "/path/to/classes",
      "cwd": "/path/to/project",
      "stopOnEntry": true
    }
  }
)
```

Key launch arguments:
- `mainClass` (required): Fully qualified class name with `main()` method
- `classpath`: Directory or classpath containing compiled `.class` files (default: `'.'`; typically needed — the JVM will not find your classes without it)
- `cwd`: Working directory for the launched JVM
- `stopOnEntry`: Whether to pause at the first line of `main()` (default: `true`)
- `javaPath`: Path to the `java` executable (overrides auto-detection)
- `vmArgs`: Additional JVM arguments (e.g., `-Xmx512m`)

### Attach Mode

Connect to a running JVM that was started with JDWP agent.

Start your JVM with JDWP enabled:
```bash
java -agentlib:jdwp=transport=dt_socket,server=y,address=5005,suspend=y \
     -cp . MyProgram
```

- `suspend=y` pauses the JVM until a debugger attaches (recommended for debugging from the start)
- `suspend=n` lets the JVM run immediately (useful for attaching to running servers)

```
use_mcp_tool(
  tool_name="attach_to_process",
  arguments={
    "sessionId": "your-session-id",
    "port": 5005,
    "host": "localhost",
    "sourcePaths": ["/path/to/source"]
  }
)
```

Key attach arguments:
- `port` (required): JDWP debug port
- `host`: Target hostname (default: `localhost`)
- `sourcePaths`: Directories containing `.java` source files for source mapping

## Debugging Workflow

### 1. Create a Debug Session

```
use_mcp_tool(
  tool_name="create_debug_session",
  arguments={
    "language": "java",
    "name": "My Java Debug Session"
  }
)
```

### 2. Set Breakpoints

Set breakpoints before starting/attaching. Breakpoints must be on executable lines (assignments, method calls, conditionals) — not on blank lines, comments, or declarations. Conditional breakpoints (with a `condition` expression) and exception breakpoints are also supported by the JDI bridge.

```
use_mcp_tool(
  tool_name="set_breakpoint",
  arguments={
    "sessionId": "your-session-id",
    "file": "/path/to/MyProgram.java",
    "line": 15
  }
)
```

### 3. Start or Attach

Use `start_debugging` for launch mode or `attach_to_process` for attach mode (see above).

### 4. Control Execution

When paused at a breakpoint:

```
# Step over (execute current line)
use_mcp_tool(tool_name="step_over", arguments={"sessionId": "..."})

# Step into (enter function calls)
use_mcp_tool(tool_name="step_into", arguments={"sessionId": "..."})

# Step out (return from current function)
use_mcp_tool(tool_name="step_out", arguments={"sessionId": "..."})

# Continue (run until next breakpoint)
use_mcp_tool(tool_name="continue_execution", arguments={"sessionId": "..."})
```

### 5. Examine Program State

```
# Get local variables in current frame
use_mcp_tool(tool_name="get_local_variables", arguments={"sessionId": "..."})

# Get call stack
use_mcp_tool(tool_name="get_stack_trace", arguments={"sessionId": "..."})

# Evaluate an expression (frameId is optional; defaults to top frame)
# The evaluator supports field access, method calls, arithmetic, and string concatenation
use_mcp_tool(
  tool_name="evaluate_expression",
  arguments={"sessionId": "...", "expression": "x + y", "frameId": 0}
)
```

### 6. Close the Session

```
use_mcp_tool(tool_name="close_debug_session", arguments={"sessionId": "..."})
```

## Deferred Breakpoints

JDI bridge handles deferred breakpoints natively via `ClassPrepareRequest`. When you set a breakpoint on a class that hasn't been loaded yet:

1. JdiDapServer registers a `ClassPrepareRequest` filter for the class name
2. When the JVM loads the class, JDI fires a `ClassPrepareEvent`
3. JdiDapServer resolves the breakpoint location and sets a `BreakpointRequest`
4. A `breakpoint(verified=true)` event is sent to the client

No manual breakpoint re-sends are needed — this works transparently in both launch and attach modes.

## Example: Launch Mode

```java
// Calculator.java
public class Calculator {
    static int add(int a, int b) {
        int result = a + b;   // Set breakpoint here (line 4)
        return result;
    }

    public static void main(String[] args) {
        int sum = add(10, 20);
        System.out.println("Sum: " + sum);
    }
}
```

```bash
# Compile with debug info
javac -g Calculator.java
```

1. Create debug session with `language: "java"`
2. Set breakpoint at line 4
3. Start debugging with `mainClass: "Calculator"`, `classpath: "."`
4. When stopped at breakpoint, inspect variables: `a=10`, `b=20`

## Example: Attach Mode

```bash
# Terminal 1: Start JVM with JDWP
javac -g MyServer.java
java -agentlib:jdwp=transport=dt_socket,server=y,address=5005,suspend=y \
     -cp . MyServer
# Output: "Listening for transport dt_socket at address: 5005"
```

1. Create debug session with `language: "java"`
2. Set breakpoints on desired lines
3. Attach with `port: 5005`, `host: "localhost"`, `sourcePaths: ["."]`
4. Continue execution to resume the suspended JVM
5. Wait for breakpoint to fire, then inspect variables

## Troubleshooting

### Empty variables list
- Compile with `javac -g` to include `LocalVariableTable`
- Verify you're paused at an executable line, not a declaration or comment
- Check that the source file matches the compiled class (recompile after edits)

### Breakpoints not firing
- Ensure the breakpoint is on an executable line (not a comment, blank line, or import)
- Verify the class name in the source path matches what the JVM loads
- In attach mode with `suspend=y`, you must `continue_execution` after attaching to let the program run to the breakpoint

### "Java not found" error
- Ensure JDK 21+ is installed: `java -version`
- Set `JAVA_HOME` or ensure `java` is on your PATH

### Connection timeout (attach mode)
- Verify the JDWP port is correct and the JVM is listening
- Check for firewall rules blocking the port
- Ensure `server=y` is set in the JDWP agent string

## Hot Reload (redefine_classes)

The `redefine_classes` MCP tool hot-swaps changed Java classes into a running JVM using JDI's `VirtualMachine.redefineClasses()`. This enables edit-compile-reload workflows without restarting the debug session.

### Workflow

1. **Attach** to a running JVM (or launch a debug session)
2. **Edit** your Java source files
3. **Recompile** with `javac -g` to produce updated `.class` files
4. **Call `redefine_classes`** with the classes directory:
   ```json
   {
     "sessionId": "your-session-id",
     "classesDir": "/project/build/classes/java/main",
     "sinceTimestamp": 0
   }
   ```
5. The tool scans for `.class` files, matches them against loaded classes, and redefines them
6. Use the returned `newestTimestamp` as `sinceTimestamp` on subsequent calls for incremental updates

### Limitations

- **No schema changes**: Adding or removing methods, fields, or interfaces will fail for the affected class (reported in the `failed` array). Other classes in the same call are still redefined successfully.
- **Class must be loaded**: Only classes already loaded by the JVM can be redefined. Unloaded classes are silently skipped (reported in `skippedNotLoaded`).
- **JVM support**: Requires a JVM that supports class redefinition (HotSpot does; some minimal JVMs may not).
- **Java only**: This tool is specific to Java debug sessions — it relies on JDI, which is a JVM-specific API.

### Example Output

```json
{
  "redefined": ["com.example.Foo", "com.example.Bar"],
  "redefinedCount": 2,
  "skippedNotLoaded": 5,
  "failedCount": 1,
  "failed": [{"fqcn": "com.example.Baz", "error": "UnsupportedOperationException: schema change"}],
  "scannedFiles": 8,
  "newestTimestamp": 1711500000000
}
```

## Additional Resources

- [Java Debug Interface (JDI)](https://docs.oracle.com/en/java/javase/17/docs/api/jdk.jdi/module-summary.html) — JVM debugging API
- [JDWP Reference](https://docs.oracle.com/en/java/javase/17/docs/specs/jdwp/jdwp-spec.html) — Wire protocol specification
- [DAP Protocol Specification](https://microsoft.github.io/debug-adapter-protocol/)
