Skip to content
nikcli
Docs
Documentation

LSP

A practical reference for nikcli language-server discovery, startup, diagnostics, and the gated LSP tool.

Overview

nikcli uses Language Server Protocol servers as an optional code-intelligence layer for tools, diagnostics, symbols, definitions, references, hover information, implementations, and call hierarchy. The service lives under packages/nikcli/src/lsp and is loaded through an Effect service, so every project instance gets its own LSP state, root detection, client list, and shutdown finalizer.

Startup

Lazy by file

Servers are not launched globally at boot. A file touch or operation selects matching servers by extension and then starts the needed client.

Workspace

Per root

Each server chooses a root from lockfiles, language manifests, or the current instance directory. Clients are reused per server plus root pair.

Config

Configurable

Built-in servers can be disabled or overridden, and custom servers can be added when their file extensions are declared.

Agent

Tool gated

The agent-facing lsp tool is experimental and is only registered when the LSP tool flag is enabled.

Runtime lifecycle

The service state is created from the current instance context and current config. If lsp is set to false, the service keeps an empty server map and all operations become unavailable. Otherwise nikcli loads every built-in server from LSPServer, filters experimental servers, applies config overrides, and records a finalizer that shuts down every connected client when the instance is disposed.

  1. Resolve config from global, project, and remote sources through Config.defaultLayer.
  2. Build the server map from packages/nikcli/src/lsp/server.ts.
  3. Apply experimental filtering: ty replaces pyright only when the Python flag is set.
  4. Merge overrides: disabled entries are removed; command overrides replace spawn behavior.
  5. Spawn on demand when touchFile, hasClients, or an LSP operation needs a matching extension.
  6. Initialize JSON-RPC with root URI, workspace folders, diagnostics, watched-file support, and configuration support.
  7. Cache clients by root plus server id, mark broken pairs, and reuse in-flight spawns.
  8. Dispose cleanly by ending the JSON-RPC connection and killing the child process.
When a server starts

A server starts the first time a matching file is touched by a debug command, status sync, or the agent-facing LSP tool. The service does not pre-launch every supported language.

Supported servers

The built-in catalog is declared in packages/nikcli/src/lsp/server.ts. Each entry has an id, extension list, root strategy, and spawn function.

AreaServer idsNotes
JavaScript and webtypescript, deno, eslint, oxlint, biome, vue, svelte, astroTypeScript uses the workspace typescript/lib/tsserver.js. Deno only activates when a Deno config file is found.
Pythonpyright, tyty is hidden unless NIKCLI_EXPERIMENTAL_LSP_TY is enabled; when enabled it disables pyright.
Systemsrust, clangd, zls, terraformRust requires rust-analyzer in PATH. Several binary servers can auto-download into the nikcli bin directory.
JVM and .NETjdtls, kotlin-ls, csharp, fsharpJava requires Java 21 or newer for JDTLS. .NET languages install tools through dotnet tool when allowed.
Other languagesgopls, ruby-lsp, elixir-ls, sourcekit-lsp, yaml-ls, lua-ls, php intelephense, prisma, dart, ocaml-lsp, bash, texlab, dockerfile, gleam, clojure-lsp, nixd, tinymist, haskell-language-serverMany use PATH first, then nikcli-managed binaries, then optional auto-install depending on the server.

Root detection

Root selection is server-specific. Most entries use NearestRoot, which walks upward from the file directory toward the current nikcli instance directory and returns the directory containing a matching marker file. If no marker exists, some servers fall back to the current instance directory; others return no root and do not start.

PatternExamplesBehavior
Package rootsbun.lock, package-lock.json, pnpm-lock.yaml, yarn.lockUsed by TypeScript, web framework servers, YAML, and format/lint servers.
Language manifestsCargo.toml, go.mod, pyproject.toml, mix.exs, composer.jsonKeep language servers scoped to the real project or module root.
Workspace-aware rootsgo.work, Rust workspace Cargo.toml, Gradle settings filesPrefer multi-module roots before module-local fallbacks.
Fixed instance rootsbash, dockerfileUse the active nikcli directory directly.

Agent tool

The lsp tool in packages/nikcli/src/tool/lsp.ts exposes editor-like navigation to agents. It accepts a file path, one-based line and character values, asks for lsp permission, opens the file through the LSP client, waits briefly for diagnostics, and then runs the selected operation.

OperationProtocol requestUse
goToDefinitiontextDocument/definitionFind the declaration or implementation target for a symbol.
findReferencestextDocument/referencesReturn all references, including declarations.
hovertextDocument/hoverRead type or documentation information at a position.
documentSymboltextDocument/documentSymbolList symbols for one document.
workspaceSymbolworkspace/symbolSearch project symbols and keep the top ten class/function-like results.
goToImplementationtextDocument/implementationFind implementations of interfaces or abstract members.
prepareCallHierarchytextDocument/prepareCallHierarchyPrepare the call hierarchy item for a function or method.
incomingCallscallHierarchy/incomingCallsFind callers after preparing call hierarchy.
outgoingCallscallHierarchy/outgoingCallsFind callees after preparing call hierarchy.
The tool is experimental

