Skip to main content
Glama

lsp2mcp starts the language servers already installed on your machine and exposes them as read-only MCP tools. It answers questions an agent cannot settle by searching text: where a symbol is defined, who references it, what type it has, what the compiler would complain about

Why

Plain text search cannot resolve types. interface implementations, overloads, re-exports and shadowed names all look identical to grep. Language servers do this resolution properly, but editor integrations are the only common way to reach them

AI coding agents typically run without a language server attached. lsp2mcp gives an agent the same queries an IDE has, over stdio MCP, reusing the servers you already trust

Related MCP server: universal-lsp

Install

Requires Node.js 20 or newer, and the language servers you want to use already on PATH

npm install -g @sglwsjxh/lsp2mcp

Create a config file next to the package or pass its path. A starting template ships as mcp.json.example

cp mcp.json.example mcp.json

The program reads mcp.json from its working directory. If the file is absent it fails loudly and prints the path it looked at, rather than silently falling back to the template

Register with a client

{
  "mcp": {
    "servers": {
      "lsp2mcp": {
        "type": "local",
        "command": ["node", "/absolute/path/to/lsp2mcp/dist/index.js"],
        "environment": {
          "LSP2MCP_CONFIG": "/absolute/path/to/lsp2mcp/mcp.json"
        }
      }
    }
  }
}

Absolute paths in the client entry are deliberate: the process working directory is owned by the client, not by this package

Configuration

Each entry maps a language to one command. The command is spawned directly when it resolves to an executable, and through cmd.exe when it resolves to a .cmd or .bat wrapper. This distinction matters on Windows only

