How to Use Language Server Protocol (LSP) with Claude Code for Large Codebase Navigation
Give Claude Code the same symbol-level search developers have in their IDE. Here's how to expose an LSP via MCP server for large codebase navigation.
Why Claude Code Loses Its Footing in Large Codebases
Working with Claude Code on a small project is straightforward. Point it at a few files, ask it to make changes, and it performs well. But drop it into a production codebase with hundreds of modules, thousands of files, and deeply nested import chains — and you’ll quickly notice the cracks.
The core problem is that Claude Code navigates code the same way a new developer would on their first day: by reading files. It doesn’t know where a symbol is defined unless it’s already read that file. It can’t find all callers of a function without grepping. It can’t resolve types across module boundaries without manually tracing imports. That works at small scale. At large scale, it breaks down fast.
The fix is giving Claude Code the same symbol-level search that developers get inside their IDE — and you can do that by exposing a Language Server Protocol (LSP) server through an MCP server. This guide walks through exactly how to set that up.
What LSP Is and Why It Matters
The Language Server Protocol is a standard, originally developed by Microsoft, that separates language intelligence from the editor that displays it. Instead of VS Code knowing how TypeScript works, VS Code talks to tsserver. Instead of Neovim knowing Python, it talks to pyright or pylsp. The editor sends requests; the language server responds with semantic information.
Hire a contractor. Not another power tool.
Cursor, Bolt, Lovable, v0 are tools. You still run the project.
With Remy, the project runs itself.
That communication happens over JSON-RPC, and it covers capabilities most developers use constantly:
- Go to definition — jump from a function call to where it’s declared
- Find all references — see every place a symbol is used
- Workspace symbol search — search by name across the entire project
- Hover documentation — get type signatures and docstrings on demand
- Diagnostics — surface errors and warnings without running the compiler
- Rename symbol — safely rename across all usages in one shot
These capabilities exist for virtually every major language. TypeScript has tsserver. Python has pyright and pylsp. Go has gopls. Rust has rust-analyzer. Java has eclipse.jdt.ls. C/C++ has clangd. The list goes on.
The reason this matters for Claude Code is precision. When you ask Claude to “find all places where AuthService.validateToken is called,” a grep-based approach returns every line that happens to contain validateToken. An LSP-based approach returns exactly the call sites for that specific method on that specific class — resolved through the actual type system.
How MCP Connects Claude Code to a Language Server
MCP (Model Context Protocol) is Anthropic’s open standard for giving AI agents access to external tools and data sources. Claude Code is built to use MCP servers natively. You configure one or more MCP servers, and Claude can call their exposed tools as part of a conversation.
The architecture for LSP-over-MCP looks like this:
Claude Code (client)
↕ MCP protocol (JSON over stdio or HTTP)
MCP Server (middleware)
↕ JSON-RPC (stdio)
Language Server (e.g., pyright, gopls, rust-analyzer)
↕ File system
Your codebase
The MCP server’s job is straightforward: it starts the language server as a subprocess, manages the LSP session (including the required initialization handshake), and exposes language server capabilities as MCP tools. Claude calls a tool like lsp_find_references, the MCP server translates that into an LSP request, gets a response, and returns it to Claude in a format it can reason about.
Setting Up an LSP MCP Server
Prerequisites
Before starting, make sure you have:
- Claude Code installed (
npm install -g @anthropic-ai/claude-codeor via the Anthropic CLI) - Node.js 18+ (most MCP servers require it)
- The language server for your target language installed globally or per-project
For Python with Pyright:
npm install -g pyright
For TypeScript:
npm install -g typescript typescript-language-server
For Go:
go install golang.org/x/tools/gopls@latest
For Rust:
rustup component add rust-analyzer
Step 1: Choose or Build Your LSP MCP Server
There are a few options:
Option A: Use an existing open-source LSP MCP wrapper
Several community-built MCP servers wrap LSP servers directly. Search for mcp-lsp or mcp-language-server on npm or GitHub. These typically accept a configuration file where you specify which language server binary to use.
A typical configuration looks like:
{
"languageServer": {
"command": "pyright-langserver",
"args": ["--stdio"],
"rootUri": "file:///path/to/your/project"
}
}
Option B: Build a minimal MCP server wrapping LSP
If you need more control, you can write one yourself. The pattern is consistent across language servers:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { spawn } from "child_process";
import { createInterface } from "readline";
// Start language server subprocess
const lsProcess = spawn("pyright-langserver", ["--stdio"]);
// Set up JSON-RPC communication
// Handle LSP initialization
// Expose MCP tools that proxy to LSP requests
- ✕a coding agent
- ✕no-code
- ✕vibe coding
- ✕a faster Cursor
The one that tells the coding agents what to build.
The critical LSP calls to wrap as MCP tools are:
textDocument/definition→ expose aslsp_goto_definitiontextDocument/references→ expose aslsp_find_referencesworkspace/symbol→ expose aslsp_search_symbolstextDocument/hover→ expose aslsp_get_hovertextDocument/documentSymbol→ expose aslsp_list_file_symbolstextDocument/diagnostic→ expose aslsp_get_diagnostics
Step 2: Handle the LSP Initialization Handshake
Language servers require an initialization sequence before they’ll respond to any requests. This is often where DIY implementations go wrong.
Your MCP server needs to send an initialize request and wait for initializeResult before exposing any tools to Claude:
const initializeRequest = {
jsonrpc: "2.0",
id: 1,
method: "initialize",
params: {
processId: process.pid,
rootUri: `file://${projectRoot}`,
capabilities: {
textDocument: {
definition: { dynamicRegistration: true },
references: { dynamicRegistration: true },
hover: { dynamicRegistration: true },
documentSymbol: { dynamicRegistration: true }
},
workspace: {
symbol: { dynamicRegistration: true }
}
}
}
};
After receiving initializeResult, send an initialized notification:
const initializedNotification = {
jsonrpc: "2.0",
method: "initialized",
params: {}
};
Only then is the language server ready to handle document requests.
Step 3: Manage Open Documents
LSP servers work on the concept of “open documents.” Before you can request information about a file, you need to tell the server it’s open by sending a textDocument/didOpen notification with the file contents.
Your MCP server should handle this automatically when a tool is called:
async function ensureDocumentOpen(uri, filePath) {
if (!openDocuments.has(uri)) {
const content = await fs.readFile(filePath, "utf-8");
sendNotification("textDocument/didOpen", {
textDocument: {
uri,
languageId: detectLanguage(filePath),
version: 1,
text: content
}
});
openDocuments.add(uri);
}
}
This is especially important for the first call on any file in a session.
Step 4: Configure Claude Code to Use the MCP Server
Claude Code looks for MCP server configurations in a claude_mcp_config.json file (in your project root or ~/.claude/) or via the --mcp-config flag.
A complete configuration looks like:
{
"mcpServers": {
"lsp": {
"command": "node",
"args": ["/path/to/your/lsp-mcp-server/index.js"],
"env": {
"PROJECT_ROOT": "/path/to/your/codebase",
"LANGUAGE": "python"
}
}
}
}
For a multi-language project, you can register multiple MCP servers:
{
"mcpServers": {
"lsp-python": {
"command": "node",
"args": ["/path/to/lsp-mcp-server/index.js"],
"env": {
"PROJECT_ROOT": "/path/to/project",
"LS_COMMAND": "pyright-langserver",
"LS_ARGS": "--stdio"
}
},
"lsp-typescript": {
"command": "node",
"args": ["/path/to/lsp-mcp-server/index.js"],
"env": {
"PROJECT_ROOT": "/path/to/project",
"LS_COMMAND": "typescript-language-server",
"LS_ARGS": "--stdio"
}
}
}
}
Restart Claude Code after editing the configuration. Verify the MCP server is running by checking for it in the active tools list at the start of a session.
Practical Navigation Workflows with LSP Tools
Once the setup is complete, the way you work with Claude Code in large codebases changes significantly.
Tracing a Feature End-to-End
Say you’re investigating a bug in a payment flow and know it originates somewhere in process_payment. Instead of asking Claude to read files speculatively, you can instruct it to:
- Use
lsp_search_symbolsto findprocess_payment - Use
lsp_goto_definitionto jump to the implementation - Use
lsp_find_referencesto trace all callers - Use
lsp_get_hoveron unfamiliar types to understand signatures without opening more files
This produces a focused investigation path rather than a sprawling file-reading session.
Understanding an Unfamiliar Module
When onboarding to a new area of code, ask Claude to:
- Use
lsp_list_file_symbolson the module’s main file to get a structured outline - Use
lsp_goto_definitionon imported types to understand the dependency graph - Use
lsp_get_diagnosticsto surface any pre-existing errors
Coding agents automate the 5%. Remy runs the 95%.
The bottleneck was never typing the code. It was knowing what to build.
Refactoring Safely
Before renaming a function or changing a signature, ask Claude to:
- Use
lsp_find_referencesto get a complete list of call sites - Group the call sites by file and assess the blast radius
- Apply changes methodically, file by file
This is dramatically more reliable than regex-based search across a large codebase.
Pinpointing Error Sources
When a diagnostic error appears, use lsp_goto_definition on the types involved to trace the type mismatch to its origin. In a large TypeScript project, this can save 20–30 minutes of manual file tracing per error.
Troubleshooting Common Issues
The Language Server Isn’t Finding Project Files
Most language servers need to be pointed at the project root and often require a project configuration file to be present (pyproject.toml, tsconfig.json, go.mod, etc.). Verify:
- The
rootUriin your initialization request matches the actual project root - The appropriate config file exists and is valid
- The language server binary is accessible from the PATH used by the MCP server process
Requests Timeout Without a Response
Language servers initialize asynchronously. If you’re sending requests before initialization completes, you’ll get no response. Add explicit promise-based waiting for the initializeResult response before marking the server as ready.
References Return Empty Results
Some language servers (notably pylsp without plugins) have limited workspace-wide reference support. Prefer pyright or jedi-language-server for Python if full reference search is required. For TypeScript, make sure tsconfig.json includes all relevant files — language servers typically only index files included in the project config.
High Memory Usage on Large Projects
Language servers index the entire project and hold it in memory. For very large monorepos, this can be significant. Options:
- Scope the
rootUrito the subdirectory you’re actively working in - Use language servers designed for large codebases (
goplshandles large Go monorepos well;rust-analyzerhas explicit large-workspace support) - Restart the MCP server between sessions if memory becomes an issue
Where MindStudio Fits in This Picture
The MCP architecture that makes LSP integration possible is the same architecture that MindStudio’s agentic MCP server capability is built on. MindStudio lets you build and expose MCP servers backed by full AI workflows — without writing the server infrastructure yourself.
This opens up a practical use case for teams who want LSP-level intelligence without building and maintaining a custom MCP server: use MindStudio to wrap code analysis workflows and expose them as MCP tools that Claude Code can call. You could build an agent that accepts a symbol name, runs codebase analysis, and returns structured context — then expose it as a tool in your Claude Code session.
More broadly, if you’re building teams of AI agents where one agent is Claude Code handling coding tasks and others handle documentation, ticket management, or code review, MindStudio’s webhook and API endpoint agents can act as the connective tissue — receiving outputs from Claude Code and routing them through downstream workflows.
The agent infrastructure (auth, retries, rate limiting) is handled by MindStudio, so you’re spending time on the reasoning logic, not the plumbing. You can try it free at mindstudio.ai.
Frequently Asked Questions
What is Language Server Protocol and how does it work?
LSP is a standard communication protocol, originally developed by Microsoft, that defines how code editors and language-aware tools exchange information. An editor (the “client”) sends requests like “what’s defined at this file position?” to a language server. The server responds with symbol locations, type information, references, and diagnostics. The protocol uses JSON-RPC over stdio or sockets. Because it’s a standard, the same language server works with VS Code, Neovim, Emacs, or — via MCP — Claude Code.
Does this work with all programming languages?
It works with any language that has a language server, which covers most languages used in production today. TypeScript, Python, Go, Rust, Java, C/C++, C#, Ruby, PHP, Kotlin, Dart, and many others all have actively maintained language servers. The setup process is the same; only the binary and language-specific configuration differs.
Is this setup complex to maintain?
The initial setup takes some effort, but maintenance is low. Language servers update independently of your MCP server. The main maintenance tasks are keeping the language server binary current and updating project config files (tsconfig.json, pyproject.toml, etc.) as the project evolves. Once configured, the MCP server starts automatically with Claude Code.
Can I use this with multiple languages in the same project?
Yes. Configure one MCP server per language in your Claude Code MCP config. Claude Code will have access to all of them simultaneously and can call the appropriate tools based on which file it’s working with. The tools can have language-specific names (e.g., python_find_references, typescript_find_references) to avoid ambiguity.
Does LSP integration work with remote or cloud-hosted codebases?
Yes, with some adaptation. If the codebase is accessible via a mounted filesystem, language servers work as normal. For remote codebases (SSH, containers, GitHub Codespaces), you’d run the MCP server and language server in the same environment as the code — the same pattern VS Code Remote uses. Cloud-based repos without local access require more setup: you’d need to clone locally or use a remote development environment.
What’s the difference between this approach and just using grep or ripgrep?
grep and ripgrep do text search. They find lines that match a pattern, but they have no understanding of the code’s structure. An LSP-based search understands the semantic meaning. It can distinguish between a function definition and a string that happens to contain the same word. It can resolve which process() method is being called when there are multiple classes with that method name. It understands type hierarchies, so a “find references” search on an interface method also returns calls through concrete implementations.
Key Takeaways
- Claude Code’s default file-reading approach breaks down in large codebases. LSP-based symbol navigation solves this by giving Claude the same semantic code intelligence developers get in their IDE.
- An MCP server bridges Claude Code and the language server, translating MCP tool calls into LSP JSON-RPC requests.
- Every major language has a mature language server. The setup pattern — install server, configure MCP wrapper, register in Claude Code config — is consistent across languages.
- The highest-value LSP tools for Claude Code are
find_references,goto_definition,search_symbols, andget_diagnostics. These cover the majority of large-codebase navigation needs. - MindStudio’s agentic MCP server capability lets you build and expose code analysis workflows as tools without maintaining server infrastructure yourself.
How Remy works. You talk. Remy ships.
If you’re spending sessions watching Claude read file after file trying to piece together context it could get in a single LSP call, this setup is worth the hour of configuration it takes to get running. Start at mindstudio.ai if you want to build the surrounding agent workflows that make Claude Code even more capable in your specific environment.