catenary-mcp 1.4.0

A high-performance multiplexing bridge between MCP (Model Context Protocol) and LSP (Language Server Protocol). Enables LLMs to access IDE-grade code intelligence across multiple languages simultaneously with smart routing and UTF-8 accuracy.
Documentation
# Plugin Architecture

Catenary ships plugins for two AI CLI hosts from a single repository. Each host
has its own plugin format and file layout, but they share the same `catenary`
binary and MCP server.

## Repository Layout

```
Catenary/
├── .claude-plugin/
│   └── marketplace.json        # Claude Code marketplace metadata
├── plugins/
│   └── catenary/               # Claude Code plugin root
│       ├── .mcp.json           # MCP server declaration
│       ├── hooks/
│       │   └── hooks.json      # Claude Code hooks
│       ├── config.example.toml
│       └── README.md
├── gemini-extension.json       # Gemini CLI extension manifest
├── hooks/
│   └── hooks.json              # Gemini CLI hooks
└── ...
```

The two plugin roots are:

| Host       | Plugin root          | Hooks file                       |
| ---------- | -------------------- | -------------------------------- |
| Claude Code | `plugins/catenary/` | `plugins/catenary/hooks/hooks.json` |
| Gemini CLI | repo root (`/`)      | `hooks/hooks.json`               |

Both hosts expect hooks in a `hooks/hooks.json` file relative to the plugin
root. The manifest file (where the MCP server is declared) is separate from the
hooks file in both cases.

## Claude Code Plugin

Installed via the marketplace:

```bash
claude plugin marketplace add MarkWells-Dev/Catenary
claude plugin install catenary@catenary
```

### Updating after a new release

Claude Code caches plugin files (including `hooks.json`) under
`~/.claude/plugins/cache/` at install time. Updating the `catenary` binary alone
does not refresh the cached hooks. To fully apply a Catenary update, remove and
reinstall the plugin:

```bash
claude plugin remove catenary@catenary
claude plugin install catenary@catenary
```

Then start a new Claude Code session. Running sessions use the hooks that were
cached when the session started, and their MCP server process runs for the
session lifetime — protocol changes require a fresh session.

### Plugin source

`.claude-plugin/marketplace.json` points to the plugin source directory:

```json
"source": "./plugins/catenary"
```

Inside `plugins/catenary/`:

- **`.mcp.json`** — declares the MCP server (`catenary` command).
- **`hooks/hooks.json`** — registers hooks for diagnostics, root sync, and
  file locking:
  - `PreToolUse` (all tools): runs `catenary sync-roots` to pick up `/add-dir`
    workspace additions and directory removals.
  - `PreToolUse` on `Edit|Write|NotebookEdit|Read`: runs `catenary acquire` to
    serialize concurrent file access across agents.
  - `PostToolUse` on `Edit|Write|NotebookEdit|Read`: runs `catenary release`
    which handles the full post-tool pipeline — diagnostics notify, mtime
    tracking, then lock release with grace period.
  - `PostToolUseFailure` on `Edit|Write|NotebookEdit|Read`: runs
    `catenary release --grace 0` for immediate lock release on failure.
- **`config.example.toml`** — example Catenary configuration.

## Gemini CLI Extension

Installed via:

```bash
gemini extensions install https://github.com/MarkWells-Dev/Catenary
```

The extension root is the repository root. Two files matter:

- **`gemini-extension.json`** — manifest declaring the MCP server. Does **not**
  contain hooks (Gemini CLI ignores hooks defined in the manifest).
- **`hooks/hooks.json`** — registers hooks for diagnostics and file locking:
  - `BeforeTool` on `read_file|write_file|replace`: runs
    `catenary acquire --format=gemini` to serialize concurrent file access.
  - `AfterTool` on `read_file|write_file|replace`: runs
    `catenary release --format=gemini` which handles the full post-tool
    pipeline — diagnostics notify, mtime tracking, then lock release.

## Hook Contracts

All hook commands (`catenary acquire`, `catenary release`, `catenary sync-roots`)
read hook JSON from stdin. They silently succeed on any error to avoid breaking
the host CLI's flow.

### `catenary acquire`

Triggered before file reads or edits (Claude Code `PreToolUse`, Gemini
`BeforeTool`). Acquires a file-level advisory lock, blocking until the lock is
available or the timeout expires. This serializes concurrent access to the same
file across multiple agents.

**Fields consumed from hook JSON:**

| Field | Used for |
| ----- | -------- |
| `session_id` | Lock owner identity (primary key) |
| `agent_id` | Lock owner identity (appended if present) |
| `tool_input.file_path` or `tool_input.file` | File to lock |
| `cwd` | Resolving relative file paths and finding the session for monitor events |

**Flags:**

| Flag | Required | Description |
| ---- | -------- | ----------- |
| `--timeout` | no (default 180) | Seconds to wait before giving up |
| `--format` | yes | Output format (`claude` or `gemini`) |

**Output:** silent on success. On timeout, returns JSON with
`permissionDecision: "deny"`. If the file was modified since the owner's last
read, returns JSON with `additionalContext` warning.

### `catenary release`

Triggered after file reads or edits (Claude Code `PostToolUse`, Gemini
`AfterTool`). Runs the full post-tool pipeline:

1. **Diagnostics notify** — connects to the session's notify socket and returns
   LSP diagnostics to stdout (when `--format` is provided).
2. **Track read** — records the file's mtime so future `acquire` calls can
   detect external modifications (when `--format` is provided).
3. **Lock release** — releases the lock with a grace period, allowing the same
   agent to re-acquire without contention during diagnostics→fix cycles.

When `--format` is omitted (failure path, e.g. `--grace 0`), skips diagnostics
and track-read, just releasing the lock immediately.

**Fields consumed from hook JSON:**

| Field | Used for |
| ----- | -------- |
| `session_id` | Lock owner identity |
| `agent_id` | Lock owner identity (appended if present) |
| `tool_input.file_path` or `tool_input.file` | File to unlock |
| `cwd` | Resolving relative file paths and finding the session for diagnostics/monitor events |

**Flags:**

| Flag | Required | Description |
| ---- | -------- | ----------- |
| `--grace` | no (default 30) | Seconds before the lock expires |
| `--format` | no | Output format (`claude` or `gemini`). When set, runs diagnostics and track-read before releasing |

### `catenary sync-roots`

Triggered before each tool use (Claude Code only). Scans the Claude Code
transcript for `/add-dir` additions and directory removals, then sends the full
workspace root set to the running Catenary session. The server diffs against its
current state, applying both additions and removals to LSP clients and the search
index.

State is persisted in `known_roots.json` (inside the session directory) to track
the transcript byte offset and the full discovered root set across invocations.

**Fields consumed from hook JSON:**

| Field | Used for |
| ----- | -------- |
| `transcript_path` | Path to the Claude Code transcript file |
| `cwd` | Identifying which Catenary session to update |

## Version Management

Three files carry the version number:

| File | Field |
| ---- | ----- |
| `Cargo.toml` | `version` |
| `.claude-plugin/marketplace.json` | `plugins[0].version` |
| `gemini-extension.json` | `version` |

The `make release-*` targets bump all three atomically. A `version_sync` test
(`tests/version_sync.rs`) verifies they stay in sync.