Skip to content

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.

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

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

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

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

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. With both servers of a family installed:

  • a Ruby project prefers ruby-lsp over solargraph
  • an npm project prefers typescript-language-server over denols
  • a Python project prefers pyright-langserver over basedpyright-langserver
  • a Cargo workspace prefers rust-glancer over rust-analyzer; when 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.

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.

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.

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.