@crabeye-ai/crabeye-mcp-bridge 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,24 +6,7 @@ Every MCP server you add to your AI assistant means another connection, another
6
6
 
7
7
  crabeye-mcp-bridge consolidates all your upstream MCP servers behind a single STDIO interface and exposes exactly **two tools** to the assistant: `search_tools` and `run_tool`. Tools from every server are discovered, namespaced, and indexed at startup, but none of them touch the context window until the assistant actually searches for them. You can have a thousand tools ready to go without bloating the context, with fuzzy search to find them and per-tool execution policies to control what runs freely, what needs approval, and what is blocked.
8
8
 
9
- ```mermaid
10
- %%{init: {'flowchart': {'nodeSpacing': 25, 'rankSpacing': 70}}}%%
11
- flowchart LR
12
- classDef client fill:#dbeafe,stroke:#2563eb,stroke-width:2px
13
- classDef bridge fill:#ede9fe,stroke:#7c3aed,stroke-width:3px
14
- classDef server fill:#d1fae5,stroke:#059669,stroke-width:2px
15
- classDef more fill:#f1f5f9,stroke:#64748b,stroke-width:2px,stroke-dasharray:5 5
16
-
17
- A["AI Assistant<br/>(Claude, Cursor, Windsurf, ...)"]:::client
18
- A -- "1 STDIO connection<br/>2 tools exposed" --> B
19
- B["crabeye-mcp-bridge<br/>search_tools + run_tool"]:::bridge
20
-
21
- B -- STDIO --> C["Linear<br/>47 tools"]:::server
22
- B -- STDIO --> D["GitHub<br/>32 tools"]:::server
23
- B -- HTTP --> E["Slack<br/>28 tools"]:::server
24
- B -- SSE --> F["Sentry<br/>19 tools"]:::server
25
- B -.-> G["N more<br/>servers"]:::more
26
- ```
9
+ ![crabeye-mcp-bridge topology: one STDIO connection from an AI assistant to the bridge exposes two tools — search_tools and run_tool — while the bridge fans out to upstream MCP servers like Linear, GitHub, Slack, Sentry, and Figma. Tool schemas stay out of the context window until a search hits.](docs/img/hero.png)
27
10
 
28
11
  ## Quick start
29
12
 
@@ -93,512 +76,18 @@ Then rename `mcpServers` to `upstreamMcpServers`, add the bridge, and replace ha
93
76
 
94
77
  That's it. Your AI assistant now has access to all tools from all configured servers through a single connection. The bridge automatically excludes itself from `mcpServers` to avoid recursion, so pointing `--config` at the same file is safe.
95
78
 
