Skip to content

nexaedge/project-lsp

vv4

Go, Python, Rust and TypeScript code intelligence for Claude Code (go to definition, find references, symbols, hover, diagnostics) that runs one language server per project, so files of other repositories and git worktrees resolve their own imports. Each server starts in its project through mise, with that project's tool versions and virtualenv, and TypeScript 7 projects get the server TypeScript 7 ships.

project-lsp

Code intelligence for Claude Code in Go, Python, Rust, TypeScript and JavaScript (go to definition, find references, document and workspace symbols, hover, call hierarchy, diagnostics after each edit) that stays right when a session works on files of several repositories or git worktrees.

Claude Code starts one language server per language for the whole session, rooted at the folder the session started in. A file of another repository, or of a worktree under .claude/worktrees/, then reads as outside that server's project: its imports fail to resolve and every edit comes back with diagnostics that are not true. project-lsp stands in front of the real server and runs one per project instead.

How it routes

  • The project of a file is the git worktree that holds it, the folder git rev-parse --show-toplevel names from the file's directory. A file outside git belongs to its own directory.
  • Each project gets its own server, started the first time one of its files is opened, through mise exec -C <project> -- <server> when mise is on PATH. The server then sees the project's own tool versions, environment and virtualenv, as a shell opened in that folder would.
  • A message about a file (open, change, definition, references, hover, call hierarchy) goes to the server of the file's project, and the diagnostics of that server come back as they are.
  • A Python script that declares its own dependencies in a PEP 723 block (# /// script, the header uv run --script reads) goes to a ty server of its project instead of pyright. ty creates and updates the script's environment from that block through uv, so the script's imports resolve against the dependencies it declares.
  • A workspace symbol search goes to one server per project, and the answer joins theirs. A project's pyright also finds the symbols of its scripts, so the ty of a project answers only while no pyright runs there.
  • Anything else goes to the server of the project the session started in, whose answer to initialize is the one Claude Code sees.
LanguageServerExtensions
Gogopls.go
Pythonpyright-langserver --stdio, and ty server for a script with a PEP 723 block.py, .pyi
Rustrust-analyzer.rs
TypeScript, JavaScripttsc --lsp --stdio from the project's node_modules on TypeScript 7 or later, typescript-language-server --stdio before that.ts, .tsx, .js, .jsx, .mts, .cts, .mjs, .cjs

TypeScript 7 carries its own language server and no longer ships the tsserver.js that typescript-language-server drives, which is why the TypeScript server is picked per project.

Installation

claude plugin install project-lsp@nexaedge-marketplace

Each server has to be on PATH, or installed by mise. bin/project-lsp runs through uv run --script, so uv has to be on PATH too, at 0.12.3 or later for ty to build the environment of a script.

Claude Code runs one language server per file extension, so disable the plugins this one replaces before enabling it: gopls-lsp, pyright-lsp, rust-analyzer-lsp and typescript-lsp from claude-plugins-official.

Tests

uv run --no-project --python 3.12 --with pytest pytest plugins/project-lsp/tests

They drive bin/project-lsp over stdio against tests/fake_server.py, a server whose answers say which folder it runs in.