The agent tool is registered only when NIKCLI_EXPERIMENTAL_LSP_TOOL is enabled, or when global experimental mode enables experimental tools. Without that gate the service can still power debug/status flows, but the agent cannot call lsp.

Debug commands

The debug command group touches files and prints raw LSP JSON responses. Use it to check whether root detection, server startup, and diagnostics are working before relying on agent calls.

Terminal
bash
nikcli debug lsp diagnostics packages/nikcli/src/lsp/index.ts
nikcli debug lsp symbols Session
nikcli debug lsp document-symbols file:///absolute/path/to/file.ts

Diagnostics waits for a publish event after opening the file, sleeps briefly, and then prints the collected diagnostics map. Symbol commands call the service directly and print JSON.

Configuration

The lsp config field accepts false to disable all language servers, or a server-id map to disable, override, or add servers. Custom server ids must declare extensions, because nikcli otherwise cannot know which files should start them.

FieldApplies toMeaning
lsp = falseAll serversDisable the LSP service for the current config scope.
lsp.SERVER.disabled = trueBuilt-in or customRemove a server from the enabled map.
lsp.SERVER.commandOverride or customCommand array used to spawn the server process.
lsp.SERVER.extensionsOverride or customFile extensions that activate the server. Required for unknown ids.
lsp.SERVER.envOverride or customEnvironment variables merged into the server child process.
lsp.SERVER.initializationOverride or customInitialization options passed during initialize and configuration requests.

Dot-path config sketch

text
lsp.typescript.disabled = true
lsp.my-language.command = my-language-server --stdio
lsp.my-language.extensions = .my, .mine
lsp.my-language.env.MY_LANGUAGE_MODE = strict
lsp.my-language.initialization.workspaceMode = full

Downloads and binaries

Some servers require a system binary in PATH. Others install language-server packages or release binaries into Global.Path.bin, which resolves to the bin directory under nikcli's data root. Auto-downloads are skipped when NIKCLI_DISABLE_LSP_DOWNLOAD is set.

Resolution pathExamplesOperational note
PATH onlydeno, rust-analyzer, dart, gleam, ocamllspInstall the binary yourself before using the file type.
Bun package installpyright, svelte-language-server, @astrojs/language-server, bash-language-servernikcli installs into its data bin area when downloads are allowed.
Release archiveclangd, zls, lua-ls, terraform-ls, texlab, tinymistArchives are fetched, extracted, marked executable, and reused.
Language toolchaingopls, rubocop, csharp-ls, fsautocompleteRequires the matching language toolchain, such as Go, Ruby, or .NET.

Diagnostics and symbols

When a file is opened, nikcli sends workspace/didChangeWatchedFiles and textDocument/didOpen. If the same file is touched again, it sends a watched-file change plus textDocument/didChange with an incremented version. Published diagnostics are normalized to filesystem paths and stored per client.

  • Diagnostics debounce waits for the matching path and server id, then resolves after a short quiet period.
  • TypeScript first diagnostic quirk suppresses the first TypeScript publish event when the file had no diagnostic entry yet.
  • Workspace symbols filter to classes, functions, methods, interfaces, variables, constants, structs, and enums.
  • Document symbols accept a file URI and return raw document or location symbols after filtering empty values.

Failure modes

SymptomLikely causeFix
No LSP server available for this file type.No enabled server matched the extension, or root detection returned no root.Check the extension, language manifest, and lsp config overrides.
Server never startsRequired binary is missing, download is disabled, or the language toolchain is absent.Install the binary or unset NIKCLI_DISABLE_LSP_DOWNLOAD for supported auto-download servers.
Server starts once and then stays unavailableThe root plus server id was marked broken after spawn or initialize failed.Fix the binary/config issue and dispose or restart the nikcli instance.
Diagnostics are emptyThe server did not publish diagnostics within the wait window, or the file opened before analysis completed.Run the diagnostics debug command again, or check the language server logs and project root.
Tool call deniedThe agent-facing tool asks for lsp permission.Approve the request or update the session permission rules.
Python server differs from expectedNIKCLI_EXPERIMENTAL_LSP_TY switches Python from pyright to ty.Unset the flag for pyright or install ty for the experimental path.

Source anchors

FileWhat to inspect
packages/nikcli/src/lsp/index.tsEffect service, config merging, client cache, operations, and status.
packages/nikcli/src/lsp/client.tsJSON-RPC connection, initialize payload, open/change notifications, diagnostics handling, shutdown.
packages/nikcli/src/lsp/server.tsBuilt-in server catalog, root detection, spawn commands, downloads, experimental Python server selection.
@nikcli-ai/util/languageFile extension to language id mapping used when opening documents.
packages/nikcli/src/tool/lsp.tsAgent tool parameters, permission request, one-based to zero-based position conversion, and operation dispatch.
packages/nikcli/src/cli/cmd/debug/lsp.tsDiagnostics and symbol debug commands.
packages/nikcli/src/config/config.tslsp schema, validation, and custom-server extension requirement.