Language Servers
Polytoken gives the model symbol-aware code navigation by starting language servers on demand. When the model opens a file, Polytoken picks a server by matching the file’s type against the table below, checking that a project marker exists in your tree, and confirming the server binary is installed. When several servers match a file, Polytoken routes to the available server with the highest weight. Polytoken breaks equal weights by server id, so the alphabetical table order does not determine routing.
Polytoken looks for the project marker in the directory holding the file and in each directory above it, up to the folder the session started in. A marker in that top folder takes precedence; otherwise the marker closest to the file wins. A Rust crate below a repository root that has no marker of its own therefore gets a server rooted at the crate: the crate’s manifest is the closest marker, and the server process runs from that directory.
Checking what is installed
Section titled “Checking what is installed”Run polytoken lsp check to list every server Polytoken knows, one per
line, with the binary Polytoken expects and whether it resolves on your
machine:
polytoken lsp checkEach line carries a green check mark for a server whose binary resolves
on your PATH, or a red cross for one that is missing, with the command
Polytoken expects in an aligned found: / needs: column:
✓ gopls (found: gopls)✗ rust-analyzer (needs: rust-analyzer)The listing never starts a server: a check mark means the binary resolves on
your PATH, nothing more. A yellow cross marks a PATH-resolvable server that
another higher-priority PATH-resolvable entry fully supersedes. The CLI does
not inspect project root markers, so this mark can differ from /lsp status.
Color appears only when the output goes to a color-capable terminal; Polytoken
strips it when you pipe or redirect the listing. polytoken lsp check always
exits 0, even when servers are missing.
Check one server by id to verify the install itself. The named check runs
the server binary’s --version under a short deadline and exits 0 only when
the binary resolves and the probe succeeds:
polytoken lsp check goplspolytoken lsp check gopls --format jsonA binary that is present but broken (a rustup shim whose component is not
installed, for example) fails the named check, and the named check’s output
shows the probe outcome. The listing’s check mark therefore reports only that
the binary resolves. When a server binary does not implement --version, the
named probe fails; the listing remains the authoritative view of what resolves
on your PATH.
--format json prints an array of objects with id, command, origin
(builtin or custom), file_types, available, and disabled fields.
With a server name, the JSON carries a single object. Whenever the binary
resolves, that object also includes version_check, the probe outcome: ok,
failed with exit N, timed out, or spawn failed. For an installed entry
that a higher-priority PATH-resolvable server fully supersedes, the human
listing adds (superseded by <id>) to the yellow mark, and JSON includes the
relationship in superseded_by. The listing’s JSON never carries
version_check, because the listing never probes.
Viewing session status
Section titled “Viewing session status”Run /lsp status in an attached session to view the language servers Polytoken has started and the catalog entries that apply to the project. Polytoken checks root markers at the project root and through two directory levels beneath the root. The card indents every body row by two spaces and uses four marks with aligned found: or needs: facts:
✓uses green for a running server. Polytoken also uses✓for an installed server that can run but is idle, and adds an(idle)tag.!uses yellow when the binary exists but the server failed. The row usesfound: <command>and includes the failure plus its retry deadline in a warning note.✗uses red when the server is not installed. The row usesneeds: <command>to name the binary that the project needs.✗uses yellow when an installed idle server is fully superseded by higher-priority detected entries at every marker root Polytoken detects (markers are probed two levels beneath the root).- When Polytoken finds a marker below the project root, the fact uses the first detected root in the server’s configured marker order. For example,
(found: rust-analyzer, for: ./rs)identifies thersdirectory. Root markers omit thefor:annotation.
The card orders rows as active, installed, then missing. Non-default live states appear as tags after the server name. A server with a recent failure remains in the active list, so Polytoken keeps the failure details visible while it can retry the server. The command does not acquire the turn slot, but Polytoken rejects /lsp status while the model is running.
Rust-glancer starts diagnostics with cargo check when you have the rust-src rustup component; Polytoken enables those startup diagnostics by default through the server’s initialization options. Set daemon.lsp.servers.rust-glancer.init_options to replace the shipped initialization payload. The value you provide replaces the built-in object wholesale, so include every diagnostics key you want applied. The server does not provide workspace/willRenameFiles, so rename_file refactoring is unavailable. Rust-glancer is an early-stage v0.1.x server and some methods, notably implementation, can be less accurate than rust-analyzer. Rust-glancer reports diagnostics through publishDiagnostics pushes rather than textDocument/diagnostic pulls. Set daemon.lsp.servers.rust-glancer.disabled = true to silence the once-per-session install hint when the binary is not available.
When a server is missing
Section titled “When a server is missing”When the model asks for a file that no configured server can serve, the result it sees names the file type and every server that matched the file, with the concrete reason each one fell through: the expected command when the binary is missing, or the expected markers when none is present in your tree. Catalog entries can also list known alternative binary names from the same language-server distribution. For a Rust file in a project without either Rust server installed, the result reads:
no language server available for .rs: rust-glancer: command
rust-glancernot found on PATH; rust-analyzer: commandrust-analyzernot found on PATH. if the operator has not provided guidance, ask the operator whether to install it
The final sentence appears only when a binary is missing. It directs the model to ask you whether to install the server instead of guessing. A missing project marker produces a factual line naming the markers Polytoken looked for, because installing a binary cannot fix a workspace-shape mismatch. A file type the table does not cover reports that no known language server exists for it.
You see the same fact once per session. The first time the model reads a file whose server binary is missing, Polytoken shows a one-line warning naming the server and the file type. The warning does not repeat for the rest of the session, including after you clear the context. When every server that can serve a file type is missing, Polytoken shows a single warning naming the highest-priority server; the remaining missing servers do not add separate warnings.
Built-in servers
Section titled “Built-in servers”| Server | Command | File types | Root markers | Weight | Notes |
|---|---|---|---|---|---|
astro | astro-ls --stdio | astro | astro.config.mjs, astro.config.js, astro.config.ts | ||
basedpyright-langserver | basedpyright-langserver --stdio | py, pyi | pyproject.toml, pyrightconfig.json, setup.py, requirements.txt | shared with pylsp, pyright-langserver, ruff | |
bashls | bash-language-server start | sh, bash, zsh | .git | ||
biome | biome lsp-proxy | ts, tsx, js, jsx, mjs, cjs, json, jsonc | biome.json, biome.jsonc | diagnostics only; shared with denols, eslint, tailwindcss, typescript-language-server, emmet-language-server, vscode-json-language-server | |
clangd | clangd --background-index --clang-tidy --header-insertion=iwyu | c, h, cc, cpp, cxx, hpp, hxx, m, mm | compile_commands.json, CMakeLists.txt, .clangd, .clang-format | ||
csharp-ls | csharp-ls | cs | global.json, Directory.Build.props, Directory.Build.targets, .git | shared with omnisharp, roslyn-language-server | |
dartls | dart language-server --protocol=lsp | dart | pubspec.yaml, pubspec.lock | ||
denols | deno lsp | ts, tsx, js, jsx | deno.json, deno.jsonc, deno.lock | shared with biome, eslint, tailwindcss, typescript-language-server, emmet-language-server | |
docker-language-server | docker-language-server start --stdio | dockerfile, Dockerfile | Dockerfile, docker-compose.yml, docker-compose.yaml, .dockerignore | shared with dockerls | |
dockerls | docker-langserver --stdio | dockerfile, Dockerfile | Dockerfile, docker-compose.yml, docker-compose.yaml, .dockerignore | shared with docker-language-server | |
elixirls | elixir-ls | ex, exs, heex, eex | mix.exs, mix.lock | ||
emmet-language-server | emmet-language-server --stdio | html, css, scss, less, jsx, tsx, vue, svelte | .git | shared with tailwindcss, vscode-html-language-server, vscode-css-language-server, biome, denols, eslint, typescript-language-server, vue-language-server, svelte | |
erlangls | erlang_ls | erl, hrl | rebar.config, erlang.mk, rebar.lock | ||
eslint | vscode-eslint-language-server --stdio | ts, tsx, js, jsx, mjs, cjs, vue, svelte | .eslintrc, .eslintrc.js, .eslintrc.json, .eslintrc.yml, eslint.config.js, eslint.config.mjs | diagnostics only; shared with biome, denols, tailwindcss, typescript-language-server, emmet-language-server, vue-language-server, svelte | |
gleam | gleam lsp | gleam | gleam.toml | ||
gopls | gopls serve | go, mod, sum | go.mod, go.work, go.sum | ||
graphql | graphql-lsp server -m stream | graphql, gql | .graphqlrc, .graphqlrc.json, .graphqlrc.yml, .graphqlrc.yaml, graphql.config.js | ||
helm-ls | helm_ls serve | yaml, yml, tpl | Chart.yaml, Chart.yml | 10 | shared with yamlls |
hls | haskell-language-server-wrapper --lsp | hs, lhs | stack.yaml, cabal.project, hie.yaml, package.yaml | ||
kotlin-lsp | kotlin-lsp --stdio | kt, kts | build.gradle, build.gradle.kts, pom.xml, settings.gradle, settings.gradle.kts | ||
lua-language-server | lua-language-server | lua | .luarc.json, .luarc.jsonc, .luacheckrc, .stylua.toml, stylua.toml | ||
marksman | marksman server | md, markdown | .marksman.toml, .git | ||
metals | metals | scala, sbt, sc | build.sbt, build.sc, build.gradle, pom.xml | ||
nixd | nixd | nix | flake.nix, default.nix, shell.nix | ||
ocamllsp | ocamllsp | ml, mli, mll, mly | dune-project, dune-workspace, .ocamlformat | ||
omnisharp | omnisharp --languageserver --encoding utf-8 | cs | omnisharp.json, global.json, Directory.Build.props, Directory.Build.targets, .git | shared with csharp-ls, roslyn-language-server | |
phpactor | phpactor language-server | php | composer.json, .phpactor.json, .phpactor.yml | ||
prismals | prisma-language-server --stdio | prisma | schema.prisma, prisma/schema.prisma | ||
pylsp | pylsp | py | pyproject.toml, setup.py, setup.cfg, requirements.txt, Pipfile | shared with basedpyright-langserver, pyright-langserver, ruff | |
pyright-langserver | pyright-langserver --stdio | py, pyi | pyproject.toml, pyrightconfig.json, setup.py, setup.cfg, requirements.txt, Pipfile | 10 | shared with basedpyright-langserver, pylsp, ruff |
roslyn-language-server | roslyn-language-server --stdio --autoLoadProjects | cs | global.json, Directory.Build.props, Directory.Build.targets, .git | 10 | shared with csharp-ls, omnisharp |
rubocop | rubocop --lsp | rb, rake | .rubocop.yml, Gemfile | diagnostics only; shared with ruby-lsp, solargraph | |
ruby-lsp | ruby-lsp | rb, rake, gemspec, erb | Gemfile, .ruby-version, .ruby-gemset | 10 | shared with rubocop, solargraph |
ruff | ruff server | py, pyi | pyproject.toml, ruff.toml, .ruff.toml | diagnostics only; shared with basedpyright-langserver, pylsp, pyright-langserver | |
rust-analyzer | rust-analyzer | rs | Cargo.toml, rust-analyzer.toml | shared with rust-glancer | |
rust-glancer | rust-glancer lsp | rs | Cargo.toml | 10 | shared with rust-analyzer |
solargraph | solargraph stdio | rb, rake, gemspec | Gemfile, .solargraph.yml, Rakefile | shared with rubocop, ruby-lsp | |
sourcekit-lsp | sourcekit-lsp | swift | Package.swift, project.yml, .swiftpm | ||
svelte | svelteserver --stdio | svelte | svelte.config.js, svelte.config.mjs, package.json | shared with emmet-language-server, eslint, tailwindcss | |
tailwindcss | tailwindcss-language-server --stdio | html, css, scss, js, jsx, ts, tsx, vue, svelte | tailwind.config.js, tailwind.config.ts, tailwind.config.mjs, tailwind.config.cjs | shared with emmet-language-server, vscode-html-language-server, vscode-css-language-server, biome, denols, eslint, typescript-language-server, vue-language-server, svelte | |
terraformls | terraform-ls serve | tf, tfvars | .terraform, terraform.tfstate | ||
typescript-language-server | typescript-language-server --stdio | ts, tsx, js, jsx, mjs, cjs | package.json, tsconfig.json, jsconfig.json | 10 | shared with biome, denols, eslint, tailwindcss, emmet-language-server |
vimls | vim-language-server --stdio | vim, vimrc, .vimrc | .git | ||
vscode-css-language-server | vscode-css-language-server --stdio | css, scss, sass, less | package.json, .git | shared with emmet-language-server, tailwindcss | |
vscode-html-language-server | vscode-html-language-server --stdio | html, htm | package.json, .git | shared with emmet-language-server, tailwindcss | |
vscode-json-language-server | vscode-json-language-server --stdio | json, jsonc | package.json, .git | shared with biome | |
vue-language-server | vue-language-server --stdio | vue | vue.config.js, nuxt.config.js, nuxt.config.ts, package.json | shared with emmet-language-server, eslint, tailwindcss | |
yamlls | yaml-language-server --stdio | yaml, yml | .git | shared with helm-ls |
Entries whose Notes column reads diagnostics only (biome, eslint,
ruff, rubocop) participate only when the model asks for diagnostics, so
they never take over navigation for a file type another server handles.
Where two servers cover the same file type, each row’s Notes column names
the servers it shares that type with. The table sorts alphabetically by
server id, but listing order is not routing order: Polytoken routes to
the highest-weight entry whose project markers and binary are present,
breaking ties by id, and an empty Weight cell means the neutral default
of zero. Weighted entries are the preferred defaults for their family,
so a Ruby project with both servers installed prefers ruby-lsp over
solargraph, an npm project with both prefers
typescript-language-server over denols, and a Python project with
both prefers pyright-langserver over basedpyright-langserver, and a Cargo
workspace with both Rust servers installed prefers rust-glancer over
rust-analyzer. If rust-glancer is absent, Polytoken uses rust-analyzer.
On machines where only Docker’s brew docker-language-server binary
resolves, routing selects the docker-language-server entry, so move
settings you configured under daemon.lsp.servers.dockerls to that key.
Project markers settle most families before weight matters: a Deno project
routes TypeScript to denols because the npm markers are absent, and a Helm
chart repository routes YAML to helm-ls because the Chart.yaml marker is
specific. Every other repository routes YAML to yamlls.
Several servers treat the presence of a .git directory as a sufficient
project marker, so opening a matching file in any git repository can start
one of them. Each row’s root markers show where .git applies.
A server started this way stays available for the rest of the session unless
daemon.lsp.idle_timeout_secs retires it after inactivity. To opt a single
server out entirely, set daemon.lsp.servers.<id>.disabled = true.
Adding and overriding servers
Section titled “Adding and overriding servers”Configuration under daemon.lsp.servers customizes the table. Use a
built-in id to override that server, or a new id to define a server the
table does not cover. See daemon.lsp.servers on the
configuration page for every field, including
per-server arguments, JSON settings, and initialization options.
{ "daemon": { "lsp": { "servers": { "gopls": { "command": "/opt/gopls-custom" }, "my-server": { "command": "my-language-server", "args": ["--stdio"], "file_types": ["myext"] } } } }}Custom entries appear in polytoken lsp check output with a custom
origin. They route after the built-in catalog.
Switching language servers off
Section titled “Switching language servers off”Set daemon.lsp.enabled = false to switch language-server support off
entirely: the model no longer sees the lsp tool, and the missing-server
warning stops appearing. The configurator’s Daemon pane exposes this as the
Language servers toggle.