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¶
Launching the MCP server¶
Added in version 10.0.
Register the MCP server, lr4j_mcp_server, with your AI agent once. It is not tied to a
recording: during the conversation, ask the agent to load a recording by its path, and the agent
opens it with the load_recording tool. Loaded recordings are addressed by a session handle
(list_recordings and unload_recording manage them), so one conversation can work on several
recordings at the same time, for example following a request across two microservice recordings,
as described in Microservices. Each loaded recording is replayed in its own process, so
sessions are isolated from one another and run in parallel.
$LR4J_HOME/bin/lr4j_mcp_server --key /path/to/keyfile
By default 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.
lr4j_mcp_server runs on the Java runtime that JAVA_HOME points to, which must
be Java 17 or later. If no such runtime is available, use the
single-recording server.
- -k <keyfile>, --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.
- --log <file>¶
Write the server’s log to file (default
/tmp/lr4j-mcp-server-<pid>.log). The server prints the path of its log on standard error when it starts, and thelist_recordingstool reports it. The log records every tool call and every decision about memory use, so include it when you report a problem to Undo Support.Added in version 10.1.
The options that control memory use are described in Memory use.
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_server",
"args": ["--key", "/path/to/keyfile"]
}
}
}
Replace the paths with the actual locations of your LR4J installation 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_server --key /path/to/keyfile
Then start Claude Code and ask it to load and debug a recording, giving the recording’s path.
Configuring Codex¶
Run the following command to add the MCP server:
codex mcp add undo-java -- /path/to/lr4j/bin/lr4j_mcp_server --key /path/to/keyfile
Or add the following to ~/.codex/config.toml:
[mcp_servers.undo-java]
command = "/path/to/lr4j/bin/lr4j_mcp_server"
args = ["--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.
Memory use¶
Added in version 10.1.
Each loaded recording is replayed by a replay engine, which can need a lot of memory. lr4j_mcp_server keeps the engines of several recordings running while the machine has memory to spare. When memory runs short, it suspends the engines of idle recordings, least recently used first, and resumes a suspended engine when the agent next uses that recording. A suspended recording keeps its session handle and its debugging state, such as the current position, breakpoints, watchpoints and object references, so the agent can carry on where it left off.
To decide, the server checks the memory available every two seconds, taking into account any memory limit on the container (cgroup) it runs in, and the kernel’s memory pressure information where the kernel provides it. It suspends idle engines when the system is under memory pressure, and starts or resumes an engine only if the memory available exceeds a reserve plus an estimate of what the engine needs. If suspending every idle engine still leaves too little memory, loading another recording fails with an error asking you to unload one. If the server cannot read the memory available, it keeps only one recording loaded.
The list_recordings tool reports which recordings have a resident engine, together with the
memory available, the reserve and the estimate, without resuming any suspended recording.
These lr4j_mcp_server options control memory use:
- --max-resident <n>¶
The maximum number of resident engines. The default,
0, leaves this to the memory available.
- --memory-reserve-mib <n>¶
The minimum amount of memory to keep available, in MiB (default
256). The reserve is the larger of this and 10% of the memory capacity, with the percentage limited to 1 GiB.
- --startup-memory-mib <n>¶
The initial estimate of the memory needed to start an engine, in MiB (default
1536). The server raises the estimate if it sees engines use more.
- --bridge-max-heap <size>¶
The maximum Java heap of each recording’s replay process (default
512m). Increase it if a recording fails withOutOfMemoryError.
- --bridge-initial-heap <size>¶
The initial Java heap of each recording’s replay process (default
32m).
- --call-timeout-seconds <n>¶
How long one operation on a recording may run, in seconds (default
600).
- --request-timeout-seconds <n>¶
How long a whole tool call may run, in seconds (default
600).
When an operation exceeds one of these time limits, the server stops that recording’s replay process, and the recording must be unloaded and loaded again.
The single-recording server¶
lr4j_mcp debugs a single recording, named when it is launched, and runs on all supported Java versions. lr4j_mcp_server runs it for each recording that it loads, and the Python API uses it directly. Register it with your AI agent only if you cannot run lr4j_mcp_server, for example because no Java 17 or later runtime is available. Configure it in the same way as lr4j_mcp_server, adding the recording:
claude mcp add undo-java -- /path/to/lr4j/bin/lr4j_mcp --input /path/to/recording.undo --key /path/to/keyfile
- -i <filename>, --input <filename>¶
The recording file to debug.
- -k <keyfile>, --key <keyfile>¶
Path to the license key file.
- -p <nnn>, --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 it if you encounterOutOfMemoryError.
MCP Tools Reference¶
The MCP server exposes the following tools, organized by category. Each tool is callable by an MCP-compatible AI agent.
Recordings¶
lr4j_mcp_server adds these tools. Its other tools take an optional session handle, which can be omitted when only one recording is loaded.
Tool |
Description |
|---|---|
|
Open a recording by its path, optionally giving it a name, and return its session handle. |
|
List the loaded recordings and their session handles, with the memory state and the path of the server’s log. |
|
Unload a recording, stopping its replay process. |
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, compile and record the demo. The archive ships its source, so compile it into a directory
of your own (-g keeps local variable names for the debugger):
javac -g -d cme-demo $LR4J_HOME/demos/java/io/undo/test/ConcurrentModificationExceptionTest.java
java -XX:UseAVX=2 \
-agentpath:$LR4J_HOME/agent/lr4j_agent_<arch>.so=save_on=always,output=cme.undo \
-cp cme-demo \
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¶
Tasks like these repeat the same step at many points in a recording. The MCP server tells the agent
to do such work in a single script using the Python API, instead of calling the
tools one at a time, and its help tool shows the agent how. The script runs next to the
recording and returns only the result, so the analysis finishes sooner and uses far fewer tokens.
You don’t need to ask for it, although adding “use the Python API” to a prompt makes sure. Scripts
apply when the agent runs on the machine that holds the recording; an agent connecting to the
server over HTTP from another machine calls the tools directly.
“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 the write calls with length greater than 4. First find all the write events in the Undo event log, noting the bbcount and the extra field (the write length). Then for each one, go to the native bbcount and read the buffer written. Report each as a string together with its fd, as a complete table.”
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.”