Skip to main content
Zero-config helpers that pick the right language server, its real project root, and tell you plainly when it isn’t installed.

Quick Start

1

Let the agent do it (nothing to configure)

2

Detect language and root yourself (advanced)

3

Probe availability before you use LSP

4

Talk to the client directly with a workspace-aware root


How It Works

Each language maps to a default server, its file extensions, and the root markers that pin a project root — the nearest marker wins.

Adding or Overriding a Language Server

Register a new language or swap a built-in server by passing servers= — no package source edits needed.
The Agent(tools=[lsp_*]) path spawns its own LSPClient and does not forward a servers= mapping today. Register custom languages with the direct-client pattern shown here — LSPClient(language=..., servers=...).

Four Ways to Configure

How the Merge Works

User entries override built-ins per language key; unset fields fall back to the built-in row. DEFAULT_SERVERS is never mutated by a merge — resolve_servers({...}) always returns a fresh dict.

Which Configuration Style?

User Interaction Flow

A registered language behaves exactly like a built-in one.
User: “Where is AuthService.login defined in this Maven repo?” Agent uses a Java LSP client registered via servers=, which finds the nearest pom.xml for the root and queries jdtls. Tool returns: src/main/java/com/acme/AuthService.java:42:5 Agent replies:AuthService.login is defined in src/main/java/com/acme/AuthService.java at line 42.”

Which Helper Should I Use?


Public API Surface

These are pure helpers plus one new keyword and one new attribute on LSPClient.

Common Patterns

Guarded startup logging — log what the LSP layer would do for each language:
Monorepo-aware navigation — prove you’re pointing at the right project root:
Fallback to grep with a real reason — surface client.last_error so the model picks a fallback intelligently:

Best Practices

LSPClient.start() no longer raises on a missing binary — it sets last_error and returns False. Reserve try/except for real IO failures.
Without it, the client falls back to os.getcwd(), which is almost never the file’s real project root. workspace_file lets detect_root_uri initialise the server against the nearest root marker.
Error text like install with \…`is for humans. For machine-readable data, callprobe(language)and read the(available, command, install_hint)` tuple.
The edit_tools note appears once per language per run. When you see it, install the server rather than ignore the diagnostic signal for the rest of the session.
Define a servers= mapping in your project bootstrap and pass it to every LSPClient, so contributors get the same server without installing an alternative. New teammates inherit the choice instead of debugging a mismatch.
When you register a private or in-house server, include install_hint. It flows into client.last_error so a teammate missing the binary sees exactly how to install it.
Put server-specific tuning in the registry entry’s initialization_options. It is auto-picked when the caller didn’t set its own — cleaner than passing the same options to every client.

LSP Navigation Tools

Go-to-definition, find-references, hover, and symbol search

LSP Tools (reference)

Per-tool parameters and output format

Post-Edit Formatter

Produces the new “diagnostics unavailable” note

Built-in Tool Registry

How Agent(tools=[…]) resolves tool names