# rdbg-debugger — Claude Code plugin

Drive the [rdbg](https://sundaymorning.app/rdbg/) Delphi remote
debugger from Claude Code. Set breakpoints, step, inspect VCL objects
through RTTI, capture window screenshots, simulate keyboard/mouse
input, and let an autonomous bug-hunter agent reproduce crashes
without human babysitting.

**Scope (v1):** local Windows debugging only. Claude Code runs on
Windows in the same interactive desktop as the daemon (typically
your RDP session). The plugin drives both `rdbgd64.exe` (Win64
targets) and `rdbgd32.exe` (Win32 targets) — the bridge picks the
right one per `launch` based on the target's PE machine type.
SSH-to-remote-Windows is a v2 feature.

---

## Prerequisites

1. **Windows 10+ x64** with Claude Code installed.
2. **rdbg 0.2 or later.** Earlier releases don't ship `rdbg-mcp.exe`.
   Download from [sundaymorning.app/rdbg/](https://sundaymorning.app/rdbg/assets/dl/)
   and unzip somewhere short and space-free, e.g. `C:\rdbg\`.
3. **Claude Code launched from inside your interactive RDP desktop**
   (Session 2+). The daemon needs DirectDraw / GDI from a real
   logged-in session; the plugin pre-checks this and refuses to
   auto-spawn from Session 0.

---

## Install

In Claude Code:

```
/plugin marketplace add https://sundaymorning.app/rdbg/.claude-plugin/marketplace.json
/plugin install rdbg-debugger@rdbg
```

Claude Code clones the plugin from
`https://sundaymorning.app/rdbg/rdbg-plugin.git` (a static-file
git mirror — no GitHub account or auth required) and prompts you
for configuration on first enable.

Defaults assume `C:\rdbg\bin\` for `rdbg-mcp.exe`, `rdbgd64.exe`,
and `rdbgd32.exe`. If you unzipped rdbg somewhere else, set
`rdbg_mcp_path`, `daemon_path_x64`, and `daemon_path_x86`
accordingly (use no-spaces paths). If you only debug one bitness,
leave the unused `daemon_path_*` blank — the bridge surfaces a
clear error if a launch needs a daemon you haven't configured.

That's it. Run `/rdbg:status` to confirm everything's connected.

**Updating.** Re-run `/plugin marketplace update rdbg` to pull a
new version published to the same git mirror.

**Manual download.** A versioned zip is published alongside the
git mirror at
[`assets/dl/rdbg-claude-plugin-latest.zip`](https://sundaymorning.app/rdbg/assets/dl/rdbg-claude-plugin-latest.zip)
for offline inspection / archival; Claude Code itself won't
install from a zip.

---

## Permissions (one-time copy-paste)

Without this step, every MCP tool call (`set_breakpoint`,
`inspect_object`, `launch`, `events`, `evaluate`, `send_keys`,
`click_at`, …) and every Bash invocation of a daemon binary
(`rdbgd64`, `rdbgd32`, `rdbgc`, `rdbgui`, `rdbg-mcp`) triggers a
permission dialog the first time it runs in a session — including
each *new* tool the bridge ships in a later release. During an
active debug loop that's a click every few seconds.

Drop the snippet below into either:

- **`.claude/settings.json`** (project-scoped) — recommended for a
  repo-shared dev setup, so every contributor opting into the rdbg
  workflow inherits the allowlist via version control.
- **`~/.claude/settings.json`** (user-scoped) — recommended for
  one-time-and-forget on a personal workstation.

```json
{
  "permissions": {
    "allow": [
      "mcp__rdbg__*",

      "Bash(bin\\rdbgd64*)",  "Bash(bin/rdbgd64*)",
      "Bash(bin\\rdbgd32*)",  "Bash(bin/rdbgd32*)",
      "Bash(bin\\rdbgc*)",    "Bash(bin/rdbgc*)",
      "Bash(bin\\rdbgui*)",   "Bash(bin/rdbgui*)",
      "Bash(bin\\rdbg-mcp*)", "Bash(bin/rdbg-mcp*)",

      "Bash(rdbgd64*)",  "Bash(rdbgd32*)",
      "Bash(rdbgc*)",    "Bash(rdbgui*)",
      "Bash(rdbg-mcp*)"
    ]
  }
}
```

The two cohorts:

- `mcp__rdbg__*` — wildcard for the plugin's MCP tools (covers all
  current and future tools without revisiting this snippet).
- `Bash(...)` entries — cover both the typical bin-zip layout
  (`bin\rdbgd64.exe`, `bin/rdbgd64.exe`) and PATH-only invocation.
  Both Windows-style and POSIX-style separators are listed because
  Claude Code's permission matcher pattern-matches the literal
  command line.

This is a workaround until Claude Code plugin manifests can declare
permissions natively; track via [link to feature request once filed].

---

## What you get

### Tools (Claude calls these directly)

| Group | Tools |
|---|---|
| Process | `hello` (now reports BOTH daemons' status), `status`, `list_processes`, `launch`, `attach`, `shutdown_daemon`, `bridge_config` (effective bridge config + LSP state) |
| Sessions | `list_sessions`, `bind_session`, `release_session` |
| Control | `interrupt`, `detach`, `kill_session` |
| Stepping | `continue`, `step_over`, `step_into`, `run_to`, `set_rip` |
| Breakpoints | `set_breakpoint` (source / symbol / address), `clear_breakpoint`, `list_breakpoints` |
| Inspection | `get_registers`, `read_param` (calling-convention arg register w/ optional decode), `read` (memory / string / dyn_array), `get_self`, `inspect_object` (with `include_methods=false` to skip method noise), `inspect_locals`, `evaluate` |
| VCL | `get_main_form`, `find_vcl_for_hwnd`, `enumerate_vcl_tree` |
| GUI | `list_windows`, `capture_window`, `screenshot_desktop`, `post_message`, `send_keys`, `set_text_field`, `click_at`, `send_input` |
| Recording | `spy` (start / stop / status) |
| Exceptions | `set_exception_filter` |
| Events | `events` (with optional `filter:[]`) |
| **LSP — navigation** | `lsp_definition`, `lsp_declaration`, `lsp_implementation`, `lsp_hover`, `lsp_references` |
| **LSP — authoring** | `lsp_completion`, `lsp_signature_help` |
| **LSP — outline** | `lsp_document_symbols`, `lsp_workspace_symbol` |
| **LSP — diagnostics** | `lsp_diagnostics` (pull-style cache snapshot) |
| **LSP — projects** | `lsp_open_project` (set active default project) |

### LSP code navigation

The plugin spawns Embarcadero's `DelphiLSP.exe` as a child of the
bridge and surfaces its standard LSP methods as MCP tools. Claude
can navigate Delphi source the way an IDE does — go-to-definition,
find-references, hover for type info, completion, signature help —
without grepping. **Supported on Delphi 11.3 → 13.1.**

The LSP tool surface is **capability-gated** per Delphi version.
On Delphi 12, `lsp_references` and `lsp_workspace_symbol` aren't
advertised by DelphiLSP itself, so:

- `lsp_references` is hidden from `tools/list` on D12
  (re-appears on 13.1+ when `referencesProvider` is advertised).
- `lsp_workspace_symbol` always surfaces — when the server
  advertises it, results are exact; otherwise the bridge falls
  back to a per-file `documentSymbol` aggregation across the
  project's units, returned with `precision: "approximate"`.

**Project model: zero-config when you have a `.dproj`.** DelphiLSP
needs a `.delphilsp.json` per project. The bridge auto-generates
one from your `.dproj` (parsing dproj XML + reading registry
library paths + walking `$(BDS)\source` for browsingPaths) the
first time a project is touched. If you'd rather use an
IDE-generated `.delphilsp.json` (tick *Tools > Options > Code
Insight > Generate LSP Config*), drop one next to the `.dproj` —
the bridge prefers an existing fresher sibling JSON over autogen.
Disable autogen entirely with `lsp_disable_autogen=1`.

**Per-call project resolution.** Each `lsp_*` tool resolves its
target project in priority order:

1. Explicit `project` arg in the tool call.
2. Walk up from the tool's `file` arg looking for a sibling
   `.delphilsp.json` or `.dproj`.
3. The current default set by `lsp_open_project` (or by the
   `lsp_default_project` plugin setting at startup).

**License caveat.** DelphiLSP's license check requires an
interactive desktop session — running rdbg-mcp from a non-
interactive context (SSH, scheduled task, Windows service) makes
DelphiLSP return *"valid license was not found"* on every method.
The bridge translates this to an actionable error directing you
to run from your interactive RDP/local desktop instead.

### Skills (auto-load when context matches)

- **`delphi-debugging-workflow`** — primary skill. The
  attach/launch/break/step/`events` rhythm.
- **`delphi-code-navigation`** — when to reach for LSP tools vs
  grep, vs runtime tools; project-resolution model.
- **`exception-triage`** — first-chance vs second-chance, frame
  walking, exception-object inspection.
- **`gui-bug-reproduction`** — demo-and-record vs autonomous
  reproduction.
- **`delphi-rtti-inspection`** — walking Delphi objects through
  RTTI without source.
- **`vcl-self-discovery`** — build a model of an unfamiliar VCL
  app's UI tree from RTTI alone, then drive it to find crashes.

### Subagent

- **`delphi-bug-hunter`** — autonomous bug-investigation agent.
  Give it a one-line bug report; it figures out reproduction,
  catches the exception, walks the stack, and returns a `.pas:line`
  diagnosis.

### Slash commands

- `/rdbg:status` — daemon connectivity + session diagnostics.
- `/rdbg:start-daemon` — instructions for launching the daemon
  manually (with `--enable-spy` flag tip for input recording).
- `/rdbg:install-help` — this README, surfaced in chat.

---

## Two daemons, one bridge

rdbg ships two daemons from one source graph:

| Binary         | Targets       | Conv. port | Spawn helper          |
|----------------|---------------|------------|-----------------------|
| `rdbgd64.exe`  | Win64 (PE32+) | 5902       | `spawn-daemon.cmd`    |
| `rdbgd32.exe`  | Win32 (PE32)  | 5903       | `spawn-daemon32.cmd`  |

The wire protocol is identical between the two; `hello` reports
`target_arch: "x64" | "x86"` and `daemon: "rdbgd64" | "rdbgd32"`.
WoW64 cross-bitness debug is **not** supported — pick the daemon
that matches your target.

You don't have to pick manually. The MCP bridge:

1. Connects to the `default_target_bitness` daemon at startup
   (defaults to `x64`).
2. On every `launch` tool call, peeks the target's PE machine
   type (file is local on the same Windows host as the daemon).
3. If the target's bitness disagrees with the connected daemon,
   disconnects, spawns the matching daemon (when
   `auto_start_daemon=true` and the matching `daemon_path_*`
   is configured), and reconnects on the new port.
4. Caches `daemon` + `target_arch` from `hello` so subsequent
   tool calls don't re-probe.

For `attach`, the daemon itself fail-fasts with a structured
`wrong_arch` error if you point a daemon at a process of the
wrong bitness — Claude sees the error and switches accordingly
on the next attempt.

## How the daemon stays alive across Claude restarts

With `auto_start_daemon=true`, both daemons are spawned
**detached** (`DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP |
CREATE_BREAKAWAY_FROM_JOB`) so they outlive the MCP server.
Closing Claude Code, reloading the plugin, or starting a new
session all leave the daemons running; the next MCP server
instance reconnects to whichever one is appropriate for the
next `launch`.

If you'd rather control the daemons yourself, set
`auto_start_daemon=false` and start them from
`/rdbg:start-daemon`'s instructions.

---

## Common workflows

### Diagnose a specific crash

> *"crashy.exe segfaults right after I click the button"*

The `delphi-bug-hunter` subagent will:
1. `launch` or `attach` to `crashy.exe`.
2. Set `kernelbase!RaiseException` as a catch-all exception BP.
3. Either drive the click via `click_at` (if a hypothesis is
   available) or ask you to demonstrate while `spy(start)`
   records.
4. On the resulting `exception` event, walk frames to the first
   `.pas:line` user-code frame and report the diagnosis.

### Find any crash in an unfamiliar app

> *"find any crash in inputtest.exe"*

The same subagent in self-discovery mode will:
1. `attach`, then `get_main_form` + `enumerate_vcl_tree` to map
   the UI in two round trips.
2. Set `set_exception_filter(first_chance=false)` to drop noise.
3. Walk the tree breadth-first, injecting nasty input via
   `set_text_field` (long strings, edge unicode, format-string
   characters, etc.) and `click_at`.
4. Surface the first input sequence that produces an exception.

### Inspect what's in a Delphi variable

The `delphi-rtti-inspection` skill auto-loads when you ask "what
is in this object". Claude reaches for `get_self` (when paused at
a method) or `inspect_object` (given an address) to walk
published properties + fields, then `read(kind=string)` to
materialize Delphi strings at addresses it discovers.

---

## Troubleshooting

**`/rdbg:status` shows the daemon as Session 0** — `capture_window`
will return blank pixels. The daemon must run inside your
interactive RDP desktop. If `rdbg-mcp.exe` was somehow spawned
from a non-interactive context, restart Claude Code from the RDP
desktop directly.

**`/mcp` shows `rdbg` red with "file not found"** — check
`rdbg_mcp_path` in the plugin config. Use the path browser; the
default `C:\rdbg\bin\rdbg-mcp.exe` requires you to have unzipped
rdbg to `C:\rdbg\`.

**Daemon already busy with another client** — `rdbgd64` serves
one client at a time. Close `rdbgui.exe` or any `rdbgc repl`
session first.

**`spy` returns "feature not enabled"** — the daemon needs the
`--enable-spy` flag. Either edit `daemon_args` in the plugin
config (auto-spawn case) or relaunch the daemon manually with
that flag. (`send_input` is on by default and needs no flag.)

---

## Development

The plugin source lives in `claude-plugin/` of the
[rdbg repo](https://github.com/p0ssess3d/rdbg). To test locally
without going through a release zip:

```powershell
$src = "C:\path\to\rdbg\claude-plugin"
$dst = "$env:USERPROFILE\.claude\plugins\rdbg-debugger"
New-Item -Type SymbolicLink -Path $dst -Target $src
```

Edit files in `claude-plugin/` and run `/plugin reload-plugins`
to pick up changes.

---

## License

MIT. See `LICENSE` in the rdbg repo root.
