About
Convert a local LSP into an MCP | 将本地 lsp 转换成 mcp
README
lsp2mcp · Convert a local LSP into an MCP
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
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", "{cache}/lsp2mcp/jdtls/{rootHash}"]
}
}
}
| 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.
jdtlsspawnscmdthenpythonthenjava, and killing only the direct child leaves orphans holding hundreds of megabytes - The Java workspace lives outside the project at
~/.cache/lsp2mcp/jdtls/<project hash>, sogit statusstays clean and no.jdtls-workspace/directory is created (remove a leftover one by hand if you have it). Never point-datainside the project root: Eclipse rejects a workspace nested in its own project and the Maven import silently degrades to a non-project mode wherelsp_hoverandlsp_definitionreturn nothing. Importing may still generate Eclipse metadata inside the project (.project,.classpath,.settings/); gitignore those yourself if you commit the repo. Other servers either write nothing into your tree or manage their own ignore files: clangd writes its index to.cache/clangd/index/and creates a.gitignorethere itself, cargo writestarget/
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
jdtlsdoes not advertisediagnosticProvider, so Java diagnostics come frompublishDiagnosticspushes and a fresh file may need a moment to settle. It does advertiseimplementationProvider- Markdown servers are not included.
vscode-markdown-language-serverrequires a client that answers its privatemarkdown/parserequest, 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
rootexplicitly, 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
Install Lsp2mcp in Claude Desktop, Claude Code & Cursor
unyly install lsp2mcpInstalls into Claude Desktop, Claude Code, Cursor & VS Code — handles npx, uvx and build-from-source repos for you.
First time? Get the CLI: curl -fsSL https://unyly.org/install | sh
Or configure manually
Run in your terminal:
claude mcp add lsp2mcp -- npx -y @sglwsjxh/lsp2mcpStep-by-step: how to install Lsp2mcp
FAQ
Is Lsp2mcp MCP free?
Yes, Lsp2mcp MCP is free — one-click install via Unyly at no cost.
Does Lsp2mcp need an API key?
No, Lsp2mcp runs without API keys or environment variables.
Is Lsp2mcp hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Lsp2mcp in Claude Desktop, Claude Code or Cursor?
Open Lsp2mcp on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Unyly Deploy
Deploy sites and MCP servers to Unyly straight from your AI agent
by UnylyAmap Maps Mcp Server
MCP server for using the AMap Maps API
by duxiaohuiPayMCP
Lightweight payments layer for MCP servers: turn tools into paid endpoints with a two-line decorator. [PyPI](https://pypi.org/project/paymcp/) · [npm](https://w
by blustAIServer For Power BI
Enables exploring Microsoft Fabric / Power BI workspaces and semantic models, and executing ad-hoc DAX queries.
by Mitsubishi-FusoTemplate MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectSupabase
Database, auth and storage
by SupabaseEverything
Reference / test server with prompts, resources, and tools.
Compare Lsp2mcp with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
