LSP for Claude Code: Symbol-Level Search at Scale
Grep slows down on big codebases. LSP gives Claude Code symbol-level search through an MCP server. Build it or use ours. Token costs included.
Agentic Orchestration Kit for Claude Code.
You ask Claude Code to "find every place we charge a customer." It runs grep -r "charge" against a 200,000-line repo and gets back 3,000 string matches. Comments mentioning the word. Log lines. Test fixtures with the word in a JSON blob. A variable in a migration script that has nothing to do with billing. Claude then opens dozens of those files trying to figure out which ones actually matter, burning context the entire time. By the time it identifies the three real call sites, half your window is gone.
This is the wall every Claude Code user hits eventually. It is not a model problem. It is a search problem. Claude Code's default code navigation is string-based, and string search does not scale.
The fix is symbol-level search, exposed to Claude through a Model Context Protocol (MCP) server that wraps a Language Server Protocol (LSP) backend. Anthropic explicitly recommends this in their large-codebase guide: "Setting this up requires installing a code intelligence plugin for your language and the corresponding language server binary." Cole Medin's open-source helpline repo ships a reference implementation. One enterprise software company they highlight "deployed LSP integrations org-wide before their Claude Code rollout" specifically so that C and C++ symbol navigation would be reliable at scale.
If you are running Claude Code against anything past 50,000 lines, this is the single highest-leverage MCP server you can add. It is also one of the seven pieces of the Claude Code harness, and the one most teams skip until grep has already wasted thousands of dollars in tokens. Here is how it works, how to build one, and how to make Claude actually use it.
What is the Language Server Protocol
LSP is the protocol your IDE has been using for years to power "go to definition," "find all references," and "rename symbol." Microsoft introduced it in 2016 to solve a quadratic problem: every editor was reimplementing language intelligence for every language. With LSP, language smarts live in a separate process (the language server) and any editor speaks one protocol to talk to any of them.
The point is that LSP servers understand code as a graph of typed symbols, not a wall of text. When you ask a language server "find references to monthly_total_cents," it knows:
- Which
monthly_total_centsyou mean (the one inbilling/calc.py, not the unrelated variable intests/fixtures.json) - Which scopes the name is visible in
- Which imports route to which definitions
- Which references are actual usages versus shadowed or rebound names
Grep knows none of that. Grep matches characters.
Anthropic puts it more bluntly in their guide: "Grep for a common function name in a large codebase returns thousands of matches and Claude burns context opening files to determine which result is relevant." LSP cuts that loop off at the knees. It returns only the references that point to the same symbol, which means the filtering happens before Claude reads anything.
That is the whole pitch. Filtering before Claude reads anything means the irrelevant 2,997 matches never enter the context window.
The Grep vs LSP Token Math
Numbers, because vague claims like "saves tokens" are worthless without numbers.
Take a real query on a six-figure-LOC monorepo: "find every place where monthly_total_cents is referenced."
| Approach | Results | Avg tokens per result | Files opened | Context spent |
|---|---|---|---|---|
grep -rn "monthly_total_cents" | ~340 matches (string occurrences across code, tests, fixtures, comments) | 80 (each match is a file path plus a line of source) | 12-18 (Claude has to open files to determine which matches are real call sites) | 35,000 to 60,000 tokens once Claude reads enough source to filter |
LSP find_references via MCP | 3 references (the actual symbol usages) plus 1 definition | 60 (structured location object per result) | 0 or 1 (LSP already resolved the symbol; Claude only opens files it intends to change) | Under 500 tokens |
This is not a hand-wavy estimate. Cole's helpline demo runs the exact same query and shows the result: grep returns dozens of irrelevant string hits, LSP returns one definition and two real references. On a 200k LOC repo, the token delta on a single such query routinely beats 50x.
Multiply that by every symbol lookup, every "where is this called," every refactor scoping question, every time Claude needs to understand the surface area of a function before changing it. The math is brutal. Grep was free in token-cost terms when everything was on disk and humans ran the searches. With Claude burning context per result, grep is one of the most expensive operations you can leave on by default.
Architecture of an LSP MCP Server
The pattern is simple. Three things talk to each other:
- Claude Code speaks MCP.
- An MCP server you (or someone else) builds speaks MCP on one side and LSP on the other. It is a thin translator.
- A language server binary (
pyright-langserverfor Python,rust-analyzerfor Rust,typescript-language-serverfor TS,clangdfor C and C++) does the actual symbol work.
The MCP server's job is to:
- Boot the language server as a subprocess.
- Pass LSP initialization messages (root URI, capabilities, workspace folders).
- Expose a handful of MCP tools that map cleanly to LSP requests.
- Translate LSP responses into structured JSON that fits in a Claude tool response.
In Cole's helpline reference, the codebase-search MCP exposes three tools: where_is (locate a symbol), find_references (find every real usage), and outline (structural map of a module or file). The repo pairs it with pyright-langserver for Python symbol navigation. The MCP itself is described as "AST-based" and "layout-agnostic," meaning it discovers your source tree by walking the repo rather than relying on a hardcoded project layout.
The interesting design choice in helpline is that LSP and the AST-based MCP live in separate layers. The MCP handles the broad "find me symbols across the repo" queries via static analysis. The language server (pyright) handles the typed, semantic queries when full type resolution is needed. For a single-language Python repo this is overkill; for a polyglot enterprise codebase, it is the right separation.
Build Your Own LSP MCP
If you want to roll your own rather than installing a reference plugin, this is the rough shape. The full code is longer than fits here, but the architecture is straightforward.
1. Pick a language server
For each language in your repo, pick a maintained server. Stable defaults as of mid-2026:
- Python:
pyright-langserver(Microsoft) orpylsp(community) - TypeScript and JavaScript:
typescript-language-server - Rust:
rust-analyzer - Go:
gopls - C and C++:
clangd - Java:
jdtls(Eclipse)
You can run multiple in parallel and route requests by file extension. Most teams start with the one that matches their primary language.
2. Wire up the MCP protocol
The MCP SDK does the heavy lifting. You declare tools in a list, register handlers, and ship the server over stdio. The skeleton in pseudocode:
That is the entire pattern. Three tools mapping to three LSP requests. The real code in helpline/tooling/mcp/codebase_search.py is fleshier (it handles AST walking for cases where the language server is slow to boot, retries on disconnect, and normalizes paths across Windows and POSIX), but the bones are these.
3. Expose three core tools
Cole settled on where_is, find_references, and outline after iterating. They map to the three queries Claude actually needs:
where_isanswers "where in this repo is this symbol defined?"find_referencesanswers "what calls it, imports it, or extends it?"outlineanswers "what is the surface area of this module?"
You can add find_implementations, rename_symbol, or hover later. Start with three. ClaudeFast's Code Kit ships an MCP setup with this same three-tool surface preconfigured, paired with the CLAUDE.md routing rule so Claude reaches for it on the first symbol query.
4. Test with a single repo
Wire the MCP into a Claude Code session, point it at a real codebase, and run Cole's test prompt verbatim: "Find every place where monthly_total_cents is referenced in this repo. Do not use grep. Use a symbol-level approach." If Claude reaches for find_references and returns under 10 results, the wiring works. If Claude falls back to grep, your CLAUDE.md is not telling it the tool exists. Fix that next.
When to Use LSP vs Grep
The most common deployment mistake is treating LSP as a grep replacement. It is not. They answer different questions and you want Claude using both, just for the right queries.
| Tool | Use for | Avoid for |
|---|---|---|
| LSP | Symbol lookups, definition lookups, finding callers, refactor scoping, "rename safely" checks | Plain string searches, log message hunts, comment audits |
| Grep | String literals, comment searches, log messages, config keys, anything not parsed as code | Symbol queries that grep can only answer with noise |
The way you communicate this to Claude is through CLAUDE.md. A short rule does it:
That CLAUDE.md block is what makes the whole investment pay off. Without it, Claude defaults to grep because grep is universal and Claude has been trained on more grep examples than on your specific MCP. With it, the agent reaches for the right tool the first time.
This is also why LSP belongs in the AI Layer harness discussion alongside the other six components, not as a standalone tool. The MCP gives you the capability. The CLAUDE.md rule routes Claude's behavior. The two together are what scale.
Enterprise Patterns
For solo work, you install one plugin and go. For an org, the rollout looks different.
Anthropic's article references one enterprise software company that "deployed LSP integrations org-wide before their Claude Code rollout." That sequencing is the lesson. They did not give developers access to Claude Code first and ask them to figure out search later. They built the LSP layer, packaged it as a plugin, distributed it to every developer, and only then enabled broader Claude Code usage. Result: every engineer's first session worked well on the C and C++ codebase, instead of every engineer independently discovering grep does not scale and burning support hours.
This pattern generalizes. If you are an agent manager rolling out Claude Code in a team or org, package the LSP MCP plus its CLAUDE.md rule together in a plugin and distribute it through a marketplace. That way the rule and the capability ship as one unit. Day-one users get working symbol search, not a fresh chance to invent a worse version.
The bigger pattern is that this is the kind of work the large codebase playbook covers end to end. LSP is Strategy 6 in that playbook for a reason: at scale, it is non-negotiable. Skip it and every other optimization is fighting against a grep cliff.
Common Pitfalls
A few traps worth knowing before you wire one up:
Cold-start latency. rust-analyzer can take 30 to 90 seconds to index a large Rust workspace from cold. If your MCP boots the language server lazily on first request, the first Claude query will look broken. Either pre-warm the server (start it when the MCP starts, not on first tool call) or stream progress notifications back through the MCP so Claude knows to wait.
Version mismatch. If the language server resolves a different toolchain than your project uses (different Python interpreter, different TS version, wrong Rust edition), symbol resolution will silently produce wrong answers. Pin the language server's interpreter or workspace config to match the project. This is why a single team-wide plugin beats per-developer setup: you control the version.
Forgetting the CLAUDE.md rule. Easily the most common failure. The MCP is installed, the language server runs, but Claude never invokes the tools because nothing in its context tells it to prefer symbol search. Always ship the rule with the plugin.
Trying to make LSP do non-symbol work. LSP is bad at "find every string that looks like an email address." That is a regex job. Resist the urge to overload the MCP with general-purpose search; keep it focused on symbol queries where it actually wins.
Where This Fits in the Harness
LSP is one of the two additional capabilities Anthropic says round out the Claude Code harness. The Component Comparison in the AI Layer pillar lists its profile cleanly: loads always (once configured), best for symbol navigation, common confusion is assuming it is automatic. It is the kind of capability that does not announce itself; it just makes the agent stop opening twenty wrong files.
Functionally, it pairs best with sub-agent exploration patterns. The Anthropic-recommended split is to use a read-only sub-agent for "map this subsystem" work, then have the main agent edit. LSP makes the exploration sub-agent's job tractable: it can answer "where is Foo used?" with three results instead of three hundred, then hand a focused report back to the main agent. Without LSP, that exploration sub-agent ends up grepping and its findings are noisier than the main agent doing the work itself.
The other natural pairing is with the rest of the MCP basics stack and the lazy-loading enabled by MCP Tool Search. With Tool Search on, the LSP MCP's three tools are not loaded into context until Claude actually needs them. You pay zero ambient token cost and only spend tokens on the queries themselves. If you are building this from scratch, also read the custom integrations guide for the MCP server boilerplate before you start.
The Bottom Line
If your codebase is past 50,000 lines and Claude Code is reaching for grep on symbol queries, you are paying a tax that has a one-time fix. An LSP MCP server, plus a CLAUDE.md rule that tells Claude when to prefer it, will save you tokens on every session for the lifetime of the project. The math on a six-figure-LOC repo gets ridiculous fast: one symbol query that costs 500 tokens via LSP costs 50,000 tokens via grep. The fixed cost of building or installing the MCP pays itself back in a week of moderate use.
Cole Medin's helpline repo ships the entire reference implementation including the AST-based fallback, the pyright LSP wiring, and a sample CLAUDE.md rule. It is the fastest path if you want to look at working code first and then adapt. If you would rather pull a pre-built harness with this and the other six components already wired up, ClaudeFast's Code Kit includes the MCP and CLAUDE.md patterns alongside 18 specialized agents and the team-orchestration command stack for $89.
Expect more first-party LSP support from language ecosystems as agentic coding becomes the default workload. The teams that will move fastest are the ones who treat symbol-level navigation as infrastructure today, not as an optimization for later. The grep cliff is real, the fix is a weekend of work, and the compounding savings start the next session.
Last updated on