{
  "servers": {
    "typescript": {
      "extensions": [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
      "languageIds": { ".tsx": "typescriptreact", ".jsx": "javascriptreact" },
      "command": ["typescript-language-server", "--stdio"]
    },
    "java": {
      "extensions": [".java"],
      "command": ["jdtls", "-data", "{root}/.jdtls-workspace"]
    }
  }
}

Field

Required

Meaning

extensions

yes

File suffixes routed to this server, matched case-insensitively

command

yes

Executable plus arguments

languageIds

no

Per-extension override of the LSP languageId, used when a suffix is not what the server expects

{root} inside any argument is replaced with the project root passed to lsp_init

Servers in the template

Language

Command

Extra requirement

TypeScript / JavaScript

typescript-language-server

none

Vue

vue-language-server

project needs a local typescript; missing it disables the entry instead of falling back to a global copy. Script-region queries additionally need the plugin shown in Vue single-file components

Python

pyright-langserver

none

C#

csharp-ls

none

Rust

rust-analyzer

none

Go

gopls

none

C / C++

clangd

a compilation database gives materially better results

Dart / Flutter

dart language-server

Flutter SDK provides this command

JSON

vscode-json-language-server

syntax checking only, no schema store lookup

HTML

vscode-html-language-server

standalone .html files

CSS

vscode-css-language-server

standalone .css files

Java

jdtls

needs jdtls on PATH and python available; the wrapper script resolves the Eclipse launcher itself

Add or edit entries freely. Nothing about the language set is compiled in

Tools

All tools are read-only. There is no rename, format or code action, because those mutate your working tree and belong to an editing tool

Tool

Input

Returns

lsp_init

root, optional languages

binds the project root, reports detected languages, starts nothing

lsp_health

none

state per language: ready, failed, crashed, missing command, plus the reason

lsp_shutdown

optional language

stops one or all servers and reaps their process tree

lsp_definition

file, line, character

where the symbol is declared

lsp_references

file, line, character, optional include_declaration, limit, offset

all usages, paginated

lsp_implementation

file, line, character

implementations of an interface or abstract member

lsp_hover

file, line, character

resolved type signature and documentation

lsp_document_symbols

file

symbols in one file with line numbers

lsp_diagnostics

file, or scope set to workspace

errors and warnings with severity, code and range

line and character are 1-based, matching editor display and file readers. They are converted internally for the 0-based LSP wire format

Servers start on demand. Calling lsp_definition on a .py file for a project whose Python server is not running yet starts it, waits for initialize, then serves the request

Behavior notes

  • A server that never declared a capability returns an explicit "not supported by this language server" instead of an empty list, so absence never reads as success

  • Crashed servers are restarted on the next call and reported through lsp_health

  • Diagnostics arrive asynchronously. A query waits within a bounded timeout rather than returning immediately empty

  • On Windows, stopping a server kills the whole process tree. jdtls spawns cmd then python then java, and killing only the direct child leaves orphans holding hundreds of megabytes

  • Before the Java server starts, <root>/.jdtls-workspace/ is created and a .gitignore containing * is written into it, so the workspace never shows up in git status. The same rule covers any argument that points at a hidden directory inside the project. Other servers need no such handling because they do not write into your tree

Vue single-file components

.vue needs two servers, and they do different halves of the job. @vue/language-server 3 and later dropped its own pipe to the TypeScript server and made the client responsible for relaying tsserver traffic: the server emits a tsserver/request notification and blocks until a matching tsserver/response arrives. Without that relay every query on a .vue file hangs until timeout

lsp2mcp implements the relay. Starting a vue server always brings up the typescript server for the same root first and routes the requests through it

The remaining split is real, not a bug:

Query on a .vue file

Answered by

lsp_document_symbols

vue — the SFC structure plus the whole template node tree

lsp_hover, lsp_definition, lsp_references, lsp_implementation

typescript — tried on vue first, retried there when it comes back empty

lsp_diagnostics

both, merged and deduplicated by file, line, column and message

Every response names the server that answered, so a fallback never looks like a native result

Script-region semantics also need the TypeScript plugin loaded in the typescript entry:

"typescript": {
  "extensions": [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
  "command": ["typescript-language-server", "--stdio"],
  "initialization": {
    "plugins": [
      {
        "name": "@vue/typescript-plugin",
        "location": "PATH/TO/node_modules/@vue/language-server",
        "languages": ["vue"]
      }
    ]
  }
}

location is a directory that contains the plugin under its node_modules; the plugin must be the same version as the running @vue/language-server. Without that block, template structure and syntax diagnostics still work, but hover, definition and references inside <script> return nothing

Known limits

  • jdtls does not advertise diagnosticProvider, so Java diagnostics come from publishDiagnostics pushes and a fresh file may need a moment to settle. It does advertise implementationProvider

  • Markdown servers are not included. vscode-markdown-language-server requires a client that answers its private markdown/parse request, which generic LSP clients do not implement

  • Position-based tools need the token to be under the cursor position you pass. Selecting a whole identifier range is ambiguous; point at its start

  • No automatic project discovery. The agent passes root explicitly, because scanning a directory tree is slow and does not prove which project a file belongs to

Development

npm ci
npm run typecheck
npm run build
node dist/index.js

dist/ is gitignored and is what gets published

License

Apache License 2.0. See LICENSE

Related MCP Connectors

Related MCP Servers

  • F
    license
    A
    quality
    D
    maintenance
    An MCP stdio server that wraps language servers to expose read-oriented code-intelligence tools (hover, definition, references, diagnostics, etc.) for coding agents, supporting TypeScript/JavaScript and Python.
    56
    1
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI agents to query language intelligence over MCP, including definitions, references, symbols, and code relationship graphs across many languages, all in one native process with no external language servers.
    Apache 2.0
  • F
    license
    Not graded
    quality
    A
    maintenance
    Enables AI coding agents and MCP clients to pull real-time language-server data from a running editor instance, including diagnostics, hover/definition/signature and type information, and symbol references with usage context. It also supports workspace-wide symbol renaming, opening files, and command execution, letting agents validate changes without waiting on slow tsc/eslint runs.
    -
  • F
    license
    Not graded
    quality
    A
    maintenance
    Enables AI coding agents and MCP clients to query real-time LSP diagnostics, type information, definitions, references, and workspace-wide symbol renaming through the editor's language servers, avoiding slow tsc/eslint checks.
    -