Catalog · How the catalog works
delphi-lsp-claude
- ★ 2 on GitHub
Anyone may press Interesting, signed in or not. It helps others find this project.
README
delphi-lsp (Claude Code plugin)
Note: The source and documentation in this project were AI-generated (Claude Code). Review before trusting in production.
Why this exists: Claude Code's official marketplace ships LSP integrations for Rust, Go, Kotlin, TypeScript, and others — they tighten the feedback loop on generated code by surfacing compile errors automatically and letting the LLM verify symbols before referencing them. Delphi didn't have one. This plugin fills the gap by wiring Embarcadero's DelphiLSP.exe (the same server VS Code uses) into Claude Code, mirroring how the official VS Code extension (embarcaderotechnologies.delphilsp) launches it.
What it adds:
- Hover, go-to-definition, find references, document/workspace symbols via DelphiLSP.
- Push diagnostics — DelphiLSP analyzes after every edit, errors arrive in the same turn.
- Per-(session, cwd) sticky project pick that survives
claude --resume. - Multi-candidate prompt flow — when a workspace has several
.delphilsp.jsonfiles (typical for multi-project repos), Claude prompts you to pick one viaAskUserQuestion, then/delphi-projectloads it. Single-candidate workspaces auto-pick silently. - Race-free correlation between concurrent Claude Code sessions in the same workspace, even when they're working on different projects.
Requirements
- Windows with RAD Studio 11+ installed. The shim auto-detects the highest installed version via the registry, prefers the 64-bit DelphiLSP (
<install>\bin64\DelphiLSP.exe) when present, and falls back to the 32-bit one (<install>\bin\DelphiLSP.exe). The 64-bit binary ships with higher-tier SKUs; lower tiers get the 32-bit one only — both work. Override viaDELPHI_LSP_EXE(absolute path) or/delphi-runtime(slash command). - A valid Delphi license (DelphiLSP refuses to start without one).
Install
Two paths depending on use case.
For regular use on a machine (recommended) — install via the bundled marketplace so Claude Code auto-updates the plugin on each new commit:
/plugin marketplace add https://github.com/jareddavison/delphi-lsp-claude
/plugin install delphi-lsp@jareddavisonFor local development — clone and load via --plugin-dir:
git clone https://github.com/jareddavison/delphi-lsp-claude
claude --plugin-dir ./delphi-lsp-claudeEither way, bin/delphi-lsp-shim.exe ships precompiled — no Delphi compiler needed to run, only to rebuild (see Building).
One-time permissions setup
Claude Code's slash commands run the bundled shim via inline shell execution (!`...` preprocessing). By default, Claude Code's permission system treats this as a bash invocation and either prompts you per-call or — under defaultMode: "auto" — has the safety classifier deny it. You only see the failure once invocations like /delphi-status or /delphi-project return Shell command permission check failed.
Add one allow rule to fix it permanently. Two equivalent options:
(a) Ask Claude: run /update-config in any Claude Code session and tell it:
Add
Bash(*delphi-lsp-shim*)to my permissions allow list.
It edits the right settings.json for you.
(b) Edit ~/.claude/settings.json directly: add the rule to your user-level settings so it applies in every workspace. Merge with whatever you already have under permissions.allow:
{
"permissions": {
"allow": [
"Bash(*delphi-lsp-shim*)"
]
}
}(For just one project, use .claude/settings.json in the project root, or .claude/settings.local.json for an un-committed per-developer override.) After this, the slash commands run inline with no per-call prompts.
Per-project .delphilsp.json
DelphiLSP needs a .delphilsp.json settings file per project (search paths, conditional defines, platform/config target). Without it, the server runs syntactic-only — no semantic queries, no diagnostics.
The IDE feature must be turned on first. Per Embarcadero's docs (Code Insight Reference: Creating .delphilsp.json Files), enable Tools → Options → User Interface → Editor → Language → Language Server Protocol → Save .delphilsp.json file when project is saved. Then save the .dproj and the IDE writes <ProjectName>.delphilsp.json next to it.
The file lives in the workspace, gets committed (or not, your call), and is picked up by this plugin automatically.
When you switch the IDE's active config or platform (Debug/Release, Win32/Win64, etc.), RAD Studio rewrites .delphilsp.json. The shim watches for that and re-fires didChangeConfiguration automatically — so semantic queries reflect the IDE's current target.
How project selection works
When Claude Code starts in a workspace:
- Single
.delphilsp.jsoncandidate → auto-picked silently. - Multiple candidates → on first session in this workspace, Claude is told (via the SessionStart hook) to call
AskUserQuestionwith the candidate list, then/delphi-project <name>to load. Your pick is persisted as sticky bindings. - Resumed session (
claude --resume <id>) → sticky bindings restore your previous pick silently. - Override anytime —
/delphi-project <name>reloads on demand and rewrites the sticky.
Sticky bindings are scoped per-(Claude session id, cwd). Two simultaneous sessions on the same repo can have different projects loaded without interfering.
Slash commands
| Command | Purpose |
|---|---|
/delphi-project <name|path> |
Switch the active .delphilsp.json. Argument is a basename match (e.g. MyApp), a relative path, or an absolute .delphilsp.json path. |
/delphi-status |
Read-only — show registered shim PIDs, active project, and other available .delphilsp.json files in the workspace. |
/delphi-reload |
Recycle DelphiLSP's child process (replays cached initialize + open documents). Useful if DelphiLSP gets into a bad state. |
/delphi-runtime <ver|path|clear> |
Override which DelphiLSP.exe to spawn — e.g. /delphi-runtime 37.0 for BDS 23.0 (Delphi 12 Athens), or an absolute .exe path. |
How the shim works
Claude Code's plugin manifest validator accepts command, args, env, extensionToLanguage, initializationOptions, settings, transport, workspaceFolder, startupTimeout (etc.) for lspServers.<name>.*. However, manifest ${...} substitution is whitelist-limited — only ${CLAUDE_PLUGIN_ROOT}, ${CLAUDE_PLUGIN_DATA}, ${user_config.*} expand; arbitrary env vars (including ${CLAUDE_CODE_SESSION_ID}) pass through literally. Combined with the fact that LSP subprocess env doesn't include CLAUDE_CODE_SESSION_ID, this plugin can't tell DelphiLSP about the project via static manifest. Hence the shim:
- Spawns
DelphiLSP.exe -LogModes 0 -LSPLogging <workspace>(matchesembarcaderotechnologies.delphilsp-1.1.0/delphiMain.ts:36-37). - Injects
initializationOptions: { serverType: "controller", agentCount: 2 }into the forwardedinitializerequest — same defaults as the VS Code extension. - Fires
workspace/didChangeConfigurationwith the chosen.delphilsp.jsonURI oninitialized, or whenever/delphi-projectswitches. - Watches the active
.delphilsp.jsonfor content changes (IDE re-writes on target switch) and re-firesdidChangeConfigurationif the hash changes. - Byte-proxies all other LSP traffic in both directions.
Plus dual-mode operation: when invoked with --hook-session-start or --hook-session-end, the same exe runs the corresponding SessionStart/SessionEnd hook (registered in hooks/hooks.json) and exits. One binary, all the heavy lifting (Win32 process tree walk, JSON parse, sticky bindings r/w) lives in shared Delphi code.
Persistent state
Under ${CLAUDE_PLUGIN_DATA} (resolves to <claude-data-dir>/plugins/data/<plugin-id>/):
| Path | Lifetime | Purpose |
|---|---|---|
session-state/<claude-session-id>.json |
Persistent | Sticky project pick keyed by sha256 of canonical cwd. Survives shim death and Claude Code restart. GC'd only when the corresponding .jsonl in <claude-data-dir>/projects/ is gone (session unresumable). |
claude-pid/<ancestor-pid>.json |
Per-session | Hook-deposited correlation drop, one per ancestor PID. Lets the shim resolve its session id race-free by walking its own ancestry. GC'd at startup if the PID is dead, or eagerly on SessionEnd. |
claude-pid/by-id-<session>.json |
Per-session | Same hook drop, keyed by session id (cwd-canonical-match fallback). |
sessions/<shim-pid>/active.txt |
Per-shim | In-session pick written by /delphi-project. |
sessions/<shim-pid>/runtime.txt |
Per-shim | DelphiLSP path override written by /delphi-runtime. |
sessions/<shim-pid>/reload.flag |
Per-shim | Sentinel deposited by /delphi-reload, consumed by the shim. |
Cleanup:
- Startup GC — sweeps stale
session-state/*.jsonandclaude-pid/*.jsonentries. Skipped entirely if the shim's own session can't be located in the projects dir (defends against data-dir overrides, sync lag, encoding-format changes). - SessionEnd hook — eagerly deletes per-session correlation drops on session close.
- Orphan session GC — sweeps
sessions/<dead-pid>/directories on every shim startup.
Tunables (env vars, all optional)
| Variable | Default | Purpose |
|---|---|---|
DELPHI_LSP_EXE |
(auto-detect) | Path or PATH name of DelphiLSP.exe. Highest BDS version is auto-resolved via registry; this overrides. |
DELPHI_LSP_BITS |
(prefer 32) | Force a specific DelphiLSP variant when both <install>\bin64\ and <install>\bin\ exist. 32 selects 32-bit, 64 selects 64-bit (fails loudly if missing rather than falling back), unset selects 32-then-64. The default flipped from 64-then-32 in response to RSS-5400 — see Troubleshooting. Doesn't apply when DELPHI_LSP_EXE is set explicitly. |
DELPHI_LSP_LOG_MODES |
0 |
Bitmask passed to -LogModes. |
DELPHI_LSP_SERVER_TYPE |
controller |
controller | agent | linter (controller spawns sub-process agents; non-controller modes don't push diagnostics). |
DELPHI_LSP_AGENT_COUNT |
2 |
1 or 2 (controller mode only; ≥2 enables Error Insight push diagnostics). |
DELPHI_LSP_SETTINGS |
(sticky → single-candidate → none) | Explicit .delphilsp.json path; bypasses sticky/auto-pick chain. |
DELPHI_LSP_SHIM_LOG |
(disabled) | Append shim diagnostics to this file. |
Branches & releases
main is the published branch — Claude Code's marketplace auto-pulls from it on every restart, so anything pushed here goes live to all users immediately. Don't merge to main unless you've tested the change end-to-end.
Work on dev (or topic branches off dev) for in-progress changes. When ready to release:
- Merge
dev→main(fast-forward or squash, your call). - Bump
versionin.claude-plugin/plugin.json(semver — patch for fixes, minor for features, major for breaking). - Tag the
mainHEAD asvX.Y.Zand push the tag (git tag v0.6.0 && git push origin v0.6.0). - Push
main.
Users on the marketplace get the new version on next Claude Code restart. If something turns out to be broken, revert main to the previous tag and push.
Building
build.batPicks the highest installed RAD Studio (C:\Program Files (x86)\Embarcadero\Studio\<X.Y>\ with bin\dcc64.exe + bin\rsvars.bat); override via BDS_VERSION=37.0. Output: bin\delphi-lsp-shim.exe — statically linked, no runtime dependencies beyond DelphiLSP.exe itself. Compile time ~0.1s.
If the shim is currently running, rename bin\delphi-lsp-shim.exe to bin\delphi-lsp-shim.exe.inuse first; the .exe.inuse* pattern is gitignored. (Or just run /delphi-shim-reload from inside Claude Code — exits the shim and Claude Code auto-respawns it lazily on the next LSP query, picking up whatever binary is on disk now.)
Troubleshooting
/delphi-* returns Shell command permission check failed for pattern "!CLAUDE_PLUGIN_DATA=..." — see One-time permissions setup. Claude Code's auto permission mode classifier denies the inline shell invocation unless an explicit Bash(*delphi-lsp-shim*) allow rule is in your settings. The Skill(update-config) slash command can add it for you.
LSP(operation: hover, ...) returns No LSP server available for file type: .pas — the plugin isn't loaded for this workspace. If you installed via the bundled marketplace, the plugin only activates in projects where it's been enabled (/plugin install delphi-lsp@jareddavison). For local development, launch Claude Code with --plugin-dir <path-to-this-repo> so the plugin loads regardless of project-scope.
/delphi-status reports Shims: none registered for this workspace even after an LSP query — likely a session-id mismatch between the shim and the slash-command shim. Confirm both processes resolved the same Claude session id by setting DELPHI_LSP_SHIM_LOG=<path> in the env and inspecting the log. The [MINE] marker in /delphi-status flags shims whose claude-session.txt matches the current command's resolved session.
Hover returns No hover information available repeatedly — the cursor isn't on a symbol at the line/character you specified, or DelphiLSP hasn't fully indexed yet. Try LSP(operation: documentSymbol, file: ...) first to find the exact line/char of a symbol, then hover that position. On a freshly-indexed large project, give DelphiLSP a few seconds before the first query.
/delphi-project DemoName returns Could not resolve to a .delphilsp.json — the name didn't match any *<DemoName>*.delphilsp.json in the workspace. Run /delphi-status to see what files are available, then pass an absolute path or a more distinctive substring.
More general LSP weirdness — try /delphi-reload to recycle DelphiLSP. If a fresh build of the shim isn't being picked up, /delphi-shim-reload kills the shim process so Claude Code respawns it lazily from disk.
Compile errors don't reach the model (empty publishDiagnostics) — known limitation in Embarcadero's 64-bit DelphiLSP.exe as of Delphi 13.1. The 64-bit server emits publishDiagnostics notifications with an empty diagnostics: [] array even for hard parse errors; the 32-bit server (<install>\bin\DelphiLSP.exe) emits proper E2xxx-coded diagnostics on the same input. Bug logged with Embarcadero as RSS-5400 — "CodeInsight LSP Compiler errors do not work with 'Use 64-bit version of the server'". The shim defaults to 32-bit when both are installed; set DELPHI_LSP_BITS=64 to force the 64-bit binary anyway (and accept the diagnostic loss). When Embarcadero ships a fix, this default can be reversed.
License & trademarks
Source code in this repository is released under the MIT License (see LICENSE).
Trademark notice — this project is not affiliated with, endorsed by, or sponsored by either organization:
- Claude, Claude Code, and the Anthropic logo are trademarks of Anthropic, PBC.
- Delphi, DelphiLSP, RAD Studio, and Embarcadero are trademarks of Embarcadero Technologies, Inc. (an Idera, Inc. company).
The names appear in this README, package metadata, and source code purely as descriptive references to the products this plugin interoperates with (nominative fair use). The repository name delphi-lsp-claude is a compound of Delphi LSP (the language-server protocol implementation this plugin proxies) and Claude (the AI assistant this plugin integrates with) — both used descriptively. The published plugin name in plugin.json is just delphi-lsp. Nothing here represents an official Anthropic or Embarcadero project.
Users are responsible for holding their own valid Delphi license to run DelphiLSP.exe.

