The MCP Server¶
Overview¶
Undo for Java includes an MCP (Model Context Protocol) server that enables AI agents to programmatically debug Java recordings. Instead of manually stepping through code in IntelliJ, you can ask an AI agent such as Claude to investigate a recording, diagnose root causes, and report findings in natural language. See Undo for Java AI for an overview of the ways to use AI with Undo for Java.
The AI agent connects to the MCP server, which provides a full set of debugging tools: breakpoints, watchpoints, forward and reverse execution, variable inspection, time travel navigation, and more. The AI agent can use these tools to autonomously explore a recording, following the same debugging strategies a human developer would - but faster and without requiring you to drive each step.
This is particularly powerful for:
Root cause analysis: Ask the AI to find the cause of an exception, working backwards through the execution history automatically.
Batch analysis: Ask the AI to collect data across an entire recording, such as profiling method calls or tracking field modifications.
Exploratory debugging: Ask the AI to investigate a symptom when you’re not sure where to start.
Native and Java mixed debugging: The MCP server provides access to both Java-level and native (C/C++) debugging tools, allowing the AI to investigate issues that cross the Java/native boundary.
Getting Started¶
Two MCP servers are provided in the LR4J-Record-*.zip archive:
lr4j_mcpdebugs a single recording, named when the server is launched.lr4j_mcp_serveris registered once and opens recordings on request during the conversation, including several at the same time. See The multi-recording server.
Launching the MCP server¶
$LR4J_HOME/bin/lr4j_mcp --input /path/to/recording.undo --key /path/to/keyfile
The server communicates over stdin/stdout using the Model Context Protocol (JSON-RPC). It is designed to be launched by an AI agent’s MCP client, not invoked directly.
Options:
- -i, –input <filename>
The recording file to debug.
- -k, –key <keyfile>
Path to the license key file.
- -p, –port <nnn>
Port for the internal JDWP connection (default: 9000).
- -cp <classpath>
Additional classpath, if needed for class resolution.
- -Xmx<size>
JVM heap size (default: 1g). Increase if you encounter
OutOfMemoryError.
Configuring Claude Desktop¶
Add the following to your Claude Desktop configuration file (claude_desktop_config.json):
{
"mcpServers": {
"undo-java": {
"command": "/absolute/path/to/lr4j/bin/lr4j_mcp",
"args": ["--input", "/path/to/recording.undo", "--key", "/path/to/keyfile"]
}
}
}
Replace the paths with the actual locations of your LR4J installation, recording file, and license key.
Configuring Claude Code¶
Run the following command to add the MCP server:
claude mcp add undo-java -- /path/to/lr4j/bin/lr4j_mcp --input /path/to/recording.undo --key /path/to/keyfile
Then start Claude Code and ask it to debug the recording.
Configuring Codex¶
Run the following command to add the MCP server:
codex mcp add undo-java -- /path/to/lr4j/bin/lr4j_mcp --input /path/to/recording.undo --key /path/to/keyfile
Or add the following to ~/.codex/config.toml:
[mcp_servers.undo-java]
command = "/path/to/lr4j/bin/lr4j_mcp"
args = [
"--input", "/path/to/recording.undo",
"--key", "/path/to/keyfile",
]
cwd = "/path/to/working/directory"
Replace the paths and server name with appropriate values for your setup.
Note
AI agents that support MCP can automatically discover the available tools and their descriptions. You do not need to teach the AI agent which tools to use; it will select the appropriate tools based on your request. The help tool provides deeper, task-specific guidance on request.
The multi-recording server¶
Added in version 10.0.
lr4j_mcp_server is an MCP front-end that is not tied to a recording at launch: register it
with your MCP client once, then open recordings during the conversation with the
load_recording tool. Loaded recordings are addressed by a session handle (list_recordings
and unload_recording manage them), so one conversation can drive several recordings at the
same time - for example following a request across two microservice recordings, as described in
Microservices. Each loaded recording runs in its own lr4j_mcp child process, so
sessions are isolated from one another and run in parallel.
lr4j_mcp_server requires a Java 17 or later runtime and ships only in the Java 17+
distributions; the single-recording lr4j_mcp remains available on all supported Java versions.
Options:
- -k, –key <keyfile>
Path to the license key file (required).
- –http
Serve over streamable HTTP instead of stdio, so a remote MCP client (for example an AI agent on a laptop) can connect to recordings on this host.
- –host <host>
Bind host for
--http(default127.0.0.1).- –port <nnn>
Bind port for
--http(default9100).
For example, with Claude Code:
claude mcp add lr4j -- /path/to/lr4j/bin/lr4j_mcp_server --key /path/to/keyfile
then ask the AI agent to load a recording by path.
MCP Tools Reference¶
The MCP server exposes the following tools, organized by category. Each tool is callable by an MCP-compatible AI agent.
Changed in version 10.0: Execution and navigation tools now block until the debugger stops and return the stop reason,
location, and updated session state (bbcount, wall-clock time, and recording-position flags)
in every response. The former wait_for_stop and get_session_state tools have been
removed.
Guidance¶
Tool |
Description |
|---|---|
|
Get detailed, task-specific guidance for using the debugger. Topics: Added in version 10.0. |
Execution Control¶
Tool |
Description |
|---|---|
|
Continue execution forward until a breakpoint, watchpoint, exception breakpoint, or end of recording is reached. |
|
Continue execution backward until a breakpoint, watchpoint, exception breakpoint, or start of recording is reached. |
|
Step into the next line of code. Optionally accepts class patterns to exclude (e.g.
|
|
Step over the current line. |
|
Step out of the current method. |
|
Step into the previous line in reverse. Optionally accepts class patterns to exclude. |
|
Step over the current line in reverse. |
|
Step out of the current method in reverse. |
Breakpoints, Watchpoints, and Exception Breakpoints¶
Tool |
Description |
|---|---|
|
Set a breakpoint at a class and line number, or a class and method name. Optionally accepts a method signature for overloaded methods. |
|
Set a watchpoint on a field. Triggers when the field value changes. Particularly useful in reverse execution to find what modified a field. |
|
Set a breakpoint that triggers when a specific exception class is thrown, or for all exceptions. |
|
List all active breakpoints, watchpoints, and exception breakpoints. |
|
Clear all breakpoints, or a specific breakpoint by ID or location. |
|
Clear all watchpoints, or a specific watchpoint by ID or location. |
|
Clear all exception breakpoints, or a specific one by ID or class. |
State Inspection¶
Tool |
Description |
|---|---|
|
Get the current execution location (class, method, line number). |
|
Get the Java stack trace for the current thread or a specific thread. |
|
Get the names and values of all local variables in a given stack frame. |
|
Get the value of an instance field for a specific object. |
|
Get the value of a static field for a specific class. |
|
List all Java threads with their IDs, names, and states. |
|
Read the console output (stdout/stderr) from the recorded application. |
|
Get the next event in the Undo event log (e.g. system calls, thread switches). Supports filtering by bbcount, event type, and value range. System call events report the recorded thread that performed them. |
|
The reverse-direction counterpart of Added in version 10.0. |
Native Debugging¶
These tools provide access to the native (C/C++) layer beneath the JVM, useful for debugging JNI code, JVM internals, or mixed Java/native issues.
Tool |
Description |
|---|---|
|
Get the native (C/C++) stack trace at the current position, with symbol resolution. |
|
Set a breakpoint on a native function. |
|
Clear native breakpoints. |
|
Read raw memory from the debuggee process at a specific address. |
|
Search for a string in the debuggee’s process memory. |
|
Get the values of CPU argument registers (x64: RDI, RSI, RDX, RCX, R8, R9; ARM64: X0-X7). Optionally takes a recorded thread id, to read the registers of the thread performing a system call event. |
|
Get the values of all general-purpose CPU registers. |
|
Look up the nearest symbol for a memory address using DWARF debug information. |
Python API¶
The MCP server ships with a typed Python API for writing analysis scripts that run against recordings. This is the recommended approach for tasks that require iteration, batch processing, or collecting data across an entire recording.
The Python API is located in the demos/python/ directory within the replay tools archive.
Setup¶
import os, sys
LR4J_HOME = os.environ['LR4J_HOME']
sys.path.insert(0, f'{LR4J_HOME}/demos/python')
from undo_mcp import Session
with Session(
mcp_path=f'{LR4J_HOME}/bin/lr4j_mcp',
input_path='/path/to/recording.undo',
key_path='/path/to/keyfile'
) as session:
# Use session.breakpoints, session.execution, session.navigation,
# session.inspection, and session.memory to interact with the recording.
pass
The Session object provides access to five manager objects:
session.breakpoints- Set and clear breakpoints, watchpoints, and exception breakpoints.session.execution- Continue, step, and reverse continue.session.navigation- Navigate by bbcount, timestamp, or object creation.session.inspection- Inspect variables, stack traces, threads, console output, and events.session.memory- Read memory, search memory, inspect registers, and access native stacks.
Python API reference¶
The Session manager objects correspond to the MCP tool categories described in
MCP Tools Reference. Most Python methods map to one MCP tool and accept the same parameters.
The source code in demos/python/undo_mcp/ contains detailed docstrings for every method,
including parameter descriptions, return types, and usage examples.
The following table summarizes the most commonly used methods.
session.breakpoints¶
Method |
Description |
|---|---|
|
Set a breakpoint at a line number or method. Provide either |
|
Break when an exception of the given class is thrown. |
|
Break when the named field is modified. |
|
List all active breakpoints, watchpoints, and exception breakpoints. |
|
Clear breakpoints: all of them, one by ID, or those matching a location. |
|
Clear all breakpoints. |
|
Clear all watchpoints, one by ID, or those matching a field. |
|
Clear all exception breakpoints. |
session.execution¶
Method |
Description |
|---|---|
|
Continue forward until execution stops. Returns a dict with |
|
Continue backward until execution stops. Returns the same dict as |
|
Step into the next line. Pass |
|
Step over the current line. |
|
Step out of the current method. |
|
Step into the previous line in reverse. |
|
Step over the current line in reverse. |
|
Step out of the current method in reverse. |
session.inspection¶
Method |
Description |
|---|---|
|
Get local variables for a stack frame. |
|
A convenience wrapper over |
|
Get the value of an instance field. |
|
Get the value of a static field. |
|
Get the Java stack trace for a thread. |
|
List all Java threads with their IDs, names, and states. |
|
Read stdout/stderr from the recorded application. |
|
Get the next event in the Undo event log (system calls, thread switches, etc.),
optionally filtered by bbcount, event type, or the event’s value (for example the number
of bytes a |
session.memory¶
Method |
Description |
|---|---|
|
Read raw bytes from the debuggee process. |
|
Search for a string in process memory. |
|
Get CPU argument registers (x64: RDI, RSI, RDX, RCX, R8, R9; ARM64: X0-X7). |
|
Get all general-purpose CPU registers. |
|
Get the native (C/C++) stack trace. |
|
Set a breakpoint on a native C/C++ function. |
|
Clear all native breakpoints, or those on the named function. |
|
Look up the nearest symbol for a memory address. |
Example: Diagnosing a ConcurrentModificationException¶
This script demonstrates the power of reverse debugging using the
ConcurrentModificationExceptionTest demo program shipped in
demos/java/ in the replay tools archive. The test has a main thread
iterating over an ArrayList while a MutatorThread adds and
removes elements concurrently, causing a ConcurrentModificationException.
This is a notoriously difficult problem to debug with conventional tools because the exception is thrown in the victim thread (the iterator), not the culprit thread (the mutator). The script finds the exception, then travels backwards in time to discover which thread modified the collection.
First, record the demo:
java -XX:-Inline -XX:UseAVX=2 \
-agentpath:$LR4J_HOME/agent/lr4j_agent_<arch>.so=save_on=always,output=cme.undo \
-cp $LR4J_HOME/demos/java \
io.undo.test.ConcurrentModificationExceptionTest
Then run the analysis script:
import os, sys
LR4J_HOME = os.environ['LR4J_HOME']
sys.path.insert(0, f'{LR4J_HOME}/demos/python')
from undo_mcp import Session
with Session(
mcp_path=f'{LR4J_HOME}/bin/lr4j_mcp',
input_path='cme.undo',
key_path='/path/to/keyfile'
) as session:
# Step 1: Find the ConcurrentModificationException.
session.breakpoints.set_exception(
"java.util.ConcurrentModificationException"
)
stop = session.execution.continue_()
print("Exception hit!")
stack = session.inspection.get_thread_stack()
for frame in stack['frames']:
print(f" {frame['className']}.{frame['methodName']}"
f":{frame['lineNumber']}")
# Step 2: Set a watchpoint on the modCount field that tracks
# structural modifications to the collection.
session.breakpoints.clear_all()
session.breakpoints.set_watchpoint(
"java.util.AbstractList", "modCount"
)
# Step 3: Travel backwards to find who modified the collection.
stop = session.execution.reverse_continue()
# Look up the thread name from the thread ID.
threads = session.inspection.list_threads()
thread_id = stop['threadId']
thread_name = next(
t['threadName'] for t in threads['threads']
if t['threadId'] == thread_id
)
print(f"\nCollection modified by thread: {thread_name}")
stack = session.inspection.get_thread_stack(threadId=thread_id)
for frame in stack['frames']:
print(f" {frame['className']}.{frame['methodName']}"
f":{frame['lineNumber']}")
This produces output like:
Exception hit!
java.util.ArrayList$Itr.checkForComodification:1043
java.util.ArrayList$Itr.next:997
io.undo.test.ConcurrentModificationExceptionTest.main:18
Collection modified by thread: MutatorThread
java.util.ArrayList.fastRemove:670
java.util.ArrayList.remove:540
io.undo.test.ConcurrentModificationExceptionTest$MutatorThread.run:62
java.lang.Thread.run:829
The script found the exception in the main thread (the victim iterating the list), then
traveled backwards in time and identified MutatorThread as the culprit - it was removing an
element from the list via ArrayList.remove while the main thread was iterating.
Note
The demos/python/examples/ directory in the replay archive contains additional example
scripts including a more detailed version of this analysis.
What you can ask AI to do¶
Here are examples of the kinds of tasks you can ask an AI agent to perform on a recording. These are natural-language requests; the AI will select the appropriate MCP tools to carry them out.
Finding Root Causes¶
“Read the console output and find the root cause of the exception.”
“There’s a
ConcurrentModificationException- find which thread modified the collection and show me the stack trace.”“Find where this
NullPointerExceptionoccurs and trace back to where the null value originated.”“Why did this HTTP request fail? Go to the error and work backwards to find the cause.”
Inspecting Objects and State¶
“Find where this object (address 0x7f1234) was created.”
“Read the console messages and find the one that mentions the instance address, then go to where that object was created.”
“Show me the local variables at the point where the exception was thrown.”
Profiling and Analysis¶
“Sample stack traces at 30 points across the recording and show me which methods are consuming the most time.”
“Set a native breakpoint on
undo_gc_finishand collect the free memory and total memory at each GC, then show me the results as a table.”“Find all write system calls with length greater than 4 and show what was written.”
Cross-Service Debugging¶
“In the API gateway recording, read the console and find the errors. Then go to that timestamp in the customers-service recording and find where the call is handled.”
Native-Level Debugging¶
“Show the native stack traces at 30 points across the recording and show me which native methods are consuming the most time.”
“Find the precise native bbcount where a specific string was created and print the native stack there.”