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:

  1. Configure the x64dbg plugin to bind on an accessible address. Edit x64dbg.ini on the guest/remote machine:

    ini [XAutomate] BindAddress=0.0.0.0 ReqRepPort=50000 PubSubPort=50001

    Or use the Plugins > x64dbg-automate > Automate Settings... menu in x64dbg.

  2. Ensure the ports are accessible (VM port forwarding, firewall rules, etc.).

  3. Ask Claude to connect:

    Connect to the remote x64dbg at 192.168.1.100 with REQ port 50000 and PUB port 50001

    Claude will call connect_remote with 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 for hex, and 36 KB for base64. disassemble is capped at 100 instructions. Results are never truncated: an oversized read is refused up front and names the limit for that format. Set X64DBG_MCP_MAX_RESPONSE_CHARS to change the budget.
  • size=0 reads 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_memory defaults to a hex dump with an ASCII sidebar, which costs roughly 5x the raw bytes. Pass format='hex' (2x) or format='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_FAILED for 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_event to 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_command passes any command directly to x64dbg's command interpreter. See the x64dbg command reference.
  • $result means 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:

  • $result is not cleared between commands. A command that aborts early — find against 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 $result to a sentinel first with execute_command("mov $result, 0xDEADBEEF") and check whether it changed.
  • Commands that report to a pane do not report through $result. findall, findallmem, and the ref* family write their matches to the References pane and leave only the count in $result; the addresses themselves are not reachable through execute_command at all.