Catenary
Catenary is an MCP server that gives AI coding agents LSP-powered code intelligence. It multiplexes one or more language servers behind a single MCP interface, providing search, diagnostics, and navigation without shell-based text scanning.
Two MCP tools (grep and glob) plus post-edit diagnostics via hooks.
The agent never needs to know which language server handles which file.
- Installation — install the binary and connect it to your CLI
- Configuration — configure language servers and settings
- CLI & Dashboard — monitor sessions and browse events
- Language Servers — per-language setup guides
Installation
Prerequisites
- Rust toolchain (for building from source)
- Language servers for the languages you want to use (see Language Servers)
Install Catenary
cargo install --git https://github.com/TwoWells/Catenary catenary-mcp
Connect to Your AI CLI
The
catenarybinary must be on your PATH before configuring any client. Plugins and extensions provide hooks and MCP server declarations but do not include the binary.
Claude Code (recommended: plugin)
claude plugin marketplace add TwoWells/Catenary
claude plugin install catenary@catenary
The plugin registers the MCP server and all hooks (post-edit diagnostics, editing state enforcement, root sync, agent lifecycle).
Gemini CLI (recommended: extension)
gemini extensions install https://github.com/TwoWells/Catenary
The extension registers the MCP server and all hooks.
Manual MCP registration
For other clients, or if you prefer manual setup:
{
"mcpServers": {
"catenary": {
"command": "catenary"
}
}
}
This registers the MCP server only. Without the plugin/extension, you will not get post-edit diagnostics or editing state enforcement.
Verify
catenary doctor
For each configured server, doctor reports:
| Status | Meaning |
|---|---|
ready | Server spawned, initialized, and capabilities listed |
command not found | Binary not on $PATH |
spawn failed | Binary found but process failed to start |
initialize failed | Process started but LSP handshake failed |
skipped | No files for this language in the workspace |
Use --root to check a different workspace:
catenary doctor --root /path/to/project
Next Steps
- Configure your language servers
- Install language servers for your languages
Configuration
Catenary loads configuration from multiple sources, in order of priority (last wins):
- Defaults:
idle_timeout = 300,log_retention_days = 7. - User config:
~/.config/catenary/config.toml. - Project config:
.catenary.tomlin the current directory or any parent (searches upward). - Explicit file:
--config <path>. - Environment variables: Prefixed with
CATENARY_(e.g.,CATENARY_IDLE_TIMEOUT=600). Use__for nested keys (e.g.,CATENARY_ICONS__PRESET=nerd).
Language Servers
Configuration uses two sections: [server.*] defines how to run a
language server, and [language.*] binds languages to servers.
[server.<name>]
command = "server-binary"
args = ["arg1", "arg2"]
[language.<language-id>]
servers = ["<name>"]
Example
idle_timeout = 300
[server.rust]
command = "rust-analyzer"
[server.rust.initialization_options]
check.command = "clippy"
cargo.features = "all"
diagnostics.disabled = ["inactive-code"]
[server.python]
command = "pyright-langserver"
args = ["--stdio"]
[server.python.settings.python]
pythonPath = "/usr/bin/python3"
[server.python.settings.python.analysis]
exclude = ["**/target", "**/node_modules"]
extraPaths = []
[server.tsserver]
command = "typescript-language-server"
args = ["--stdio"]
[server.gopls]
command = "gopls"
[language.rust]
servers = ["rust"]
[language.python]
servers = ["python"]
[language.typescript]
servers = ["tsserver"]
[language.go]
servers = ["gopls"]
Initialization Options
Server-specific options passed during the LSP initialize request.
These go on the [server.*] entry:
[server.rust.initialization_options]
check.command = "clippy"
cargo.features = "all"
Refer to your language server’s documentation for available options.
Server Settings
Some language servers request configuration from the client via
workspace/configuration. The settings table provides these values
on the [server.*] entry. The TOML nesting mirrors the JSON object
the server expects — Catenary matches the section path from each
request and returns the corresponding subtree.
[server.python]
command = "pyright-langserver"
args = ["--stdio"]
[server.python.settings.python]
pythonPath = "/usr/bin/python3"
[server.python.settings.python.analysis]
exclude = ["**/target", "**/node_modules"]
extraPaths = []
When pyright sends workspace/configuration with
{ "items": [{ "section": "python.analysis" }] }, Catenary returns
{ "exclude": ["**/target", ...], "extraPaths": [] }.
Items with no matching path receive {}.
Language IDs
The [language.<language-id>] key in the language section must match the LSP language identifier.
Catenary auto-detects languages from file extensions, filenames, and
shebangs (#! lines in extensionless scripts). Any language with an LSP
server works — this table covers what Catenary recognises automatically.
By extension
| Extension | Language ID |
|---|---|
.rs | rust |
.go | go |
.c | c |
.cpp, .cc, .cxx, .h, .hpp | cpp |
.zig | zig |
.d | d |
.v | v |
.nim | nim |
.java | java |
.kt, .kts | kotlin |
.scala, .sc | scala |
.groovy, .gvy | groovy |
.clj, .cljs, .cljc | clojure |
.cs | csharp |
.fs, .fsx, .fsi | fsharp |
.swift | swift |
.m, .mm | objective-c |
.py | python |
.rb | ruby |
.pl, .pm | perl |
.php | php |
.lua | lua |
.tcl | tcl |
.cr | crystal |
.js, .mjs, .cjs | javascript |
.ts, .mts, .cts | typescript |
.tsx | typescriptreact |
.jsx | javascriptreact |
.hs, .lhs | haskell |
.ml, .mli | ocaml |
.elm | elm |
.gleam | gleam |
.ex, .exs | elixir |
.erl, .hrl | erlang |
.purs | purescript |
.sh, .bash, .zsh, .ebuild, .eclass, .install | shellscript |
.fish | fish |
.ps1, .psm1, .psd1 | powershell |
.r, .R | r |
.jl | julia |
.mojo | mojo |
.html, .htm | html |
.css | css |
.scss | scss |
.sass | sass |
.less | less |
.svelte | svelte |
.vue | vue |
.json, .jsonc | json |
.yaml, .yml | yaml |
.toml | toml |
.xml, .xsl, .xslt, .xsd | xml |
.sql | sql |
.graphql, .gql | graphql |
.proto | proto |
.md, .mdx | markdown |
.rst | restructuredtext |
.tex, .latex | latex |
.typ | typst |
.nix | nix |
.tf, .tfvars | terraform |
.cmake | cmake |
.dart | dart |
.dockerfile | dockerfile |
By filename
| Filename | Language ID |
|---|---|
Dockerfile | dockerfile |
Makefile, GNUmakefile | makefile |
CMakeLists.txt | cmake |
Cargo.toml, Cargo.lock | toml |
Gemfile, Rakefile | ruby |
Justfile, justfile | just |
PKGBUILD | shellscript |
By shebang
For files without a recognised extension, Catenary reads the first line.
If it starts with #!, the interpreter name is matched:
| Interpreter | Language ID |
|---|---|
bash, sh, zsh, dash, ksh | shellscript |
fish | fish |
python, python3, python2 | python |
node, nodejs | javascript |
deno | typescript |
ruby, irb | ruby |
perl | perl |
php | php |
lua, luajit | lua |
tclsh, wish | tcl |
Rscript | r |
julia | julia |
elixir, iex | elixir |
erl | erlang |
swift | swift |
kotlin | kotlin |
scala | scala |
groovy | groovy |
crystal | crystal |
Global Options
| Option | Default | Description |
|---|---|---|
idle_timeout | 300 | Seconds before auto-closing idle documents. 0 to disable. |
log_retention_days | 7 | Days to keep dead session data. 0 = remove on startup. -1 = retain forever. |
Icons
The [icons] table controls icons in the TUI dashboard.
| Preset | Description |
|---|---|
unicode (default) | Safe symbols for any terminal font. |
nerd | Nerd Font glyphs (requires a patched font). |
[icons]
preset = "nerd"
CLI & Dashboard
Dashboard (TUI)
Running catenary in an interactive terminal launches the TUI dashboard.
When stdin and stdout are pipes (launched by an MCP client), it serves
MCP instead — no flags needed.
The dashboard is the primary way to observe Catenary. It shows all sessions (active and historical), their language servers, and a live stream of protocol messages (MCP, LSP, hooks). All messages are stored in a SQLite database, so historical sessions can be browsed after the fact.
catenary # launch dashboard
Keybindings
Keybinding hints appear in each pane’s border.
Sessions pane:
| Key | Action |
|---|---|
j / Down | Next session |
k / Up | Previous session |
Space | Toggle expand/collapse |
h / l | Scroll horizontally (events) |
r | Refresh |
x | Delete session data (dead sessions only) |
q / Esc | Quit |
Events pane:
| Key | Action |
|---|---|
j / Down | Next event |
k / Up | Previous event |
Space | Toggle expand/collapse |
h / l | Scroll horizontally |
Ctrl-u | Page up |
Ctrl-d | Page down |
G | Jump to latest |
y | Yank selected event |
f | Open filter input |
F | Clear filter |
Protocol Transparency
Catenary logs every protocol message — every MCP tool call, every LSP request and response, every hook invocation — to a local SQLite database. The TUI shows the full message flow in real time: what Catenary sends to your language servers, what they send back, and how long each exchange takes.
You can see exactly what Catenary does. Nothing is hidden.
CLI Commands
catenary list
List active and historical sessions.
catenary list
catenary monitor <id>
Stream events from a session to the terminal. Accepts a prefix of either the Catenary session ID or the host CLI session ID.
catenary monitor 029b
catenary monitor 029b --raw # raw JSON output
catenary monitor 029b --filter hover
catenary query
Query events from the database. Useful for debugging and bug reports.
catenary query --session 029ba740 --since 1h
catenary query --kind diagnostics --since today
catenary query --search "hover" --format json
catenary query --sql "SELECT * FROM events WHERE payload LIKE '%timeout%'"
catenary gc
Garbage-collect old session data.
catenary gc --older-than 7d
catenary gc --dead
catenary gc --session 029ba740
catenary doctor
Verify language servers and hook installation. See Installation.
Language Servers
Setup guides for individual language servers. Each page covers installation and Catenary configuration.
Languages
| Language(s) | Page | Server |
|---|---|---|
| CSS, HTML, JSON | CSS-HTML-JSON | vscode-langservers-extracted |
| Go | Go | gopls |
| JavaScript | JavaScript | typescript-language-server |
| Julia | Julia | LanguageServer.jl |
| Markdown | Markdown | marksman |
| PHP | PHP | intelephense |
| Python | Python | pyright |
| Rust | Rust | rust-analyzer |
| Shell (Bash) | Shell | bash-language-server |
| Termux & Packaging | Termux | termux-language-server |
| TypeScript | TypeScript | typescript-language-server |
Contributing
Want to add a language?
- Create
your-language.mdin thelsp/folder following the template below - Add a row to the table above
- Submit a PR
Template
# YourLanguage
## Install
### macOS
```bash
# install command
```
### Linux
```bash
# install command
```
### Windows
```bash
# install command
```
## Config
Add to `~/.config/catenary/config.toml`:
```toml
[server.your-language-server]
command = "your-language-server"
args = ["--stdio"]
[language.yourlanguage]
servers = ["your-language-server"]
```
## Notes
Any gotchas, tips, or links to official docs.
CSS, HTML, JSON
These three languages are bundled together in one package: vscode-langservers-extracted.
Install
macOS
npm install -g vscode-langservers-extracted
Linux
npm install -g vscode-langservers-extracted
Windows
npm install -g vscode-langservers-extracted
Config
Add to ~/.config/catenary/config.toml:
[server.vscode-css]
command = "vscode-css-language-server"
args = ["--stdio"]
[server.vscode-html]
command = "vscode-html-language-server"
args = ["--stdio"]
[server.vscode-json]
command = "vscode-json-language-server"
args = ["--stdio"]
[language.css]
servers = ["vscode-css"]
[language.scss]
servers = ["vscode-css"]
[language.html]
servers = ["vscode-html"]
[language.json]
servers = ["vscode-json"]
What’s Included
The vscode-langservers-extracted package provides:
| Server | Languages |
|---|---|
vscode-css-language-server | CSS, SCSS, Less |
vscode-html-language-server | HTML |
vscode-json-language-server | JSON, JSONC |
vscode-markdown-language-server | Markdown |
vscode-eslint-language-server | ESLint |
Notes
- These servers are extracted from VS Code, so they’re well-maintained and feature-complete
- SCSS and Less use the same CSS server — it auto-detects the language
- For Tailwind CSS support, use
tailwindcss-language-server(separate server)
Links
Go
Install
macOS
go install golang.org/x/tools/gopls@latest
Or via Homebrew:
brew install gopls
Linux
go install golang.org/x/tools/gopls@latest
Windows
go install golang.org/x/tools/gopls@latest
Config
Add to ~/.config/catenary/config.toml:
[server.gopls]
command = "gopls"
[language.go]
servers = ["gopls"]
Notes
goplsis the official Go language server- Ensure
$GOPATH/bin(or$HOME/go/bin) is in your PATH - Works with Go modules out of the box
- First run indexes your module cache — may take a moment
Links
JavaScript
JavaScript uses the same language server as TypeScript.
Install
macOS
npm install -g typescript typescript-language-server
Linux
npm install -g typescript typescript-language-server
Windows
npm install -g typescript typescript-language-server
Config
Add to ~/.config/catenary/config.toml:
[server.tsserver]
command = "typescript-language-server"
args = ["--stdio"]
[language.javascript]
servers = ["tsserver"]
Notes
- Same server as TypeScript — install once, configure both
- Works with
.js,.jsx,.mjs,.cjsfiles - Provides type inference even in plain JavaScript
- Add a
jsconfig.jsonto customize project settings
JSX / React
JSX is handled automatically. Catenary ships a default inherit entry
that routes javascriptreact to the javascript server — no extra
config needed. To customize the variant independently:
[language.javascriptreact]
inherit = "javascript"
min_severity = "error" # optional per-variant override
Links
Julia
Install
macOS / Linux / Windows
From the Julia REPL:
using Pkg
Pkg.add("LanguageServer")
Config
Add to ~/.config/catenary/config.toml:
[server.julia-ls]
command = "julia"
args = ["--startup-file=no", "--history-file=no", "-e", "using LanguageServer; runserver()"]
[language.julia]
servers = ["julia-ls"]
Notes
- The server starts a Julia process, which has some startup time
--startup-file=noand--history-file=nospeed up startup- First run on a project may take time to load packages and index
- Works best with projects that have a
Project.toml
Reducing Startup Time
For faster startup, you can create a custom sysimage:
using PackageCompiler
create_sysimage([:LanguageServer], sysimage_path="languageserver.so")
Then use:
[server.julia-ls]
command = "julia"
args = ["--sysimage=/path/to/languageserver.so", "-e", "using LanguageServer; runserver()"]
[language.julia]
servers = ["julia-ls"]
Links
Markdown
Install
macOS
brew install marksman
Linux
Download the latest binary from GitHub releases:
# Example for x86_64
curl -L https://github.com/artempyanykh/marksman/releases/latest/download/marksman-linux-x64 -o marksman
chmod +x marksman
sudo mv marksman /usr/local/bin/
Windows
# With Chocolatey
choco install marksman
# Or download from GitHub releases
# https://github.com/artempyanykh/marksman/releases
Config
Add to ~/.config/catenary/config.toml:
[server.marksman]
command = "marksman"
args = ["server"]
[language.markdown]
servers = ["marksman"]
Notes
- Marksman provides document symbols (headings), go-to-definition for wiki-links, and references
- Works well with
codebase_mapto show document structure - Supports wiki-style
[[links]]and standard[links](url)
Links
PHP
Install
Intelephense is the most popular PHP language server.
macOS
npm install -g intelephense
Linux
npm install -g intelephense
Windows
npm install -g intelephense
Config
Add to ~/.config/catenary/config.toml:
[server.intelephense]
command = "intelephense"
args = ["--stdio"]
[language.php]
servers = ["intelephense"]
Notes
- Intelephense has a free tier and a premium tier with additional features
- The free tier includes: completions, hover, definitions, references, diagnostics, formatting
- Premium adds: rename, code actions, go to implementation
- Works great with Laravel, Symfony, WordPress, and vanilla PHP
Alternatives
phpactor
A free, open-source alternative:
# Install via composer
composer global require phpactor/phpactor
[server.phpactor]
command = "phpactor"
args = ["language-server"]
[language.php]
servers = ["phpactor"]
Links
Python
Install
Pyright is a fast, feature-rich Python language server from Microsoft.
macOS
npm install -g pyright
Or via Homebrew:
brew install pyright
Linux
npm install -g pyright
Windows
npm install -g pyright
Config
Add to ~/.config/catenary/config.toml:
[server.python]
command = "pyright-langserver"
args = ["--stdio"]
[server.python.settings.python]
pythonPath = "/usr/bin/python3"
[server.python.settings.python.analysis]
exclude = ["**/target", "**/node_modules"]
extraPaths = []
[language.python]
servers = ["python"]
Settings
Pyright requests configuration via workspace/configuration. Use the settings
table on the [server.*] entry to provide Python interpreter paths, analysis
exclusions, and other options (shown above).
Without these settings, pyright may fall back to scanning the entire workspace
(including large directories like target/ or node_modules/), which can
cause extremely slow initialization.
See the Pyright configuration docs for the full list of available settings.
Notes
- Pyright provides type checking even for untyped code (infers types)
- Works well with virtual environments — activate your venv before starting your MCP client
- For Django/Flask projects, Pyright handles most patterns out of the box
Alternatives
Pylsp (python-lsp-server)
A community-maintained server with plugin support:
pip install python-lsp-server
[server.pylsp]
command = "pylsp"
[language.python]
servers = ["pylsp"]
Jedi Language Server
Lightweight, uses Jedi for completions:
pip install jedi-language-server
[server.jedi]
command = "jedi-language-server"
[language.python]
servers = ["jedi"]
Links
Rust
Install
macOS
rustup component add rust-analyzer
Linux
rustup component add rust-analyzer
Windows
rustup component add rust-analyzer
Config
Add to ~/.config/catenary/config.toml:
[server.rust]
command = "rust-analyzer"
[server.rust.initialization_options]
check.command = "clippy"
cargo.features = "all"
diagnostics.disabled = ["inactive-code"]
[language.rust]
servers = ["rust"]
Notes
- rust-analyzer is the official Rust language server
- Installing via rustup ensures it stays in sync with your Rust toolchain
- First run on a project may take time to index (watch for “Indexing” status)
Links
Shell (Bash)
Install
macOS
npm install -g bash-language-server
Linux
npm install -g bash-language-server
Windows
npm install -g bash-language-server
Config
Add to ~/.config/catenary/config.toml:
[server.bash-ls]
command = "bash-language-server"
args = ["start"]
[language.shellscript]
servers = ["bash-ls"]
Notes
- The language ID is
shellscript, notbashorsh - Works with
.sh,.bash,.zshfiles - Provides completions for commands, variables, and functions
- Integrates with ShellCheck for linting (install separately)
Optional: ShellCheck Integration
For better diagnostics, install ShellCheck:
# macOS
brew install shellcheck
# Linux (Debian/Ubuntu)
apt install shellcheck
# Linux (Arch)
pacman -S shellcheck
The language server will automatically use it if available.
Links
Termux & Packaging
The termux-language-server provides advanced support for specialized shell scripts used in Termux, Arch Linux (PKGBUILD), Gentoo (ebuild), and Debian development.
Install
macOS
pip install termux-language-server
Linux
pip install termux-language-server
Windows
pip install termux-language-server
Config
Add to ~/.config/catenary/config.toml:
[server.termux-ls]
command = "termux-language-server"
args = ["--stdio"]
[language.termux]
servers = ["termux-ls"]
[language.pkgbuild]
servers = ["termux-ls"]
[language.ebuild]
servers = ["termux-ls"]
[language.eclass]
servers = ["termux-ls"]
[language.makepkg]
servers = ["termux-ls"]
[language.devscripts]
servers = ["termux-ls"]
[language.mdd]
servers = ["termux-ls"]
[language.subpackage]
servers = ["termux-ls"]
[language.install]
servers = ["termux-ls"]
[language.gentoo-make-conf]
servers = ["termux-ls"]
[language."make.conf"]
servers = ["termux-ls"]
[language."color.map"]
servers = ["termux-ls"]
Notes
- This server is specifically designed for packaging and system-level shell scripts.
- It extends the features of
bash-language-serverfor specialized formats. - Supports file types like
PKGBUILD,build.sh,*.ebuild, and*.mdd.
Links
TypeScript
Install
macOS
npm install -g typescript typescript-language-server
Linux
npm install -g typescript typescript-language-server
Windows
npm install -g typescript typescript-language-server
Config
Add to ~/.config/catenary/config.toml:
[server.tsserver]
command = "typescript-language-server"
args = ["--stdio"]
[language.typescript]
servers = ["tsserver"]
Notes
- The same server handles both TypeScript and JavaScript (see JavaScript)
- Requires
typescriptas a peer dependency - Works with
.ts,.tsx,.mts,.ctsfiles - Reads your
tsconfig.jsonfor project settings