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.
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
ClassPrepareRequestfor classes not yet loaded - Maps JDI events (breakpoints, steps, thread events) to DAP events
- Compiles with
javac --release 21(no external dependencies)
- JDK 21+ recommended (installed from 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.
javaon your PATH (orJAVA_HOMEset) for running the JDI bridge;javacis additionally needed to compile the bridge on first use and to compile your target Java sources with debug info
Verify your installation:
java -version # Should show JDK 21+
javac -version # Should show matching versionYou 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.
# Correct: includes LocalVariableTable for variable inspection
javac -g MyProgram.java
# Wrong: variables will be empty in the debugger
javac MyProgram.javaIf you use a build tool:
- Gradle: Debug info is included by default (
-gis the default forcompileJava) - Maven: Debug info is included by default (
maven-compiler-pluginuses-gby default)
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 withmain()methodclasspath: Directory or classpath containing compiled.classfiles (default:'.'; typically needed — the JVM will not find your classes without it)cwd: Working directory for the launched JVMstopOnEntry: Whether to pause at the first line ofmain()(default:true)javaPath: Path to thejavaexecutable (overrides auto-detection)vmArgs: Additional JVM arguments (e.g.,-Xmx512m)
Connect to a running JVM that was started with JDWP agent.
Start your JVM with JDWP enabled:
java -agentlib:jdwp=transport=dt_socket,server=y,address=5005,suspend=y \
-cp . MyProgramsuspend=ypauses the JVM until a debugger attaches (recommended for debugging from the start)suspend=nlets 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 porthost: Target hostname (default:localhost)sourcePaths: Directories containing.javasource files for source mapping
use_mcp_tool(
tool_name="create_debug_session",
arguments={
"language": "java",
"name": "My Java Debug Session"
}
)
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
}
)
Use start_debugging for launch mode or attach_to_process for attach mode (see above).
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": "..."})
# 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}
)
use_mcp_tool(tool_name="close_debug_session", arguments={"sessionId": "..."})
JDI bridge handles deferred breakpoints natively via ClassPrepareRequest. When you set a breakpoint on a class that hasn't been loaded yet:
- JdiDapServer registers a
ClassPrepareRequestfilter for the class name - When the JVM loads the class, JDI fires a
ClassPrepareEvent - JdiDapServer resolves the breakpoint location and sets a
BreakpointRequest - 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.
// 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);
}
}# Compile with debug info
javac -g Calculator.java- Create debug session with
language: "java" - Set breakpoint at line 4
- Start debugging with
mainClass: "Calculator",classpath: "." - When stopped at breakpoint, inspect variables:
a=10,b=20
# 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"- Create debug session with
language: "java" - Set breakpoints on desired lines
- Attach with
port: 5005,host: "localhost",sourcePaths: ["."] - Continue execution to resume the suspended JVM
- Wait for breakpoint to fire, then inspect variables
- Compile with
javac -gto includeLocalVariableTable - 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)
- 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 mustcontinue_executionafter attaching to let the program run to the breakpoint
- Ensure JDK 21+ is installed:
java -version - Set
JAVA_HOMEor ensurejavais on your PATH
- Verify the JDWP port is correct and the JVM is listening
- Check for firewall rules blocking the port
- Ensure
server=yis set in the JDWP agent string
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.
- Attach to a running JVM (or launch a debug session)
- Edit your Java source files
- Recompile with
javac -gto produce updated.classfiles - Call
redefine_classeswith the classes directory:{ "sessionId": "your-session-id", "classesDir": "/project/build/classes/java/main", "sinceTimestamp": 0 } - The tool scans for
.classfiles, matches them against loaded classes, and redefines them - Use the returned
newestTimestampassinceTimestampon subsequent calls for incremental updates
- No schema changes: Adding or removing methods, fields, or interfaces will fail for the affected class (reported in the
failedarray). 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.
{
"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
}- Java Debug Interface (JDI) — JVM debugging API
- JDWP Reference — Wire protocol specification
- DAP Protocol Specification