96
- The bridge also reads `upstreamServers` (shorthand), `servers` (VS Code Copilot), and `context_servers` (Zed) as input keys. On duplicate names, earlier sources win: `upstreamMcpServers` > `upstreamServers` > `servers` > `context_servers` > `mcpServers`. Self-exclusion applies to `mcpServers` and `context_servers`.
97
-
98
- Alternatively, you can add the bridge alongside your existing `mcpServers` entries without renaming anything:
99
-
100
- ```json
101
- {
102
- "mcpServers": {
103
- "bridge": {
104
- "command": "npx",
105
- "args": ["-y", "@crabeye-ai/crabeye-mcp-bridge", "--config", "/path/to/this/file.json"]
106
- },
107
- "linear": {
108
- "command": "npx",
109
- "args": ["-y", "@anthropic/linear-mcp-server"]
110
- },
111
- "github": {
112
- "command": "npx",
113
- "args": ["-y", "@anthropic/github-mcp-server"],
114
- "env": {
115
- "GITHUB_TOKEN": "${credential:github-pat}"
116
- }
117
- }
118
- }
119
- }
120
- ```
121
-
122
- The bridge will pick up the other servers from `mcpServers` automatically (excluding itself). This makes it easier to see at a glance in your client which MCP servers are configured. However, you'll want to disable the other MCP servers in your client so the assistant uses the bridge as the single entry point rather than calling them directly, otherwise it defeats the purpose of using this tool.
123
-
124
- You can, of course, also use a completely different config file for the bridge. It will work as long as you add the bridge to your client's MCP config.
125
-
126
- ## How it works
127
-
128
- On startup the bridge launches every configured upstream server, connects to it, and discovers its tools. Each tool is namespaced by server name (e.g. `linear__create_issue`, `github__list_repos`) so tools from different servers never collide.
129
-
130
- Two meta-tools are exposed to the AI assistant:
131
-
132
- - **`search_tools`** — Fuzzy-search across all discovered tools by name, description, provider, or category. Matching tools are automatically enabled for use.
133
- - **`run_tool`** — Execute any discovered tool directly by its namespaced name (e.g. `linear__create_issue`).
134
-
135
- The AI assistant calls `search_tools` automatically when it detects a relevant intent, then uses `run_tool` or calls the auto-enabled tools directly.
136
-
137
- When `search_tools` is called, the assistant receives the matching tools and their input schemas. The bridge also directly exposes the searched tools to the assistant so they can be called natively. Some assistants don't refresh their tool list mid-session, so they may not see the newly exposed tools — but they can still call them through `run_tool` and the call is executed exactly as if made directly on the original tool.
138
-
139
- The bridge tracks how many tokens it saves compared to exposing all upstream tool definitions directly. Token savings are always logged to stderr after each search. To also include them in `search_tools` responses, pass `--stats`:
140
-
141
- ```json
142
- {
143
- "session_stats": {
144
- "tokens_saved": 11200,
145
- "baseline_tokens": 14200,
146
- "bridge_tokens": 3000
147
- },
148
- "results": [...]
149
- }
150
- ```
151
-
152
- - **`baseline_tokens`** — estimated tokens if all upstream tool definitions were injected into context without the bridge
153
- - **`bridge_tokens`** — cumulative tokens used by the bridge's two meta-tools plus all search results returned so far
154
- - **`tokens_saved`** — the difference (baseline − bridge)
155
-
156
- Token counts are estimated using a chars/4 heuristic.
157
-
158
- ## Examples
159
-
160
- ### Discovering providers
161
-
162
- The assistant starts by searching for providers to see what's available. Without a `tool` filter, provider summaries are returned — name, category, and tool count, but no tool details:
163
-
164
- ```json
165
- {
166
- "name": "search_tools",
167
- "arguments": {
168
- "queries": [{ "provider": "linear" }]
169
- }
170
- }
171
- ```
172
-
173
- Response:
174
-
175
- ```json
176
- {
177
- "results": [
178
- {
179
- "providers": [
180
- {
181
- "name": "linear",
182
- "category": "project management",
183
- "tool_count": 47,
184
- "tools": []
185
- }
186
- ],
187
- "total": 47,
188
- "count": 0,
189
- "offset": 0,
190
- "limit": 10
191
- }
192
- ]
193
- }
194
- ```
195
-
196
- To get full tool definitions, add `expand_tools: true` or use a `tool` filter to drill in:
197
-
198
- ```json
199
- {
200
- "name": "search_tools",
201
- "arguments": {
202
- "queries": [{ "provider": "linear", "expand_tools": true }]
203
- }
204
- }
205
- ```
206
-
207
- ### Searching for tools
208
-
209
- The assistant calls `search_tools` with a `tool` filter to find specific tools by name or description. Results are grouped by provider:
210
-
211
- ```json
212
- {
213
- "name": "search_tools",
214
- "arguments": {
215
- "queries": [{ "tool": "create issue" }]
216
- }
217
- }
218
- ```
219
-
220
- The bridge returns matching tools with their full input schemas, grouped by provider:
221
-
222
- ```json
223
- {
224
- "results": [
225
- {
226
- "providers": [
227
- {
228
- "name": "linear",
229
- "category": "project management",
230
- "tool_count": 47,
231
- "tools": [
232
- {
233
- "tool_name": "linear__create_issue",
234
- "source": "linear",
235
- "description": "Create a new Linear issue",
236
- "input_schema": {
237
- "type": "object",
238
- "properties": {
239
- "title": { "type": "string" },
240
- "team": { "type": "string" },
241
- "description": { "type": "string" }
242
- },
243
- "required": ["title", "team"]
244
- }
245
- }
246
- ]
247
- },
248
- {
249
- "name": "github",
250
- "tool_count": 32,
251
- "tools": [
252
- {
253
- "tool_name": "github__create_issue",
254
- "source": "github",
255
- "description": "Create a GitHub issue",
256
- "input_schema": { "..." }
257
- }
258
- ]
259
- }
260
- ],
261
- "total": 2,
262
- "count": 2,
263
- "offset": 0,
264
- "limit": 10
265
- }
266
- ]
267
- }
268
- ```
269
-
270
- Matching tools are automatically enabled for direct use by the assistant — no extra step needed.
271
-
272
- ### Multiple queries
273
-
274
- Pass multiple query objects to search for different things in a single call. Results are deduplicated across queries — first query wins. Summary and detail queries can be mixed:
275
-
276
- ```json
277
- {
278
- "name": "search_tools",
279
- "arguments": {
280
- "queries": [
281
- { "tool": "create issue" },
282
- { "provider": "github" },
283
- { "category": "design", "expand_tools": true }
284
- ]
285
- }
286
- }
287
- ```
288
-
289
- The first query returns tool details (has `tool` filter), the second returns a provider summary, and the third returns expanded tool details for design tools. Each query produces its own result set with independent pagination.
290
-
291
- ### Running a tool directly
292
-
293
- If the assistant already knows the namespaced name, it can skip the search and call `run_tool` directly:
79
+ The bridge also reads `upstreamServers` (shorthand), `servers` (VS Code Copilot), and `context_servers` (Zed) as input keys. See [docs/configuration.md](docs/configuration.md) for the full priority order and self-exclusion rules.
294
80
 
