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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# CLI Integration

Integrate catenary-mcp with existing AI coding assistants (Claude Code, Gemini
CLI) by constraining their built-in tools so the model uses catenary's
LSP-backed navigation instead of text scanning.

## Why Not a Custom CLI?

The original plan was to build `catenary-cli` to control the model agent loop.
This was abandoned because:

**Subscription plans are tied to official CLI tools.** Claude Code and Gemini
CLI use subscription billing ($20/month Pro tier). A custom CLI would require
API keys with pay-per-token billing — different billing system, higher cost for
the target audience (individual developers).

**The constraint we wanted is achievable without a custom CLI.** Both tools
support:

1. Disabling built-in tools
2. Adding MCP servers as replacements
3. Workspace-level configuration

We get the same outcome — model forced to use catenary tools — without
maintaining a CLI.

## Design Principles

Preserved from the original CLI design:

### LSP-First

- Hover instead of file read (for type info)
- Symbols instead of grep (for definitions)
- Diagnostics on write (catch errors immediately)

### Efficient

- Every token counts — users are on Pro tier, not unlimited
- LSP queries cost fewer tokens than file reads
- Diagnostics prevent wasted cycles on broken code

## Configuration

### Gemini CLI

Location: `~/.gemini/policies/` (user) or `.gemini/settings.json` (workspace)

**Recommended: Extension + Constrained Mode.**

1.  **Install the Extension:** The Catenary extension provides
    `BeforeTool` / `AfterTool` hooks that run `catenary acquire` /
    `catenary release` around file operations. This ensures file locking
    and the model sees LSP diagnostics immediately.

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

2.  **Constrained mode.** Use the Policy Engine to deny text-scanning commands while
    keeping Gemini's native file I/O and shell tools available. Create the file
    `~/.gemini/policies/catenary-constrained.toml`:

```toml
# Catenary constrained mode — forces LSP-first navigation
# Place in ~/.gemini/policies/catenary-constrained.toml

# --- 1. Search (Grep Family) ---
[[rule]]
toolName = "run_shell_command"
commandPrefix = [
  "rg", "ag", "ack", "fd",
  "grep", "egrep", "fgrep", "rgrep", "zgrep",
  "git grep",
]
decision = "deny"
priority = 900
deny_message = "Use Catenary's search tool instead."

# --- 2. Navigation (Listing Family) ---
[[rule]]
toolName = "run_shell_command"
commandPrefix = [
  "ls", "dir", "vdir", "tree", "find",
  "locate", "mlocate", "whereis", "which",
  "git ls-files", "git ls-tree",
]
decision = "deny"
priority = 900
deny_message = "Use Catenary's list_directory tool instead."

# --- 3. Peeking (Reading Family) ---
[[rule]]
toolName = "run_shell_command"
commandPrefix = [
  "cat", "head", "tail", "more", "less", "nl",
  "od", "hexdump", "xxd", "strings", "dd", "tee",
]
decision = "deny"
priority = 900
deny_message = "Use the native read_file tool instead."

# --- 4. Text Processing (Scripting Family) ---
[[rule]]
toolName = "run_shell_command"
commandPrefix = [
  "awk", "sed", "perl",
  "cut", "paste", "sort", "uniq", "join",
]
decision = "deny"
priority = 900
deny_message = "Text processing commands are not allowed in constrained mode."

# --- 5. Reconnaissance (Metadata Family) ---
[[rule]]
toolName = "run_shell_command"
commandPrefix = ["file", "stat", "du", "df"]
decision = "deny"
priority = 900
deny_message = "Metadata commands are not allowed in constrained mode."

# --- 6. Executors & Shells (The Wrapper Family) ---
[[rule]]
toolName = "run_shell_command"
commandPrefix = [
  "bash", "sh", "zsh", "dash", "fish",
  "ash", "csh", "ksh", "tcsh",
]
decision = "deny"
priority = 900
deny_message = "Shell wrappers are not allowed in constrained mode."

# --- 7. The Command Runners (Prevents Masquerading) ---
[[rule]]
toolName = "run_shell_command"
commandPrefix = [
  "env", "sudo", "su", "nohup", "timeout", "watch", "time",
  "eval", "exec", "command", "builtin", "type", "hash",
]
decision = "deny"
priority = 900
deny_message = "Command runners are not allowed in constrained mode."

# --- 8. The Multiplexers ---
[[rule]]
toolName = "run_shell_command"
commandPrefix = ["xargs", "parallel"]
decision = "deny"
priority = 900
deny_message = "Multiplexers are not allowed in constrained mode."

# --- 9. Framework Tool Blocks ---
[[rule]]
toolName = "grep_search"
decision = "deny"
priority = 900
deny_message = "Use Catenary's search tool instead."

[[rule]]
toolName = "glob"
decision = "deny"
priority = 900
deny_message = "Use Catenary's list_directory tool instead."

[[rule]]
toolName = "read_many_files"
decision = "deny"
priority = 900
deny_message = "Use Catenary's LSP tools for code navigation."

[[rule]]
toolName = "list_directory"
decision = "deny"
priority = 900
deny_message = "Use Catenary's list_directory tool instead."
```

