A production-ready, async-first Python client for the Language Server Protocol (LSP). Built for developers who need fine-grained control and extensibility when integrating language intelligence into their tools.
lsp-client is engineered for developers building production-grade tooling that requires precise control over language server environments:
- 🧩 Intelligent Capability Management: Zero-overhead mixin system with automatic registration, negotiation, and availability checks. Only access methods for registered capabilities.
- 🎨 Ergonomic API Design: Every capability method is designed for developer productivity. The SDK handles complex LSP response types (like
LocationvsLocationLink), providing consistent, high-level Python objects instead of raw JSON-RPC structures. - 🎯 Universal LSP Support: Full 3.17 specification coverage. Supports all standard client requests, notifications, and server-to-client interactions. If a capability exists in the LSP spec, you can use or implement it here.
- 🛡️ Fail-Safe Capability Validation: Sophisticated pre-flight checks ensure that the server's capabilities perfectly match the client's requirements before the first request is ever sent. Catch configuration mismatches at startup rather than during runtime.
- ⚡ Production-Ready & Modern: Explicit environment control with no auto-downloads. Built with async patterns, comprehensive error handling, retries, and full type safety.
uv add lsp-clientThe following code snippet can be run as-is, try it out:
# NOTE: install pyrefly with `uv tool install pyrefly` first
import anyio
from lsp_client import Position, PyreflyClient
async def main():
async with PyreflyClient() as client:
refs = await client.request_references(
file_path="example.py",
position=Position(10, 5)
)
for ref in refs:
print(f"Reference at {ref.uri}: {ref.range}")
anyio.run(main)The examples/ directory contains comprehensive usage examples:
rust_analyzer.py- Rust code intelligence with Rust-Analyzerpyrefly.py- Python linting and analysis with Pyreflyprotocol.py- Direct LSP protocol usage
Run examples with:
uv run examples/pyrefly.pyDefining a custom client is super easy with the capability mixin:
@define
class MyPythonClient(
Client,
WithRequestHover, # textDocument/hover
WithRequestDefinition, # textDocument/definition
WithRequestReferences, # textDocument/references
WithNotifyDidChangeConfiguration, # workspace/didChangeConfiguration
# ... and other capabilities as needed
):
def create_default_servers(self) -> DefaultServers:
return DefaultServers(
local=LocalServer(program="pylsp", args=["--stdio"]),
)
def create_initialization_options(self) -> dict:
return {"plugins": {"pyflakes": {"enabled": True}}} # custom init options
def check_server_compatibility(self, info: lsp_type.ServerInfo | None) -> None:
return # Custom compatibility checks if needed| Language Server | Module Path | Language |
|---|---|---|
| Pyright | lsp_client.clients.pyright |
Python |
| Basedpyright | lsp_client.clients.basedpyright |
Python |
| Pyrefly | lsp_client.clients.pyrefly |
Python |
| Ty | lsp_client.clients.ty |
Python |
| Rust Analyzer | lsp_client.clients.rust_analyzer |
Rust |
| Deno | lsp_client.clients.deno |
TypeScript/JavaScript |
| TypeScript Language Server | lsp_client.clients.typescript |
TypeScript/JavaScript |
| Gopls | lsp_client.clients.gopls |
Go |
- Method Safety: You can only call methods for capabilities you've registered. No runtime surprises from unavailable capabilities.
- Automatic Registration: The mixin system automatically handles client registration, capability negotiation, and availability checks behind the scenes.
- Zero Boilerplate: No manual capability checking, no complex initialization logic, no error handling for missing capabilities.
- Type Safety: Full type annotations ensure you get compile-time guarantees about available methods.
- Composability: Mix and match exactly the capabilities you need, creating perfectly tailored clients.
lsp-client implements a prioritized server loading strategy to ensure your tool works across different environments without manual configuration:
- Explicit Server: If you provide a specific
Serverinstance, it will be used first. - Local Environment: It checks if the required language server is already installed in the local system path.
- Auto-Install: As a last resort, it can attempt to automatically install the server locally if an installation hook is defined.
The mixin-based architecture allows you to define exactly what your client supports. This is not just for organization; it directly affects the Initialize request sent to the server, ensuring the server only sends relevant notifications and doesn't waste resources on unused features.
lsp-client features a sophisticated configuration system designed for production use:
- Sensible Defaults: Every built-in client comes pre-configured with optimized settings. Features like inlay hints, auto-imports, and advanced diagnostics are enabled out-of-the-box.
- Hierarchical Overrides: Use
ConfigurationMapto manage global settings and path-based overrides (e.g., different linting rules fortests/vssrc/). - Deep Merging: Settings are merged recursively, allowing you to override specific sub-keys without losing the rest of the default configuration.
- Automatic Sync: The SDK automatically handles
workspace/didChangeConfigurationnotifications, ensuring the language server always has the latest settings without a restart.
The library is built with extensibility as a core principle. You are never locked into the default behavior:
- Capability Overriding: You can easily customize how the client handles specific LSP requests or notifications by overriding the capability methods. Want to filter diagnostics or transform hover content before it reaches your application? Just override the corresponding method in your custom client.
- Middleware Support: Intercept and modify outgoing requests or incoming responses to implement custom logic like caching, logging, or request debouncing.
- Custom Servers: You can implement your own
Serverclass to connect to language servers over custom transports (e.g., WebSockets, named pipes, or remote SSH).
We welcome contributions! Please see our Contributing Guide for details on:
- Adding new language server support
- Extending protocol capabilities
- Development workflow
Note: Container-based language server execution is currently under maintenance and refactoring. This feature will be re-enabled in the next minor version release with improved stability and performance. For now, please use local language server installations.
This project is licensed under the MIT License - see the LICENSE file for details.
- Built on the Language Server Protocol specification
- Uses lsprotocol for LSP type definitions
- Architecture inspired by multilspy and other LSP clients