295
- ```json
296
- {
297
- "name": "run_tool",
298
- "arguments": {
299
- "name": "linear__create_issue",
300
- "arguments": {
301
- "title": "Fix login crash",
302
- "team": "Engineering",
303
- "description": "The app crashes on login when the session token is expired"
304
- }
305
- }
306
- }
307
- ```
308
-
309
- The response is passed through exactly as the upstream server returns it.
310
-
311
- ### Filtering by provider
312
-
313
- Combine a tool search with a provider filter to narrow results:
314
-
315
- ```json
316
- {
317
- "name": "search_tools",
318
- "arguments": {
319
- "queries": [{ "tool": "list", "provider": "github" }]
320
- }
321
- }
322
- ```
323
-
324
- Only tools from the `github` server matching "list" are returned, grouped under the `github` provider. Provider matching uses prefix match by default (`"git"` matches `"github"`, but `"hub"` does not).
81
+ Alternatively, you can add the bridge alongside your existing `mcpServers` entries without renaming anything — the bridge will pick up the other servers from `mcpServers` automatically (excluding itself). Disable the other MCP servers in your client so the assistant uses the bridge as the single entry point.
325
82
 
326
- ### Regex search
327
-
328
- For precise matching, prefix the tool query with `regex:`:
329
-
330
- ```json
331
- {
332
- "name": "search_tools",
333
- "arguments": {
334
- "queries": [{ "tool": "regex:^list_" }]
335
- }
336
- }
337
- ```
338
-
339
- This finds all tools whose name starts with `list_` — across every server.
340
-
341
- ## Configuration
342
-
343
- ### STDIO servers
344
-
345
- Servers that run as local subprocesses:
346
-
347
- ```json
348
- {
349
- "upstreamMcpServers": {
350
- "my-server": {
351
- "command": "node",
352
- "args": ["./server.js"],
353
- "env": { "API_KEY": "..." }
354
- }
355
- }
356
- }
357
- ```
358
-
359
- ### HTTP servers
360
-
361
- Remote servers accessible via HTTP:
362
-
363
- ```json
364
- {
365
- "upstreamMcpServers": {
366
- "remote-server": {
367
- "url": "https://mcp.example.com/sse",
368
- "type": "sse",
369
- "headers": { "Authorization": "Bearer ..." }
370
- }
371
- }
372
- }
373
- ```
374
-
375
- `type` defaults to `"streamable-http"`. Use `"sse"` for servers that use Server-Sent Events transport.
376
-
377
- ### Categories
378
-
379
- Assign a category to a server so tools can be discovered by domain rather than server name:
380
-
381
- ```json
382
- {
383
- "upstreamMcpServers": {
384
- "linear": {
385
- "command": "npx",
386
- "args": ["-y", "@anthropic/linear-mcp-server"],
387
- "_bridge": {
388
- "category": "project management"
389
- }
390
- },
391
- "figma": {
392
- "command": "npx",
393
- "args": ["-y", "@anthropic/figma-mcp-server"],
394
- "_bridge": {
395
- "category": "design"
396
- }
397
- }
398
- }
399
- }
400
- ```
401
-
402
- The assistant can then search by category: `{ "queries": [{ "category": "design" }] }`. Category matching uses prefix match by default, so `"project"` matches `"project management"`. Use `regex:` prefix for pattern matching.
403
-
404
- ### Authentication
405
-
406
- Secrets belong in the encrypted credential store, not in config files. Store a secret once, then reference it with `${credential:key}` in `env` or `headers`:
407
-
408
- ```bash
409
- # Store credentials
410
- crabeye-mcp-bridge credential set github-pat ghp_abc123
411
- crabeye-mcp-bridge credential set remote-api-key sk_live_xyz
412
- ```
413
-
414
- ```json
415
- {
416
- "upstreamMcpServers": {
417
- "github": {
418
- "command": "npx",
419
- "args": ["-y", "@anthropic/github-mcp-server"],
420
- "env": { "GITHUB_TOKEN": "${credential:github-pat}" }
421
- },
422
- "remote-api": {
423
- "url": "https://mcp.example.com/sse",
424
- "type": "sse",
425
- "headers": { "Authorization": "Bearer ${credential:remote-api-key}" }
426
- }
427
- }
428
- }
429
- ```
430
-
431
- Templates are resolved on every connect and reconnect, so rotating a credential takes effect without restarting the bridge.
432
-
433
- **How it works:** Credentials are encrypted with AES-256-GCM. The master key is stored in your OS keychain (macOS Keychain, Linux secret-tool, Windows Credential Manager). Set `MCP_BRIDGE_MASTER_KEY` (64 hex chars) to use a static key instead.
434
-
435
- **CLI commands:**
436
-
437
- | Command | Description |
438
- |---------|-------------|
439
- | `credential set <key> [value]` | Store a plain string secret |
440
- | `credential set <key> --json '<json>'` | Store a typed credential (bearer/oauth2/secret) |
441
- | `credential get <key>` | Retrieve a credential (masked by default, `--show-secret` for full) |
442
- | `credential delete <key>` | Delete a stored credential |
443
- | `credential list` | List all stored credential keys |
444
-
445
- Pipe-friendly: `echo "ghp_abc123" | crabeye-mcp-bridge credential set github-pat`
446
-
447
- ### Tool policies
448
-
449
- Control which tools can run automatically, require confirmation, or are blocked entirely. Three policy values:
450
-
451
- - `"always"` — tool runs without confirmation (default)
452
- - `"prompt"` — user is asked to approve each call via MCP elicitation
453
- - `"never"` — tool is disabled and cannot be called
454
-
455
- Policies cascade in this order (first match wins):
456
-
457
- 1. Per-tool (`_bridge.tools.<toolName>`)
458
- 2. Per-server (`_bridge.toolPolicy`)
459
- 3. Global (`_bridge.toolPolicy`)
460
- 4. Default: `"always"`
461
-
462
- ```json
463
- {
464
- "upstreamMcpServers": {
465
- "linear": {
466
- "command": "npx",
467
- "args": ["-y", "@anthropic/linear-mcp-server"],
468
- "_bridge": {
469
- "toolPolicy": "prompt",
470
- "tools": {
471
- "list_issues": "always",
472
- "delete_issue": "never"
473
- }
474
- }
475
- }
476
- },
477
- "_bridge": {
478
- "toolPolicy": "always"
479
- }
480
- }
481
- ```
482
-
483
- In this example, all Linear tools require confirmation except `list_issues` (runs freely) and `delete_issue` (blocked). Tools from other servers use the global default (`"always"`).
484
-
485
- ### Rate limiting
486
-
487
- Prevent the bridge from exceeding upstream API quotas by setting per-server rate limits. When the limit is hit, calls wait in a FIFO queue until the sliding window opens — the LLM just sees slightly higher latency instead of an error.
488
-
489
- ```json
490
- {
491
- "upstreamMcpServers": {
492
- "github": {
493
- "command": "npx",
494
- "args": ["-y", "@anthropic/github-mcp-server"],
495
- "_bridge": {
496
- "rateLimit": {
497
- "maxCalls": 30,
498
- "windowSeconds": 60
499
- }
500
- }
501
- }
502
- }
503
- }
504
- ```
505
-
506
- In this example, the bridge allows at most 30 tool calls to the `github` server per 60-second sliding window. If a 31st call arrives before the window slides, it waits until a slot opens. If the wait exceeds 30 seconds, the call fails with an error.
507
-
508
- Rate limit configuration is hot-reloadable — changes take effect without restarting the bridge.
509
-
510
- ### Discovery mode
511
-
512
- Control how searched tools are surfaced to the assistant with `--discovery-mode`:
513
-
514
- - **`both`** (default) — Search results include tool names, descriptions, and full input schemas. Matching tools are also added to the MCP tools list so the assistant can call them directly.
515
- - **`search`** — Search results include full tool details, but tools are NOT added to the MCP tools list. The assistant must use `run_tool` to execute them. Use this when your client doesn't handle dynamic tool list changes well.
516
- - **`tools_list`** — Matching tools are added to the MCP tools list with full schemas, but search results omit `input_schema` to avoid duplication. The assistant calls discovered tools directly by their namespaced name, or via `run_tool`.
517
-
518
- ```bash
519
- npx @crabeye-ai/crabeye-mcp-bridge --config config.json --discovery-mode search
520
- ```
521
-
522
- ## CLI
523
-
524
- ```
525
- npx @crabeye-ai/crabeye-mcp-bridge [command] [options]
526
- ```
527
-
528
- ### Commands
529
-
530
- | Command | Description |
531
- |---------|-------------|
532
- | *(default)* | Start the bridge |
533
- | `init` | Discover MCP client configs and set up the bridge |
534
- | `restore` | Remove the bridge from client configs and restore originals |
535
- | `credential set <key> [value]` | Store a credential (plain string, `--json` for typed, or pipe stdin) |
536
- | `credential get <key>` | Retrieve a credential (`--show-secret` to unmask) |
537
- | `credential delete <key>` | Delete a stored credential |
538
- | `credential list` | List all stored credential keys |
539
-
540
- ### Options (bridge mode)
541
-
542
- | Flag | Description |
543
- |------|-------------|
544
- | `-c, --config <path>` | Path to config file (or set `MCP_BRIDGE_CONFIG`) |
545
- | `--validate` | Validate config and list upstream servers, then exit |
546
- | `--stats` | Include `session_stats` in `search_tools` responses (always logged to stderr) |
547
- | `--discovery-mode <mode>` | How searched tools are surfaced: `search`, `tools_list`, or `both` (default) |
548
- | `-V, --version` | Print version |
549
- | `-h, --help` | Print help |
550
-
551
- ### `init`
552
-
553
- Scans for MCP client config files (Claude Desktop, Cursor, VS Code Copilot, Windsurf, Zed), lets you select which ones the bridge should use, and optionally injects the bridge entry into those configs.
554
-
555
- ```
556
- $ npx @crabeye-ai/crabeye-mcp-bridge init
557
- Scanning for MCP config files...
558
-
559
- ? Select config files to use with the bridge:
560
- [x] Claude Desktop ~/.claude/claude_desktop_config.json
561
- [x] Cursor ~/.cursor/mcp.json
562
- [ ] VS Code Copilot ~/Library/Application Support/Code/User/settings.json
563
-
564
- Saved config to ~/.crabeye-mcp-bridge/config.json
565
-
566
- ? Add bridge entry to selected client configs? (Y/n) y
567
- Updated ~/.claude/claude_desktop_config.json
568
- Updated ~/.cursor/mcp.json
569
-
570
- Done! Run `crabeye-mcp-bridge` to start.
571
- ```
572
-
573
- After `init`, you can start the bridge without `--config` — it reads the saved config paths automatically.
574
-
575
- ### `restore`
576
-
577
- Reverses `init` by removing the bridge entry from client configs and renaming the upstream servers key back to its original name. All changes are done via JSONC-aware editing that preserves comments and formatting.
578
-
579
- ```
580
- $ npx @crabeye-ai/crabeye-mcp-bridge restore
581
- ? Restore ~/.claude/claude_desktop_config.json? (Y/n) y
582
- Restored ~/.claude/claude_desktop_config.json
583
- ? Restore ~/.cursor/mcp.json? (Y/n) y
584
- Restored ~/.cursor/mcp.json
585
- ? Delete bridge config entirely? (y/N) n
586
- Done.
587
- ```
588
-
589
- ### Validating your config
590
-
591
- Use `--validate` to check your config file without starting the bridge:
592
-
593
- ```
594
- $ npx @crabeye-ai/crabeye-mcp-bridge --config config.json --validate
595
- Config OK — 3 upstream servers
596
- linear (stdio) [project management]
597
- github (stdio)
598
- sentry (streamable-http)
599
- ```
83
+ ## Features
600
84
 
601
- Exits with code 0 on success, code 1 on validation errors.
85
+ - **Discovery + search.** Two meta-tools (`search_tools`, `run_tool`) instead of N×M tool definitions in context. See [docs/how-it-works.md](docs/how-it-works.md).
86
+ - **Configuration.** STDIO, HTTP, and SSE upstreams; categories; multi-source config keys. See [docs/configuration.md](docs/configuration.md).
87
+ - **Authentication.** Encrypted credential store, OS-keychain-backed master key, `${credential:key}` templates. See [docs/auth.md](docs/auth.md).
88
+ - **Policies.** Per-tool / per-server / global tool policies (`always` / `prompt` / `never`), rate limiting, discovery modes. See [docs/policies.md](docs/policies.md).
89
+ - **STDIO manager.** STDIO upstreams routed through a per-user manager process so multiple bridges share a single subprocess per upstream. See [docs/stdio-manager.md](docs/stdio-manager.md).
90
+ - **CLI.** `init`, `restore`, `credential`, `daemon`, `--validate`. See [docs/cli.md](docs/cli.md).
602
91
 
603
92
  ## License
604
93