Then add the MCP server to `.gemini/settings.json`:

```json
{
  "mcpServers": {
    "catenary": {
      "command": "catenary"
    }
  }
}
```

**Built-in tool names** (from `packages/core/src/tools/tool-names.ts`):

| Tool              | Internal Name       |
| ----------------- | ------------------- |
| LSTool            | `list_directory`    |
| ReadFileTool      | `read_file`         |
| WriteFileTool     | `write_file`        |
| EditTool          | `replace`           |
| GrepTool          | `grep_search`       |
| GlobTool          | `glob`              |
| ReadManyFilesTool | `read_many_files`   |
| ShellTool         | `run_shell_command` |
| WebFetchTool      | `web_fetch`         |
| WebSearchTool     | `google_web_search` |
| MemoryTool        | `save_memory`       |

### Claude Code

Location: `.claude/settings.json` (workspace) or `~/.claude/settings.json`
(user)

**Recommended: Hook-based integration.** Claude Code's native `Read`, `Edit`,
and `Write` tools handle file I/O with inline diffs and syntax highlighting.
Catenary provides file locking and LSP diagnostics via `PreToolUse` /
`PostToolUse` hooks — the lock is held through the full edit→diagnostics cycle.

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write|NotebookEdit|Read",
        "hooks": [
          {
            "type": "command",
            "command": "catenary acquire --format=claude"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write|NotebookEdit|Read",
        "hooks": [
          {
            "type": "command",
            "command": "catenary release --format=claude"
          }
        ]
      }
    ],
    "PostToolUseFailure": [
      {
        "matcher": "Edit|Write|NotebookEdit|Read",
        "hooks": [
          {
            "type": "command",
            "command": "catenary release --grace 0"
          }
        ]
      }
    ]
  },
  "mcpServers": {
    "catenary": {
      "command": "catenary"
    }
  }
}
```

The `catenary release` command reads the hook's JSON from stdin, finds the
running Catenary session for the workspace, returns any LSP diagnostics,
records the file's mtime, and releases the lock. It exits silently on any
error so it never blocks Claude Code's flow.

**Alternative: Constrained mode.** Keep Claude Code's native `Read`, `Edit`,
`Write`, and `Bash` tools but deny text-scanning commands to force LSP-first
navigation. This deny list blocks grep, file listing, manual reads, text
processing, shell wrappers, and framework tools that would bypass Catenary.

```json
{
  "permissions": {
    "allow": [
      "WebSearch",
      "WebFetch",
      "mcp__catenary__*",
      "mcp__plugin_catenary_catenary__*",
      "ToolSearch",
      "AskUserQuestion",
      "Bash"
    ],
    "deny": [
      "// --- 1. Search (Grep Family) ---",
      "Bash(rg *)",
      "Bash(ag *)",
      "Bash(ack *)",
      "Bash(fd *)",
      "Bash(grep *)",
      "Bash(egrep *)",
      "Bash(fgrep *)",
      "Bash(rgrep *)",
      "Bash(zgrep *)",
      "Bash(git grep *)",
      "// --- 2. Navigation (Listing Family) ---",
      "Bash(ls *)",
      "Bash(dir *)",
      "Bash(vdir *)",
      "Bash(tree *)",
      "Bash(find *)",
      "Bash(locate *)",
      "Bash(mlocate *)",
      "Bash(whereis *)",
      "Bash(which *)",
      "Bash(git ls-files *)",
      "Bash(git ls-tree *)",
      "// --- 3. Peeking (Reading Family) ---",
      "Bash(cat *)",
      "Bash(head *)",
      "Bash(tail *)",
      "Bash(more *)",
      "Bash(less *)",
      "Bash(nl *)",
      "Bash(od *)",
      "Bash(hexdump *)",
      "Bash(xxd *)",
      "Bash(strings *)",
      "Bash(dd *)",
      "Bash(tee *)",
      "// --- 4. Text Processing (Scripting Family) ---",
      "Bash(awk *)",
      "Bash(sed *)",
      "Bash(perl *)",
      "Bash(cut *)",
      "Bash(paste *)",
      "Bash(sort *)",
      "Bash(uniq *)",
      "Bash(join *)",
      "// --- 5. Reconnaissance (Metadata Family) ---",
      "Bash(file *)",
      "Bash(stat *)",
      "Bash(du *)",
      "Bash(df *)",
      "// --- 6. Executors & Shells (The Wrapper Family) ---",
      "Bash(bash *)",
      "Bash(sh *)",
      "Bash(zsh *)",
      "Bash(dash *)",
      "Bash(fish *)",
      "Bash(ash *)",
      "Bash(csh *)",
      "Bash(ksh *)",
      "Bash(tcsh *)",
      "// --- 7. The Command Runners (Prevents Masquerading) ---",
      "Bash(env *)",
      "Bash(sudo *)",
      "Bash(su *)",
      "Bash(nohup *)",
      "Bash(timeout *)",
      "Bash(watch *)",
      "Bash(time *)",
      "Bash(eval *)",
      "Bash(exec *)",
      "Bash(command *)",
      "Bash(builtin *)",
      "Bash(type *)",
      "Bash(hash *)",
      "// --- 8. The Multiplexers ---",
      "Bash(xargs *)",
      "Bash(parallel *)",
      "// --- 9. Framework Blocks ---",
      "Grep",
      "Glob",
      "Task"
    ]
  },
  "mcpServers": {
    "catenary": {
      "command": "catenary"
    }
  }
}
```

This keeps `Bash` available for build/test/git commands while blocking every
path that would let the model fall back to text scanning. The model uses:

- **Catenary LSP tools** for navigation (`search`, `hover`, `definition`, etc.)
- **Catenary `list_directory`** for directory browsing (replaces `ls`, `tree`, `find`)
- **Claude Code `Read`/`Edit`/`Write`** for file I/O (with `catenary release` hook for diagnostics)
- **Claude Code `Bash`** for build, test, and git commands only

## Experiment Results

### Current: Policy Engine (Gemini) + Deny List (Claude)

Validated 2026-02-17.

| Test                     | Gemini CLI                | Claude Code                                 |
| ------------------------ | ------------------------- | ------------------------------------------- |
| Restriction method       | Policy Engine (`deny`)    | `permissions.deny` list + block `Grep/Glob/Task` |
| MCP tools discovered     |||
| Text scanning blocked    |||
| Model adapts gracefully  | ✓ (immediately)           | ✓ (immediately)                             |
| Sub-agent escape blocked | N/A                       | ✓ (requires denying `Task`)                 |

The policy engine approach gives models clear feedback on *why* a tool is
blocked and *what to use instead* (via `deny_message`). This eliminates the
thrashing seen with earlier approaches — models go straight to Catenary tools
on the first turn without attempting workarounds.

Tested with `gemini-3-flash-preview` and `claude-opus-4-6`. Both adapted
on the first prompt with zero fallback attempts.

### Historical: `tools.core` Allowlist (Gemini, deprecated)

Validated 2026-02-06.

The original Gemini approach used `tools.core` to allowlist only non-file
tools (`web_fetch`, `google_web_search`, `save_memory`), hiding all built-in
file and shell tools. This worked but models adapted slowly — Gemini would
try several workarounds (WebFetch for local files, sub-agent delegation)
before settling on Catenary tools. The policy engine approach replaced this
by giving explicit deny messages instead of silently removing tools.

## Catenary Tool Coverage

Catenary provides LSP intelligence and directory browsing:

| Tool                | Category  | Notes                                    |
| ------------------- | --------- | ---------------------------------------- |
| `list_directory`    | File I/O  | Files, dirs, symlinks                    |
| `search`            | LSP       | Workspace symbols + grep fallback        |
| `find_references`   | LSP       | LSP references                           |
| `codebase_map`      | LSP       | File tree with symbols                   |
| `document_symbols`  | LSP       | File structure                           |
| `hover`             | LSP       | Type info, docs                          |
| `diagnostics`       | LSP       | Errors, warnings                         |
| ...                 | LSP       | [Full list]overview.md#available-tools |

File I/O is handled by the host tool's native file operations. Catenary
provides post-edit diagnostics via the `catenary release` hook.

## Limitations

### LSP Dependency

Some operations require LSP:

- Find references (no grep fallback currently)
- Rename symbol
- Code actions

If LSP is unavailable for a language, these tools return errors. `search` has
a grep fallback for basic text matching when no LSP server covers the file.

## See Also

- [Archive: CLI Design]archive/cli-design.md — Original custom CLI design
  (abandoned)
- [Configuration]configuration.md — catenary-mcp configuration reference