lsp-cli is a command-line tool for talking to Language Server Protocol (LSP) servers from the terminal without an editor.
It helps you do editor-style code navigation and inspection from a terminal:
- detect which language and LSP server fit a project
- download LSP server if it is missing in the system
- call simple LSP commands like references, callers, list-symbols
- collect diagnostics
- format files
- inspect server capabilities
- keep a server warm in a background daemon
The goal is simple: point lsp-cli at a file or project directory, let it choose a matching LSP server, and query that server from the shell.
The preferred installation method.
Assuming you already have cargo installed, install lsp-cli with Cargo:
cargo install lsp-cliCheck that it works:
lsp-cli --help
Generate SKILL.md for your code agent (if any):
mkdir -p .claude/skills/lsp-cli/
lsp-cli agent-skill >.claude/skills/lsp-cli/SKILL.md
Clone the repository with its data files and run:
git clone --recurse-submodules https://github.com/segoon/lsp-cli.git
cd lsp-cli
cargo build
# Run the debug executable from ./target/debug/
cargo run -- detect playground/python
cargo run -- grep Order playground/rust
cargo run -- definition format_order playground/c --lsp clangdPull requests run make check plus a required, explicitly tagged smoke suite. Nightly and
manually dispatched End-to-end
compatibility workflows run every supported pair. A red required job therefore indicates a
regression or newly incompatible upstream release; known unsupported pairs are explicit reviewed
exclusions rather than silently skipped tests.
The split keeps pull-request feedback reasonably fast, but a regression in a non-preferred server may first appear in the nightly run. See the E2E guide for local commands, manual workflow selectors, prerequisites, caching, and exclusion details.
You might want to use lsp-cli if:
- you want to make simple LSP requests from the terminal
- your code agent doesn't support a rare/proprietary LSP server (e.g. for a rare language)
- you implement an IDE/editor that should support a wide range of languages, but you don't want to mess around LSP server configuration / distribution
At a high level, lsp-cli usually does the following:
- scans the given directory
- detects matching languages from filenames
- chooses one or more configured LSP servers for those languages
- chooses a workspace root for the selected server
- (downloads an LSP server if it is missing)[DOWNLOAD.md]
- starts the server, or reuses a matching daemon
- sends the requested LSP query
- prints a human-readable result or JSON
This means you do not have to assemble server command lines by hand or even install LSP server at all.
Detect what lsp-cli would run:
lsp-cli detect path/to/project
lsp-cli detect --lang python path/to/projectSearch for symbols across a workspace:
lsp-cli grep MySymbol path/to/projectFind definitions, declarations, implementations, type definitions, and references by symbol name:
lsp-cli definition MySymbol path/to/project
lsp-cli declaration MySymbol path/to/project
lsp-cli implementation MySymbol path/to/project
lsp-cli type-definition MySymbol path/to/project
lsp-cli references MySymbol path/to/projectFind callers and callees by function name:
lsp-cli callers format_order path/to/project
lsp-cli callees format_order path/to/projectList symbols in one file or all matching files in a workspace:
lsp-cli list-symbols path/to/project/src/main.rs
lsp-cli list-symbols path/to/projectList functions:
lsp-cli list-functions path/to/projectCollect diagnostics for the workspace:
lsp-cli diagnostics path/to/projectFormat a file:
lsp-cli format path/to/file.rs
# exits with status 0 if the files is already formatted
lsp-cli format --check path/to/file.rs
# do not change the original file, the formatted content is written to stdout
lsp-cli format --stdout path/to/file.rsUse JSON output for scripts and code agents:
lsp-cli grep --json MySymbol path/to/projectInspect the selected server capabilities:
lsp-cli server-capabilities --lsp rust-analyzer path/to/projectYou may start an LSP server in background to reuse it later:
# start
lsp-cli daemon playground/python
# reuse the existing background daemon, do not spawn a new one
lsp-cli references playground/python
# stop a specific daemon
lsp-cli stop playground/python
# stop all background daemons
lsp-cli stop-allThe same background daemon is spawned and left idle after lsp-cli <CMD> --detach is finished.
A new connection has two seconds to send its first complete message. Silent or
incomplete connections are closed without interrupting the active client. Up to
16 connections can wait to start a session; additional connections are closed,
including stop connections while all 16 slots are occupied.
If you want to customize lsp-cli, the three important config layers are:
lsp-cli.yamlfor defaults and preferences (especially LSP server preference order)filetypes/*.yamlfor language detectionlsp/*.yamlfor server definitions
The data root is ~/.local/share/lsp-cli/data.
It contains:
filetypes/*.yaml- language detection settingslsp/*.yaml- LSP server settingslsp-cli.yaml
User-specific overrides live in a separate file:
$XDG_CONFIG_HOME/lsp-cli/lsp-cli.yaml (or ~/.config/lsp-cli/lsp-cli.yaml if $XDG_CONFIG_HOME is empty).
Settings are applied in this order:
- global
lsp-cli.yamlfrom the data root - user
lsp-cli.yaml - command-line flags
User config overrides global config. Command-line flags override both.
lsp-cli.yaml stores CLI defaults and server preference order:
# Install a missing server automatically when a command needs it.
download: false
# Which lsp-cli data release `update` should install.
download-version: latest
# Reuse or start background daemons for LSP-backed commands.
detach: false
# Print JSON.
json: false
# Print verbose logs and raw LSP traffic to stderr.
debug: false
# Default per-request timeout.
# It supports two formats:
# - 10.1 means 10.1 seconds
# - 100ms means 0.1 seconds
timeout: "10"
# Maximum outstanding document-symbol requests while finding a symbol by name
# (references, definition, declaration, implementation, type-definition, callers,
# and callees). Default: 20.
# Use 1 for sequential requests or a smaller value to reduce server load.
max-requests-in-flight: 20
# A file-symbol timeout fails the query instead of returning incomplete results.
# Default maximum number of printed results.
# Useful for code agents.
limit: 100
detect:
# Print only suggested command lines for `detect`.
quiet: false
daemon:
# Shut down an idle daemon after this much time.
idle-timeout: "60"
# Disconnect an output peer that remains backlogged for this long. Default: 2 seconds.
write-stall-timeout: "2"
lsp:
# Example server preference list for C++.
cpp:
- clangd
# Prefer these Python servers in this order.
python:
- ty
- pyright-langserver
- jedi-language-serverEach daemon output uses an ordered writer queue. A queue is marked stalled at 64
messages or 8 MiB of framed data and unmarked after both values fall below those
limits. The daemon continues accepting messages during the grace period, so memory
can grow if traffic continues; it disconnects a slow client or stops an unresponsive
server if the queue stays marked for write-stall-timeout. A message larger than
8 MiB is allowed and begins writing after earlier messages on that output.
Server restart and shutdown also advance through daemon events. The daemon keeps
accepting control traffic while a process starts or exits; an unresponsive server
is force-stopped after the existing two-second shutdown and exit deadlines.
Daemon traffic, lifecycle, error, and captured-server-stderr logs use a 64-record
worker queue, so formatting, stderr, and the global log file do not block forwarding.
New records are dropped when this queue is full, and the daemon reports the count
after logging resumes. Normal shutdown waits at most 100 milliseconds for logs.
Files in filetypes/ define how lsp-cli recognizes a language.
The filename becomes the language id:
filetypes/python.yamldefines thepythonlanguagefiletypes/cpp.yamldefines thecpplanguage
Example:
# filetypes/python.yaml
# Match files by extension.
extensions:
- "py"
# Match files by filename regex when needed.
patterns: []Another example for a filename-based language:
# filetypes/BUILD.bazel.yaml
# No extension-based matching.
extensions: []
# Match special filenames.
patterns:
- "^BUILD(\\.bazel)?$"Files in lsp/ define how lsp-cli can run a language server.
cmdline may include $WORKSPACE, which is replaced with the resolved workspace path
The workspace root is identified the following way.
First, lsp-cli walks upward until it finds one of the configured root_markers.
If no marker is found, it uses the input directory or input file parent directory as the workspace root.
Example:
# lsp/pyright.yaml
# Languages this server handles.
filetypes:
- "python"
# Search upward for these files to choose the workspace root.
root_markers:
- "pyrightconfig.json"
- "pyproject.toml"
- "setup.py"
- ".git"
# User-visible server name.
# This is the value used with `--lsp`.
name: "pyright-langserver"
# Command used to start the server.
cmdline: "pyright-langserver --stdio"Example with $WORKSPACE and indexing behavior:
# lsp/clangd.yaml
filetypes:
- "c"
- "cpp"
root_markers:
- ".clangd"
- "compile_commands.json"
- ".git"
name: "clangd"
# `$WORKSPACE` is replaced with the resolved workspace path.
cmdline: "clangd --background-index --compile-commands-dir=$WORKSPACE"
# Whether commands that can wait for indexing should do so by default.
wait-for-index: false
# Use only for servers known not to expose a terminal indexing signal. The default is confirmed.
build-index-completion: best-effortbuild-index-completion: best-effort keeps the command's bounded wait and still reports transport,
protocol, server, and shutdown errors. It only treats expiry without a terminal progress signal as
a successful best-effort attempt; success does not confirm that the whole workspace was indexed.
The maintained best-effort server list is in docs/SERVERS.md.
An LSP config can declare mason-extra-packages when its Mason package has an incomplete
dependency constraint. With automatic downloads, lsp-cli installs these package specifications in
the same npm or Python environment as the server. For example, CMake Language Server currently
uses pygls<2 because its released code imports the pygls 1.x API. This is a compatibility escape
hatch; prefer an upstream package constraint when one is available.
See the command reference for all commands and options.
Detect candidate servers for a project:
lsp-cli detect playground/python
lsp-cli detect playground/python --lang python
lsp-cli detect playground/python --lsp pyright-langserverSearch workspace symbols:
lsp-cli grep Order playground/rust
lsp-cli grep --json Order playground/rustList symbols and functions:
lsp-cli list-symbols playground/java/src/main/java/playground/order/Order.java
lsp-cli list-functions playground/rustFind locations and call relationships:
lsp-cli definition format_order playground/c --lsp clangd
lsp-cli declaration format_order playground/c --lsp clangd
lsp-cli implementation format_order playground/c --lsp clangd
lsp-cli type-definition format_order playground/c --lsp clangd
lsp-cli references OrderFormatter playground/csharp
lsp-cli callers format_order playground/c --lsp clangd
lsp-cli callees format_order playground/c --lsp clangdDiagnostics and formatting:
lsp-cli diagnostics playground/python
lsp-cli diagnostics --json playground/python
lsp-cli format playground/rust/src/main.rs
lsp-cli format --check playground/rust/src/main.rs
lsp-cli format --stdout playground/rust/src/main.rsInspect the selected server:
lsp-cli server-capabilities playground/rust --lsp rust-analyzer
lsp-cli build-index playground/rust --lsp rust-analyzerUse background daemons:
lsp-cli daemon playground/python
lsp-cli stop playground/python
lsp-cli stop-allList known languages and servers:
lsp-cli languages
lsp-cli servers
lsp-cli servers --lang pythonGenerate shell completion:
lsp-cli completion bash > /tmp/lsp-cli.bashGenerate a generic Markdown skill file for a code agent:
lsp-cli agent-skill > SKILL.mdThe repository contains small multi-file sample projects in playground/ for manual testing.
They are useful for learning what each command prints before pointing lsp-cli at a real project.
lsp-cliworks through LSP servers, so results are only as good as the selected server- not every server supports every feature
- some features may silently fail with some LSP servers
- symbol search quality (and regex syntax) varies between servers
- background indexing support varies between servers
I'd like to say "thank you" to the following opensource projects:
- nvim-lspconfig was used to fill LSP servers database
- mason inspired me to implement LSP server autodownload
- mason-registry is used as an LSP server registry