Right now, every Claude Code user is running without LSP. That means every time you ask "where is processPayment defined?", Claude Code does what you'd do with a terminal. It greps. It searches text patterns across your entire codebase, reads through dozens of files, and tries to figure out which match is the actual definition.
It works. But it's slow, it's fuzzy, and on large codebases it regularly misses or gets confused. Search for User in a real project and you get 847 matches across 203 files: class definitions, variable names, comments, imports, CSS classes, SQL columns. The thing you actually wanted? Buried somewhere in the middle. Claude Code has to read through each match to narrow it down. That takes 30-60 seconds. Sometimes longer.
There's a feature that changes this entirely. It's called LSP, the Language Server Protocol. It's not enabled by default. It's not prominently documented. The setup requires a flag discovered through a GitHub issue, not the official docs. But once it's on, the same query ("where is processPayment defined?") returns the exact file and line number in 50 milliseconds. Not 30 seconds. Fifty milliseconds. With 100% accuracy.
That's not an incremental improvement. That's a category change in how Claude Code navigates your code.
Claude Code ships without LSP enabled. Enabling it gives Claude the same code intelligence your IDE has: go-to-definition, find references, type info, real-time error detection. From my debug logs: ~50ms per query vs 30-60s with grep. Two minutes of setup. Jump to setup →
What You're Currently Running
By default, Claude Code navigates your codebase with text search tools: Grep, Glob, and Read. It's the same as having a very fast developer with grep and find at a terminal. Smart pattern matching, but fundamentally just matching text.
The core problem: grep treats code as text. But code is not text. It has structure, meaning, and relationships. When you ask "where is getUserById defined?", you want the one function definition, not the 50 places that call it plus the 12 comments that mention it. Grep can't tell the difference. LSP can.
What LSP Actually Is
Before 2016, every code editor had to build its own language support from scratch. VS Code needed a Python plugin. Vim needed a separate Python plugin. Emacs, Sublime, Atom — each one reinventing the same work. Twenty editors times fifty languages meant a thousand separate implementations, most of them incomplete.
In 2016, Microsoft had an insight: separate the language intelligence from the editor. Create a protocol, a standard way for any editor to talk to any language server. The editor says "where is this symbol defined?" in JSON-RPC. The language server (a separate process that deeply understands one language) answers.
That's LSP. It turned a thousand-implementation problem into a seventy-implementation one. And it's why your VS Code Python experience is exactly as good as your Neovim Python experience — they're both talking to Pyright.
The Performance Gap
Here's the thing nobody talks about: AI coding assistants had the exact same problem that editors had before LSP. Without it, Claude Code does text search. Grep, Glob, Read. It works. But the cost is measured in seconds per query, multiplied by dozens of queries per task. It adds up fast.
What Claude Code Gets from LSP
LSP gives Claude Code two categories of superpowers: things that happen automatically and things it can actively request.
Passive: Self-Correcting Edits
This is the most valuable part, and most people don't even realize it's happening. After every file edit, the language server pushes diagnostics: type errors, missing imports, undefined variables. Claude Code sees these immediately and fixes them in the same turn, before you ever see the error.
Think about what this means in practice. You ask Claude to add an email parameter to createUser(). Claude edits the function signature. The language server instantly reports 3 errors at three call sites that now have the wrong number of arguments. Claude sees the errors, finds all three call sites, and fixes them. You get the result with zero errors on the first try.
Without LSP, Claude would edit the function, hand you the result, you'd try to compile, see 3 errors, paste them back to Claude, and iterate. With LSP, that entire loop collapses into one step.
Active: On-Demand Code Intelligence
Beyond automatic diagnostics, Claude Code can explicitly ask the language server questions:
goToDefinition— "Where isprocessOrderdefined?" → exact file and linefindReferences— "Find all places that callvalidateUser" → every call site with locationhover— "What type is theconfigvariable?" → full type signature and docsdocumentSymbol— "List all functions in this file" → every symbol with locationworkspaceSymbol— "Find thePaymentServiceclass" → search symbols across the entire projectgoToImplementation— "What classes implementAuthProvider?" → concrete implementations of interfacesincomingCalls/outgoingCalls— "What callsprocessPayment?" → full call hierarchy tracing
You don't need to use these operations explicitly. Just ask Claude Code naturally. "Where is authenticate defined?", "find all usages of UserService", "what type is response?" It routes to the right LSP operation automatically.
Setting It Up
Here's the full setup. It takes about 2 minutes, and you only do it once.
Prerequisites
- Claude Code version 2.0.74 or later (run
claude --versionto check) - The language server binary for your language(s) installed and in
$PATH
Step 1: Enable the LSP Tool
This is the part that trips people up. You need to add a flag to your Claude Code settings:
Add this to your ~/.claude/settings.json:
"ENABLE_LSP_TOOL": "1"
ENABLE_LSP_TOOL is not officially documented as of February 2026. It was discovered via GitHub Issue #15619 as a community workaround. It may change or become unnecessary in future versions. I also recommend adding export ENABLE_LSP_TOOL=1 to your shell profile (~/.zshrc on macOS, ~/.bashrc on Linux) as a fallback.
Step 2: Install the Language Server
Install the binary for each language you work with. These are the same language servers your IDE uses. LSP is universal.
| Language | Plugin | Install Command |
|---|---|---|
| Python | pyright-lsp |
npm i -g pyright |
| TypeScript/JS | typescript-lsp |
npm i -g typescript-language-server typescript |
| Go | gopls-lsp |
go install golang.org/x/tools/gopls@latest |
| Rust | rust-analyzer-lsp |
rustup component add rust-analyzer |
| Java | jdtls-lsp |
brew install jdtls |
| C/C++ | clangd-lsp |
brew install llvm |
| C# | csharp-lsp |
dotnet tool install -g csharp-ls |
| PHP | php-lsp |
npm i -g intelephense |
| Kotlin | kotlin-lsp |
GitHub releases |
| Swift | swift-lsp |
Included with Xcode |
| Lua | lua-lsp |
GitHub releases |
Step 3: Install and Enable the Plugin
First, update the marketplace catalog:
claude plugin marketplace update claude-plugins-official
Then install the plugin for your language:
claude plugin install pyright-lsp
Python
claude plugin install typescript-lsp
TypeScript/JS
claude plugin install gopls-lsp
Go
claude plugin install rust-analyzer-lsp
Rust
claude plugin install jdtls-lsp
Java
claude plugin install clangd-lsp
C/C++
claude plugin install csharp-lsp
C#
claude plugin install php-lsp
PHP
claude plugin install kotlin-lsp
Kotlin
claude plugin install swift-lsp
Swift
claude plugin install lua-lsp
Lua
Verify it's installed and enabled:
claude plugin list
A plugin can be installed but disabled. A disabled plugin won't register its LSP server at startup. If claude plugin list shows Status: disabled, run claude plugin enable <name> and restart Claude Code.
To be safe, I also explicitly set them to true in ~/.claude/settings.json:
{
"ENABLE_LSP_TOOL": "1",
"enabledPlugins": {
"pyright-lsp@claude-plugins-official": true,
"typescript-lsp@claude-plugins-official": true,
"gopls-lsp@claude-plugins-official": true
}
}
This single issue — plugins installed but not enabled — accounts for most "LSP isn't working" problems.
Step 4: Restart Claude Code
LSP servers initialize at startup. After installing plugins, you need a full restart. Then verify by asking Claude: "What type is [some variable]?" — if it uses the LSP hover operation instead of reading the file, you're good.
What Happens at Startup
Here's something I found interesting while digging through debug logs. When Claude Code starts, all enabled LSP servers launch simultaneously. They don't wait for you to open a file.
From my actual debug logs (4 language servers enabled):
# From ~/.claude/debug/ — session on Feb 23, 2026
05:53:56.216 [LSP MANAGER] Starting async initialization
05:53:56.573 Total LSP servers loaded: 4
05:53:56.757 gopls initialized (+0.5s)
05:53:56.762 typescript initialized (+0.5s)
05:53:56.819 pyright initialized (+0.6s)
05:54:04.791 jdtls initialized (+8.6s)
Index is warm — all LSP operations now ~50ms
Two things stand out. First, the Java server takes ~8 seconds because of JVM warmup — this is normal, not a bug. Second, servers start indexing your entire project immediately. They scan all files of their language type, build symbol tables, and resolve dependencies. By the time you ask your first question, the index is already warm.
This means goToDefinition, findReferences, and hover work for any symbol in your project — not just files you've opened. The language server has already seen everything.
Using It in Practice
You don't need to learn any new commands. Just talk to Claude Code the way you normally would:
| You say... | Claude uses... |
|---|---|
"Where is authenticate defined?" |
goToDefinition |
"Find all usages of UserService" |
findReferences |
"What type is response?" |
hover |
| "What functions are in auth.ts?" | documentSymbol |
"Find the PaymentService class" |
workspaceSymbol |
"What implements AuthProvider?" |
goToImplementation |
"What calls processPayment?" |
incomingCalls |
"What does handleOrder call?" |
outgoingCalls |
The real power shows up in refactoring sessions. When you're renaming a method, adding a parameter, or changing a return type, LSP ensures Claude finds every reference and updates them correctly — not the "grep found 47 out of 52 actual usages" situation.
You can also press Ctrl+O to see diagnostics pushed by LSP servers. This shows you what the language servers are seeing in real time.
The Gotchas
I hit most of these so you don't have to.
| Issue | Cause | Fix |
|---|---|---|
| LSP tool not available at all | ENABLE_LSP_TOOL not set |
Add to settings.json, restart |
| "Plugin not found in any marketplace" | Stale marketplace catalog | claude plugin marketplace update claude-plugins-official |
| Plugin installed but disabled | Not enabled after install | claude plugin enable <name> + restart |
| "Executable not found in $PATH" | Binary not installed or not in PATH | Install binary, verify with which <binary> |
| "No server available" | Race condition during startup | Restart Claude Code, wait for servers to initialize |
| "Total LSP servers loaded: 0" in logs | Plugins installed but all disabled | Enable plugins, restart |
Debug Checklist
When things aren't working:
- Check the binary is installed:
which pyright-langserver(or whatever binary your language needs) - Check plugin status:
claude plugin list— look forStatus: enabled - Check debug logs at
~/.claude/debug/latest— search for "Total LSP servers loaded: N" where N should be > 0
Every Claude Code session you run without LSP is a session where every "find this definition" takes 30-60 seconds instead of 50ms. Where every refactor misses call sites that a language server would have caught instantly. Where errors that LSP would have surfaced and auto-fixed slip through to your review.
The setup is two minutes. The feature flag is one line in your settings. The performance difference is not subtle — it's the gap between text search and semantic code intelligence. The same gap that made IDEs better than Notepad, except now it's making your AI assistant better at its job.
If you're already using Claude Code, enable LSP. You'll feel it on the very first query.
Want to know when I publish the next one?
Grab the RSS feed