Skip to main content
MindStudio
Pricing
Blog About
My Workspace

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.

MindStudio Team RSS
How to Use Language Server Protocol (LSP) with Claude Code 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-code or 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
REMY IS NOT
  • a coding agent
  • no-code
  • vibe coding
  • a faster Cursor
IT IS
a general contractor for software

The one that tells the coding agents what to build.

The critical LSP calls to wrap as MCP tools are:

  • textDocument/definition → expose as lsp_goto_definition
  • textDocument/references → expose as lsp_find_references
  • workspace/symbol → expose as lsp_search_symbols
  • textDocument/hover → expose as lsp_get_hover
  • textDocument/documentSymbol → expose as lsp_list_file_symbols
  • textDocument/diagnostic → expose as lsp_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:

  1. Use lsp_search_symbols to find process_payment
  2. Use lsp_goto_definition to jump to the implementation
  3. Use lsp_find_references to trace all callers
  4. Use lsp_get_hover on 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:

  1. Use lsp_list_file_symbols on the module’s main file to get a structured outline
  2. Use lsp_goto_definition on imported types to understand the dependency graph
  3. Use lsp_get_diagnostics to surface any pre-existing errors
TIME SPENT BUILDING REAL SOFTWARE
5%
95%
5% Typing the code
95% Knowing what to build · Coordinating agents · Debugging + integrating · Shipping to production

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:

  1. Use lsp_find_references to get a complete list of call sites
  2. Group the call sites by file and assess the blast radius
  3. 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 rootUri in 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 rootUri to the subdirectory you’re actively working in
  • Use language servers designed for large codebases (gopls handles large Go monorepos well; rust-analyzer has 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, and get_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.

YOU14:02
Build me a sales CRM with a pipeline view and email integration.
REMY14:03 → 14:11
Scoping the project
Wiring up auth, database, API
Building pipeline UI + email integration
Running QA tests
✓ Live at yourapp.msagent.ai

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.

Presented by MindStudio

No spam. Unsubscribe anytime.