The technical side of Autopilot for Delphi: how it compares with Embarcadero Kai, how the bridge and the MCP server work, the 13 tools the AI calls, and what Autopilot deliberately does not do.
How does Autopilot compare with Embarcadero Kai?
They are not the same thing — and they work well together. Kai is Embarcadero’s agentic-AI add-on for RAD Studio. It writes code, compiles, and fixes compiler errors — all inside the IDE, on your source. The moment the app is built and running, Kai stops: it can’t press a button or read a label. That is the gap. Autopilot for Delphi lives on the other side of it — in the running app — and that’s exactly where Kai can’t go.
| Kai | Autopilot | |
|---|---|---|
| Write code in the IDE | yes | no |
| Build, fix compiler errors | yes | yes* |
| Drive and operate the running app | no | yes |
| Debug / test the GUI (click, type, read state) | no | yes |
| Screenshot the running app | no | yes |
* Autopilot itself doesn’t compile — but it runs inside Claude Code, which writes and builds the code. So end to end, the Autopilot workflow does build and fix.
Run both and the loop closes with no human in the middle: Kai (or Claude Code) writes and fixes the code; Autopilot drives the running result and reports whether it truly works.
Everything above is what Autopilot does. Everything below is how it is built — technical detail, safe to skip.
How it works
Three small pieces and one pipe. Your app links in a tiny bridge unit; a separate server process speaks the Model Context Protocol (MCP) to the AI; the two talk over a private Windows named pipe.
You add one unit and one call to your project:
uses
Vcl.Forms,
Autopilot.Bridge.Vcl, // 1. add the unit (Autopilot.Bridge.Fmx for FireMonkey)
FormMain in 'FormMain.pas' {frmMain};
begin
Application.Initialize;
Application.CreateForm(TfrmMain, frmMain);
Autopilot.Bridge.Vcl.StartBridge; // 2. start the bridge
Application.Run;
end.The bridge is wrapped in {$IFDEF AUTOPILOT}. You add that define to your debug build only — so release builds compile StartBridge to nothing. No thread, no pipe, no automation surface in production. Your shipped binaries are exactly as they were.
The AI sees your form’s real component tree and acts on it through a small set of typed tools (the full list is below). Because it reads and writes properties through RTTI directly, it works with any third-party control pack — DevExpress, TMS, anything — with no OCR and no fragile screen coordinates. Setup is a one-time registration of the server with your AI host — on Claude Code that’s a single line, claude mcp add autopilot -- C:\Path\To\Autopilot.Mcp.exe. From then on it auto-discovers your running app — no port to configure, no manual attach.
The tools the AI calls
No macro recorder, no script to maintain — under the hood it’s a small, typed toolset that the assistant calls straight from a chat session. Thirteen tools cover the whole operate-and-observe loop:
| Tool | What it does |
|---|---|
list_tree | Enumerate every form and control — names, classes, paths, text, enabled/visible state. The AI’s map of your app. |
click | Click a button, or anything with an OnClick. Repeat N times in a single call; override the per-call timeout. An opt-in message mode presses a button that opens a modal dialog without blocking: the click is queued, the answer comes back at once, then dismiss_dialog closes the popup. |
execute_action | Fire a TAction directly — for shortcut-only actions and actions shared by several controls. |
get_text / set_text | Read and write Caption / Text / Lines on edits, memos and labels. Writing fires OnChange. |
set_checked | Toggle a checkbox or radio button; fires its OnClick (VCL) or OnChange (FMX) event. |
set_property | The Swiss-army knife: write any published property — Tag, Color, Position, ItemIndex, sets, even AI-friendly colors like clRed or #FF8000. Mistype a name and it hands back the full list of writable properties with their live values, so the AI corrects itself without another round-trip. |
read_property | Read any published property via RTTI — and what it returns is exactly what set_property accepts back. |
wait_for | Poll a property until it matches or times out — for async work (TTask, database queries, timers). |
screenshot | Capture the form as a PNG, for visual and layout checks. |
attach | Pick a target when several apps are running. Usually unnecessary — every tool auto-attaches. |
set_keep_awake | Keep the device screen on while the AI drives an Android target, so the OS doesn’t freeze the app mid-session. A no-op on Windows. |
dismiss_dialog | List and dismiss native OS dialogs (MessageBox, Task Dialogs, file dialogs) that the component tools can’t see — details in the next section. |
The assistant bundles several of these into a single turn — find the control, type, click, then check the result — so a whole reproduce-and-verify sequence is one round-trip, not ten.
Native OS dialogs
A TForm.ShowModal dialog is an ordinary VCL/FMX form, so the component tools (click, set_text, …) drive it directly. A native OS dialog — Application.MessageBox, the Vista Task Dialog behind a default ShowMessage/MessageDlg, a common file dialog — is a raw Win32 window with no TComponent, so those tools cannot see it and return not_found. The dismiss_dialog tool reaches it through Win32 instead: it lists the dialogs that are up and dismisses one by clicking a button (by role, caption, or control id). A visible modal dialog runs a message loop, so the bridge still reaches the main thread to act even while the app looks blocked. And the click that opens the dialog doesn’t block either: the AI presses that button in message mode, gets its answer immediately, then dismisses the popup.
| Popup | How the AI drives it |
|---|---|
Your own TForm.ShowModal | Component tools — it is in Screen.Forms[] |
ShowMessage / MessageDlg in a VCL-styled app | Component tools — it renders as a TForm |
ShowMessage / MessageDlg, default (native Task Dialog) | dismiss_dialog |
Application.MessageBox, TOpenDialog / TSaveDialog | dismiss_dialog |
Windows targets only; against an Android FMX target dismiss_dialog reports supported:false.
What it deliberately doesn’t do
Autopilot for Delphi acts directly on your Delphi objects; it doesn’t fake Windows mouse and keyboard input. That’s a deliberate trade, and it has a flip side — bugs that only show up through the real input pipeline won’t reproduce through it. Things like:
- Focus-change validation (
OnExit, kill-focus handlers) - IME composition and Asian-language input
- Keyboard accelerators that travel through the Windows message loop
- Hover-driven UI (mouse-enter / mouse-leave)
- Drag-and-drop started by a real mouse-down-and-move
A message-based click mode covers some of this for buttons; for full input-pipeline coverage you would pair Autopilot with a SendInput-style tool (TestComplete, Ranorex, AutoIt).
In return for that trade you get direct property access with no OCR and no fragile screen coordinates, sub-millisecond round-trips, and compatibility with any third-party control pack — because RTTI is universal.
Found a bug?
Get in touch via my About page. Every session writes a log to %TEMP%\Autopilot\<ExeName>-<PID>.log — every command in and out, plus the dispatch timings. Attach the one from a run that reproduces the problem and I’ll have what I need.