Claude Cursor opencode Skill

lsp-setup

Configure a Language Server (LSP) for a specific language so editor/agent tooling — diagnostics, go-to-definition, find-references, rename — works. Use when you need to: configure LSP, lsp setup, set up or install a language server, fix 'no LSP server configured' / 'server not in

LLM Mart · 0 points · 14 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download code-yeongyu-oh-my-openagent-packages_shared-skills_skills_lsp-setup-05dcba6.zip · 32 KB
Part of code-yeongyu/oh-my-openagent — 51 skills

Install

skills CLI npx skills add https://github.com/code-yeongyu/oh-my-openagent/tree/dev/packages/shared-skills/skills/lsp-setup
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install code-yeongyu-oh-my-openagent@llmmart
Git git clone https://github.com/code-yeongyu/oh-my-openagent.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole code-yeongyu/oh-my-openagent collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

LSP Setup

Configure the right Language Server for a project so the lsp MCP tools (diagnostics, goto_definition, find_references, symbols, rename) actually work. This skill is an index: detect what a project needs, install the server, write the config, then verify with a real roundtrip.

The list of servers we ship as builtin is the source of truth in packages/lsp-tools-mcp/src/lsp/server-definitions.ts (BUILTIN_SERVERS + LSP_INSTALL_HINTS). The per-language references below mirror it.


PHASE 0 — LANGUAGE GATE (run first)

Identify the language from the file extension, then read the matching reference before installing or configuring anything.

Extension(s) Reference
.ts .tsx .js .jsx .mjs .cjs .mts .cts .vue .svelte .astro references/typescript/README.md
.py .pyi references/python/README.md
.go references/go/README.md
.rs references/rust/README.md
.c .cpp .cc .cxx .h .hpp .hh .hxx references/c-cpp/README.md
.java references/java/README.md
.kt .kts references/kotlin/README.md
.cs .razor .cshtml references/csharp/README.md
.swift references/swift/README.md
.rb .rake .gemspec .ru references/ruby/README.md
.php references/php/README.md
.dart references/dart/README.md
.ex .exs references/elixir/README.md
.zig .zon references/zig/README.md
.lua references/lua/README.md
.sh .bash .zsh .ksh references/bash/README.md
.yaml .yml references/yaml/README.md
.tf .tfvars references/terraform/README.md
.hs .lhs references/haskell/README.md
.jl references/julia/README.md

WORKFLOW — detect → install → configure → verify

1. Detect

Scan the project to see which languages are present and whether each server is installed and configured:

bun scripts/detect-lsp.ts <projectDir>      # human report (default: cwd)
bun scripts/detect-lsp.ts <projectDir> --json

For each detected language it prints the builtin server id, the executable it needs on PATH, whether that executable is installed, an install hint, and whether a project config file already references it.

2. Install

Open references/<language>/README.md and run the install command for your OS. Then confirm the executable resolves:

command -v <server-executable>   # e.g. typescript-language-server, gopls, rust-analyzer

3. Configure

Most builtin servers need no config — they are resolved automatically by file extension. Write config only to: pick between competing servers, set a priority, pass initialization options, override extensions, set env, or disable a server.

Two project-scoped config files, identical JSON shape:

  • Codex harness → .codex/lsp-client.json (user: ~/.codex/lsp-client.json)
  • OpenCode/omo harness → .opencode/lsp.json (also .omo/lsp.json)
{
  "lsp": {
    "<server-id>": {
      "command": ["<bin>", "<args>"],   // optional for builtin ids (supplied automatically)
      "extensions": [".ext"],            // optional override
      "priority": 100,                    // higher wins when several servers match an extension
      "initialization": { },              // server-specific initializationOptions
      "env": { "KEY": "value" },          // optional
      "disabled": false                   // set true to turn a server off
    }
  }
}

Rules enforced by config-loader.ts:

  • In a project config (.codex/lsp-client.json, .opencode/lsp.json) an entry whose id is a builtin server inherits command automatically — you only override extensions / priority / initialization. A non-builtin id in a project config is ignored.
  • To define a fully custom (non-builtin) server with its own command, put it in the user config (~/.codex/lsp-client.json, or the path set by LSP_TOOLS_MCP_USER_CONFIG), where command + extensions are honored.
  • Project entries win over user entries; both win over builtin defaults.

Each language reference gives a ready-to-paste snippet.

4. Verify

Run a real diagnostics roundtrip against a source file. This spawns the server, opens the file, requests diagnostics, and reports OK/FAIL:

bun scripts/verify-lsp.ts <path/to/file.ext>
bun scripts/verify-lsp.ts <file> --timeout=90000

OK = the server started and answered. FAIL: language server not installed = go back to step 2. Other FAIL text carries the server/startup error. SKIP = the engine source could not be located; run from inside the omo repo/worktree, or call the lsp MCP diagnostics tool directly.


Scripts

Script Purpose
scripts/detect-lsp.ts Scan a directory; per detected language report server id, install status, install hint, config status. --json for machine output.
scripts/verify-lsp.ts Real LSP diagnostics roundtrip for one file via the lsp-tools-mcp engine; OK/FAIL/SKIP + exit code 0/1/3.
scripts/lsp-server-table.ts Embedded snapshot of the primary builtin server per language (mirrors server-definitions.ts).

Run with Bun: curl -fsSL https://bun.sh/install | bash.

