Language Servers
Polytoken gives the model symbol-aware code navigation by starting language servers on demand. When the model reads 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. If several servers match the file, Polytoken routes to the available server with the highest weight. Equal weights resolve 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 directory the session started in. A marker in that top directory 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.
For .ts and .tsx files, Polytoken uses the project’s installed TypeScript
language service. With TypeScript 7 or newer, Polytoken uses the compiler’s
native language server when the project includes node_modules/typescript
and its tsc executable. An explicit override of
daemon.lsp.servers.typescript-language-server.command or
daemon.lsp.servers.typescript-language-server.args keeps the configured
wrapper command instead. Older TypeScript versions continue to use
typescript-language-server.
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 when
higher-priority PATH-resolvable entries jointly cover every file type that
server serves. 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 color when you pipe or redirect the
listing. polytoken lsp check always exits 0, even when servers are missing.
Check one server by id for extra detail:
polytoken lsp check goplspolytoken lsp check gopls --format jsonThe named check runs the server binary’s --version under a short deadline
and appends the outcome as an informational note. For a known id, the
command exits 0 when the binary resolves, regardless of the probe outcome,
and exits 1 when the binary does not resolve. An unknown id exits 1 with a
hint to run the full listing.
For a binary that is present but broken (a rustup shim whose component is
not installed, for example), the named check still exits 0, and its output
shows the probe outcome as a note. Exit codes cannot distinguish a broken shim
from a server binary that simply does not implement --version, so the
probe stays advisory; read the note for the install-level signal. The
check mark and the found: fact report only that the binary resolves.
--format json prints an array of objects with id, command,
command_aliases, origin (builtin or custom), file_types, available,
and disabled fields. The shipped entries have empty command_aliases
arrays. With a server name, the JSON carries a single object. Whenever the
binary resolves, that object also includes version_check, the advisory
probe outcome: ok, failed with exit N, failed (terminated by signal),
timed out, or spawn failed.
For a superseded entry, the human listing adds (superseded by <id>) to the
yellow mark, and JSON includes the relationship in superseded_by. The id
names the highest-priority entry participating in the joint file-type
coverage. 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. When the same server runs at several roots, each active row also includes an (at <path>) tag identifying its root. A server with a recent failure remains in the active list, so Polytoken keeps the failure details visible while retry remains possible. /lsp status runs between turns: Polytoken rejects the command while the model is running.
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, Polytoken returns the file type and every matching server, with the concrete reason each server fell through: the expected command when the binary is missing, or the expected markers when no marker exists in your tree. 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. For a file type the table does not cover, Polytoken reports that no known language server exists for it.
When the model reads a file, Polytoken checks matching servers with detected project markers. If at least one such server matches but none has an installed binary, Polytoken shows one warning naming the highest-priority missing server and the file type. Other missing servers do not add separate warnings. If a matching server has both a detected project marker and an installed binary, Polytoken suppresses the missing-server warning. The warning does not repeat for the rest of the session, including after you clear the context.
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. With both servers of a family installed:
- a Ruby project prefers
ruby-lspoversolargraph - an npm project prefers
typescript-language-serveroverdenols - a Python project prefers
pyright-langserveroverbasedpyright-langserver - a Cargo workspace prefers
rust-glanceroverrust-analyzer; whenrust-glanceris absent, Polytoken usesrust-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.
Rust-glancer
Section titled “Rust-glancer”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. Your value replaces the entire built-in object, so
include every diagnostics key you want Rust-glancer to use.
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. Polytoken keeps the file open while it
waits for a push-only server’s diagnostics, and a clear publication sent
after the file closes does not replace the report.
Set daemon.lsp.servers.rust-glancer.disabled = true to silence the
once-per-session install hint when the binary is not available.
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. When the
server requests workspace/configuration sections, Polytoken matches each
requested section name exactly against the settings you configured: a
dotted subsection such as css.lint does not inherit from a configured
css entry.
{ "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.