DAP and MCP frontends
TDapServer and TMcpServer are protocol adapters, not debuggers.
Everything that actually debugs lives below them, in
TDebugSession and the engine.
What each one adds
| DAP frontend | MCP frontend | |
|---|---|---|
| Client | VS Code and any other DAP client | Any MCP client — Claude Code, VS Code, others |
| Shape of the API | The Debug Adapter Protocol’s requests and events, as specified | Around forty semantic tools, each answering a question rather than fetching a resource |
| Who drives | The editor, in response to a person clicking | A model, in response to a person asking |
| Optimised for | Interactive latency — many small requests, a UI that re-asks constantly | Round trips and tokens — hence get_compact_debug_snapshot, which answers in one call what DAP would take three or four to assemble |
That last row is the real design difference. DAP assumes a client that can afford to ask twenty cheap questions because a human is waiting a few milliseconds; an agent pays for every round trip in latency and context. So the MCP surface is coarser on purpose, and its tool descriptions carry the caveats a model needs in order not to misread an answer.
One cycle, both frontends
Setting a breakpoint and hitting it, from either client. Everything below
TDebugSession is identical — the frontend only translates at the top and the
bottom.
sequenceDiagram participant C as "Client
(VS Code or agent)" participant F as "Frontend
(TDapServer / TMcpServer)" participant S as TDebugSession participant E as "Debug event loop" participant D as Debuggee C->>F: set breakpoint (file, line) F->>S: resolve line to address S->>D: write INT3 at address S-->>F: verified / not verified F-->>C: breakpoint state C->>F: continue F->>S: resume S->>E: ContinueDebugEvent E->>D: run D-->>E: EXCEPTION_BREAKPOINT E->>S: stop event S->>S: identify thread, walk frames, read locals S-->>F: stopped (reason, thread, location) F-->>C: stopped event / snapshot
TDebugSession is shared. A DAP client and an MCP client
issuing the equivalent request take the same path through the engine, hit the same symbol
providers and get the same answer — the difference is only in how it is spelled on the
way out.
Where the surfaces are not symmetric
One engine does not mean two identical APIs. What the DAP frontend exposes and the MCP one does not:
- Set Next Statement.
-
setVariable— writing a variable by name. Over MCP onlywrite_memoryandset_registermutate state. - Hardware watchpoints on a bare local. DAP pins one to the frame invocation and withdraws it when the frame dies; the MCP tool refuses a local with a reason instead.
- Lazy per-module symbol loading. Over MCP, modules carrying debug-info sidecars currently load eagerly once breakpoints exist.
And two things the DAP frontend deliberately does not implement, which are worth
recording as decisions rather than gaps: the modules request (the same
information is already on every stack frame, so it would be a second source of truth), and
setExpression (Watch-panel inline editing).
The complete list is in the MCP tool reference and Known limitations.
Why two frontends rather than one
An MCP server that shelled out to a DAP adapter would have been less code. It would also have inherited DAP’s shape — scope references, variable reference indirection, an event stream designed for a UI — and handed a model an API built for a different kind of consumer.
The concrete example is scopes. DAP models variables as scopes you dereference in a second
request. The MCP surface returns locals directly, with opaque handles for whatever is
expandable, and has no get_scopes at all — because in Delphi debug
information arguments are not reliably separable from locals, so a scope split would have
promised a distinction the data cannot support. Going through DAP would have forced that
fiction.