lspf
Build language capabilities in Rust for IDEs and AI agents. LSPF provides the Language Server Protocol runtime for both servers and clients.
Docs · crates.io · API · Examples · 简体中文
- Complete stable LSP 3.18 feature catalog, with capabilities generated from registrations.
- Framework-owned documents, workspace state, and position encoding negotiation.
- Cancellation, bounded concurrency, and partial results.
- stdio, TCP, WebSocket, WASM worker channels, and custom transports.
- Typed Client calls and supervised language-server processes for IDE and Agent hosts.
Try a working server: run the Markdown demo in Neovim, VS Code, or Zed. To build your own, start with the example below.
Minimal example
A hover handler that reads the document managed by LSPF. Save this as
src/main.rs with the dependencies below, then run cargo build.
use Arc;
use ;
use ;
async
async
Point your editor's LSP command at target/debug/my-language-server.
Opening a document and hovering returns its word count; LSPF handles
initialization, synchronization, and shutdown. The
server tutorial adds diagnostics and commands.
See it work in 30 seconds

lspf-markdown checks local Markdown links, shows their resolved targets on
hover, and jumps to target headings. This is a real Neovim session using the
in-tree server over stdio. Replay or record the demo.
Requires Rust 1.98+, Neovim 0.11+, and Cargo's bin directory on PATH.
Compilation happens before the 30-second demo. Put the cursor inside
missing.md to inspect the diagnostic, then inside guide.md for hover and
definition. The Neovim quick start includes explicit
commands and a small configuration you can adapt to your own server.
Why LSPF?
Let the framework own the protocol runtime
Register the language operations your application needs with
.feature(lspf::features::hover(), hover) or
.feature(lspf::features::completion(options), complete), as in the
expanded example below.
LSPF owns initialize, shutdown and exit, JSON-RPC dispatch, document
synchronization, workspace state, and UTF-8/UTF-16/UTF-32 position negotiation.
It also provides cancellation tokens, bounded concurrent dispatch,
request-scoped partial results, typed server-to-client requests, and transport
adapters. Handlers access this state through ServerContext.
Documents are updated before document hooks run. Capabilities come from the same registrations that dispatch requests; conflicting registrations fail at build time. Your application owns parsing, indexing, analysis, and the policy for using those results.
Extend language capabilities for IDEs and agents
A definition handler can power an editor's navigation action or an agent's source lookup through LSP. Diagnostics can appear in an editor or feed an automated edit-and-check loop. Implement the operation once in a server and expose it to hosts that speak the protocol.
LSPF also supplies the Client endpoint. An IDE or Agent host can launch a
language server as a supervised stdio child, receive diagnostics, and issue
typed requests through ServerHandle:
async
The Client tutorial is a runnable host that opens a document, receives diagnostics, requests hover, and executes a command. Use it as the starting point for an Agent's code tools. The host owns document contents and versions, tool schemas, model integration, and decisions about applying edits. LSPF supplies the typed protocol connection and process lifecycle.
Keep language logic independent of the host
Use stdio for a local editor or Agent process, TCP or WebSocket for a native
connection, or worker channels in a WASM host. The
transport examples
reuse shared handlers. Custom Transport implementations can connect an
embedded host or a test peer to the same protocol engine.
Quick start
Create a binary crate:
Add the dependencies under Install, then paste the minimal example
into src/main.rs and run cargo build. Launch the resulting executable
through an LSP client; it waits for protocol messages on stdin. Write logs to
stderr because stdout carries the protocol.
The expanded example below adds completion, a workspace command, disk access, and tracing.
use Arc;
use ;
use ;
/// Only your own application state — the framework owns the documents, the
/// workspace, and the client, and hands them to handlers through `ServerContext`.
;
/// A standard typed feature. The `features::hover()` descriptor fixes the
/// wire method, this handler's parameter and result types, and the
/// `hoverProvider` capability the server will advertise — all at once.
async
/// A second typed feature; the options supplied here are exactly what the
/// generated `completionProvider` advertises.
async
/// A typed Command, dispatched by name beneath `workspace/executeCommand`.
/// Registering it adds the name to the generated `executeCommandProvider`,
/// in registration order.
async
async
No handwritten ServerCapabilities and no framework change are involved:
the capabilities come from the registrations themselves. For a ready-to-copy
server and editor extension, start from
lspf-vscode-extension-template
or
lspf-intellij-platform-plugin-template.
The framework's public feature, workspace, client, and stdio seams are covered
by the crates/lspf integration tests and the in-tree lspf-markdown
reference server. The
feature registration
and workspace state
guides walk through each piece. To choose a Transport adapter, enable only
its Cargo dependencies, or implement another message-framed channel, see
Choosing a Transport, then
continue to stdio and custom transports
when the host owns a child process or a custom message-framed channel.
To build the same server one step at a time, start with the
Server tutorial, then use the
Client tutorial to drive it as a supervised
stdio child. For custom Client Transports and reverse handlers, follow the
Client adoption guide.
Runnable servers for individual LSP features are indexed in
crates/lspf/examples/README.md.
Install
[]
= "1.0.0"
= { = "1", = ["macros", "rt-multi-thread", "process"] }
= "0.1"
= { = "0.3", = ["env-filter"] }
The crate requires Rust 1.98 or newer. The minimal example needs only lspf and tokio; the expanded example also uses the tracing dependencies. Both examples use the default stdio
feature; select a different feature set for another Transport.
List every crate your application names directly. Tokio's process feature
is included for the Client example. The expanded server uses
tracing-subscriber to configure framework logs on stderr.
Editor quick starts
All three guides use the same lspf-markdown executable and fixture:
- Neovim: built-in LSP, no plugin required.
- VS Code: the bundled language-client extension.
- Zed: the bundled development extension.
For a new server bundled into an editor extension, use
lspf-vscode-extension-template
or, for a JetBrains IDE 2026.2 or later,
lspf-intellij-platform-plugin-template.
Each template's README covers generation, renaming, and the build and debug
commands.
For individual protocol operations, browse the
runnable examples.
The editor validation journeys record the
shared protocol checks and editor observations.
Concepts
The vocabulary below is taken from CONTEXT.md; the
project deliberately standardizes on these terms in the public API and
the docs.
| Term | Meaning |
|---|---|
Server |
Owns exactly one LSP connection; built by Server::builder(state) and served over a Transport. |
| Handler | An async function registered for one LSP method. User handlers take priority over the built-ins. |
| Built-in handler | A handler the framework ships. Lifecycle, document sync, and cancellation are protocol built-ins. |
| Post-mutation hook | What registering a built-in document notification records: it observes the mutation, never replaces it. |
Command |
A user closure dispatched by name on workspace/executeCommand. |
Document |
A text resource tracked by the framework: URI, language id, version, and rope-backed contents. |
DocumentsView |
The read-only document handle a handler reaches through ctx.documents(). |
NotebooksView |
The read-only notebook handle a handler reaches through ctx.notebooks(): structure, not cell text. |
Workspace |
The cloneable handle to the connection's workspace state: folders, configuration, and documents. |
FileProvider |
The configurable resolver for resources that are not open in the editor. |
ServerContext |
The cheap-to-clone framework-state handle every handler receives: documents, workspace, client, scope. |
ClientHandle |
The typed handle for server-to-client notifications and requests (ctx.client()). |
PartialResultSink |
The request-scoped typed sink for result chunks, lent by ctx.partial_results(). |
Client |
Configures one outbound LSP connection over a caller-provided Transport or supervised stdio child. |
ClientConnection |
Owns one initialized generic Client connection and its inbound protocol driver. |
ClientContext |
The protocol-only context passed to reverse handlers; editor state remains caller-owned. |
ServerHandle |
The cloneable handle for typed client-to-server calls and Client lifecycle transitions. |
ChildConnection |
Owns one initialized stdio Client connection and its supervised language-server process. |
CancellationToken |
The cancellation signal passed to request handlers. |
Transport |
A message-framed channel split into reader and writer halves for the protocol engine. |
Outcome |
How one connection ended, returned by serving; it carries the LSP exit code but never exits the process. |
Architecture
The project documentation covers features, transports, testing, operations, and both endpoint tutorials. The repository keeps the material needed to maintain those contracts:
CONTEXT.mddefines the domain language.docs/adr/records architecture decisions.docs/public-interface.mdfreezes the 1.0 public interface.SECURITY.mddefines supported platforms, compatibility, and vulnerability reporting.
See CONTRIBUTING.md before changing these boundaries.
Current scope
See the package changelog for release history.
The support contract is authoritative for maintained Rust versions, hosts, targets, and Cargo feature combinations. The operations guide records the deployment and application responsibilities that lspf deliberately leaves to its host.
Available today:
stdio, single-client TCP, WebSocket, and WASM worker-channel adapters, plus the public custom-transport interface.- A typed Client endpoint over custom Transport and supervised stdio child, with reverse handlers, bounded resources, deadlines, and lifecycle control.
- The built
Server: typed requests, notifications, commands, the sealed feature catalog covering the complete stable LSP 3.18 surface, userLayers, and the oneconfigure_initializetransaction. The 3.18 additions aretextDocument/inlineCompletion,workspace/textDocumentContent, andtextDocument/rangesFormatting. - Lifecycle hooks through shutdown and exit, incremental or full text-document synchronization, and post-mutation document hooks.
- Notebook document synchronization across
notebookDocument/didOpen,didChange,didSave, anddidClose, with notebook structure read throughctx.notebooks()and cell text through the samectx.documents()view as any other document. - The multi-root
Workspace, latest configuration settings, andFileProvider-backed unopened-file lookup. - Typed server-to-client notifications and correlated requests through
ClientHandle, including all seven stable workspace refresh requests. - Partial-result reporting: a handler for a request that carries
a
partialResultTokenreports typed chunks throughctx.partial_results()under the connection's outbound budget. - Concurrent dispatch, bounded concurrency, request cancellation, and
tracingspans. - Rope-backed documents with UTF-8/UTF-32/UTF-16 position negotiation.
Contributing
Read CONTRIBUTING.md for setup, project context, tests,
documentation rules, debugging, and pull request conventions. Issues live on
the GitHub tracker.
License
Dual-licensed under either of
at your option.