--- key: session.local-code-intelligence title: '2026-08-14 — Brief subagents at the csharp-lsp MCP tools; no subagent has been observed to reach the LSP tool (#777)' status: active since: '2026-08-14' supersedes: none superseded-by: none rule: 'C# and TypeScript find-all-references are available again; brief delegated agents to the `csharp-lsp` MCP tools (`csharp_references`, `csharp_diagnostics`, …) rather than the `LSP` tool, which no dispatched subagent has been observed to resolve (Claude Code 2.1.232, agent types `general-purpose` and `Explore`, 2026-08-14). Preconditions are machine-local — `env.DOTNET_ROOT` in `.claude/settings.local.json` and a root `node_modules/typescript` link — and checkable with `scripts/check-local-lsp.sh`.' signals: 'find all references, findReferences, csharp-ls, typescript-language-server, LSP tool, csharp-lsp MCP, libhostfxr, MSBuildLocator, DOTNET_ROOT, subagent tool availability · paths: `docs/local-lsp-tooling.md`, `scripts/check-local-lsp.sh`, `.claude/settings.local.json`, `.mcp.json` · issues: #777, #773, #403, #671' mechanics: '`docs/local-lsp-tooling.md` — surfaces, configuration, traps, verification' --- **The capability that would address the residue of Family A was configured, enabled, and dead.** The population that `testing.guard-derives-population-from-source` cannot reach is the one whose members are *sites in code* rather than values — #403 (5 of 6 dispatch sites) and #671 (4 of 10 media types). Find-all-references answers exactly that, and across 811 session transcripts the `LSP` tool was invoked **zero** times, against 23,661 `Bash` calls over the same corpus. Both C# surfaces failed to start; nothing reported it, because a language server that cannot initialize is indistinguishable from one nobody asked. **Two surfaces, and only one of them has been observed to cross the subagent boundary.** A dispatched subagent's `ToolSearch` returns "No matching deferred tools found" for `select:LSP` while the same query resolves in the main session — measured on Claude Code 2.1.232, agent types `general-purpose` and `Explore`, 2026-08-14, plus the independent §5.1 observation. Three observations on one client version: design around it, but it is measured behaviour, not a guaranteed contract. The MCP side is positively evidenced — 326 MCP calls from inside subagent turns across four servers in the transcript corpus — though not yet for `csharp-lsp` specifically, which only became startable on 2026-08-14. So the standing note telling *workflow agents* to use C# code intelligence was unsatisfiable as written against the `LSP` tool, and is satisfiable once pointed at the MCP tools. Naming the surface is the load-bearing part: an agent that cannot resolve the tool substitutes Grep and does not say so. **Both failures were a config naming a path the machine does not have.** `MSBuildLocator` needs a dotnet root owning `host/fxr`, which a Homebrew `bin/dotnet` does not have (`libexec` does); the MCP entry named a dotnet install that no longer existed, while `~/.codex/config.toml`'s copy of the same server had been migrated. Neither returned a wrong answer — each refused to start, which is the benign half of environment divergence and the reason it survived so long. **`.mcp.json` and `.mcp/` are gitignored, so the working configuration is not recoverable from the repo.** That is what let one client's copy drift from the other's with nothing to compare against. `docs/local-lsp-tooling.md` carries the entry verbatim so a second machine can reconstruct it, and `scripts/check-local-lsp.sh` turns every precondition into a check. The script is deliberately wired to **no** CI job: every dependency it tests is a developer-machine install, so a red on a runner would carry no information. **Rejected: making find-all-references a mandatory step before multi-site fixes.** Not for availability reasons — C# is *expected* to reach delegated agents through the MCP server, so a mandate would likely be enforceable there (an inference from the MCP boundary generally, not a measurement of `csharp-lsp` from a subagent); only the TypeScript route is main-session-bound. The reason is readiness, and it bites each server separately: `csharp-ls` answers `[]` while `ErsatzTV.sln` loads (minutes), and `typescript-language-server` answers with the declaration alone while its own project graph loads — 20 references reported as 1, with nothing marking it incomplete. Mandating the step without a way to prove the server had settled would license treating that answer as a population — the precise failure the step exists to prevent, now carrying the authority of a rule. It is documented as available and recommended for the multi-site case, with the re-issue-and-confirm discipline attached, and the zero-invocation baseline above is what a future measurement should be compared against.