pi-lsp-adapter

Pi extension that gives coding agents Language Server Protocol tools for diagnostics, hover, definitions, references, and symbol search

Packages

Package details

extension

Install pi-lsp-adapter from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-lsp-adapter
Package
pi-lsp-adapter
Version
0.1.2
Published
May 30, 2026
Downloads
not available
Author
nikmmd
License
MIT
Types
extension
Size
195.6 KB
Dependencies
5 dependencies · 2 peers
Pi manifest JSON
{
  "extensions": [
    "./src/index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-lsp-adapter

Language-server intelligence for Pi agents: diagnostics, hover/type info, definitions, references, and symbol search on demand.

pi-lsp-adapter gives Pi a small set of read-only LSP tools without turning startup into an IDE boot. Language servers are lazy at session startup, installed servers can be warmed in the background when Pi reads source files, managed installs are isolated under Pi's runtime directory, and large LSP responses are paginated through a short-lived cache so they do not flood the context window.

Why this exists

Agents make better edits when they can ask semantic questions instead of guessing from text search alone:

  • What type is this symbol?
  • Where is this function defined?
  • What references will this change affect?
  • Did the language server report a type or lint error?

LSP already answers those questions. This extension makes those answers available to Pi as compact, read-only tools. It starts servers only when needed, keeps missing-server installs explicit by default, and returns the first useful page instead of dumping thousands of references into the model.

Install

Install from npm:

pi install npm:pi-lsp-adapter

Or install directly from git:

pi install git:github.com/nikmmd/pi-lsp-adapter

Restart Pi after installation, then check the extension:

/lsp status

For local development from a checkout:

git clone https://github.com/nikmmd/pi-lsp-adapter.git
cd pi-lsp-adapter
npm install
pi -e "$PWD"

The default install mode is prompt: agent tool calls will not silently install missing language servers. Install servers explicitly with /lsp install <serverId>, or set installMode to auto if you want first-use installation.

What happens on first use

You do this What happens
Run /lsp in an interactive Pi session Pi opens a compact LSP panel showing configured servers, install state, tracked processes, and config warnings.
Run /lsp status Pi prints the same status as text.
Run /lsp install pyright The server is installed under ~/.pi/agent/lsp/, and the resolved command is recorded in Pi's LSP lockfile.
Read a supported source file with Pi's read tool If warmup is enabled, the matching installed server is started in the background. Missing servers are ignored.
Ask for diagnostics/hover/definitions on a source file The extension detects the filetype and project root, reuses a warmed server or starts the matching server if needed, then runs the LSP request.
Query many references or symbols The first ranked page is returned immediately. If more pages exist, the result includes a resultId for lsp_more.
Configure a Mason/Nix/system binary Pi uses that command but does not own, update, or uninstall it. Use global config for executable overrides.

Quick start

/lsp status
/lsp install vtsls

Then ask Pi something that benefits from semantic context:

Check diagnostics for src/index.ts, then use hover on the exported function before editing it.

Useful first commands:

/lsp doctor vtsls
/lsp install pyright
/lsp start

Tools

The extension registers these read-only tools:

Tool Use it for Required input
lsp_diagnostics Compiler/type/lint diagnostics for one file filePath
lsp_hover Type, signature, and documentation at a position filePath, 1-based line, 1-based column
lsp_definition Definition locations for a symbol filePath, 1-based line, 1-based column
lsp_references Reference locations for a symbol filePath, 1-based line, 1-based column
lsp_document_symbols Classes, functions, variables, and other file symbols filePath
lsp_workspace_symbols Symbol search across active or selected workspaces query; optional serverId
lsp_more Next page from a cached multi-page LSP result Exact resultId returned by a previous paginated call

Examples:

lsp_diagnostics({ filePath: "src/index.ts" })
lsp_hover({ filePath: "src/index.ts", line: 42, column: 13 })
lsp_workspace_symbols({ query: "RuntimeManager", serverId: "vtsls" })

Line and column inputs are 1-based. For hover, definitions, and references, put the column on the identifier token itself rather than on whitespace or an import path string.

Supported servers

Built-in catalog entries currently include:

Server Languages/filetypes Managed installer
vtsls JavaScript, JSX, TypeScript, TSX npm
pyright Python npm
gopls Go go
rust-analyzer Rust GitHub release
yamlls YAML npm
jsonls JSON, JSONC npm
jdtls Java GitHub release

You can override these definitions or add new server definitions in config.

Configuration

Most users only need one file:

~/.pi/agent/lsp.json

If that file does not exist, the extension behaves as if this config were present:

{
  "installMode": "prompt",
  "warmup": true,
  "servers": {}
}

servers: {} means "use the built-in server catalog unchanged". Add entries only when you want to override a built-in server or define a new one.

Config files and merge order

Priority Path Purpose
1 built-in catalog Default server definitions for vtsls, pyright, gopls, rust-analyzer, yamlls, etc.
2 ~/.pi/agent/lsp.json User-global config. Best place for executable, install, PATH, installMode, and warmup.
3 .pi/lsp.json Project-local config. Safe server fields are honored before trust; process-starting fields require /lsp trust.

Path gotcha: config is the file ~/.pi/agent/lsp.json. Runtime state lives under the directory ~/.pi/agent/lsp/.

Runtime paths

Do not edit these by hand unless you are debugging or cleaning up state:

Path Purpose
~/.pi/agent/lsp/packages/ Pi-managed language-server installs
~/.pi/agent/lsp/bin/ Pi-managed executable links
~/.pi/agent/lsp/lsp.lock.json Resolved managed install metadata
~/.pi/agent/lsp/logs/ Language-server logs
~/.pi/agent/lsp/pids/ Per-session process registries used for lifecycle cleanup
~/.pi/agent/lsp/workspaces/ Per-server workspaces, for example JDT LS data directories
~/.pi/agent/lsp/cache/ Runtime caches owned by the extension

Do not put extension source code under ~/.pi/agent/lsp/; that directory is only for runtime state.

Top-level config fields

Field Default Behavior
installMode "prompt" Missing servers are installed only when explicitly requested or interactively confirmed.
warmup true Pi read calls for supported source files start matching installed servers in the background.
servers {} Per-server overrides merged into the built-in catalog.

installMode can be:

Mode Behavior
prompt Install only when explicitly requested or interactively confirmed.
auto Install missing servers automatically when an LSP tool needs them.
off Never install automatically. Use system commands or explicit installs only.

warmup never prompts and never installs missing servers. It only prepares already-installed servers after Pi reads a matching source file. lazy still means servers do not start at session startup.

Common config snippets

Disable read warmup:

{
  "warmup": false
}

Auto-install missing servers on first explicit LSP tool use:

{
  "installMode": "auto"
}

Tune Pyright analysis from project config without changing executables:

{
  "servers": {
    "pyright": {
      "settings": {
        "python": {
          "analysis": {
            "diagnosticMode": "workspace"
          }
        }
      }
    }
  }
}

Server override fields

A server definition can include:

Field Description
displayName Human-readable name shown in status output
filetypes Filetypes handled by the server
rootMarkers Files/directories used to detect the project root
install Managed install spec: npm, go, github, or system
command Command used to start the language server
cwd Working directory for the server command
env Environment overrides; supports $env:VAR references
settings LSP settings returned through workspace/configuration
initializationOptions LSP initialization options
lazy Whether the server should avoid session-start eager startup

command supports placeholders such as {installBin}, {installDir}, {platform}, and {workspaceDir}. Relative path-like values are resolved from the detected project root, and ~ is expanded.

Project config is intentionally conservative. In untrusted projects, executable and install overrides are ignored; put those in global config.

Using existing LSP binaries

You do not need Pi to manage every language server. If you already have servers installed through Mason.nvim, your distro, Nix, mise/asdf, or another tool, point pi-lsp-adapter at those binaries with global config.

Prefer global config for executable overrides:

~/.pi/agent/lsp.json

Example: use Mason.nvim's Pyright:

{
  "servers": {
    "pyright": {
      "install": {
        "type": "system",
        "command": ["~/.local/share/nvim/mason/bin/pyright-langserver", "--stdio"]
      }
    }
  }
}

For type: "system", command is inferred from install.command when omitted.

Example: use a binary already on PATH:

{
  "servers": {
    "gopls": {
      "install": {
        "type": "system",
        "command": ["gopls"]
      }
    }
  }
}

Managed install overrides

Use install without type: "system" when you want Pi to own the install lifecycle. In prompt mode, the server is still missing until you run /lsp install <serverId>. The resolved install metadata is then written to ~/.pi/agent/lsp/lsp.lock.json.

Pin a managed npm server version:

{
  "servers": {
    "pyright": {
      "install": {
        "type": "npm",
        "packages": {
          "pyright": "1.1.410"
        },
        "bin": "pyright-langserver"
      }
    }
  }
}

Pin a managed GitHub release server:

{
  "servers": {
    "rust-analyzer": {
      "install": {
        "type": "github",
        "repo": "rust-lang/rust-analyzer",
        "version": "2026-05-25",
        "asset": "rust-analyzer-{platform}.gz",
        "bin": "rust-analyzer"
      }
    }
  }
}

Add a custom GitHub-release server:

{
  "servers": {
    "example-ls": {
      "displayName": "Example Language Server",
      "filetypes": ["example"],
      "rootMarkers": [".git"],
      "install": {
        "type": "github",
        "repo": "example/example-ls",
        "version": "v1.2.3",
        "asset": "example-ls-linux-x64.tar.gz",
        "bin": "example-ls",
        "stripComponents": 1
      },
      "command": ["{installBin}/example-ls", "--stdio"],
      "settings": {},
      "initializationOptions": {},
      "lazy": true
    }
  }
}

Why configure this explicitly instead of symlinking Mason into Pi's managed directory?

  • Ownership stays clear: Mason owns Mason installs; Pi owns ~/.pi/agent/lsp/.
  • /lsp uninstall <serverId> will not remove external binaries.
  • /lsp doctor <serverId> can show the configured command directly.
  • Broken or stale symlinks are avoided.

Caching and pagination

List-like LSP tools return a first page instead of dumping every result into context. When more results exist, output includes a resultId:

Showing 1-25 of 143.
More available: call lsp_more with resultId: lspres_...

Fetch the next page with:

lsp_more({ resultId: "lspres_..." })

Cache behavior:

  • Only multi-page results are cached.
  • Pages are returned sequentially by lsp_more.
  • Result IDs are session-local and in memory only.
  • Cache budget defaults to 64 MB and 128 entries.
  • Entries expire after 15 minutes of inactivity.
  • Cache is cleared on reload, shutdown, and session replacement.
  • Tool result details contain only the shown page, not the full result set.

Commands

Command What it does
/lsp Open the interactive LSP panel, or show status in non-UI sessions
/lsp status Print status as text
/lsp doctor Show config warnings and server details
/lsp doctor <serverId> Show resolved details for one server
/lsp install <serverId[@version]> Install a managed language server
/lsp update <serverId[@version]> Update/reinstall one managed server
/lsp update --all Update all configured managed servers
/lsp uninstall <serverId> Remove Pi-managed install state and stop matching processes
/lsp start [serverId] Start one installed server or all installed servers
/lsp restart [serverId] Restart one installed server or all installed servers
/lsp stop [serverId] Stop tracked LSP processes

Interactive /lsp panel keys:

↑↓ select • enter doctor • i install • u update • x uninstall • s stop • r refresh • esc close

How it works

  • The extension adds read-only LSP tools and prompt guidance to Pi.
  • File requests are resolved by filetype and nearest project root marker.
  • Server definitions come from the built-in catalog plus global/project config.
  • Missing-server behavior follows installMode; read warmup never installs missing servers.
  • Servers start lazily over stdio and are tracked in a process registry.
  • When warmup is enabled, source-file reads can prepare installed servers in the background before an LSP tool call.
  • LSP responses are normalized into compact text plus structured details.
  • Diagnostics, locations, and symbols are sorted/paged before reaching the model.
  • Extra pages are stored in the in-memory result cache and fetched with lsp_more.
  • The Pi status line shows active LSP servers, total configured servers, and warning count.

Development

npm run format:check
npm run lint
npm run typecheck
npm test

For local development with Pi:

mkdir -p ~/.pi/agent/extensions
ln -sfnT "$PWD" ~/.pi/agent/extensions/pi-lsp-adapter
pi

Run /reload in Pi after editing the extension.

Release

Publishing is CI-driven. Do not create release tags by hand. To cut a release, make a normal commit that bumps package.json and package-lock.json; after that commit lands on main and CI passes, .github/workflows/publish.yml creates the matching semver tag and publishes to npm.

npm publishing uses Trusted Publishing for .github/workflows/publish.yml, so no npm token secret is required. The npm package must have a Trusted Publisher configured for nikmmd/pi-lsp-adapter and workflow filename publish.yml.

npm version 0.1.2 --no-git-tag-version
git add package.json package-lock.json
git commit -m "chore: release v0.1.2"
git push origin main

Non-release pushes to main also trigger the publish workflow, but it exits without publishing when the current package.json version already exists on npm.

Stable versions such as 0.1.2 publish with the npm latest dist-tag. Prerelease versions such as 0.2.0-beta.1 publish with the npm next dist-tag.

Troubleshooting

server is not installed

Install it explicitly:

/lsp install <serverId>

Or set global config to auto-install:

{
  "installMode": "auto"
}

Check the resolved command

/lsp doctor <serverId>

This is the quickest way to see the final command, root markers, install state, and config warnings for one server.

Reuse Mason.nvim servers

Configure the server as type: "system" in ~/.pi/agent/lsp.json and point command at the Mason binary. Pi will use it but will not manage or uninstall it.

Start fresh for one server

/lsp stop <serverId>
/lsp uninstall <serverId>
/lsp install <serverId>

This only removes Pi-managed install state. It does not remove external system/Mason binaries configured with type: "system".

Limitations

  • Tools are read-only. Rename, code action, and formatting support are intentionally not exposed yet.
  • A language server must support the requested LSP capability for the matching tool to return data.
  • First explicit LSP use can still be slower while a server starts, initializes, or installs, especially when warmup is disabled or the server was not installed when the file was read.
  • lsp_workspace_symbols searches active clients by default; pass serverId to start/query a specific configured server.
  • Pagination result IDs are not persistent and can expire. Re-run the original LSP query if lsp_more says the cached result is gone.

License

MIT © 2026 pi-lsp-adapter developers