Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

Prerequisites

Install Catenary

cargo install --git https://github.com/TwoWells/Catenary catenary-mcp

Connect to Your AI CLI

The catenary binary 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 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 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:

StatusMeaning
readyServer spawned, initialized, and capabilities listed
command not foundBinary not on $PATH
spawn failedBinary found but process failed to start
initialize failedProcess started but LSP handshake failed
skippedNo files for this language in the workspace

Use --root to check a different workspace:

catenary doctor --root /path/to/project

Next Steps

  1. Configure your language servers
  2. Install language servers for your languages

Configuration

Catenary loads configuration from multiple sources, in order of priority (last wins):

  1. Defaults: idle_timeout = 300, log_retention_days = 7.
  2. User config: ~/.config/catenary/config.toml.
  3. Project config: .catenary.toml in the current directory or any parent (searches upward).
  4. Explicit file: --config <path>.
  5. 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

ExtensionLanguage ID
.rsrust
.gogo
.cc
.cpp, .cc, .cxx, .h, .hppcpp
.zigzig
.dd
.vv
.nimnim
.javajava
.kt, .ktskotlin
.scala, .scscala
.groovy, .gvygroovy
.clj, .cljs, .cljcclojure
.cscsharp
.fs, .fsx, .fsifsharp
.swiftswift
.m, .mmobjective-c
.pypython
.rbruby
.pl, .pmperl
.phpphp
.lualua
.tcltcl
.crcrystal
.js, .mjs, .cjsjavascript
.ts, .mts, .ctstypescript
.tsxtypescriptreact
.jsxjavascriptreact
.hs, .lhshaskell
.ml, .mliocaml
.elmelm
.gleamgleam
.ex, .exselixir
.erl, .hrlerlang
.purspurescript
.sh, .bash, .zsh, .ebuild, .eclass, .installshellscript
.fishfish
.ps1, .psm1, .psd1powershell
.r, .Rr
.jljulia
.mojomojo
.html, .htmhtml
.csscss
.scssscss
.sasssass
.lessless
.sveltesvelte
.vuevue
.json, .jsoncjson
.yaml, .ymlyaml
.tomltoml
.xml, .xsl, .xslt, .xsdxml
.sqlsql
.graphql, .gqlgraphql
.protoproto
.md, .mdxmarkdown
.rstrestructuredtext
.tex, .latexlatex
.typtypst
.nixnix
.tf, .tfvarsterraform
.cmakecmake
.dartdart
.dockerfiledockerfile

By filename

FilenameLanguage ID
Dockerfiledockerfile
Makefile, GNUmakefilemakefile
CMakeLists.txtcmake
Cargo.toml, Cargo.locktoml
Gemfile, Rakefileruby
Justfile, justfilejust
PKGBUILDshellscript

By shebang

For files without a recognised extension, Catenary reads the first line. If it starts with #!, the interpreter name is matched:

InterpreterLanguage ID
bash, sh, zsh, dash, kshshellscript
fishfish
python, python3, python2python
node, nodejsjavascript
denotypescript
ruby, irbruby
perlperl
phpphp
lua, luajitlua
tclsh, wishtcl
Rscriptr
juliajulia
elixir, iexelixir
erlerlang
swiftswift
kotlinkotlin
scalascala
groovygroovy
crystalcrystal

Global Options

OptionDefaultDescription
idle_timeout300Seconds before auto-closing idle documents. 0 to disable.
log_retention_days7Days to keep dead session data. 0 = remove on startup. -1 = retain forever.

Icons

The [icons] table controls icons in the TUI dashboard.

PresetDescription
unicode (default)Safe symbols for any terminal font.
nerdNerd 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:

KeyAction
j / DownNext session
k / UpPrevious session
SpaceToggle expand/collapse
h / lScroll horizontally (events)
rRefresh
xDelete session data (dead sessions only)
q / EscQuit

Events pane:

KeyAction
j / DownNext event
k / UpPrevious event
SpaceToggle expand/collapse
h / lScroll horizontally
Ctrl-uPage up
Ctrl-dPage down
GJump to latest
yYank selected event
fOpen filter input
FClear 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)PageServer
CSS, HTML, JSONCSS-HTML-JSONvscode-langservers-extracted
GoGogopls
JavaScriptJavaScripttypescript-language-server
JuliaJuliaLanguageServer.jl
MarkdownMarkdownmarksman
PHPPHPintelephense
PythonPythonpyright
RustRustrust-analyzer
Shell (Bash)Shellbash-language-server
Termux & PackagingTermuxtermux-language-server
TypeScriptTypeScripttypescript-language-server

Contributing

Want to add a language?

  1. Create your-language.md in the lsp/ folder following the template below
  2. Add a row to the table above
  3. 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:

ServerLanguages
vscode-css-language-serverCSS, SCSS, Less
vscode-html-language-serverHTML
vscode-json-language-serverJSON, JSONC
vscode-markdown-language-serverMarkdown
vscode-eslint-language-serverESLint

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)

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

  • gopls is 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

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, .cjs files
  • Provides type inference even in plain JavaScript
  • Add a jsconfig.json to 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

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=no and --history-file=no speed 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"]

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_map to show document structure
  • Supports wiki-style [[links]] and standard [links](url)

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"]

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"]

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)

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, not bash or sh
  • Works with .sh, .bash, .zsh files
  • 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.

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-server for specialized formats.
  • Supports file types like PKGBUILD, build.sh, *.ebuild, and *.mdd.

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 typescript as a peer dependency
  • Works with .ts, .tsx, .mts, .cts files
  • Reads your tsconfig.json for project settings