claude-roslyn-lsp
Roslyn's language server — Microsoft.CodeAnalysis.LanguageServer, the engine behind the C# Dev Kit — packaged as a
Claude Code plugin, for C# and Visual Basic. It runs as one shared per-workspace server with
thin per-session clients, so several Claude Code instances share a single loaded solution instead of each loading its own.
It ships three things:
- the LSP server (diagnostics, go-to-definition, find-references, hover, rename, code actions, workspace symbols);
- a refactoring MCP (
roslyn-refactor) for the mutating operations Claude Code's read-only LSP tool lacks, plus a crlsp
command-line client of the same shared server;
- an extension API that lets a repo surface a running system through the LSP — see Extensions.
Why it exists
Two problems with running a C# language server under Claude Code, and what this does about them.
Build locks. A language server loads Roslyn analyzers and source generators to do its work, which holds OS handles on
those DLLs. In a repo that builds its own analyzers or generators, the next build can't overwrite the locked output —
MSB3027, "being used by another process". The fix the IDEs use is shadow-copying: load a copy of the analyzers from a
temp directory so the build output stays free. The Roslyn server does this by default; csharp-ls does not and can't be
configured to. This plugin extends the same idea to its own binaries: clients and the daemon run from a per-build shadow
copy, so rebuilding the plugin never blocks on a running instance either.
One solution per instance. A language server loads the whole solution into memory. Run one per editor instance and you
pay that cost N times. This plugin runs a single daemon per workspace and connects each Claude Code instance to it as a thin
client, so N instances cost roughly 1× the memory, not N×.
Architecture
Claude #1 ─ client ─┐
Claude #2 ─ client ─┤ Sluice shared memory ┌─ one daemon per workspace
Claude #3 ─ client ─┴───────────────────────▶ │ └─ one Microsoft.CodeAnalysis.LanguageServer
│ (solution loaded once → 1× memory)
- client (
ClaudeRoslynLsp.Client) — spawned by Claude Code per session. It derives a per-workspace key, connects to
the daemon (electing to start one, under a named mutex, if none is running), and forwards messages between Claude Code's
stdio and the daemon. It also routes files that live in a different repo to that repo's own daemon, and exits cleanly if
its daemon dies so Claude Code reconnects.
- daemon (
ClaudeRoslynLsp.Daemon) — one per workspace. It downloads and caches
Microsoft.CodeAnalysis.LanguageServer, starts it, opens the solution, and multiplexes every client onto that one server:
a shared initialize, id-namespaced requests, reference-counted document opens, and per-client diagnostic routing so one
instance's edits don't flood the others. It shuts down after a period with no clients.
The client↔daemon hop uses Sluice shared-memory channels rather than a named pipe.
Both build from source on first run — the only download is Microsoft's server package. Claude Code spawns the components
through boot/run.ps1, which runs them from a shadow copy of the latest build. Building writes to bin, which is never
executed directly, so a rebuild can't be blocked by a running instance no matter how many are active.
Requirements
- .NET 10 SDK on
PATH (dotnet --version reports 10.x). The client and daemon target net8.0 with RollForward
and run on the net10 runtime, so no separate .NET 8 install is needed.
- The Claude Code LSP tool must be enabled — see Enabling the LSP tool. The refactoring MCP and
the
crlsp CLI work without it; only the in-editor LSP server depends on it.
Install
From a running claude:
/plugin marketplace add erinloy/claude-roslyn-lsp
/plugin install roslyn@claude-roslyn-lsp
Restart Claude Code. The first launch builds the components and downloads the server (one-time, a minute or two); later
launches connect to the running daemon immediately. If you have more than one C# LSP plugin installed, disable the others so
only one claims .cs.
Enabling the LSP tool
Claude Code's built-in LSP tool — the part that actually spawns a registered .cs server and surfaces its diagnostics and
navigation — is gated behind a client patch, tweakcc's fix-lsp-support. Without it the server is registered but never
launched, and ENABLE_LSP_TOOL=1 alone is not enough on current builds. Claude Code auto-updates replace the binary and
revert the patch, so it has to be re-applied after each update. This gates every LSP plugin (gopls, pyright, OmniSharp,
roslyn) the same way; it is not specific to this one.
The plugin tracks the dependency for you: