--- name: platform-lsp-integrate description: "Reference/contract for the Salesforce LSP MCP tools and unavailability handling. Use when asking how to use the LSP, which tools exist, what apex.diagnostics / validate_soql / complete_soql do, why a tool errors with lsp_disabled, no_apex_workspace, or no_org_connected, or how to debug/disable it. DO NOT TRIGGER for editing Apex/LWC/metadata (platform-apex-generate), deploys (platform-metadata-deploy), or SOQL authoring (platform-soql-query); scoped to LSP-layer/MCP-tool questions only." allowed-tools: Bash Read mcp__plugin_salesforce-development_salesforce-lsp__lsp_health metadata: mcpTools: # Native salesforce-lsp server tool ID (the bundled LSP host registers it # dotted as lsp.health). In allowed-tools it appears in Claude Code's # sanitized form: mcp__plugin_salesforce-development_salesforce-lsp__lsp_health. salesforce-lsp: tools: ["lsp.health"] semver: ">=0.1.0" cliTools: - tool: ["sf"] semver: ">=2.0.0" relatedSkills: - "platform-apex-generate" - "platform-metadata-deploy" - "platform-soql-query" --- # Using the Salesforce LSP The `salesforce-development` plugin hosts a local MCP server named **`salesforce-lsp`** that lazily spawns Salesforce Language Server children and exposes their semantic capabilities as MCP tools. This skill is the **contract** other skills follow when they call those tools, and the answer to "how do I use the Salesforce LSP?" — what each tool does, how to read its result, what every error code means, and how to fall back when the host is absent. This is a documentation/reference skill. It does not author or deploy code; it tells you (and other skills) how to drive the LSP tools correctly. > **This plugin build vendors Apex + SOQL only.** The `salesforce-lsp` host > ships the Apex language server (`@salesforce/apex-ls`) and the SOQL language > server. The **LWC** language server is intentionally **not** bundled in this > plugin — the `lwc.*` tools are registered by the host but will always return > an `unavailable`-class envelope here. Treat any `lwc.*` call as unavailable > and use the fallback (read the component source / deploy-compile). ## When to Use This Skill - A user asks how to use the Salesforce LSP, or which LSP/MCP tools are available. - A user asks what a specific tool does (`apex.diagnostics`, `validate_soql`, `complete_soql`, etc.) or how to read its output. - An LSP tool returned an error envelope (`lsp_disabled`, `spawn_timeout`, `circuit_open`, `no_apex_workspace`, `no_org_connected`) and you need to know what it means and how to recover. - You're authoring or reviewing another skill that calls an LSP tool and need the canonical call/fallback pattern. - The LSP seems broken and you need to debug it (`lsp.health`, `${CLAUDE_PLUGIN_ROOT}/bin/lsp-doctor`, the kill switch). ## The Tools All tools are served by the MCP server **`salesforce-lsp`** (invoke names follow the plugin-prefixed pattern: `mcp__plugin_salesforce-development_salesforce-lsp__`, where dots in tool names become underscores — e.g. `apex.diagnostics` becomes `mcp__plugin_salesforce-development_salesforce-lsp__apex_diagnostics`). Spawning is lazy: a tool that needs a language server brings the child up on first call (a one-time cold start of a few seconds for Apex), then reuses it. The pure static-analysis tools never spawn anything. ### Apex (spawns the Apex LSP) | Tool | Purpose | Key input | Key output | |---|---|---|---| | `apex.diagnostics` | Compile-check a `.cls`/`.trigger`; surface errors/warnings | `{ filePath }` | `{ ok, diagnostics: [{ line, column, severity, message }] }` | | `apex.hover` | Type/signature at a position | `{ filePath, line, character }` | hover markdown | | `apex.documentSymbol` | Outline of a file's symbols | `{ filePath }` | symbol tree | | `apex.completion` | Code-completion at a position | `{ filePath, line, character }` | completion items | ### SOQL | Tool | Purpose | Spawns? | Key output | |---|---|---|---| | `validate_soql` | Parse a SOQL string for **syntax** errors | SOQL LSP | `{ ok, diagnostics: [{ line, column, severity, message }] }` | | `complete_soql` | Schema-aware completion at a cursor (SObjects, fields, picklist values resolved against the org) | SOQL LSP | `{ ok, items, expanded, unresolved, hint? }` | | `extract_soql_from_apex` | Statically pull every inline `[SELECT …]` out of Apex | **No** (pure static) | `{ ok, totalQueries, totalDynamic, files }` | | `check_soql_selectivity` | Selectivity heuristics (optional org LIMIT-0 probe) | **No** by default | selectivity report | | `refresh_org_schema` | Invalidate the cached org describe so completion re-fetches | **No** | `{ ok, removed }` | > **Just deployed a field/object and it won't resolve?** When a SOQL or Apex > reference to a *freshly-deployed* field fails (e.g. `No such column 'Foo__c'`, > or `complete_soql` doesn't offer it) right after a deploy, the cached org > describe is stale — call **`refresh_org_schema`** to invalidate it, then re-run > the check before assuming a code error or renaming anything. This is the lever > for post-deploy schema lag; reach for it *before* treating the failure as a bug > in your query/class. (It only clears the local cache; it can't speed up > server-side propagation, so if the org itself hasn't finished publishing the > field, re-running after a moment is the fallback.) ### LWC — not available in this build The `lwc.*` tools (`lwc.diagnostics`, `lwc.hover`, `lwc.definition`, `lwc.completion`, `lwc.workspace_symbols`) are registered by the host but the LWC language server is **not vendored** in this plugin. Every `lwc.*` call returns an unavailable envelope. For LWC work, fall back to reading the component source/templates directly or deploy-compiling and reading the CLI errors. ### Diagnostics / health | Tool | Purpose | Spawns? | |---|---|---| | `lsp.health` | Read-only view of kill-switch mode, workspace, per-server state, circuit-breaker state, cold-spawn timing, apex-ls version | **No** — never spawns | Coordinates everywhere are **one-based** (`line`/`column`). `validate_soql` diagnostic coordinates are relative to the **query string**, not a file — when a query came from `extract_soql_from_apex`, translate back using that query's range. ## The Call / Fallback Contract Every skill that calls an LSP tool follows the same three rules: 1. **Prefer the LSP tool over guessing.** If a tool exists for the step (validate a query, compile-check a class, complete against the org schema), call it before falling back to hand-analysis. 2. **Treat an error envelope as "unavailable," never as "passed."** A tool may return `{ error: }` instead of a result (see Error Codes). On any such code, record `=unavailable: ` in your report and continue down the fallback path — **never** report the input as valid/clean just because the check didn't run. 3. **Degrade, don't fail.** The LSP is an accelerator, not a hard dependency. If the host isn't available at all, the `salesforce-lsp` tools simply won't exist — fall back to the CLI/manual path for that step and say so. ### When the LSP host is absent If the plugin/host isn't available, the MCP tools above are not registered, so a call to (e.g.) `validate_soql` will not resolve. Detect this the same way you detect an error envelope — the tool is unavailable — and use the documented fallback: | LSP tool unavailable | Fallback | |---|---| | `apex.diagnostics` | Deploy/compile via `sf project deploy` and read CLI errors | | `validate_soql` | Smoke-check by running the query read-only via `sf data query --json` (a parse error surfaces in the CLI error) | | `complete_soql` | `sf sobject describe --sobject --json` for fields; author from the user's stated names | | `extract_soql_from_apex` | Read the file(s) and locate `[SELECT … ]` by hand | | `apex.completion` / `apex.hover` | Read the source directly | | `lwc.*` (always unavailable here) | Read the component source/templates directly, or deploy-compile and read CLI errors | ## Error Codes A tool returns `{ error: }` (or, for `complete_soql`, a `hint`) instead of a result. Each maps to a recovery: | Code | Meaning | What to do | |---|---|---| | `lsp_disabled` | The kill switch (`SFDX_LSP`) forbids this language | Use the non-LSP fallback; or re-enable the LSP (see Debugging) | | `spawn_timeout` | The language server didn't come up in time | Retry once; if it persists, fall back and run `${CLAUDE_PLUGIN_ROOT}/bin/lsp-doctor` | | `circuit_open` | Repeated spawn failures tripped the breaker; calls are short-circuited | Fall back now; investigate with `lsp.health` / `${CLAUDE_PLUGIN_ROOT}/bin/lsp-doctor` | | `no_apex_workspace` | No `classes/*.cls` under any package dir — Apex tools won't spawn | Expected in a non-Apex project; nothing to check | | `no_org_connected` | (`complete_soql` hint) org schema couldn't be resolved | Keyword completion still works; ask for exact field/object names or connect an org. **Do not** surface `__…_PLACEHOLDER` labels as real fields | The first three are transient/config states shared by every spawning tool; the rest are expected, healthy states for a particular workspace/org/file context — not signs of a broken install. (`no_lwc_bundles` / `unsupported_lwc_file` are LWC-only codes; in this build the LWC server is not vendored, so `lwc.*` returns unavailable regardless.) ## Debugging the LSP Three layers, cheapest first: 1. **`lsp.health` (MCP tool).** The fastest check — never spawns a child. Reports the kill-switch mode, resolved workspace, per-server status, circuit-breaker state, last cold-spawn timing, and the vendored apex-ls version. Ask Claude to "run lsp.health," or call the `lsp.health` tool directly. 2. **`bin/lsp-doctor` (CLI).** A deeper, install-level diagnostic for support and onboarding. Verifies the committed bundles exist, the vendored apex-ls artifacts resolve, the org-schema cache is parseable, and each LSP child can actually spawn. Exits `0` when healthy, `1` with structured per-check output when something is wrong. ```bash "${CLAUDE_PLUGIN_ROOT}"/bin/lsp-doctor # human-readable report "${CLAUDE_PLUGIN_ROOT}"/bin/lsp-doctor --json # machine-readable "${CLAUDE_PLUGIN_ROOT}"/bin/lsp-doctor --no-spawn # skip child-spawn probes (CI/restricted) ``` (In this build `lsp-doctor` will report the LWC server as missing — that is expected; only Apex + SOQL are vendored here.) 3. **`SFDX_LSP_DEBUG=1`.** Emits single-line JSON telemetry (spawn timing, cache hits, circuit events, per-tool latency) to stderr. Off by default (zero overhead). ### The kill switch — `SFDX_LSP` The fast way to control or disable the LSP. Set the environment variable: | `SFDX_LSP` | Effect | |---|---| | unset / `all` | Every vendored LSP may spawn (default) | | `apex-only` | Only the Apex LSP may spawn; SOQL tools return `lsp_disabled` | | `disabled` | No LSP spawns; every LSP tool returns `lsp_disabled` | To rule the LSP in or out of a problem, set `SFDX_LSP=disabled` and re-run: if the issue persists it isn't the LSP, and skills will have fallen back to their non-LSP paths automatically. An unknown value defaults to `all` (with a warning) so a typo never silently disables the feature. ## Pre-deploy diagnostics gate A `PreToolUse` hook (`bin/lsp-precheck`) runs Apex diagnostics on the `.cls`/`.trigger` files a `sf project deploy start`/`validate` is about to push, and emits a decision. It is **fail-open**: any error (including a missing/slow LSP) allows the deploy. Mode is controlled by `SFDX_LSP_DEPLOY_GATE` (`off` | `warn` | `block`, default `warn`) — `warn` surfaces diagnostics without blocking; `block` denies a deploy that has Apex compile errors. ## Verification - Asked "how do I use the Salesforce LSP tools?", this skill is the match and lists the tools, their inputs/outputs, and the fallback contract. - Appears in the `/skills` listing as `platform-lsp-integrate`. - Given an error code (e.g. `no_apex_workspace`), it explains the meaning and the correct recovery without treating the unrun check as a pass. ## Cross-Skill Integration | Need | Delegate to | |---|---| | Compile-check + analyze Apex you just wrote | `platform-apex-generate` | | Validate inline SOQL in `.cls`/`.trigger` | `validate_soql` (this skill); fallback: run read-only via `sf data query --json` | | Author & run a SOQL query against the org | `complete_soql` + `sf data query` | | Pre-deploy diagnostics gate behavior | see "Pre-deploy diagnostics gate" above |