MCP Server
What is the MCP Server?
The x64dbg Automate MCP server exposes the debugger's capabilities as Model Context Protocol tools. This allows LLM clients like Claude Code to directly control x64dbg for reverse engineering, malware analysis, and debugging tasks.
The MCP server wraps the same Python API documented in the Client Reference sections, so all the same functionality is available.
Installation
Install the client library with the mcp extra:
pip install x64dbg_automate[mcp] --upgrade
Claude Code Configuration
Add the following to your Claude Code MCP settings. You can do this via the CLI:
# User level
claude mcp add --scope user x64dbg -- x64dbg-automate-mcp
# Project level
claude mcp add --scope project x64dbg -- x64dbg-automate-mcp
Or manually create/edit .mcp.json in your project root:
{
"mcpServers": {
"x64dbg": {
"command": "x64dbg-automate-mcp",
"env": {
"X64DBG_PATH": "C:\\path\\to\\x96dbg.exe"
}
}
}
}
Restart Claude Code after adding the configuration. You will be prompted to approve the MCP server on first use.
Environment Variables
| Variable | Description |
|---|---|
X64DBG_PATH |
Path to x64dbg installation (x96dbg.exe, x64dbg.exe, or x32dbg.exe). Used as a default when x64dbg_path is not passed to start_session or connect_to_session. |
Setting X64DBG_PATH via the env block in .mcp.json lets the LLM connect without searching for the debugger each time.
Local Development
To run the MCP server from a local source checkout (e.g. for testing changes), install with poetry and point .mcp.json at the entry point:
cd C:\path\to\x64dbg-automate-pyclient
poetry install --extras mcp
{
"mcpServers": {
"x64dbg": {
"command": "poetry",
"args": [
"-C", "C:\\path\\to\\x64dbg-automate-pyclient",
"run", "x64dbg-automate-mcp"
],
"env": {
"X64DBG_PATH": "C:\\path\\to\\x96dbg.exe"
}
}
}
}
Available Tools
The MCP server provides ~40 tools organized into the following groups:
| Group | Tools | Description |
|---|---|---|
| Session | list_sessions, start_session, connect_to_session, connect_remote, disconnect, terminate_session |
Manage debugger instances |
| Debug Control | go, pause, step_into, step_over, skip_instruction, run_to_return, get_debugger_status |
Control execution |
| Memory | read_memory, read_memory_many, write_memory, allocate_memory, free_memory, get_memory_map |
Read/write debuggee memory |
| Registers | get_register, set_register, get_all_registers |
Register access |
| Expressions | eval_expression, execute_command |
x64dbg expression evaluator and raw commands |
| Breakpoints | set_breakpoint, clear_breakpoint, toggle_breakpoint, list_breakpoints |
Software, hardware, and memory breakpoints |
| Assembly | disassemble, assemble |
Disassemble and assemble instructions |
| Annotations | set_label, get_label, set_comment, get_comment, get_symbol |
Labels, comments, and symbol lookup |
| Threads | create_thread, terminate_thread, pause_resume_thread, switch_thread |
Thread management |
| Events | get_latest_event, wait_for_event |
Debug event queue |
| Settings | get_setting, set_setting |
x64dbg configuration |
| GUI | log_message, get_log, refresh_gui |
Debugger UI interaction, log capture |
Remote Debugging (VM / Network)
To connect to x64dbg running inside a VM or on a remote machine:
-
Configure the x64dbg plugin to bind on an accessible address. Edit
x64dbg.inion the guest/remote machine:ini [XAutomate] BindAddress=0.0.0.0 ReqRepPort=50000 PubSubPort=50001Or use the Plugins > x64dbg-automate > Automate Settings... menu in x64dbg.
-
Ensure the ports are accessible (VM port forwarding, firewall rules, etc.).
-
Ask Claude to connect:
Connect to the remote x64dbg at 192.168.1.100 with REQ port 50000 and PUB port 50001Claude will call
connect_remotewith the host and ports. All other tools work the same after connecting.
Walkthrough: Debugging with Claude
This walkthrough demonstrates a typical analysis session using the MCP server from Claude Code. The x64dbg plugin must be installed and the MCP server configured as described above.
Step 1: Start a session
Ask Claude to start a debug session:
Launch x64dbg from C:\x64dbg\release and debug C:\targets\to_analyze.exe
Claude will call start_session with your x64dbg path and target executable.
Step 2: Explore the target
Ask Claude to look around:
Disassemble the first 20 instructions at the entry point
Claude will call disassemble with RIP as the address (resolved via x64dbg's expression evaluator), giving you annotated disassembly output.
Show me the memory map and read 256 bytes at RSP
Claude will call get_memory_map and read_memory, returning a hex dump with ASCII sidebar.
get_memory_map lists committed regions by default and can be narrowed further — a full
map is typically ~1000 regions and too large for one response:
Show me the committed private read-write regions larger than 64KB
Claude will call get_memory_map with mem_type='private', protect='rw', and
min_size=65536. Pass state='' to include reserved and free regions, as_json=True
for a machine-readable form, and offset/limit to page through large results.
Step 3: Set breakpoints and run
Set a breakpoint on MessageBoxA and resume execution
Claude will call set_breakpoint with the symbol name and then go to resume.
Step 4: Inspect state at a breakpoint
Show me all registers and disassemble 10 instructions at the current position
Claude will call get_all_registers and disassemble with RIP, giving you a full picture of the current state.
Step 5: Modify and continue
Write 0x90 0x90 (NOPs) at 0x401032 and step over 5 instructions
Claude will call write_memory to patch the bytes and step_over to advance.
Step 6: Clean up
Disconnect from the debugger
Claude will call disconnect, leaving x64dbg running for manual inspection, or terminate_session to close it entirely.
Tips
- Let Claude drive: Describe your analysis goal in plain language. Claude can chain multiple tools together to investigate, set breakpoints, read memory, and modify state.
- Expressions work everywhere: Any tool that takes an address also accepts registers, symbols, and arithmetic expressions — just like the x64dbg command bar.
- Memory reads are capped by response size, not byte count: encodings expand the data by different amounts, so the per-call limit depends on the format — roughly 9 KB for
dump, 24 KB forhex, and 36 KB forbase64.disassembleis capped at 100 instructions. Results are never truncated: an oversized read is refused up front and names the limit for that format. SetX64DBG_MCP_MAX_RESPONSE_CHARSto change the budget. size=0reads as much as fits: it returns the largest readable run that fits in one response, stopping at the end of the containing memory region. Use it instead of guessing a byte count.- Compact output for bulk reads:
read_memorydefaults to a hex dump with an ASCII sidebar, which costs roughly 5x the raw bytes. Passformat='hex'(2x) orformat='base64'(1.33x) when reading structures repeatedly or pulling larger ranges. - Batch scattered reads: When walking linked structures,
read_memory_many(['esi+0x10:4', '0x1000:16'])fetches several ranges in one call. A read that fails is reported inline, so a partially-mapped structure still yields its readable fields. The batch shares one response budget; a read that does not fit in what remains is skipped whole and reported, never truncated. - "No debuggee" is reported distinctly: x64dbg returns the same
XERROR_READ_FAILEDfor a bad address and for a dead debuggee, and its memory map is a cache that survives detach — so a detached session would otherwise return a stale map that looks like a successful result. The memory tools check for a debuggee and say so explicitly instead. - Events for synchronization: Use
wait_for_eventto wait for breakpoints, DLL loads, or other debug events before inspecting state. - Raw commands: If a feature isn't exposed as a dedicated tool,
execute_commandpasses any command directly to x64dbg's command interpreter. See the x64dbg command reference. $resultmeans different things per command: see below before relying on it.
Reading $result
execute_command returns only whether the command succeeded. Commands that produce a
value communicate it through the $result variable, which you read with a separate
eval_expression("$result") call.
What $result holds depends entirely on which command set it, and the two conventions
are easy to confuse:
| Command | $result |
|---|---|
find |
Address of the first match, or 0 if not found |
findall |
Count of occurrences |
findallmem / findmemall |
Count of occurrences |
findasm / asmfind |
Count of occurrences |
reffind / ref |
Count of references found |
refstr / strref |
Count of string references found |
bphitcount |
Hit count of the breakpoint |
loadlib |
Base address of the loaded library |
So find gives an address while findallmem gives a count — reading one as the other
silently yields nonsense.
Two further caveats:
$resultis not cleared between commands. A command that aborts early —findagainst an unmapped address, for example — returns without touching it, so a stale value from an earlier command is still there. If you need to detect "this command did not set a result", set$resultto a sentinel first withexecute_command("mov $result, 0xDEADBEEF")and check whether it changed.- Commands that report to a pane do not report through
$result.findall,findallmem, and theref*family write their matches to the References pane and leave only the count in$result; the addresses themselves are not reachable throughexecute_commandat all.