README
¶
lsp-proxy
Proof of Concept — exploratory project, not production-ready.
A transparent stdio proxy for Language Server Protocol (LSP) servers. Sits between an editor and any stdio-based language server, forwarding JSON-RPC messages in both directions and allowing them to be inspected and modified in transit.
Currently supports saving and enriching hover documentation (markdown) per symbol, then injecting the modified content back into hover responses.
Table of contents
- Screenshots
- How it works
- Getting the binary
- Usage
- Zed configuration
- Inspecting LSP logs in Zed
- Development
- Testing without an editor
- License
Screenshots
Go


Rust


How it works
Transparent proxy — forwards all messages unchanged:
┌────────┐ ┌───────────┐ ┌────────────┐
│ Editor │─ requests ──►│ lsp-proxy │─ requests ──►│ LSP server │
│ │◄─ responses ─│ │◄─ responses ─│ │
└────────┘ └───────────┘ └────────────┘
With hover documentation (-save-md / -read-md) — intercepts hover responses, saves the markdown to a file, and optionally injects your edited version on the next hover:
┌────────┐ ┌───────────┐ ┌────────────┐
│ Editor │─ requests ──►│ lsp-proxy │─ requests ──►│ LSP server │
│ │◄─ responses ─│ │◄─ responses ─│ │
└────────┘ └─────┬─────┘ └────────────┘
│ reads / writes
┌────▼──────┐
│ *.md │
└───────────┘
Getting the binary
Download a pre-built release
Pre-built binaries for all platforms are attached to each release on Codeberg.
Download the binary for your platform:
| Platform | File |
|---|---|
| macOS (Apple Silicon) | lsp-proxy-darwin-arm64 |
| macOS (Intel) | lsp-proxy-darwin-amd64 |
| Linux (arm64) | lsp-proxy-linux-arm64 |
| Linux (amd64) | lsp-proxy-linux-amd64 |
| Windows (amd64) | lsp-proxy-windows-amd64.exe |
Make it executable on macOS / Linux:
chmod +x lsp-proxy-darwin-arm64
Install with Go
If you have Go installed, you can install directly without cloning the repo:
go install codeberg.org/poctastic/lsp-proxy@latest
The binary is placed in $GOPATH/bin (usually ~/go/bin). Make sure that directory is on your $PATH.
Usage
# macOS / Linux
./lsp-proxy -lsp-path <path-to-lsp-server> [flags]
# Windows
.\lsp-proxy.exe -lsp-path <path-to-lsp-server> [flags]
Flags
| Flag | Required | Description |
|---|---|---|
-lsp-path |
yes | Path to the upstream LSP server binary |
-save-md |
no | Save hover markdown per symbol to -md-path (only on first encounter) |
-read-md |
no | Read back markdown from -md-path and inject into hover responses |
-md-path |
when -save-md or -read-md |
Directory where hover markdown files are saved/read |
Examples
Transparent pass-through (no modification):
macOS / Linux:
./lsp-proxy -lsp-path /usr/local/bin/gopls
Windows:
.\lsp-proxy.exe -lsp-path C:\path\to\gopls.exe
Save hover docs as markdown files:
macOS / Linux:
./lsp-proxy -lsp-path /usr/local/bin/gopls \
-save-md \
-md-path ./docs/
Windows:
.\lsp-proxy.exe -lsp-path C:\path\to\gopls.exe `
-save-md `
-md-path .\docs\
Save and inject modified docs:
macOS / Linux:
./lsp-proxy -lsp-path /usr/local/bin/gopls \
-save-md \
-read-md \
-md-path ./docs/
Windows:
.\lsp-proxy.exe -lsp-path C:\path\to\gopls.exe `
-save-md `
-read-md `
-md-path .\docs\
Markdown files are named {hash}_{symbol}.md (e.g. a3f1c2d0_Atoi.md). Each file includes an Edit here link at the bottom — click it to open the file directly. Edit it freely; on the next hover, your version is returned to the editor instead of the server's original response.
Zed configuration
Point Zed's LSP binary at the proxy and pass the real server as -lsp-path.
1. Open the Zed settings file directly:
| Platform | Path |
|---|---|
| macOS | ~/.config/zed/settings.json |
| Linux | ~/.config/zed/settings.json |
| Windows | %APPDATA%\Zed\settings.json |
Or open it from inside Zed: cmd-shift-p (macOS / Linux) / ctrl-shift-p (Windows) → zed: open settings.
2. Add an lsp block (or extend the existing one) with the languages you want to proxy:
{
"lsp": {
"gopls": {
"binary": {
"path": "/path/to/lsp-proxy",
"arguments": [
"-lsp-path", "/path/to/gopls",
"-save-md",
"-read-md",
"-md-path", "/path/to/markdown/dir/"
]
}
},
"rust-analyzer": {
"binary": {
"path": "/path/to/lsp-proxy",
"arguments": [
"-lsp-path", "/path/to/rust-analyzer",
"-save-md",
"-read-md",
"-md-path", "/path/to/markdown/dir/"
]
}
}
}
}
3. Fill in the paths:
| Placeholder | macOS | Linux | Windows |
|---|---|---|---|
/path/to/lsp-proxy |
./lsp-proxy |
./lsp-proxy |
.\lsp-proxy.exe |
/path/to/gopls |
~/Library/Application Support/Zed/languages/gopls/gopls_<version> |
~/.local/share/zed/languages/gopls/gopls_<version> |
%APPDATA%\Zed\languages\gopls\gopls_<version>.exe |
/path/to/rust-analyzer |
~/Library/Application Support/Zed/languages/rust-analyzer/rust-analyzer-<date> |
~/.local/share/zed/languages/rust-analyzer/rust-analyzer-<date> |
%APPDATA%\Zed\languages\rust-analyzer\rust-analyzer-<date>.exe |
/path/to/markdown/dir/ |
any writable directory | any writable directory | any writable directory |
4. Restart the language server for the changes to take effect — no need to restart Zed.
Open a code file, then open the command palette (cmd-shift-p / ctrl-shift-p) and run editor: restart language server.
The proxy works with any editor that launches an LSP server over stdio — the Zed configuration above is the reference setup.
Inspecting LSP logs in Zed
Zed exposes the full JSON-RPC traffic between the editor and the language server (i.e. the proxy in this case).
Open a code file, then open the command palette (cmd-shift-p / ctrl-shift-p) and run dev: open language server logs.
A panel opens showing:
- Logs tab — stderr output from the language server (and the proxy, since the proxy forwards the server's stderr directly).
- RPC Messages tab — the raw JSON-RPC messages exchanged between Zed and the proxy, useful for verifying that messages are being forwarded and modified correctly.
This is the primary tool for debugging when something isn't working — check the RPC messages tab to confirm the proxy is running (you'll see initialize / initialized) and the logs tab for any errors the proxy or the server emits.
Development
Requirements
- Go 1.26.2+
- Make
Build
make build # build for host platform → bin/lsp-proxy
make build-all # cross-compile for macOS / Linux / Windows
Testing without an editor
During development you can drive the proxy directly from the terminal using a shell function that prepends the Content-Length header to a JSON-RPC message (mimicking what an editor sends):
lsp_frame() {
local msg="$1"
printf "Content-Length: %d\r\n\r\n%s" "${#msg}" "$msg"
}
Then pipe a sequence of LSP messages into the proxy:
{
lsp_frame '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":null,"rootUri":null,"capabilities":{}}}'
sleep 0.5
lsp_frame '{"jsonrpc":"2.0","method":"initialized","params":{}}'
sleep 0.2
lsp_frame '{"jsonrpc":"2.0","id":"2x","method":"shutdown"}'
sleep 0.2
lsp_frame '{"jsonrpc":"2.0","method":"exit"}'
} | go run . -lsp-path "/path/to/lsp-server" -save-md -read-md -md-path "./debug/"
The sleep calls give the language server time to process each message before the next one arrives. The proxy's responses are written to stdout, so you'll see the full JSON-RPC exchange in the terminal.
Replace go run . with ./bin/lsp-proxy if you've already built the binary.
Other targets
make vet # run go vet
make test # run tests
make run # build and run (requires -lsp-path set via environment)
make help # list all targets
License
Documentation
¶
There is no documentation for this package.