MCP tool reference
Forty-four tools, grouped by what you would be doing when you reach for them. Most take no arguments beyond the obvious ones; where a tool refuses something, that refusal is noted, because it is usually the interesting part.
get_loaded_modules, get_source_files,
get_debug_session_status and the two output tools also work while the
debuggee is running.
Session and process
| Tool | What it does |
|---|---|
list_debuggable_processes |
Running processes with pid, executable name and path, command line, parent pid, start time and architecture. Optional name filter. |
launch_debuggee |
Start a process under the debugger. Takes program plus optional args, mapFile, rsmFile, sourceRoot, sourceSearchPaths, stopAtEntry and the exception filters. |
launch_from_config |
Launch from an existing VS Code launch.json — the same file the DAP debugger uses, JSONC and ${workspaceFolder} / ${env:} included — so nothing has to be restated. Stops at entry. |
attach_to_process |
Attach by processId or processName. An ambiguous name fails and lists the candidates rather than picking one. A target of a different architecture is rejected. Detaching leaves the process running by default. |
attach_from_config |
The same, from a launch.json attach configuration. |
get_debug_session_status |
Session state — none, launching, attaching, running, stopped, exited, detached, terminated — plus the stop reason and location when stopped. |
detach_debugger |
Detach, leaving the process running. |
terminate_debuggee |
Kill the debuggee. |
stop_debugging |
End the session: detaches an attached one, terminates a launched one, unless killOnDetach says otherwise. |
Breakpoints
| Tool | What it does |
|---|---|
set_breakpoint |
One source-line breakpoint, with optional condition, hitCondition ("5", ">5", ">=5", "%5") and logMessage. Reports whether the line resolved to real code. |
set_breakpoints |
Several at once. Per file, this replaces that file’s previous set; files not mentioned are untouched. |
list_breakpoints |
Everything currently set, source and address, with verified state. |
remove_all_breakpoints |
Clears both kinds. |
set_breakpoint_at_address |
Break at an absolute address instead of a line — what makes disassemble actionable in a frame with no symbols. Stored as (module, offset), never as the bare address, so it survives a relaunch or an ASLR-rebased package. An address in a module that is not loaded is refused with a reason. |
remove_breakpoint_at_address |
Removes one address breakpoint by id. |
Data breakpoints (hardware watchpoints)
| Tool | What it does |
|---|---|
set_data_breakpoint |
Stop when memory is written, or read-or-written. Takes an expression, a size of 1, 2, 4 or 8 bytes, and an access of write or readWrite. |
list_data_breakpoints |
Every watchpoint with its resolved address, owning module and RVA, size, access, whether it armed, the hardware slot it holds (0–3, or −1 when refused), and a message carrying the refusal reason or the read-or-write caveat. |
remove_data_breakpoint |
Removes one by id and frees its slot. Requires the session to be stopped. |
The expression is resolved by one rule, worth learning because it is what makes this tool sharp:
- A bare name is watched at its own storage.
@Xis already an address.-
Anything else is watched where it lives — so
Arr[High(Arr)]watches the last element, andPByte(@Arr[0]) - 1watches the byte just before the buffer, which is how you find who writes past an array.
What it refuses, and why:
- A fifth watchpoint. There are four CPU debug registers, shared by the whole process. The refusal names what already holds the slots.
-
access: "read". x86 and x64 have no read-only watchpoint.readWriteis the closest thing and it also fires on writes; the response says so rather than pretending otherwise. - A bare local variable, with a reason — its address is only valid for the lifetime of its frame. Watch the containing global or object field, or pass a literal address. This is the one place the MCP surface is narrower than VS Code, where locals can be watched and are withdrawn automatically when the frame dies.
- A misaligned or odd-width request. The address must already be aligned to the size. It is refused, never quietly rounded.
When one fires, the stop reason is dataBreakpoint and
dataBreakpointDescription names the watched expression, the old and new
values, and the thread that wrote it — frequently the entire answer to “who did
this?”.
Execution control
| Tool | What it does |
|---|---|
continue_and_wait |
Resume and wait for the next stop or exit, returning a compact snapshot of the new state. timeoutMs defaults to 30000, after which it returns a still-running result. |
step_over |
One source line, then wait. Optional threadId steps another thread — only that one advances, the rest stay frozen, and run control afterwards targets it. |
step_into |
Into a call on the current line. Same threadId option. |
step_out |
Run until the current function returns. |
pause_execution |
Interrupt a running debuggee. |
wait_until_stopped |
Wait without issuing a command, for a target left running. |
All three step tools accept granularity: "instruction", which steps exactly
one machine instruction and needs no line table — the way through a package built without
debug info, where a source-level step has no terminating condition. A refused instruction
step returns an error with the reason and nothing moves, not even
partially: there is no fallback decoder in this project, so an unavailable disassembler or
undecodable bytes are reported rather than worked around.
step_out at instruction granularity requires a proven return address
— .pdata on x64, [EBP+4] on x86 — and refuses rather than run
somewhere plausible.
Exceptions
| Tool | What it does |
|---|---|
set_exception_filters |
Change which exceptions break, on the live session: any of delphi, av, all, unhandled. [] means never break on first-chance. delphiExceptionClasses narrows the delphi filter to named classes. |
get_exception_details |
When stopped on an exception: its class, message, description and stack. |
The rule engine itself is configured in launch.json, not through a tool —
see Exception handling. An
agent reaches it via launch_from_config.
Location, stack and threads
| Tool | What it does |
|---|---|
get_compact_debug_snapshot |
Session state, stop reason, current thread and location, top frames, top-frame locals and exception info — in one call. The round-trip saver. |
get_current_source_location |
Function, source file and line at the stop. |
get_call_stack |
Innermost frame first, with function, file and line. Optional threadId walks another thread. |
get_threads |
Threads at the current stop: id, name, whether stopped, and which one is current. After a pause, current is the main application thread, not the transient thread the OS injected to break in. |
get_raw_stack_scan |
Last resort, and not a call stack — see below. |
get_raw_stack_scan brute-forces the stack for words that could be return
addresses. Use it only when get_call_stack stops short, which on 32-bit
targets happens at the first routine built without a frame pointer. Each hit is marked:
proven=true means the instruction ending there was decoded and is a call,
proven=false means there was no line table to decode from. Neither
means the routine is still on the chain — a call that already returned leaves its
return address behind and no sweep can tell the difference. These are places the program
has been, never callers, and they must never be merged into a call stack.
Variables and expressions
| Tool | What it does |
|---|---|
get_locals |
A frame’s locals with formatted values and types. Anything expandable carries a handle. |
get_variable |
One named variable. Prefers a frame local — so a class, record or array result carries an expansion handle — and otherwise evaluates the name, including fields, globals and dotted paths. |
expand_variable |
The child fields of an expandable value, by handle. Nested results carry their own handles, so an object graph is walked step by step. Handles are valid until the next stop. |
evaluate_expression |
A Pascal expression in the context of a frame: locals, fields, globals, arithmetic, casts, method calls. |
pause_execution that is
usually inside a system DLL where nothing resolves. Read the stack first and pass the
frameIndex of a frame in your own code. Each also takes a
threadId.
Expression evaluation executes method and getter calls in the debuggee, with their side effects — see Expression evaluation.
Modules and sources
| Tool | What it does |
|---|---|
get_loaded_modules |
Every image mapped in the debuggee with load base, size, symbol state and the debug-info formats that actually loaded. symbols is loaded, noSymbols or indexing; formats lists what registered — td32, map, rsm, dcp, jdbg, tds — not what was looked for. Also how you tell whether an unbound breakpoint is waiting for a package that has not loaded yet. |
get_source_files |
The source files the loaded debug info can name, grouped by module. This is the file spelling set_breakpoint expects — use it rather than guessing from a unit name. A listedBy of null means the module’s loaded formats cannot enumerate files (.rsm, .dcp and .jdbg map addresses but hold no file index), which means unknown, not no source files; a breakpoint there may still bind. |
Registers
| Tool | What it does |
|---|---|
get_registers |
General-purpose registers and flags of the stopped thread, named for the target’s bitness. Values are hex strings, never bare numbers — a 64-bit register does not survive an IEEE double intact. |
set_register |
Write one by name; either spelling works on either bitness, and on x64 an E-name writes the low 32 bits and zero-extends as the hardware does. Returns the value re-read from the thread, so the response proves the write instead of echoing the request. Naming a register the target does not have — R8–R15 on 32-bit — is an error, not a silent no-op. |
Memory and disassembly
| Tool | What it does |
|---|---|
read_memory |
1 to 4096 bytes, returned as hex plus little-endian integer interpretations, for structures the evaluator does not surface. |
write_memory |
Write contiguous hex bytes at an address. Mutates the live process. |
disassemble |
Zydis, x86 and x64, from an address or a frame’s instruction pointer. Each instruction reports its address, raw bytes, Intel-syntax text and — where a symbol provider knows one — the nearest function plus offset and the source file and line. |
Three things about disassemble that matter to an agent:
-
Use it instead of reading bytes with
read_memoryand decoding them yourself. Hand-decoding x86 produces a confident wrong answer; this tool exists to prevent that. -
Bytes Zydis cannot decode come back as
db XXwithdecoded:false— never a guessed mnemonic. -
beforeasks for preceding instructions. x86 cannot be decoded backwards, so this is answered only when a proven earlier boundary exists and decoding forward from it lands exactly on the requested address. Otherwisebeforecomes backrefused:truewith a reason — while the forward instructions are still returned. A refusedbeforeis not a failed call, and the result never mixes proven and unproven instructions in one list.
disassemble returns
available:false with a reason and no instructions. That is the ordinary
answer on a machine without it, not a failure.
Output
| Tool | What it does |
|---|---|
get_debuggee_output |
The program’s own stdout since the previous call. Incremental. |
get_debugger_output |
Debugger-generated output since the previous call — notably logpoint messages. Incremental. |
What is not here
The engine is shared with the VS Code debugger, but the tool surface is not a complete mirror of it. Absent by design or not yet built:
- No Set Next Statement.
-
No
setVariableequivalent —write_memoryandset_registerare the only ways to mutate state. -
expand_variabledoes not expand getter-backed properties or Variant arrays. - Module loading is currently eager: every module carrying debug-info sidecars loads once breakpoints exist, rather than lazily as each package maps.
-
No
get_scopesorget_arguments. This one is deliberate — locals are returned directly with opaque handles, because arguments are not reliably separable from locals in the underlying debug information, and a scope indirection would have promised a distinction the data cannot support.