Tooling & Integration

How Deep Is Your LSP Integration? Three Probes That Tell You

David Guzenburg/ / 8 min read

A comparison table of editors would be stale within a quarter. What survives is knowing how to find out what your own setup actually knows.

LSPlanguage serversintegrationdiagnostics

Why comparing tools directly is a trap

The obvious article here would rank editors and agents by how well they use language server data. It would also be wrong within a quarter — this tooling changes between releases, and a comparison table is stale before it ranks well.

What does not change as fast is the underlying question: what does a given integration actually know, and how would you find out? That question survives version bumps, and answering it for your own setup is more useful than any table.

Four levels of integration

LevelThe agent canSymptom when this is the level
NoneRead files, run commandsInvents symbols; greps constantly
Diagnostics onlySee current errors and warningsFixes errors well; still invents when exploring
Query accessAsk for definitions, references, typesNavigates precisely; rarely guesses
BidirectionalAlso apply server-provided edits and code actionsRenames are exact across the project

Most integrations sit at level one or two. Level three is where behaviour changes noticeably, because "not found" becomes a reliable answer rather than a gap the model fills.

Finding out which level you have

Vendor documentation is often vague on this, and the behaviour is easy to test directly. Three probes, five minutes.

Probe 1 — the nonexistent symbol

Ask about a function that does not exist, with a plausible name for your codebase:

Where is `validate_subscription_tier` defined?

An integration with query access says it cannot find it. One without invents a plausible location, or describes what such a function "would" do. This single probe separates level three from levels one and two more reliably than any documentation.

Probe 2 — the interface implementation

Pick an interface with several implementations and ask which ones exist. Text search cannot answer this — implementations do not mention the interface name in a greppable way in most languages. A complete, correct list means real symbol resolution.

Probe 3 — the fresh error

Introduce a type error in a file the agent has not read, then ask what is currently wrong in the project. An integration with diagnostics access names it immediately. One without has to be told where to look.

Re-run after upgrades

Integration depth changes between releases, silently and in both directions. Keep these three probes somewhere and re-run them after a major version bump. It takes five minutes and occasionally explains a change in output quality you would otherwise attribute to the model.

Getting to level three when your tool is at level one

If your agent can run shell commands, you can hand it query access without waiting for the vendor. Most language servers ship a CLI, or have one available.

#!/bin/bash
# lsq — language-server queries as shell commands
case "$1" in
  def)   pyright --outputjson --verifytypes "$2" | jq -r '...' ;;
  refs)  rg -n --json "\b$2\b" | jq -r '...' ;;   # fallback
  diag)  mypy --no-error-summary --no-pretty "${2:-src/}" ;;
  type)  pyright --outputjson "$2" | jq -r '...' ;;
  *)     echo "usage: lsq {def|refs|diag|type} " ; exit 1 ;;
esac
## Code navigation
- `./lsq def <symbol>`  — where it is defined, or "not found"
- `./lsq diag [path]`     — current type errors
- Prefer these over grep for symbol questions. "Not found" is
  authoritative: the symbol does not exist. Do not assume otherwise.

The final instruction is the one that changes behaviour. Exposing the tool is half the work; telling the agent that a negative result is trustworthy is the other half, and it is the half people skip.

Where integrations commonly fail

FailureLooks likeCheck
Server not startedNo diagnostics ever appearEditor's LSP status output
Wrong interpreter or SDKStdlib symbols unresolvedWhich environment the server uses
Index incompleteCorrect answers for some files onlyWait for indexing to finish; retry
Generated code missingHoles exactly where the data model livesRun codegen before starting
Monorepo root wrongCross-package resolution failsServer's configured workspace root

Row four is worth calling out. In repositories where protobuf or ORM stubs are generated during the build, a fresh checkout has no generated code, so the language server has no model of the data layer — which is precisely where most work happens. The agent then guesses about your core types, and the output degrades in a way nobody connects to a missing codegen step.

Diagnostics are the cheapest win

If you get only one thing from an integration, make it diagnostics. The reason is the feedback loop: an agent that sees type errors immediately after an edit converges, and one that has to run a command and parse text output does so more slowly and less reliably.

The measurable difference shows up in a specific behaviour. With diagnostics, an agent making a change to a shared interface sees every resulting error across the project at once and fixes them as a set. Without, it fixes the file in front of it, runs the build, discovers three more errors, fixes those, runs again — several round trips for the same outcome, each consuming context.

This is also why a broken language server degrades agent output so much more than it degrades human output. A person navigating a project with no working LSP is mildly inconvenienced. An agent loses its only source of continuous ground truth and falls back on plausibility.

What to standardise across a team

Mixed tooling is normal and mostly fine. Three things are worth agreeing on regardless of which editor people use:

That third point is what makes tool choice largely irrelevant. If every agent reads the same context file, and that file names the same commands, then behaviour converges regardless of which editor produced it — which is a better outcome than standardising on a tool that will be superseded.

Takeaway

Do not compare tools; probe your own setup. Three tests — a nonexistent symbol, an interface's implementations, a fresh type error — tell you what your integration actually knows. If it is shallow, wrap the language server in shell commands yourself, and tell the agent explicitly that "not found" is authoritative.

Keep reading
Tooling & Integration

LSP and Agents: Two Different Models of Understanding Code

A language server holds a resolved symbol graph; an agent holds text in a window. Why one says 'not found' and the other invents, and how to combine them.

Tooling & Integration

Building a Local MCP Server: Exposing What the Filesystem Cannot Answer

A working TypeScript MCP server over stdio, why the tool description decides whether it is ever called, returning errors the model can read, and the read-only role to create before it runs.

Tooling & Integration

Giving Claude a Second Opinion: Connecting Gemini Through a Local MCP Server

A working walkthrough for wiring another model into Claude Code as a tool: the server code, the registration command, the flag ordering that breaks it, and why the context parameter is the part people get wrong.

Codex vs Claude

What a Tool Protocol Standardises, and What It Leaves to You

Connection and discovery are solved; tool design is not. Context cost per attached server, why a CLI often beats a server, error messages as agent input, splitting read from write, and the small server over your own systems that nobody else can write.

← Build System Integration: Your Toolchain Is the Agent's Feedback Loop  ·  Benchmarking Autocomplete: Measure Latency, Not Completion Quality →

All tooling & integration articles  ·  Every article