Skip to content

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.

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:

Terminal window
polytoken lsp check

Each 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:

Terminal window
polytoken lsp check gopls
polytoken lsp check gopls --format json

A 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.

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 uses found: <command> and includes the failure plus its retry deadline in a warning note.
  • uses red when the server is not installed. The row uses needs: <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 the rs directory. Root markers omit the for: 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 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-glancer not found on PATH; rust-analyzer: command rust-analyzer not 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.

ServerCommandFile typesRoot markersWeightNotes
astroastro-ls --stdioastroastro.config.mjs, astro.config.js, astro.config.ts
basedpyright-langserverbasedpyright-langserver --stdiopy, pyipyproject.toml, pyrightconfig.json, setup.py, requirements.txtshared with pylsp, pyright-langserver, ruff
bashlsbash-language-server startsh, bash, zsh.git
biomebiome lsp-proxyts, tsx, js, jsx, mjs, cjs, json, jsoncbiome.json, biome.jsoncdiagnostics only; shared with denols, eslint, tailwindcss, typescript-language-server, emmet-language-server, vscode-json-language-server
clangdclangd --background-index --clang-tidy --header-insertion=iwyuc, h, cc, cpp, cxx, hpp, hxx, m, mmcompile_commands.json, CMakeLists.txt, .clangd, .clang-format
csharp-lscsharp-lscsglobal.json, Directory.Build.props, Directory.Build.targets, .gitshared with omnisharp, roslyn-language-server
dartlsdart language-server --protocol=lspdartpubspec.yaml, pubspec.lock
denolsdeno lspts, tsx, js, jsxdeno.json, deno.jsonc, deno.lockshared with biome, eslint, tailwindcss, typescript-language-server, emmet-language-server
docker-language-serverdocker-language-server start --stdiodockerfile, DockerfileDockerfile, docker-compose.yml, docker-compose.yaml, .dockerignoreshared with dockerls
dockerlsdocker-langserver --stdiodockerfile, DockerfileDockerfile, docker-compose.yml, docker-compose.yaml, .dockerignoreshared with docker-language-server
elixirlselixir-lsex, exs, heex, eexmix.exs, mix.lock
emmet-language-serveremmet-language-server --stdiohtml, css, scss, less, jsx, tsx, vue, svelte.gitshared with tailwindcss, vscode-html-language-server, vscode-css-language-server, biome, denols, eslint, typescript-language-server, vue-language-server, svelte
erlanglserlang_lserl, hrlrebar.config, erlang.mk, rebar.lock
eslintvscode-eslint-language-server --stdiots, tsx, js, jsx, mjs, cjs, vue, svelte.eslintrc, .eslintrc.js, .eslintrc.json, .eslintrc.yml, eslint.config.js, eslint.config.mjsdiagnostics only; shared with biome, denols, tailwindcss, typescript-language-server, emmet-language-server, vue-language-server, svelte
gleamgleam lspgleamgleam.toml
goplsgopls servego, mod, sumgo.mod, go.work, go.sum
graphqlgraphql-lsp server -m streamgraphql, gql.graphqlrc, .graphqlrc.json, .graphqlrc.yml, .graphqlrc.yaml, graphql.config.js
helm-lshelm_ls serveyaml, yml, tplChart.yaml, Chart.yml10shared with yamlls
hlshaskell-language-server-wrapper --lsphs, lhsstack.yaml, cabal.project, hie.yaml, package.yaml
kotlin-lspkotlin-lsp --stdiokt, ktsbuild.gradle, build.gradle.kts, pom.xml, settings.gradle, settings.gradle.kts
lua-language-serverlua-language-serverlua.luarc.json, .luarc.jsonc, .luacheckrc, .stylua.toml, stylua.toml
marksmanmarksman servermd, markdown.marksman.toml, .git
metalsmetalsscala, sbt, scbuild.sbt, build.sc, build.gradle, pom.xml
nixdnixdnixflake.nix, default.nix, shell.nix
ocamllspocamllspml, mli, mll, mlydune-project, dune-workspace, .ocamlformat
omnisharpomnisharp --languageserver --encoding utf-8csomnisharp.json, global.json, Directory.Build.props, Directory.Build.targets, .gitshared with csharp-ls, roslyn-language-server
phpactorphpactor language-serverphpcomposer.json, .phpactor.json, .phpactor.yml
prismalsprisma-language-server --stdioprismaschema.prisma, prisma/schema.prisma
pylsppylsppypyproject.toml, setup.py, setup.cfg, requirements.txt, Pipfileshared with basedpyright-langserver, pyright-langserver, ruff
pyright-langserverpyright-langserver --stdiopy, pyipyproject.toml, pyrightconfig.json, setup.py, setup.cfg, requirements.txt, Pipfile10shared with basedpyright-langserver, pylsp, ruff
roslyn-language-serverroslyn-language-server --stdio --autoLoadProjectscsglobal.json, Directory.Build.props, Directory.Build.targets, .git10shared with csharp-ls, omnisharp
rubocoprubocop --lsprb, rake.rubocop.yml, Gemfilediagnostics only; shared with ruby-lsp, solargraph
ruby-lspruby-lsprb, rake, gemspec, erbGemfile, .ruby-version, .ruby-gemset10shared with rubocop, solargraph
ruffruff serverpy, pyipyproject.toml, ruff.toml, .ruff.tomldiagnostics only; shared with basedpyright-langserver, pylsp, pyright-langserver
rust-analyzerrust-analyzerrsCargo.toml, rust-analyzer.tomlshared with rust-glancer
rust-glancerrust-glancer lsprsCargo.toml10shared with rust-analyzer
solargraphsolargraph stdiorb, rake, gemspecGemfile, .solargraph.yml, Rakefileshared with rubocop, ruby-lsp
sourcekit-lspsourcekit-lspswiftPackage.swift, project.yml, .swiftpm
sveltesvelteserver --stdiosveltesvelte.config.js, svelte.config.mjs, package.jsonshared with emmet-language-server, eslint, tailwindcss
tailwindcsstailwindcss-language-server --stdiohtml, css, scss, js, jsx, ts, tsx, vue, sveltetailwind.config.js, tailwind.config.ts, tailwind.config.mjs, tailwind.config.cjsshared with emmet-language-server, vscode-html-language-server, vscode-css-language-server, biome, denols, eslint, typescript-language-server, vue-language-server, svelte
terraformlsterraform-ls servetf, tfvars.terraform, terraform.tfstate
typescript-language-servertypescript-language-server --stdiots, tsx, js, jsx, mjs, cjspackage.json, tsconfig.json, jsconfig.json10shared with biome, denols, eslint, tailwindcss, emmet-language-server
vimlsvim-language-server --stdiovim, vimrc, .vimrc.git
vscode-css-language-servervscode-css-language-server --stdiocss, scss, sass, lesspackage.json, .gitshared with emmet-language-server, tailwindcss
vscode-html-language-servervscode-html-language-server --stdiohtml, htmpackage.json, .gitshared with emmet-language-server, tailwindcss
vscode-json-language-servervscode-json-language-server --stdiojson, jsoncpackage.json, .gitshared with biome
vue-language-servervue-language-server --stdiovuevue.config.js, nuxt.config.js, nuxt.config.ts, package.jsonshared with emmet-language-server, eslint, tailwindcss
yamllsyaml-language-server --stdioyaml, yml.gitshared 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.

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.

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.