Per-agent x64dbg session through debugger()/debugger_tools()

Harbor starts the x64dbg-automate MCP server (stdio) on first use and keeps
it for the life of the agent's Harbor process; the debuggee is terminated
when that process exits. Configured by debugger_command/_path/_temp in
[reverse].
This commit is contained in:
KeelrLeek committed 2026-10-05 12:44:25 -05:00
1 parent 7ed914ebdb
commit 584fabeb74
5 files changed
+175 -4

No files matched your search

+34 -1
View File
@@ -37,7 +37,9 @@ review queue, presence and stop flags. Protocol:
11. To read the original machine code use re_disassemble, re_xrefs, re_bytes and re_lookup: they read the project's
own image with the team's current names, so do not write your own disassembler or offset maths.
For Ghidra's view (decompiler, p-code, stack frames, struct field references, types) use ghidra_tools() once,
then ghidra(tool, arguments): one shared read-only Ghidra session holding the team's annotations."""
then ghidra(tool, arguments): one shared read-only Ghidra session holding the team's annotations.
To check a value in the running game (x87 control word, `this`, struct fields), use debugger_tools() and
debugger(tool, arguments): your own x32dbg session; always end it with debugger('terminate_session')."""
KindName = Literal["note", "question", "answer", "request", "finding", "retraction", "decision", "status", "handoff"]
Importance = Literal["normal", "high", "urgent"]
@@ -517,6 +519,37 @@ def ghidra(tool: str, arguments: dict | None = None) -> str:
return _gh(lambda g: g.call(tool, arguments or {}))
_debugger = {}
def _dbg(fn):
try:
h = harbor()
d = _debugger.get(h.project.name)
if d is None:
from .reverse import Debugger
d = _debugger[h.project.name] = Debugger(h.project.reverse)
return fn(d)
except (HarborError, projects.ProjectError) as error:
raise ToolError(str(error)) from None
@mcp.tool()
def debugger_tools() -> list:
"""The tools of your own x64dbg session (x64dbg-automate on x32dbg): start_session, set_breakpoint, go,
get_all_registers, read_memory, eval_expression, ... Call them with debugger(tool, arguments)."""
return _dbg(lambda d: d.tools())
@mcp.tool()
def debugger(tool: str, arguments: dict | None = None) -> str:
"""Run one x64dbg tool in your own debugger session, e.g. debugger('start_session', {'target_exe':
"R:/SteamLibrary/steamapps/common/Sid Meier's Pirates!/Pirates!.exe"}), debugger('set_breakpoint', ...),
debugger('go'), debugger('get_all_registers'). The session lasts until debugger('terminate_session') or
until your app closes. Use it to read values, not to patch the game."""
return _dbg(lambda d: d.call(tool, arguments or {}))
def _report_in():
"""Tell Harbor this agent's app has its tools running: at start, then every 5 minutes while the app keeps the
server open (updates the connection only, not the agent's last-seen time)."""