Files (oh-my-openagent)
  • references
    • bash
      • README.md 2.1 KB
        # Bash — LSP setup
        
        - **Builtin server:** `bash` — `bash-language-server start`
        - **Extensions:** `.sh .bash .zsh .ksh`
        - **Install hint:** `npm install -g bash-language-server`
        
        An alias id `bash-ls` exists with the identical command; either id works.
        
        ## Install
        
        - **macOS:** `npm install -g bash-language-server`
        - **Linux:** `npm install -g bash-language-server`
        - **Windows:** `npm install -g bash-language-server` (PowerShell)
        
        For real diagnostics, also install `shellcheck`:
        
        - **macOS:** `brew install shellcheck`
        - **Linux:** `apt install shellcheck` (or `dnf install ShellCheck`)
        - **Windows:** `scoop install shellcheck`
        
        Confirm it resolves:
        
        ```bash
        command -v bash-language-server
        command -v shellcheck
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "bash": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        None commonly required. `bash-language-server` discovers `shellcheck` on PATH automatically. To point at a non-PATH binary, export `SHELLCHECK_PATH` via `env`:
        
        ```json
        { "lsp": { "bash": { "env": { "SHELLCHECK_PATH": "/opt/bin/shellcheck" } } } }
        ```
        
        ## Alternatives
        
        - `shellcheck` standalone as a linter-only flow (no LSP).
        - `shfmt` for formatting (complements, does not replace, the LSP).
        
        ## Troubleshooting
        - **PATH:** `bash-language-server` on PATH; reopen shell after `npm -g` install.
        - **No diagnostics:** `shellcheck` missing — diagnostics are powered by it; install and reopen.
        - **Wrong shell dialect:** `.zsh`/`.ksh` are linted as bash; shellcheck may flag shell-specific syntax.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/file.sh
        ```
        
    • c-cpp
      • README.md 2.4 KB
        # C / C++ — LSP setup
        
        - **Builtin server:** `clangd` — `clangd --background-index --clang-tidy`
        - **Extensions:** `.c .cpp .cc .cxx .c++ .h .hpp .hh .hxx .h++`
        - **Install hint:** `https://clangd.llvm.org/installation`
        
        ## Install
        
        - **macOS:** `brew install llvm` (clangd ships in the LLVM keg; add its `bin` to PATH)
        - **Linux:** `apt install clangd` (Debian/Ubuntu); use your distro package elsewhere
        - **Windows:** install LLVM from `https://releases.llvm.org` or `winget install LLVM.LLVM`
        
        See `https://clangd.llvm.org/installation` for other platforms.
        
        Confirm it resolves:
        
        ```bash
        command -v clangd
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "clangd": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        None commonly required. clangd reads flags from a project `.clangd` file rather
        than initializationOptions. The builtin command already passes
        `--background-index --clang-tidy`.
        
        ## Compile commands
        
        clangd needs a `compile_commands.json` at the project root (or in `build/`) for
        accurate diagnostics and cross-file navigation. Generate it with:
        
        - **CMake:** `cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON` (symlink/copy `build/compile_commands.json` to the root)
        - **Make / other:** `bear -- make`
        
        Without it, clangd falls back to heuristic flags and reports spurious errors.
        
        ## Alternatives
        
        None builtin. `ccls` exists as a third-party server but is not builtin — it would
        need a custom `command` in the USER config.
        
        ## Troubleshooting
        - **PATH:** `clangd` must be on PATH; reopen shell after install. Homebrew LLVM is keg-only — add `$(brew --prefix llvm)/bin` to PATH.
        - **Spurious "file not found" / unknown flags:** missing or stale `compile_commands.json` — regenerate it after changing the build.
        - **Header-only diagnostics wrong:** ensure the header's translation unit appears in the compile database, or add a `.clangd` `CompileFlags` block.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/file.cpp
        ```
        
    • csharp
      • README.md 2.4 KB
        # C# — LSP setup
        
        - **Builtin server:** `csharp` — `csharp-ls`
        - **Extensions:** `.cs`
        - **Install hint:** `dotnet tool install -g csharp-ls`
        
        ## Install
        
        Requires the **.NET SDK**. Install the tool globally:
        
        - **macOS:** `dotnet tool install -g csharp-ls`
        - **Linux:** `dotnet tool install -g csharp-ls`
        - **Windows:** `dotnet tool install -g csharp-ls`
        
        Global .NET tools land in `~/.dotnet/tools` — ensure that directory is on PATH (Windows: `%USERPROFILE%\.dotnet\tools`).
        
        Confirm it resolves:
        
        ```bash
        command -v csharp-ls
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "csharp": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        None commonly required. `csharp-ls` picks up the nearest `.sln` or `.csproj`; keep the solution restorable (`dotnet restore`).
        
        ## Razor / Blazor
        
        Razor and Blazor files use a separate builtin server:
        
        - **Builtin server:** `razor` — `roslyn-language-server --stdio`
        - **Extensions:** `.razor .cshtml`
        - **Install hint:** `dotnet tool install -g roslyn-language-server --prerelease` (requires **v5.8.0+**; see [dotnet/razor](https://github.com/dotnet/razor))
        
        ```bash
        dotnet tool install -g roslyn-language-server --prerelease
        command -v roslyn-language-server
        ```
        
        Enable it in a project/user config:
        
        ```json
        { "lsp": { "razor": { } } }
        ```
        
        ## Alternatives
        
        - **OmniSharp** — legacy C# language server (not builtin). Still works but is being superseded by the Roslyn-based servers; prefer `csharp-ls` / `roslyn-language-server`.
        
        ## Troubleshooting
        
        - **PATH:** `csharp-ls` / `roslyn-language-server` on PATH (`~/.dotnet/tools`); reopen shell after install.
        - **No .NET SDK:** install the SDK (not just the runtime) before installing the tool.
        - **No symbols:** run `dotnet restore`; an unrestored solution yields empty results.
        - **Razor needs v5.8.0+:** older `roslyn-language-server` builds lack the `--stdio` Razor support — install with `--prerelease`.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/File.cs
        ```
        
    • dart
      • README.md 1.9 KB
        # Dart — LSP setup
        
        - **Builtin server:** `dart` — `dart language-server --lsp`
        - **Extensions:** `.dart`
        - **Install hint:** `Included with the Dart/Flutter SDK`
        
        ## Install
        
        The language server ships inside the Dart SDK (and the Flutter SDK, which bundles Dart). There is no separate package to install — just put `dart` (or `flutter`) on PATH.
        
        - **macOS:** `brew install dart` (or install Flutter and use its bundled `dart`)
        - **Linux:** install the Dart SDK from your package manager / `https://dart.dev/get-dart`, or install Flutter
        - **Windows:** install the Dart SDK or Flutter SDK and add its `bin` to PATH
        
        Confirm it resolves:
        
        ```bash
        command -v dart
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "dart": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        None commonly required.
        
        ## Alternatives
        
        None.
        
        ## Troubleshooting
        - **PATH:** `dart` must be on PATH; reopen the shell after installing the SDK. Flutter users: ensure `<flutter>/bin/cache/dart-sdk/bin` or the Flutter `bin` is exported.
        - **Flutter vs Dart:** if you only have Flutter installed, the bundled `dart` works — make sure Flutter's `bin` is on PATH rather than relying on a separate Dart install.
        - **SDK out of date:** run `dart --version` / `flutter upgrade` if analysis behaves oddly on newer language features.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/file.dart
        ```
        
    • elixir
      • README.md 2.1 KB
        # Elixir — LSP setup
        
        - **Builtin server:** `elixir-ls` — `elixir-ls`
        - **Extensions:** `.ex .exs`
        - **Install hint:** `https://github.com/elixir-lsp/elixir-ls`
        
        ## Install
        
        ElixirLS needs Erlang/OTP and Elixir installed first. Build the release from
        `https://github.com/elixir-lsp/elixir-ls` and put the `elixir-ls` launcher script on PATH.
        
        - **macOS:** `brew install elixir-ls` (Homebrew provides the launcher), or build the release manually
        - **Linux:** clone elixir-ls, run `mix deps.get && mix compile && mix elixir_ls.release2 -o release`, then add `release/` to PATH
        - **Windows:** build the release and add the `release` dir (use the `.bat` launcher) to PATH
        
        Confirm it resolves:
        
        ```bash
        command -v elixir-ls
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "elixir-ls": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        None commonly required.
        
        ## Alternatives
        
        - **lexical** (not builtin): `lexical` — fast, modern alternative LSP.
        - **next-ls** (not builtin): `nextls --stdio` — from the elixir-tools project.
        
        ## Troubleshooting
        - **PATH:** `elixir-ls` must be on PATH; reopen the shell after install.
        - **asdf users:** the launcher is a shim — after `asdf install`, run `asdf reshim elixir` so the `elixir-ls` shim resolves, and ensure the Erlang/Elixir versions match the build.
        - **First start is slow:** ElixirLS compiles your deps on first run; initial diagnostics can take a while on large projects.
        - **OTP mismatch:** build elixir-ls with the same Erlang/Elixir versions you use for the project to avoid bytecode errors.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/file.ex
        ```
        
    • go
      • README.md 1.9 KB
        # Go — LSP setup
        
        - **Builtin server:** `gopls` — `gopls`
        - **Extensions:** `.go`
        - **Install hint:** `go install golang.org/x/tools/gopls@latest`
        
        ## Install
        
        - **macOS:** `go install golang.org/x/tools/gopls@latest` (or `brew install gopls`)
        - **Linux:** `go install golang.org/x/tools/gopls@latest`
        - **Windows:** `go install golang.org/x/tools/gopls@latest`
        
        Requires the Go toolchain. `go install` drops the binary in `$GOPATH/bin`
        (default `~/go/bin`) — that directory must be on PATH.
        
        ```bash
        export PATH="$PATH:$(go env GOPATH)/bin"
        ```
        
        Confirm it resolves:
        
        ```bash
        command -v gopls
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "gopls": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        None commonly required. To enable extra analyses or staticcheck:
        
        ```json
        { "lsp": { "gopls": { "initialization": { "staticcheck": true } } } }
        ```
        
        ## Alternatives
        
        None — `gopls` is the official and de facto sole Go language server.
        
        ## Troubleshooting
        - **PATH:** `gopls` must be on PATH; ensure `$(go env GOPATH)/bin` is exported, then reopen the shell.
        - **No diagnostics / "no required module":** open the directory containing `go.mod` as the workspace root. Outside a module, gopls degrades. Run `go mod tidy` if dependencies are unresolved.
        - **Stale toolchain:** reinstall with `go install golang.org/x/tools/gopls@latest` after upgrading Go.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/file.go
        ```
        
    • haskell
      • README.md 2.3 KB
        # Haskell — LSP setup
        
        - **Builtin server:** `haskell-language-server` — `haskell-language-server-wrapper --lsp`
        - **Extensions:** `.hs .lhs`
        - **Install hint:** `ghcup install hls`
        
        The `-wrapper` binary detects your project's GHC version and dispatches to the matching HLS build.
        
        ## Install
        
        - **macOS:** `ghcup install hls` (install ghcup via `brew install ghcup` or the official script)
        - **Linux:** `ghcup install hls` (ghcup script from https://www.haskell.org/ghcup/)
        - **Windows:** `ghcup install hls` (ghcup is installed via the Windows installer / PowerShell bootstrap)
        
        HLS needs a working GHC plus Cabal and/or Stack. Install a matching toolchain first:
        
        ```bash
        ghcup install ghc
        ghcup install cabal
        ```
        
        Confirm it resolves:
        
        ```bash
        command -v haskell-language-server-wrapper
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "haskell-language-server": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        None commonly required. Per-project plugin/formatter settings normally live in a `hie.yaml` (cradle) and `.haskell-language-server` files rather than init options.
        
        ## Alternatives
        
        - `ghcide` (the core HLS engine, standalone) — largely superseded by HLS.
        - `hlint` standalone for lint-only checks; `ormolu`/`fourmolu` for formatting.
        
        ## Troubleshooting
        - **PATH:** `haskell-language-server-wrapper` on PATH; reopen shell after `ghcup install`.
        - **GHC mismatch:** the installed HLS must support your project's GHC version — run `ghcup install hls` for that GHC, or align GHC to a supported one.
        - **No cradle:** multi-package repos may need a `hie.yaml`; generate one with `gen-hie > hie.yaml`.
        - **Slow first load:** HLS compiles dependencies on first open; let it finish indexing.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/file.hs
        ```
        
    • java
      • README.md 2.6 KB
        # Java — LSP setup
        
        - **Builtin server:** `jdtls` — `jdtls`
        - **Extensions:** `.java`
        - **Install hint:** `https://github.com/eclipse-jdtls/eclipse.jdt.ls`
        
        ## Install
        
        - **macOS:** `brew install jdtls`
        - **Linux:** Download from [eclipse-jdtls/eclipse.jdt.ls](https://github.com/eclipse-jdtls/eclipse.jdt.ls) releases, extract, and wrap the launcher as `jdtls` on PATH (some distros package it as `jdtls`/`jdt-language-server`).
        - **Windows:** Download the release archive and add the `jdtls` launcher (`bin/jdtls.bat` or the Python wrapper) to PATH.
        
        Requires a **JDK 17+** to run the language server itself (the project may target an older Java version).
        
        Confirm it resolves:
        
        ```bash
        command -v jdtls
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "jdtls": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        jdtls maintains a per-project **workspace data directory** and the **first index is slow** (it resolves the full classpath and builds). Point `JAVA_HOME` at a JDK 17+ if `jdtls` cannot find one:
        
        ```json
        { "lsp": { "jdtls": { "env": { "JAVA_HOME": "/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home" } } } }
        ```
        
        Most settings (runtimes, format, import order) are passed via `settings.java.*` initialization options; defaults work for Maven/Gradle projects with a standard layout.
        
        ## Alternatives
        
        - **No mainstream alternative.** `jdtls` (Eclipse JDT Language Server) is the de-facto standard and powers the official VS Code Java extension.
        
        ## Troubleshooting
        
        - **PATH:** `jdtls` on PATH; reopen shell after install.
        - **No JDK found:** server exits immediately — set `JAVA_HOME` to a JDK 17+.
        - **Slow / no completions at first:** the initial classpath index can take a minute or more on large Maven/Gradle projects; wait for it to finish.
        - **Stale state:** delete the jdtls workspace data dir to force a clean re-index if results go wrong after big dependency changes.
        - **Build tool required:** keep `pom.xml` / `build.gradle` valid; a broken build descriptor breaks symbol resolution.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/File.java
        ```
        
    • julia
      • README.md 2.3 KB
        # Julia — LSP setup
        
        - **Builtin server:** `julials` — `julia --startup-file=no --history-file=no -e using LanguageServer; runserver()`
        - **Extensions:** `.jl`
        - **Install hint:** `julia -e 'using Pkg; Pkg.add("LanguageServer")'`
        
        The PATH executable is `julia`; LanguageServer.jl is launched through the `-e` snippet, not as its own binary.
        
        ## Install
        
        Install Julia (juliaup recommended), then add the `LanguageServer` package:
        
        - **macOS:** `brew install juliaup && juliaup add release`
        - **Linux:** `curl -fsSL https://install.julialang.org | sh` (installs juliaup)
        - **Windows:** `winget install julia -s msstore` (installs juliaup)
        
        Then add the package — ideally into a shared `@lsp` environment so it is not tied to one project:
        
        ```bash
        julia --project=@lsp -e 'using Pkg; Pkg.add("LanguageServer")'
        ```
        
        Confirm Julia resolves (the LSP binary IS `julia`):
        
        ```bash
        command -v julia
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "julials": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        None commonly required. To pin which environment hosts LanguageServer.jl, set `JULIA_PROJECT` (or `JULIA_DEPOT_PATH`) via `env`:
        
        ```json
        { "lsp": { "julials": { "env": { "JULIA_PROJECT": "@lsp" } } } }
        ```
        
        ## Alternatives
        
        - The VS Code Julia extension bundles the same LanguageServer.jl server.
        
        ## Troubleshooting
        - **PATH:** `julia` on PATH (not a `julials` binary); reopen shell after juliaup install.
        - **First run precompiles — be patient:** the initial launch compiles LanguageServer.jl and may take minutes with no output; do not kill it. Subsequent starts are fast.
        - **Package not found:** `LanguageServer` must be installed in the environment the server runs in (e.g. `@lsp`); add it there and set `JULIA_PROJECT`.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/file.jl
        ```
        
    • kotlin
      • README.md 2.4 KB
        # Kotlin — LSP setup
        
        - **Builtin server:** `kotlin-ls` — `kotlin-lsp`
        - **Extensions:** `.kt .kts`
        - **Install hint:** `https://github.com/Kotlin/kotlin-lsp`
        
        ## Install
        
        The official **JetBrains Kotlin LSP** is pre-release. Download a build from the [Kotlin/kotlin-lsp](https://github.com/Kotlin/kotlin-lsp) releases and put the `kotlin-lsp` launcher on PATH.
        
        - **macOS:** Download the release archive, extract, then symlink the launcher: `ln -s /path/to/kotlin-lsp/kotlin-lsp.sh /usr/local/bin/kotlin-lsp`
        - **Linux:** Same as macOS — extract the release and place/symlink `kotlin-lsp` on PATH.
        - **Windows:** Extract the release and add the directory containing `kotlin-lsp.bat` to PATH (invoke as `kotlin-lsp`).
        
        Requires a **JDK** on the machine to run the server.
        
        Confirm it resolves:
        
        ```bash
        command -v kotlin-lsp
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "kotlin-ls": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        None commonly required. If `kotlin-lsp` cannot find a Java runtime, set `JAVA_HOME`:
        
        ```json
        { "lsp": { "kotlin-ls": { "env": { "JAVA_HOME": "/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home" } } } }
        ```
        
        The server resolves classpath from Gradle/Maven; keep the build descriptor importable.
        
        ## Alternatives
        
        - **`fwcd/kotlin-language-server`** — older community server (not builtin). Still usable but less actively maintained than the official JetBrains one.
        
        ## Troubleshooting
        
        - **PATH:** `kotlin-lsp` on PATH; reopen shell after install.
        - **Pre-release churn:** the JetBrains server is early; pin a known-good release and expect occasional breakage.
        - **No JDK:** server fails to start — install a JDK and/or set `JAVA_HOME`.
        - **Slow first import:** Gradle resolution on first open can be slow on large projects; let it complete.
        - **`.kts` scripts:** build/script files resolve more slowly than `.kt` sources; this is expected.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/File.kt
        ```
        
    • lua
      • README.md 2 KB
        # Lua — LSP setup
        
        - **Builtin server:** `lua-ls` — `lua-language-server`
        - **Extensions:** `.lua`
        - **Install hint:** `https://github.com/LuaLS/lua-language-server`
        
        ## Install
        
        See `https://github.com/LuaLS/lua-language-server`.
        
        - **macOS:** `brew install lua-language-server`
        - **Linux:** download a release from GitHub, or `pacman -S lua-language-server` (Arch) / AUR
        - **Windows:** download a release from the GitHub releases page and add its `bin` to PATH
        
        Confirm it resolves:
        
        ```bash
        command -v lua-language-server
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "lua-ls": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        For Neovim config development, point the server at the Neovim runtime and set the Lua runtime version so `vim` globals and stdlib resolve:
        
        ```json
        {
          "lsp": {
            "lua-ls": {
              "initialization": {
                "Lua": {
                  "runtime": { "version": "LuaJIT" },
                  "workspace": {
                    "library": ["/usr/share/nvim/runtime/lua"]
                  },
                  "diagnostics": { "globals": ["vim"] }
                }
              }
            }
          }
        }
        ```
        
        ## Alternatives
        
        None.
        
        ## Troubleshooting
        - **PATH:** `lua-language-server` must be on PATH; reopen the shell after install.
        - **Undefined `vim` global:** add `vim` to `Lua.diagnostics.globals` and set `Lua.workspace.library` (see above) for Neovim work.
        - **Wrong runtime version:** set `Lua.runtime.version` (`LuaJIT`, `Lua 5.4`, etc.) to match your interpreter, or stdlib functions report as undefined.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/file.lua
        ```
        
    • php
      • README.md 1.9 KB
        # PHP — LSP setup
        
        - **Builtin server:** `php` — `intelephense --stdio`
        - **Extensions:** `.php`
        - **Install hint:** `npm install -g intelephense`
        
        ## Install
        
        Intelephense is a Node package, so Node.js (and npm) must be installed first.
        
        - **macOS:** `npm install -g intelephense`
        - **Linux:** `npm install -g intelephense`
        - **Windows:** `npm install -g intelephense`
        
        Confirm it resolves:
        
        ```bash
        command -v intelephense
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "php": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        Intelephense's premium features (rename, find-all-implementations, declaration providers, etc.) require a licence key. Supply it via `initialization`:
        
        ```json
        {
          "lsp": {
            "php": {
              "initialization": {
                "licenceKey": "YOUR-LICENCE-KEY"
              }
            }
          }
        }
        ```
        
        Without a key the server runs fine in free mode.
        
        ## Alternatives
        
        - **phpactor** (not builtin): `phpactor language-server`. Pure-PHP, no Node dependency.
        
        ## Troubleshooting
        - **PATH:** `intelephense` must be on PATH; reopen the shell after a global npm install. If missing, check `npm bin -g` is on PATH.
        - **No Node:** Intelephense fails to start without Node.js. Install Node, then reinstall.
        - **Wrong PHP version inference:** set `intelephense.environment.phpVersion` via `initialization` to match your project.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/file.php
        ```
        
    • python
      • README.md 2.9 KB
        # Python — LSP setup
        
        - **Builtin server:** `basedpyright` — `basedpyright-langserver --stdio`
        - **Extensions:** `.py .pyi`
        - **Install hint:** `pip install basedpyright`
        
        ## Install
        
        - **macOS:** `pip install basedpyright` (or `uv tool install basedpyright`)
        - **Linux:** `pip install basedpyright` (or `uv tool install basedpyright`)
        - **Windows:** `pip install basedpyright`
        
        Prefer `uv tool install basedpyright` when the project uses uv — it keeps the
        server isolated from project venvs and always on PATH.
        
        Confirm it resolves:
        
        ```bash
        command -v basedpyright-langserver
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "basedpyright": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        None commonly required. Type-check mode is usually set via `pyrightconfig.json`
        or `[tool.basedpyright]` in `pyproject.toml`, not the LSP config.
        
        ## Choosing a server
        
        All four are builtin. Type checkers and the linter serve different roles — run a
        type server, and optionally `ruff` ALONGSIDE it (not instead).
        
        | id            | command                       | install                | role                                    |
        | ------------- | ----------------------------- | ---------------------- | --------------------------------------- |
        | `basedpyright`| `basedpyright-langserver --stdio` | `pip install basedpyright` | strictest types, **default**       |
        | `pyright`     | `pyright-langserver --stdio`  | `pip install pyright`  | upstream Microsoft type checker         |
        | `ty`          | `ty server`                   | `pip install ty`       | Astral, very fast, pre-1.0/experimental |
        | `ruff`        | `ruff server`                 | `pip install ruff`     | lint + format only, complements a type server |
        
        Recommended priority: **basedpyright** (default) > pyright > ty (experimental).
        `ruff` complements via priority — it does not type-check, so keep a type server
        enabled.
        
        Enable ruff alongside basedpyright, disabling pyright:
        
        ```json
        { "lsp": {
          "basedpyright": { "priority": 100 },
          "ruff": { "priority": 90 },
          "pyright": { "disabled": true }
        } }
        ```
        
        ## Troubleshooting
        - **PATH:** `basedpyright-langserver` must be on PATH; reopen shell after install. `uv tool install` writes to `~/.local/bin`.
        - **Wrong interpreter / missing imports:** the server must see the project venv. Set `python.pythonPath` / `venvPath` in `pyrightconfig.json`, or activate the venv before launching.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/file.py
        ```
        
    • ruby
      • README.md 2.3 KB
        # Ruby — LSP setup
        
        - **Builtin server:** `ruby-lsp` — `rubocop --lsp`
        - **Extensions:** `.rb .rake .gemspec .ru`
        - **Install hint:** `gem install ruby-lsp`
        
        > **Note:** the builtin id is `ruby-lsp`, but the executable actually invoked is **`rubocop`** (`rubocop --lsp`). RuboCop must be installed: `gem install rubocop`.
        
        ## Install
        
        - **macOS:** `gem install rubocop` (and `gem install ruby-lsp` for the install hint's gem)
        - **Linux:** `gem install rubocop`
        - **Windows:** `gem install rubocop`
        
        In a Bundler project, prefer adding `rubocop` to the `Gemfile` and running via `bundle exec`.
        
        Confirm it resolves (check `rubocop`, since that is what runs):
        
        ```bash
        command -v rubocop
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "ruby-lsp": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        None commonly required. Behavior is driven by your `.rubocop.yml`; the server surfaces RuboCop diagnostics, formatting, and code actions over LSP.
        
        ## Alternatives
        
        - **`ruby-lsp` gem server** (not builtin) — the standalone Shopify Ruby LSP binary (`ruby-lsp` executable), richer navigation than RuboCop alone. Configure as a custom server with `command: ["ruby-lsp"]` in the USER config.
        - **`solargraph`** (not builtin) — older completion/type server; install with `gem install solargraph`, custom `command: ["solargraph", "stdio"]`.
        
        ## Troubleshooting
        
        - **PATH:** `rubocop` on PATH (that is the invoked binary, not `ruby-lsp`); reopen shell after install.
        - **`rubocop` not found:** the builtin fails even if the `ruby-lsp` gem is installed — install RuboCop with `gem install rubocop`.
        - **Bundler mismatch:** if the project pins RuboCop in its `Gemfile`, run inside the bundle so versions match.
        - **No diagnostics:** check `.rubocop.yml` is valid and not disabling everything.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/file.rb
        ```
        
    • rust
      • README.md 2 KB
        # Rust — LSP setup
        
        - **Builtin server:** `rust` — `rust-analyzer`
        - **Extensions:** `.rs`
        - **Install hint:** `rustup component add rust-analyzer`
        
        ## Install
        
        - **macOS:** `rustup component add rust-analyzer` (or `brew install rust-analyzer`)
        - **Linux:** `rustup component add rust-analyzer`
        - **Windows:** `rustup component add rust-analyzer`
        
        The rustup component is the recommended path — it stays pinned to your toolchain.
        `rust-analyzer` also needs the `rust-src` component to index the standard library
        (`rustup component add rust-src`).
        
        Confirm it resolves:
        
        ```bash
        command -v rust-analyzer
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "rust": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        None commonly required. To switch the check command to clippy:
        
        ```json
        { "lsp": { "rust": { "initialization": { "check": { "command": "clippy" } } } } }
        ```
        
        ## Alternatives
        
        None — `rust-analyzer` is the official and sole Rust language server.
        
        ## Troubleshooting
        - **PATH:** `rust-analyzer` must be on PATH; reopen shell after install. The rustup shim lives in `~/.cargo/bin`.
        - **Exits while loading rust-src:** if rust-analyzer crashes during stdlib indexing, reinstall the source component:
        
          ```bash
          rustup component remove rust-src && rustup component add rust-src
          ```
        
        - **No proc-macro / build script support:** ensure the project builds with `cargo check`; rust-analyzer reuses the same toolchain.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/file.rs
        ```
        
    • swift
      • README.md 2.2 KB
        # Swift — LSP setup
        
        - **Builtin server:** `sourcekit-lsp` — `sourcekit-lsp`
        - **Extensions:** `.swift .objc .objcpp`
        - **Install hint:** `Included with Xcode or the Swift toolchain`
        
        ## Install
        
        `sourcekit-lsp` ships with the Swift toolchain — no separate install.
        
        - **macOS:** `xcode-select --install` (or install full Xcode). It resolves to the active toolchain selected by `xcode-select`.
        - **Linux:** Install a swift.org toolchain (`sourcekit-lsp` ships inside it); add the toolchain's `usr/bin` to PATH.
        - **Windows:** Install the swift.org Windows toolchain; `sourcekit-lsp` is bundled.
        
        Confirm it resolves:
        
        ```bash
        command -v sourcekit-lsp
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "sourcekit-lsp": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        None commonly required. For best results the project needs a **SwiftPM `Package.swift`** or a `compile_commands.json` compilation database so the server can resolve modules. Pure-Xcode-project files without these resolve poorly.
        
        ## Alternatives
        
        - **No mainstream alternative.** `sourcekit-lsp` is the official Apple/swift.org server and the only practical choice.
        
        ## Troubleshooting
        
        - **PATH:** `sourcekit-lsp` on PATH; reopen shell after install (or after `xcode-select -s`).
        - **Wrong toolchain (macOS):** point `xcode-select` at the right Xcode/toolchain; mismatches cause stale or missing results.
        - **No `Package.swift` / compile db:** add a SwiftPM manifest or generate `compile_commands.json` for accurate indexing.
        - **Objective-C (`.objc`/`.objcpp`):** needs a compilation database to resolve headers and frameworks.
        - **First build slow:** the server builds the module graph on first open; wait for it.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/File.swift
        ```
        
    • terraform
      • README.md 2.2 KB
        # Terraform — LSP setup
        
        - **Builtin server:** `terraform` — `terraform-ls serve`
        - **Extensions:** `.tf .tfvars`
        - **Install hint:** See https://github.com/hashicorp/terraform-ls
        
        An alias id `terraform-ls` exists with the identical command; either id works.
        
        ## Install
        
        - **macOS:** `brew install hashicorp/tap/terraform-ls`
        - **Linux:** download a release from https://github.com/hashicorp/terraform-ls/releases and place `terraform-ls` on PATH (or `apt`/`dnf` via the HashiCorp repo)
        - **Windows:** `choco install terraform-ls` (or download a release zip)
        
        `terraform-ls` requires the `terraform` binary itself to be installed and on PATH.
        
        Confirm both resolve:
        
        ```bash
        command -v terraform-ls
        command -v terraform
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "terraform": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        None commonly required. Provider/module completion comes from schemas generated by `terraform init`. Run it in each module root before expecting full completion:
        
        ```bash
        terraform init
        ```
        
        To force schema indexing, you can set experimental features via `initialization`:
        
        ```json
        { "lsp": { "terraform": { "initialization": { "experimentalFeatures": { "validateOnSave": true } } } } }
        ```
        
        ## Alternatives
        
        - `tflint` standalone for opinionated linting (complements the LSP).
        - `terraform fmt` for formatting.
        
        ## Troubleshooting
        - **PATH:** `terraform-ls` AND `terraform` both on PATH; reopen shell after install.
        - **No provider completion:** run `terraform init` so the `.terraform/` schema cache exists.
        - **`.tfvars` not analyzed:** open the containing module so the server has root context.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/file.tf
        ```
        
    • typescript
      • README.md 3.2 KB
        # TypeScript / JavaScript — LSP setup
        
        - **Builtin server:** `typescript` — `typescript-language-server --stdio`
        - **Extensions:** `.ts .tsx .js .jsx .mjs .cjs .mts .cts`
        - **Install hint:** `npm install -g typescript-language-server typescript`
        
        ## Install
        
        - **macOS:** `npm install -g typescript-language-server typescript`
        - **Linux:** `npm install -g typescript-language-server typescript`
        - **Windows:** `npm install -g typescript-language-server typescript` (PowerShell or cmd)
        
        `typescript-language-server` is only a thin wrapper — it needs the `typescript`
        package (`tsserver`) present too, either globally or in the project's
        `node_modules`. Always install both.
        
        Confirm it resolves:
        
        ```bash
        command -v typescript-language-server
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "typescript": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        None commonly required. To favor inlay hints or tweak preferences:
        
        ```json
        { "lsp": { "typescript": { "initialization": { "preferences": { "includeInlayParameterNameHints": "all" } } } } }
        ```
        
        ## Alternatives
        
        All builtin — pick by toolchain. Raise the alternative's `priority` and/or
        `disabled` the default so the file routes to your choice:
        
        | id           | command                                  | when to choose                          |
        | ------------ | ---------------------------------------- | --------------------------------------- |
        | `deno`       | `deno lsp`                               | Deno projects (handles `.ts/.tsx/.js`)  |
        | `biome`      | `biome lsp-proxy --stdio`                | Biome lint/format as the LSP            |
        | `eslint`     | `vscode-eslint-language-server --stdio`  | ESLint diagnostics (install below)      |
        | `oxlint`     | `oxlint --lsp`                           | fast Oxc-based linting                  |
        | `vue`        | `vue-language-server --stdio`            | `.vue` single-file components           |
        | `svelte`     | `svelteserver --stdio`                   | `.svelte` files                         |
        | `astro`      | `astro-ls --stdio`                       | `.astro` files                          |
        
        `eslint` install: `npm i -g vscode-langservers-extracted`.
        
        Pick Deno or Biome over the default:
        
        ```json
        { "lsp": {
          "typescript": { "disabled": true },
          "deno": { "priority": 100 }
        } }
        ```
        
        (Swap `"deno"` for `"biome"` to use Biome instead.)
        
        ## Troubleshooting
        - **PATH:** `typescript-language-server` must be on PATH; reopen shell after `npm i -g`. Check your global bin with `npm bin -g`.
        - **Missing tsserver:** errors like "Could not find tsserver" mean the `typescript` package is absent — install it globally or in the project.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/file.ts
        ```
        
    • yaml
      • README.md 2.3 KB
        # YAML — LSP setup
        
        - **Builtin server:** `yaml-ls` — `yaml-language-server --stdio`
        - **Extensions:** `.yaml .yml`
        - **Install hint:** `npm install -g yaml-language-server`
        
        ## Install
        
        - **macOS:** `npm install -g yaml-language-server`
        - **Linux:** `npm install -g yaml-language-server`
        - **Windows:** `npm install -g yaml-language-server` (PowerShell)
        
        Confirm it resolves:
        
        ```bash
        command -v yaml-language-server
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "yaml-ls": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        Schema association is the main reason to configure yaml-ls. Map globs to a schema URL or local path under `yaml.schemas`:
        
        ```json
        {
          "lsp": {
            "yaml-ls": {
              "initialization": {
                "yaml": {
                  "schemas": {
                    "https://json.schemastore.org/github-workflow.json": ".github/workflows/*.yml",
                    "https://json.schemastore.org/kustomization.json": "kustomization.yaml",
                    "./schemas/my-config.schema.json": "config/*.yaml"
                  },
                  "validate": true,
                  "completion": true,
                  "format": { "enable": true }
                }
              }
            }
          }
        }
        ```
        
        Set `"yaml.schemaStore": { "enable": true }` to auto-resolve schemas from SchemaStore (catalog at https://www.schemastore.org/). Inline `# yaml-language-server: $schema=<url>` modelines also work without config.
        
        ## Alternatives
        
        - `redhat.vscode-yaml` bundles the same server in editors.
        - `yamllint` standalone for style/lint-only checks.
        
        ## Troubleshooting
        - **PATH:** `yaml-language-server` on PATH; reopen shell after `npm -g` install.
        - **No validation:** no schema matched — add a `yaml.schemas` glob or a `$schema` modeline.
        - **Wrong schema applied:** SchemaStore guessed by filename; pin explicitly under `yaml.schemas`.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/file.yaml
        ```
        
    • zig
      • README.md 1.7 KB
        # Zig — LSP setup
        
        - **Builtin server:** `zls` — `zls`
        - **Extensions:** `.zig .zon`
        - **Install hint:** `https://github.com/zigtools/zls`
        
        ## Install
        
        ZLS (the Zig Language Server) must be built against the **same Zig version** you use.
        See `https://github.com/zigtools/zls`.
        
        - **macOS:** `brew install zls`
        - **Linux:** download a prebuilt release matching your Zig version, or `zig build -Doptimize=ReleaseSafe` from the zls source
        - **Windows:** download the matching release from the zls GitHub releases, or build from source
        
        Confirm it resolves:
        
        ```bash
        command -v zls
        ```
        
        ## Configure
        
        Builtin — usually NO config needed (auto-resolved by extension). Configure only to set priority, init options, override extensions, or disable. Same JSON shape in `.codex/lsp-client.json` (Codex) AND `.opencode/lsp.json` (OpenCode/omo):
        
        ```json
        { "lsp": { "zls": { "priority": 100 } } }
        ```
        
        For builtin ids in a PROJECT config, `command` is supplied automatically — only set `priority`/`initialization`/`extensions`/`disabled`/`env`. A fully custom (non-builtin) server with its own `command` must go in the USER config (`~/.codex/lsp-client.json`).
        
        ### Initialization options (only if commonly needed)
        
        None commonly required.
        
        ## Alternatives
        
        None.
        
        ## Troubleshooting
        - **VERSION MATCH (critical):** zls version MUST match your zig version — build/install zls against the exact same Zig. A mismatch causes crashes, parse errors, or silent failures. After upgrading Zig, upgrade/rebuild zls too.
        - **PATH:** `zls` must be on PATH; reopen the shell after install.
        - **zig not found:** zls invokes `zig` for builds — make sure `zig` itself is also on PATH.
        
        ## Verify
        
        ```bash
        bun ../../scripts/verify-lsp.ts path/to/file.zig
        ```
        
  • scripts
    • detect-lsp.ts 6.5 KB
      #!/usr/bin/env bun
      // detect-lsp.ts <targetDir> [--json] — scan a directory for source languages and
      // report, per detected language: the builtin LSP server, whether its executable
      // is on PATH, an install hint, and whether a project LSP config references it.
      
      import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"
      import { delimiter, extname, join, sep } from "node:path"
      import process from "node:process"
      
      import { LANGUAGES, type LanguageServer, PROJECT_CONFIG_FILES } from "./lsp-server-table"
      
      const SKIP_DIRECTORIES = new Set<string>([
      	"node_modules",
      	".git",
      	"dist",
      	"build",
      	".next",
      	"out",
      	"target",
      	".venv",
      	"venv",
      	"vendor",
      	".cache",
      	"__pycache__",
      	".turbo",
      	"coverage",
      ])
      
      const MAX_FILES = 50_000
      
      // Mirrors effectiveExtension() in packages/lsp-tools-mcp/src/lsp/effective-extension.ts:
      // extensionless Dockerfile/Containerfile resolve to .dockerfile (exact-case basenames).
      const BASENAME_EXTENSIONS: Record<string, string> = {
      	Dockerfile: ".dockerfile",
      	Containerfile: ".dockerfile",
      }
      
      interface ConfigFileState {
      	readonly path: string
      	readonly exists: boolean
      	readonly serverIds: readonly string[]
      }
      
      interface DetectionResult {
      	readonly server: LanguageServer
      	readonly executable: string
      	readonly installed: boolean
      	readonly resolvedPath: string | null
      	readonly configuredIn: readonly string[]
      }
      
      function collectExtensions(root: string): ReadonlySet<string> {
      	const found = new Set<string>()
      	const stack: string[] = [root]
      	let visited = 0
      
      	while (stack.length > 0) {
      		const current = stack.pop()
      		if (current === undefined) break
      
      		let entries: string[]
      		try {
      			entries = readdirSync(current)
      		} catch {
      			continue
      		}
      
      		for (const entry of entries) {
      			if (visited >= MAX_FILES) return found
      			const fullPath = join(current, entry)
      
      			let kind: "dir" | "file" | "other" = "other"
      			try {
      				const stat = statSync(fullPath)
      				kind = stat.isDirectory() ? "dir" : stat.isFile() ? "file" : "other"
      			} catch {
      				continue
      			}
      
      			if (kind === "dir") {
      				if (!SKIP_DIRECTORIES.has(entry)) stack.push(fullPath)
      			} else if (kind === "file") {
      				visited += 1
      				const ext = (BASENAME_EXTENSIONS[entry] ?? extname(entry)).toLowerCase()
      				if (ext.length > 0) found.add(ext)
      			}
      		}
      	}
      
      	return found
      }
      
      function pathDirectories(): readonly string[] {
      	return (process.env["PATH"] ?? "").split(delimiter).filter((dir: string) => dir.length > 0)
      }
      
      function resolveExecutable(command: string): string | null {
      	const extensions =
      		process.platform === "win32" ? (process.env["PATHEXT"]?.split(";") ?? [".EXE", ".CMD", ".BAT"]) : [""]
      	const bases = command.includes("/") || command.includes(sep) ? [command] : pathDirectories().map((dir: string) => join(dir, command))
      
      	for (const base of bases) {
      		for (const ext of extensions) {
      			const candidate = ext.length > 0 ? `${base}${ext}` : base
      			try {
      				if (existsSync(candidate) && statSync(candidate).isFile()) return candidate
      			} catch {
      				continue
      			}
      		}
      	}
      	return null
      }
      
      function isRecord(value: unknown): value is Record<string, unknown> {
      	return typeof value === "object" && value !== null && !Array.isArray(value)
      }
      
      function parseConfiguredServerIds(path: string): readonly string[] {
      	let parsed: unknown
      	try {
      		parsed = JSON.parse(readFileSync(path, "utf-8"))
      	} catch {
      		return []
      	}
      	if (!isRecord(parsed)) return []
      	const lsp = parsed["lsp"]
      	return isRecord(lsp) ? Object.keys(lsp) : []
      }
      
      function readConfigState(root: string): readonly ConfigFileState[] {
      	return PROJECT_CONFIG_FILES.map((relative: string): ConfigFileState => {
      		const path = join(root, relative)
      		if (!existsSync(path)) return { path: relative, exists: false, serverIds: [] }
      		return { path: relative, exists: true, serverIds: parseConfiguredServerIds(path) }
      	})
      }
      
      function detect(root: string, configState: readonly ConfigFileState[]): readonly DetectionResult[] {
      	const extensions = collectExtensions(root)
      	const results: DetectionResult[] = []
      
      	for (const server of LANGUAGES) {
      		if (!server.extensions.some((ext: string) => extensions.has(ext))) continue
      
      		const executable = server.command[0] ?? server.serverId
      		const resolvedPath = resolveExecutable(executable)
      		const configuredIn = configState
      			.filter((state) => state.serverIds.includes(server.serverId))
      			.map((state) => state.path)
      
      		results.push({ server, executable, installed: resolvedPath !== null, resolvedPath, configuredIn })
      	}
      
      	return results
      }
      
      function renderReport(root: string, results: readonly DetectionResult[], configState: readonly ConfigFileState[]): string {
      	const lines: string[] = [`LSP setup scan: ${root}`]
      	const configSummary = configState
      		.map((state) => `${state.path} (${state.exists ? `present: ${state.serverIds.length} server(s)` : "absent"})`)
      		.join(", ")
      	lines.push(`Config files: ${configSummary}`, "")
      
      	if (results.length === 0) {
      		lines.push("No languages with a builtin LSP server were detected here.")
      		return lines.join("\n")
      	}
      
      	lines.push("DETECTED LANGUAGES (primary builtin server per language)")
      	for (const result of results) {
      		const mark = result.installed ? "OK  " : "MISS"
      		const state = result.installed ? `installed (${result.resolvedPath})` : "NOT installed"
      		const config = result.configuredIn.length > 0 ? `configured in ${result.configuredIn.join(", ")}` : "builtin-default"
      		lines.push(`[${mark}] ${result.server.language.padEnd(12)} server=${result.server.serverId}  exe=${result.executable}  ${state}  ${config}`)
      		if (!result.installed) lines.push(`        install: ${result.server.installHint}`)
      	}
      
      	const missing = results.filter((result) => !result.installed)
      	lines.push(
      		"",
      		missing.length === 0
      			? `All ${results.length} detected server(s) installed.`
      			: `${missing.length}/${results.length} server(s) NOT installed: ${missing.map((m) => m.server.language).join(", ")}`,
      		"Next: read references/<language>/README.md, then configure .codex/lsp-client.json AND .opencode/lsp.json.",
      	)
      	return lines.join("\n")
      }
      
      function main(): void {
      	const args = process.argv.slice(2)
      	const wantsJson = args.includes("--json")
      	const root = args.find((arg: string) => !arg.startsWith("--")) ?? process.cwd()
      
      	if (!existsSync(root)) {
      		process.stderr.write(`detect-lsp: target directory does not exist: ${root}\n`)
      		process.exit(2)
      	}
      
      	const configState = readConfigState(root)
      	const results = detect(root, configState)
      
      	if (wantsJson) {
      		process.stdout.write(`${JSON.stringify({ root, configState, results }, null, 2)}\n`)
      		return
      	}
      	process.stdout.write(`${renderReport(root, results, configState)}\n`)
      }
      
      main()
      
    • lsp-server-table.ts 5.1 KB
      // SOURCE OF TRUTH: packages/lsp-tools-mcp/src/lsp/server-definitions.ts
      // (BUILTIN_SERVERS + LSP_INSTALL_HINTS). This is a hand-maintained snapshot of
      // the primary builtin server per reference language, embedded so detect-lsp.ts
      // runs standalone in any user project. Mirror command/extensions when that file
      // changes.
      
      export interface LanguageServer {
      	readonly language: string
      	readonly serverId: string
      	readonly command: readonly string[]
      	readonly extensions: readonly string[]
      	readonly installHint: string
      }
      
      export const LANGUAGES: readonly LanguageServer[] = [
      	{
      		language: "typescript",
      		serverId: "typescript",
      		command: ["typescript-language-server", "--stdio"],
      		extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
      		installHint: "npm install -g typescript-language-server typescript",
      	},
      	{
      		language: "python",
      		serverId: "basedpyright",
      		command: ["basedpyright-langserver", "--stdio"],
      		extensions: [".py", ".pyi"],
      		installHint: "pip install basedpyright (or: uv tool install basedpyright)",
      	},
      	{
      		language: "go",
      		serverId: "gopls",
      		command: ["gopls"],
      		extensions: [".go"],
      		installHint: "go install golang.org/x/tools/gopls@latest",
      	},
      	{
      		language: "rust",
      		serverId: "rust",
      		command: ["rust-analyzer"],
      		extensions: [".rs"],
      		installHint: "rustup component add rust-analyzer",
      	},
      	{
      		language: "c-cpp",
      		serverId: "clangd",
      		command: ["clangd", "--background-index", "--clang-tidy"],
      		extensions: [".c", ".cpp", ".cc", ".cxx", ".c++", ".h", ".hpp", ".hh", ".hxx", ".h++"],
      		installHint: "macOS: brew install llvm | Linux: apt install clangd | https://clangd.llvm.org/installation",
      	},
      	{
      		language: "java",
      		serverId: "jdtls",
      		command: ["jdtls"],
      		extensions: [".java"],
      		installHint: "macOS: brew install jdtls | https://github.com/eclipse-jdtls/eclipse.jdt.ls",
      	},
      	{
      		language: "kotlin",
      		serverId: "kotlin-ls",
      		command: ["kotlin-lsp"],
      		extensions: [".kt", ".kts"],
      		installHint: "https://github.com/Kotlin/kotlin-lsp",
      	},
      	{
      		language: "csharp",
      		serverId: "csharp",
      		command: ["csharp-ls"],
      		extensions: [".cs"],
      		installHint: "dotnet tool install -g csharp-ls",
      	},
      	{
      		language: "razor",
      		serverId: "razor",
      		command: ["roslyn-language-server", "--stdio"],
      		extensions: [".razor", ".cshtml"],
      		installHint: "dotnet tool install -g roslyn-language-server --prerelease (see references/csharp/README.md)",
      	},
      	{
      		language: "swift",
      		serverId: "sourcekit-lsp",
      		command: ["sourcekit-lsp"],
      		extensions: [".swift", ".objc", ".objcpp"],
      		installHint: "Included with Xcode (xcode-select --install) or the Swift toolchain",
      	},
      	{
      		language: "ruby",
      		serverId: "ruby-lsp",
      		command: ["rubocop", "--lsp"],
      		extensions: [".rb", ".rake", ".gemspec", ".ru"],
      		installHint: "gem install ruby-lsp (builtin runs `rubocop --lsp`; gem install rubocop)",
      	},
      	{
      		language: "php",
      		serverId: "php",
      		command: ["intelephense", "--stdio"],
      		extensions: [".php"],
      		installHint: "npm install -g intelephense",
      	},
      	{
      		language: "dart",
      		serverId: "dart",
      		command: ["dart", "language-server", "--lsp"],
      		extensions: [".dart"],
      		installHint: "Included with the Dart/Flutter SDK",
      	},
      	{
      		language: "elixir",
      		serverId: "elixir-ls",
      		command: ["elixir-ls"],
      		extensions: [".ex", ".exs"],
      		installHint: "https://github.com/elixir-lsp/elixir-ls",
      	},
      	{
      		language: "zig",
      		serverId: "zls",
      		command: ["zls"],
      		extensions: [".zig", ".zon"],
      		installHint: "https://github.com/zigtools/zls (match zls version to your zig version)",
      	},
      	{
      		language: "lua",
      		serverId: "lua-ls",
      		command: ["lua-language-server"],
      		extensions: [".lua"],
      		installHint: "macOS: brew install lua-language-server | https://github.com/LuaLS/lua-language-server",
      	},
      	{
      		language: "bash",
      		serverId: "bash",
      		command: ["bash-language-server", "start"],
      		extensions: [".sh", ".bash", ".zsh", ".ksh"],
      		installHint: "npm install -g bash-language-server",
      	},
      	{
      		language: "yaml",
      		serverId: "yaml-ls",
      		command: ["yaml-language-server", "--stdio"],
      		extensions: [".yaml", ".yml"],
      		installHint: "npm install -g yaml-language-server",
      	},
      	{
      		language: "terraform",
      		serverId: "terraform",
      		command: ["terraform-ls", "serve"],
      		extensions: [".tf", ".tfvars"],
      		installHint: "macOS: brew install hashicorp/tap/terraform-ls | https://github.com/hashicorp/terraform-ls",
      	},
      	{
      		language: "dockerfile",
      		serverId: "dockerfile",
      		command: ["docker-langserver", "--stdio"],
      		extensions: [".dockerfile"],
      		installHint: "npm install -g dockerfile-language-server-nodejs",
      	},
      	{
      		language: "haskell",
      		serverId: "haskell-language-server",
      		command: ["haskell-language-server-wrapper", "--lsp"],
      		extensions: [".hs", ".lhs"],
      		installHint: "ghcup install hls",
      	},
      	{
      		language: "julia",
      		serverId: "julials",
      		command: ["julia", "--startup-file=no", "--history-file=no", "-e", "using LanguageServer; runserver()"],
      		extensions: [".jl"],
      		installHint: "julia -e 'using Pkg; Pkg.add(\"LanguageServer\")' (see references/julia/README.md)",
      	},
      ] as const
      
      export const PROJECT_CONFIG_FILES: readonly string[] = [
      	".codex/lsp-client.json",
      	".opencode/lsp.json",
      	".omo/lsp.json",
      	".omo/lsp-client.json",
      ] as const
      
    • tsconfig.json 397 B
      {
        "compilerOptions": {
          "target": "ESNext",
          "module": "ESNext",
          "moduleResolution": "bundler",
          "strict": true,
          "noUncheckedIndexedAccess": true,
          "esModuleInterop": true,
          "skipLibCheck": true,
          "forceConsistentCasingInFileNames": true,
          "resolveJsonModule": true,
          "noEmit": true,
          "lib": ["ESNext"],
          "types": ["bun-types"]
        },
        "include": ["*.ts"]
      }
      
    • verify-lsp.ts 5.1 KB
      #!/usr/bin/env bun
      // verify-lsp.ts <file> [--timeout=ms] — perform a real LSP diagnostics roundtrip
      // for <file> through the lsp-tools-mcp engine and report ok/fail with error text.
      // The engine source is located by walking up from this script and the cwd, so
      // run it inside the omo repo/worktree (where packages/lsp-tools-mcp/src exists).
      
      import { existsSync, statSync } from "node:fs"
      import { dirname, isAbsolute, join, resolve } from "node:path"
      import process from "node:process"
      import { fileURLToPath, pathToFileURL } from "node:url"
      
      const ENGINE_TOOLS = "packages/lsp-tools-mcp/src/tools.ts"
      const ENGINE_CONTEXT = "packages/lsp-tools-mcp/src/request-context.ts"
      const ENGINE_MANAGER = "packages/lsp-tools-mcp/src/lsp/manager.ts"
      const DEFAULT_TIMEOUT_MS = 60_000
      
      interface ToolExecutionResult {
      	readonly content: ReadonlyArray<{ readonly type: "text"; readonly text: string }>
      	readonly isError?: boolean
      	readonly details?: unknown
      }
      
      interface DiagnosticsDetails {
      	readonly mode: "file" | "directory"
      	readonly totalDiagnostics: number
      	readonly error?: string
      	readonly errorKind?: "missing_dependency" | "no_files" | "invalid_path"
      }
      
      interface ToolsModule {
      	readonly executeLspDiagnostics: (params: Record<string, unknown>, signal?: AbortSignal) => Promise<ToolExecutionResult>
      }
      
      interface ContextModule {
      	readonly runWithRequestContext: <T>(context: { cwd?: string; env?: Record<string, string> }, fn: () => T) => T
      }
      
      interface ManagerModule {
      	readonly disposeDefaultLspManager: () => Promise<void>
      }
      
      function findUp(relativeTarget: string): string | null {
      	const starts = [dirname(fileURLToPath(import.meta.url)), process.cwd()]
      	for (const start of starts) {
      		let current = start
      		while (true) {
      			const candidate = join(current, relativeTarget)
      			if (existsSync(candidate)) return candidate
      			const parent = dirname(current)
      			if (parent === current) break
      			current = parent
      		}
      	}
      	return null
      }
      
      function buildEnv(): Record<string, string> {
      	const env: Record<string, string> = {}
      	for (const [key, value] of Object.entries(process.env)) {
      		if (value !== undefined) env[key] = value
      	}
      	return env
      }
      
      function isDiagnosticsDetails(value: unknown): value is DiagnosticsDetails {
      	return typeof value === "object" && value !== null && "mode" in value && "totalDiagnostics" in value
      }
      
      async function loadModule<T>(relativeTarget: string): Promise<T> {
      	const path = findUp(relativeTarget)
      	if (path === null) {
      		throw new EngineNotFoundError(relativeTarget)
      	}
      	return (await import(pathToFileURL(path).href)) as T
      }
      
      class EngineNotFoundError extends Error {
      	constructor(public readonly target: string) {
      		super(`lsp-tools-mcp engine not found (looked for ${target}). Run verify-lsp.ts inside the omo repo/worktree.`)
      		this.name = "EngineNotFoundError"
      	}
      }
      
      function parseTimeout(args: readonly string[]): number {
      	const flag = args.find((arg) => arg.startsWith("--timeout="))
      	if (flag === undefined) return DEFAULT_TIMEOUT_MS
      	const parsed = Number.parseInt(flag.slice("--timeout=".length), 10)
      	return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TIMEOUT_MS
      }
      
      async function run(filePath: string, timeoutMs: number): Promise<number> {
      	const tools = await loadModule<ToolsModule>(ENGINE_TOOLS)
      	const context = await loadModule<ContextModule>(ENGINE_CONTEXT)
      	const manager = await loadModule<ManagerModule>(ENGINE_MANAGER)
      	const absolute = isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath)
      
      	try {
      		const signal = AbortSignal.timeout(timeoutMs)
      		const result = await context.runWithRequestContext({ cwd: process.cwd(), env: buildEnv() }, () =>
      			tools.executeLspDiagnostics({ filePath: absolute, severity: "all" }, signal),
      		)
      		const details = isDiagnosticsDetails(result.details) ? result.details : null
      		const text = result.content.map((part) => part.text).join("\n")
      
      		if (details?.errorKind === "missing_dependency") {
      			process.stdout.write(`FAIL ${absolute}: language server not installed\n${text}\n`)
      			return 1
      		}
      		if (result.isError === true || details?.errorKind === "invalid_path" || details?.errorKind === "no_files") {
      			process.stdout.write(`FAIL ${absolute}: ${details?.error ?? text}\n`)
      			return 1
      		}
      
      		const count = details?.totalDiagnostics ?? 0
      		process.stdout.write(`OK ${absolute}: LSP roundtrip succeeded (${count} diagnostic(s))\n${text}\n`)
      		return 0
      	} finally {
      		await manager.disposeDefaultLspManager()
      	}
      }
      
      async function main(): Promise<void> {
      	const args = process.argv.slice(2)
      	const filePath = args.find((arg) => !arg.startsWith("--"))
      	if (filePath === undefined) {
      		process.stderr.write("Usage: bun verify-lsp.ts <file> [--timeout=ms]\n")
      		process.exit(2)
      	}
      	if (!existsSync(filePath) || !statSync(filePath).isFile()) {
      		process.stderr.write(`verify-lsp: not a file: ${filePath}\n`)
      		process.exit(2)
      	}
      
      	try {
      		const code = await run(filePath, parseTimeout(args))
      		process.exit(code)
      	} catch (error) {
      		if (error instanceof EngineNotFoundError) {
      			process.stderr.write(`SKIP: ${error.message}\n`)
      			process.exit(3)
      		}
      		process.stderr.write(`FAIL ${filePath}: ${error instanceof Error ? error.message : String(error)}\n`)
      		process.exit(1)
      	}
      }
      
      await main()
      
  • SKILL.md 5.4 KB
    ---
    name: lsp-setup
    description: "Configures a language server so editor/agent tooling (diagnostics, go-to-definition, references, rename) works. Use when a project needs an LSP installed or wired, or a 'no LSP server configured' error appears."
    ---
    
    # LSP Setup
    
    Configure the right Language Server for a project so the `lsp` MCP tools
    (`diagnostics`, `goto_definition`, `find_references`, `symbols`, `rename`)
    actually work. This skill is an index: detect what a project needs, install the
    server, write the config, then verify with a real roundtrip.
    
    The list of servers we ship as **builtin** is the source of truth in
    `packages/lsp-tools-mcp/src/lsp/server-definitions.ts` (`BUILTIN_SERVERS` +
    `LSP_INSTALL_HINTS`). The per-language references below mirror it.
    
    ---
    
    ## PHASE 0 — LANGUAGE GATE (run first)
    
    Identify the language from the file extension, then **read the matching
    reference before installing or configuring anything**.
    
    | Extension(s) | Reference |
    |---|---|
    | `.ts .tsx .js .jsx .mjs .cjs .mts .cts .vue .svelte .astro` | `references/typescript/README.md` |
    | `.py .pyi` | `references/python/README.md` |
    | `.go` | `references/go/README.md` |
    | `.rs` | `references/rust/README.md` |
    | `.c .cpp .cc .cxx .h .hpp .hh .hxx` | `references/c-cpp/README.md` |
    | `.java` | `references/java/README.md` |
    | `.kt .kts` | `references/kotlin/README.md` |
    | `.cs .razor .cshtml` | `references/csharp/README.md` |
    | `.swift` | `references/swift/README.md` |
    | `.rb .rake .gemspec .ru` | `references/ruby/README.md` |
    | `.php` | `references/php/README.md` |
    | `.dart` | `references/dart/README.md` |
    | `.ex .exs` | `references/elixir/README.md` |
    | `.zig .zon` | `references/zig/README.md` |
    | `.lua` | `references/lua/README.md` |
    | `.sh .bash .zsh .ksh` | `references/bash/README.md` |
    | `.yaml .yml` | `references/yaml/README.md` |
    | `.tf .tfvars` | `references/terraform/README.md` |
    | `.hs .lhs` | `references/haskell/README.md` |
    | `.jl` | `references/julia/README.md` |
    
    ---
    
    ## WORKFLOW — detect → install → configure → verify
    
    ### 1. Detect
    
    Scan the project to see which languages are present and whether each server is
    installed and configured:
    
    ```bash
    bun scripts/detect-lsp.ts <projectDir>      # human report (default: cwd)
    bun scripts/detect-lsp.ts <projectDir> --json
    ```
    
    For each detected language it prints the builtin server id, the executable it
    needs on `PATH`, whether that executable is installed, an install hint, and
    whether a project config file already references it.
    
    ### 2. Install
    
    Open `references/<language>/README.md` and run the install command for your OS.
    Then confirm the executable resolves:
    
    ```bash
    command -v <server-executable>   # e.g. typescript-language-server, gopls, rust-analyzer
    ```
    
    ### 3. Configure
    
    Most builtin servers need **no config** — they are resolved automatically by
    file extension. Write config only to: pick between competing servers, set a
    `priority`, pass `initialization` options, override `extensions`, set `env`, or
    `disable` a server.
    
    Two project-scoped config files, **identical JSON shape**:
    
    - Codex harness → `.codex/lsp-client.json` (user: `~/.codex/lsp-client.json`)
    - OpenCode/omo harness → `.opencode/lsp.json` (also `.omo/lsp.json`)
    
    ```jsonc
    {
      "lsp": {
        "<server-id>": {
          "command": ["<bin>", "<args>"],   // optional for builtin ids (supplied automatically)
          "extensions": [".ext"],            // optional override
          "priority": 100,                    // higher wins when several servers match an extension
          "initialization": { },              // server-specific initializationOptions
          "env": { "KEY": "value" },          // optional
          "disabled": false                   // set true to turn a server off
        }
      }
    }
    ```
    
    Rules enforced by `config-loader.ts`:
    
    - In a **project** config (`.codex/lsp-client.json`, `.opencode/lsp.json`) an
      entry whose id is a **builtin** server inherits `command` automatically — you
      only override `extensions` / `priority` / `initialization`. A non-builtin id
      in a project config is **ignored**.
    - To define a **fully custom** (non-builtin) server with its own `command`, put
      it in the **user** config (`~/.codex/lsp-client.json`, or the path set by
      `LSP_TOOLS_MCP_USER_CONFIG`), where `command` + `extensions` are honored.
    - Project entries win over user entries; both win over builtin defaults.
    
    Each language reference gives a ready-to-paste snippet.
    
    ### 4. Verify
    
    Run a real diagnostics roundtrip against a source file. This spawns the server,
    opens the file, requests diagnostics, and reports `OK`/`FAIL`:
    
    ```bash
    bun scripts/verify-lsp.ts <path/to/file.ext>
    bun scripts/verify-lsp.ts <file> --timeout=90000
    ```
    
    `OK` = the server started and answered. `FAIL: language server not installed`
    = go back to step 2. Other `FAIL` text carries the server/startup error.
    `SKIP` = the engine source could not be located; run from inside the omo
    repo/worktree, or call the `lsp` MCP `diagnostics` tool directly.
    
    ---
    
    ## Scripts
    
    | Script | Purpose |
    |---|---|
    | `scripts/detect-lsp.ts` | Scan a directory; per detected language report server id, install status, install hint, config status. `--json` for machine output. |
    | `scripts/verify-lsp.ts` | Real LSP diagnostics roundtrip for one file via the `lsp-tools-mcp` engine; `OK`/`FAIL`/`SKIP` + exit code 0/1/3. |
    | `scripts/lsp-server-table.ts` | Embedded snapshot of the primary builtin server per language (mirrors `server-definitions.ts`). |
    
    Run with [Bun](https://bun.sh): `curl -fsSL https://bun.sh/install | bash`.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related