This guide explains how to configure language servers for the OMP coding agent.
Source of truth in code:
- Server config type:
packages/coding-agent/src/lsp/types.ts(ServerConfig) - Config loader:
packages/coding-agent/src/lsp/config.ts - Built-in server definitions:
packages/coding-agent/src/lsp/defaults.json
OMP starts from the built-in server definitions, applies config overrides, then keeps enabled servers that meet both conditions:
- The current working directory contains at least one of the server's
rootMarkers. - The server binary is available — checked in supported project-local bin directories first (for example
node_modules/.bin/, Python virtual environments, Ruby binstubs, and projectbin/for Go), then$PATH.
Root-marker detection at startup is cwd-only; it does not search parent directories. Wildcard markers such as *.cabal match entries directly inside the cwd and do not recurse. No configuration is required for common setups; see defaults.json for the full built-in set.
OMP merges LSP config from multiple sources, lowest to highest precedence:
| Precedence | Location |
|---|---|
| Lowest | ~/lsp.json, ~/.lsp.json, ~/lsp.yaml, ~/.lsp.yaml, ~/lsp.yml, ~/.lsp.yml |
Plugin LSP configs (marketplace / --plugin-dir roots) |
|
User config dirs: active native agent directory, then ~/.claude/lsp.*, ~/.codex/lsp.*, ~/.gemini/lsp.* |
|
Cwd config dirs: <cwd>/.omp/lsp.*, <cwd>/.claude/lsp.*, <cwd>/.codex/lsp.*, <cwd>/.gemini/lsp.* |
|
| Highest | Cwd root: <cwd>/lsp.* and <cwd>/.lsp.* |
Within each config-directory row, directories are listed highest to lowest priority. Each location accepts .json, .yaml, and .yml, including hidden variants. When multiple variants coexist in one location, precedence from highest to lowest is lsp.json, .lsp.json, lsp.yaml, .lsp.yaml, lsp.yml, .lsp.yml. All readable variants are merged; this is not a first-file-only search.
Merging is shallow per server: a higher-precedence server object overrides only its top-level fields, but object-valued fields such as settings, initOptions, capabilities, and workspaceReadyTimings replace the lower value as a whole rather than deep-merging it. Servers absent from override files remain at built-in defaults.
The native user config directory follows PI_CONFIG_DIR and active profiles; ~/.omp/agent/lsp.json is the default-profile spelling. This shared config lookup does not use PI_CODING_AGENT_DIR as an arbitrary replacement base. Claude's user directory honors CLAUDE_CONFIG_DIR. Foreign user sources (Claude, Codex, Gemini) are opt-in through the enabled-provider configuration; setting CLAUDE_CONFIG_DIR also opts Claude in unless explicitly disabled. Project and cwd sources do not walk ancestors and are not subject to that user-source opt-in.
Recommended locations:
- User-wide preferences → active native agent directory's
lsp.json - Project-specific overrides →
<cwd>/.omp/lsp.json
Note: There is no separate configured-server mode: built-in defaults, partial overrides, and new custom servers all pass through the same root-marker/binary/
disabledfilter. A config that only setsidleTimeoutMsleaves server selection unchanged.
Config is cached per cwd. After changing LSP files, root markers, or installed binaries, call the lsp tool with action: "reload", file: "*" (or omit file) to reread config and shut down clients whose configuration is no longer current.
Both JSON and YAML are accepted. The top-level object can use either a servers wrapper key or a flat map directly:
{
"servers": {
"server-name": { ... }
},
"idleTimeoutMs": 300000
}or (flat, without the servers wrapper):
{
"server-name": { ... },
"idleTimeoutMs": 300000
}Top-level keys:
servers— map of server name toServerConfig(optional wrapper; flat form is equivalent)idleTimeoutMs— shut down idle language servers after this many milliseconds; omitted, zero, and negative values leave idle shutdown disabled
Do not mix wrapped and flat server entries: when servers is an object, sibling keys other than idleTimeoutMs are not treated as servers.
| Field | Type | Required for a new server | Description |
|---|---|---|---|
command |
string |
yes | Binary name (resolved through local bins / PATH) or absolute path |
args |
string[] |
no | Arguments passed to the binary |
fileTypes |
string[] |
yes | Extensions or basenames, for example [".ts", ".tsx", "Dockerfile"]; case-insensitive, with or without a leading dot |
languageId |
string |
no | LSP language id sent in textDocument/didOpen; inferred from the file path when omitted |
rootMarkers |
string[] |
yes | Files/directories indicating a project root; one-level wildcard patterns such as *.cabal are supported |
initOptions |
object |
no | Sent as initializationOptions during the LSP handshake |
settings |
object |
no | Pushed via workspace/didChangeConfiguration |
disabled |
boolean |
no | Set true to disable this server |
warmupTimeoutMs |
number |
no | Warmup initialize timeout in milliseconds (default 5000; built-in marksman uses 2000) |
isLinter |
boolean |
no | Marks a dedicated linter/formatter server: excluded from type-intelligence, but preferred over type-checkers when choosing the formatOnWrite formatter |
capabilities |
object |
no | Opt-in server-specific features; see Capabilities |
workspaceReadyTimings |
object |
no | Advanced rust-analyzer workspace-readiness timing overrides; see below |
The required fields may be omitted from an override of a built-in server because they are inherited before validation. A genuinely new server normally needs all three. resolvedCommand and createClient are runtime-owned fields and must not be configured.
For plugin-compatible definitions, initializationOptions is accepted when initOptions is not an object. extensionToLanguage can supply fileTypes from its keys and defaults missing rootMarkers to ["."]; its values do not set per-extension language IDs. Marketplace entries can provide an inline lspServers map or a config-file path contained within the plugin root.
The capabilities object enables optional server-specific features that OMP supports on a per-server basis:
{
"capabilities": {
"flycheck": true,
"ssr": true,
"expandMacro": true,
"runnables": true,
"relatedTests": true
}
}All fields are boolean and optional. They are currently used by rust-analyzer.
workspaceReadyTimings tunes rust-analyzer's workspace-ready polling:
{
"servers": {
"rust-analyzer": {
"workspaceReadyTimings": {
"timeoutMs": 30000,
"pollMs": 250,
"settleMs": 2000,
"statusRequestTimeoutMs": 2000
}
}
}
}All four fields are optional millisecond values. Defaults are timeoutMs: 5000, pollMs: 100, settleMs: 2000, and statusRequestTimeoutMs: 1000. This is an advanced tuning surface; normal configurations should use the defaults.
Partial overrides are merged onto the built-in defaults. You only need to specify the fields you want to change.
{
"servers": {
"typescript-language-server": {
"args": ["--stdio", "--log-level", "4"]
}
}
}servers:
gopls:
settings:
gopls:
gofumpt: false
staticcheck: false{
"servers": {
"eslint": {
"disabled": true
}
}
}New native-format servers require non-empty command, fileTypes, and rootMarkers. Invalid new definitions are ignored with a warning; invalid overrides retain the previous server definition. An unreadable file or invalid JSON/YAML is ignored; the loader continues with the remaining sources.
{
"servers": {
"my-lsp": {
"command": "my-lsp-server",
"args": ["--stdio"],
"fileTypes": [".xyz"],
"rootMarkers": [".xyz-project", ".git"]
}
}
}Shut down language servers that have been inactive for more than five minutes:
{
"idleTimeoutMs": 300000
}Place the override in <project>/.omp/lsp.json:
{
"servers": {
"pylsp": {
"disabled": true
}
}
}The user-level config in ~/.omp/agent/lsp.json is unaffected; pylsp is only suppressed in this project.
The following servers ship in defaults.json and are eligible for auto-detection:
| Server key | Language(s) | Binary |
|---|---|---|
rust-analyzer |
Rust | rust-analyzer |
clangd |
C, C++, ObjC, CUDA | clangd |
zls |
Zig | zls |
gopls |
Go | gopls |
typescript-language-server |
TypeScript, JavaScript (≤ 6) | typescript-language-server |
typescript-native |
TypeScript, JavaScript (7+) | tsc --lsp --stdio |
denols |
TypeScript, JavaScript (Deno) | deno |
biome |
TS/JS/JSON/CSS (linter) | biome |
eslint |
TS/JS/Vue/Svelte (linter) | vscode-eslint-language-server |
vscode-html-language-server |
HTML | vscode-html-language-server |
vscode-css-language-server |
CSS, SCSS, Less | vscode-css-language-server |
vscode-json-language-server |
JSON | vscode-json-language-server |
tailwindcss |
HTML, CSS, TS/JS | tailwindcss-language-server |
svelte |
Svelte | svelteserver |
vue-language-server |
Vue | vue-language-server |
astro |
Astro | astro-ls |
pyright |
Python | pyright-langserver |
basedpyright |
Python | basedpyright-langserver |
pylsp |
Python | pylsp |
ty |
Python | ty |
ruff |
Python (linter) | ruff |
jdtls |
Java | jdtls |
kotlin-lsp |
Kotlin | kotlin-lsp |
metals |
Scala | metals |
hls |
Haskell | haskell-language-server-wrapper |
ocamllsp |
OCaml | ocamllsp |
elixirls |
Elixir | elixir-ls |
expert |
Elixir | expert |
erlangls |
Erlang | erlang_ls |
gleam |
Gleam | gleam |
solargraph |
Ruby | solargraph |
ruby-lsp |
Ruby | ruby-lsp |
rubocop |
Ruby (linter) | rubocop |
bashls |
Bash, Zsh | bash-language-server |
lua-language-server |
Lua | lua-language-server |
intelephense |
PHP | intelephense |
phpactor |
PHP | phpactor |
omnisharp |
C# | omnisharp |
yamlls |
YAML | yaml-language-server |
terraformls |
Terraform | terraform-ls |
dockerls |
Dockerfile | docker-langserver |
helm-ls |
Helm | helm_ls |
nixd |
Nix | nixd |
nil |
Nix | nil |
ols |
Odin | ols |
dartls |
Dart | dart |
marksman |
Markdown | marksman |
texlab |
LaTeX | texlab |
graphql |
GraphQL | graphql-lsp |
prismals |
Prisma | prisma-language-server |
vimls |
Vim script | vim-language-server |
emmet-language-server |
HTML, CSS, JSX | emmet-language-server |
sourcekit-lsp |
Swift | sourcekit-lsp |
swiftlint |
Swift (linter) | swiftlint |
tlaplus |
TLA+ | tlapm_lsp |
Only one TypeScript server is kept per project: when the resolved tsc belongs to a TypeScript install without lib/tsserver.js (TypeScript 7+), typescript-native wins and typescript-language-server is dropped, since it cannot drive that install; otherwise typescript-native is dropped because older tsc rejects --lsp.