@crabeye-ai/crabeye-mcp-bridge 1.0.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 +21 -472
- package/dist/chunk-4P5BLKL7.js +60 -0
- package/dist/chunk-4P5BLKL7.js.map +1 -0
- package/dist/chunk-R3ZSKJAO.js +3366 -0
- package/dist/chunk-R3ZSKJAO.js.map +1 -0
- package/dist/chunk-TGVTIQEU.js +215 -0
- package/dist/chunk-TGVTIQEU.js.map +1 -0
- package/dist/daemon-H6ICAFWF.js +269 -0
- package/dist/daemon-H6ICAFWF.js.map +1 -0
- package/dist/index.js +900 -365
- package/dist/index.js.map +1 -1
- package/dist/init-RYB2DWAQ.js +130 -0
- package/dist/init-RYB2DWAQ.js.map +1 -0
- package/dist/restore-AU4RJCMZ.js +120 -0
- package/dist/restore-AU4RJCMZ.js.map +1 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -6,28 +6,23 @@ 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
|
-
|
|
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
|
|
9
|
+

|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
The fastest way to get started is with `init`, which discovers your MCP client configs and sets up the bridge automatically:
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
15
|
+
```bash
|
|
16
|
+
npx @crabeye-ai/crabeye-mcp-bridge init
|
|
26
17
|
```
|
|
27
18
|
|
|
28
|
-
|
|
19
|
+
This scans for config files from Claude Desktop, Cursor, VS Code Copilot, Windsurf, and Zed, lets you pick which ones to use, and optionally injects the bridge entry. After that, just run `npx @crabeye-ai/crabeye-mcp-bridge` — no `--config` flag needed.
|
|
29
20
|
|
|
30
|
-
|
|
21
|
+
To undo, run `npx @crabeye-ai/crabeye-mcp-bridge restore`.
|
|
22
|
+
|
|
23
|
+
### Manual setup
|
|
24
|
+
|
|
25
|
+
If you prefer to set things up manually, say your MCP client config looks like this today:
|
|
31
26
|
|
|
32
27
|
```json
|
|
33
28
|
{
|
|
@@ -81,464 +76,18 @@ Then rename `mcpServers` to `upstreamMcpServers`, add the bridge, and replace ha
|
|
|
81
76
|
|
|
82
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.
|
|
83
78
|
|
|
84
|
-
The bridge also reads `upstreamServers` (shorthand), `servers` (VS Code Copilot), and `context_servers` (Zed) as input keys.
|
|
85
|
-
|
|
86
|
-
Alternatively, you can add the bridge alongside your existing `mcpServers` entries without renaming anything:
|
|
87
|
-
|
|
88
|
-
```json
|
|
89
|
-
{
|
|
90
|
-
"mcpServers": {
|
|
91
|
-
"bridge": {
|
|
92
|
-
"command": "npx",
|
|
93
|
-
"args": ["-y", "@crabeye-ai/crabeye-mcp-bridge", "--config", "/path/to/this/file.json"]
|
|
94
|
-
},
|
|
95
|
-
"linear": {
|
|
96
|
-
"command": "npx",
|
|
97
|
-
"args": ["-y", "@anthropic/linear-mcp-server"]
|
|
98
|
-
},
|
|
99
|
-
"github": {
|
|
100
|
-
"command": "npx",
|
|
101
|
-
"args": ["-y", "@anthropic/github-mcp-server"],
|
|
102
|
-
"env": {
|
|
103
|
-
"GITHUB_TOKEN": "${credential:github-pat}"
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
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.
|
|
111
|
-
|
|
112
|
-
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.
|
|
113
|
-
|
|
114
|
-
## How it works
|
|
115
|
-
|
|
116
|
-
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.
|
|
117
|
-
|
|
118
|
-
Two meta-tools are exposed to the AI assistant:
|
|
119
|
-
|
|
120
|
-
- **`search_tools`** — Fuzzy-search across all discovered tools by name, description, provider, or category. Matching tools are automatically enabled for use.
|
|
121
|
-
- **`run_tool`** — Execute any discovered tool directly by its namespaced name (e.g. `linear__create_issue`).
|
|
122
|
-
|
|
123
|
-
The AI assistant calls `search_tools` automatically when it detects a relevant intent, then uses `run_tool` or calls the auto-enabled tools directly.
|
|
124
|
-
|
|
125
|
-
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.
|
|
126
|
-
|
|
127
|
-
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`:
|
|
128
|
-
|
|
129
|
-
```json
|
|
130
|
-
{
|
|
131
|
-
"session_stats": {
|
|
132
|
-
"tokens_saved": 11200,
|
|
133
|
-
"baseline_tokens": 14200,
|
|
134
|
-
"bridge_tokens": 3000
|
|
135
|
-
},
|
|
136
|
-
"results": [...]
|
|
137
|
-
}
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
- **`baseline_tokens`** — estimated tokens if all upstream tool definitions were injected into context without the bridge
|
|
141
|
-
- **`bridge_tokens`** — cumulative tokens used by the bridge's two meta-tools plus all search results returned so far
|
|
142
|
-
- **`tokens_saved`** — the difference (baseline − bridge)
|
|
143
|
-
|
|
144
|
-
Token counts are estimated using a chars/4 heuristic.
|
|
145
|
-
|
|
146
|
-
## Examples
|
|
147
|
-
|
|
148
|
-
### Discovering providers
|
|
149
|
-
|
|
150
|
-
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:
|
|
151
|
-
|
|
152
|
-
```json
|
|
153
|
-
{
|
|
154
|
-
"name": "search_tools",
|
|
155
|
-
"arguments": {
|
|
156
|
-
"queries": [{ "provider": "linear" }]
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
Response:
|
|
162
|
-
|
|
163
|
-
```json
|
|
164
|
-
{
|
|
165
|
-
"results": [
|
|
166
|
-
{
|
|
167
|
-
"providers": [
|
|
168
|
-
{
|
|
169
|
-
"name": "linear",
|
|
170
|
-
"category": "project management",
|
|
171
|
-
"tool_count": 47,
|
|
172
|
-
"tools": []
|
|
173
|
-
}
|
|
174
|
-
],
|
|
175
|
-
"total": 47,
|
|
176
|
-
"count": 0,
|
|
177
|
-
"offset": 0,
|
|
178
|
-
"limit": 10
|
|
179
|
-
}
|
|
180
|
-
]
|
|
181
|
-
}
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
To get full tool definitions, add `expand_tools: true` or use a `tool` filter to drill in:
|
|
185
|
-
|
|
186
|
-
```json
|
|
187
|
-
{
|
|
188
|
-
"name": "search_tools",
|
|
189
|
-
"arguments": {
|
|
190
|
-
"queries": [{ "provider": "linear", "expand_tools": true }]
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### Searching for tools
|
|
196
|
-
|
|
197
|
-
The assistant calls `search_tools` with a `tool` filter to find specific tools by name or description. Results are grouped by provider:
|
|
198
|
-
|
|
199
|
-
```json
|
|
200
|
-
{
|
|
201
|
-
"name": "search_tools",
|
|
202
|
-
"arguments": {
|
|
203
|
-
"queries": [{ "tool": "create issue" }]
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
The bridge returns matching tools with their full input schemas, grouped by provider:
|
|
209
|
-
|
|
210
|
-
```json
|
|
211
|
-
{
|
|
212
|
-
"results": [
|
|
213
|
-
{
|
|
214
|
-
"providers": [
|
|
215
|
-
{
|
|
216
|
-
"name": "linear",
|
|
217
|
-
"category": "project management",
|
|
218
|
-
"tool_count": 47,
|
|
219
|
-
"tools": [
|
|
220
|
-
{
|
|
221
|
-
"tool_name": "linear__create_issue",
|
|
222
|
-
"source": "linear",
|
|
223
|
-
"description": "Create a new Linear issue",
|
|
224
|
-
"input_schema": {
|
|
225
|
-
"type": "object",
|
|
226
|
-
"properties": {
|
|
227
|
-
"title": { "type": "string" },
|
|
228
|
-
"team": { "type": "string" },
|
|
229
|
-
"description": { "type": "string" }
|
|
230
|
-
},
|
|
231
|
-
"required": ["title", "team"]
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
]
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
"name": "github",
|
|
238
|
-
"tool_count": 32,
|
|
239
|
-
"tools": [
|
|
240
|
-
{
|
|
241
|
-
"tool_name": "github__create_issue",
|
|
242
|
-
"source": "github",
|
|
243
|
-
"description": "Create a GitHub issue",
|
|
244
|
-
"input_schema": { "..." }
|
|
245
|
-
}
|
|
246
|
-
]
|
|
247
|
-
}
|
|
248
|
-
],
|
|
249
|
-
"total": 2,
|
|
250
|
-
"count": 2,
|
|
251
|
-
"offset": 0,
|
|
252
|
-
"limit": 10
|
|
253
|
-
}
|
|
254
|
-
]
|
|
255
|
-
}
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
Matching tools are automatically enabled for direct use by the assistant — no extra step needed.
|
|
259
|
-
|
|
260
|
-
### Multiple queries
|
|
261
|
-
|
|
262
|
-
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:
|
|
263
|
-
|
|
264
|
-
```json
|
|
265
|
-
{
|
|
266
|
-
"name": "search_tools",
|
|
267
|
-
"arguments": {
|
|
268
|
-
"queries": [
|
|
269
|
-
{ "tool": "create issue" },
|
|
270
|
-
{ "provider": "github" },
|
|
271
|
-
{ "category": "design", "expand_tools": true }
|
|
272
|
-
]
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
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.
|
|
278
|
-
|
|
279
|
-
### Running a tool directly
|
|
280
|
-
|
|
281
|
-
If the assistant already knows the namespaced name, it can skip the search and call `run_tool` directly:
|
|
282
|
-
|
|
283
|
-
```json
|
|
284
|
-
{
|
|
285
|
-
"name": "run_tool",
|
|
286
|
-
"arguments": {
|
|
287
|
-
"name": "linear__create_issue",
|
|
288
|
-
"arguments": {
|
|
289
|
-
"title": "Fix login crash",
|
|
290
|
-
"team": "Engineering",
|
|
291
|
-
"description": "The app crashes on login when the session token is expired"
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
The response is passed through exactly as the upstream server returns it.
|
|
298
|
-
|
|
299
|
-
### Filtering by provider
|
|
300
|
-
|
|
301
|
-
Combine a tool search with a provider filter to narrow results:
|
|
302
|
-
|
|
303
|
-
```json
|
|
304
|
-
{
|
|
305
|
-
"name": "search_tools",
|
|
306
|
-
"arguments": {
|
|
307
|
-
"queries": [{ "tool": "list", "provider": "github" }]
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
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).
|
|
313
|
-
|
|
314
|
-
### Regex search
|
|
315
|
-
|
|
316
|
-
For precise matching, prefix the tool query with `regex:`:
|
|
317
|
-
|
|
318
|
-
```json
|
|
319
|
-
{
|
|
320
|
-
"name": "search_tools",
|
|
321
|
-
"arguments": {
|
|
322
|
-
"queries": [{ "tool": "regex:^list_" }]
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
This finds all tools whose name starts with `list_` — across every server.
|
|
328
|
-
|
|
329
|
-
## Configuration
|
|
330
|
-
|
|
331
|
-
### STDIO servers
|
|
332
|
-
|
|
333
|
-
Servers that run as local subprocesses:
|
|
334
|
-
|
|
335
|
-
```json
|
|
336
|
-
{
|
|
337
|
-
"upstreamMcpServers": {
|
|
338
|
-
"my-server": {
|
|
339
|
-
"command": "node",
|
|
340
|
-
"args": ["./server.js"],
|
|
341
|
-
"env": { "API_KEY": "..." }
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
### HTTP servers
|
|
348
|
-
|
|
349
|
-
Remote servers accessible via HTTP:
|
|
350
|
-
|
|
351
|
-
```json
|
|
352
|
-
{
|
|
353
|
-
"upstreamMcpServers": {
|
|
354
|
-
"remote-server": {
|
|
355
|
-
"url": "https://mcp.example.com/sse",
|
|
356
|
-
"type": "sse",
|
|
357
|
-
"headers": { "Authorization": "Bearer ..." }
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
`type` defaults to `"streamable-http"`. Use `"sse"` for servers that use Server-Sent Events transport.
|
|
364
|
-
|
|
365
|
-
### Categories
|
|
366
|
-
|
|
367
|
-
Assign a category to a server so tools can be discovered by domain rather than server name:
|
|
368
|
-
|
|
369
|
-
```json
|
|
370
|
-
{
|
|
371
|
-
"upstreamMcpServers": {
|
|
372
|
-
"linear": {
|
|
373
|
-
"command": "npx",
|
|
374
|
-
"args": ["-y", "@anthropic/linear-mcp-server"],
|
|
375
|
-
"_bridge": {
|
|
376
|
-
"category": "project management"
|
|
377
|
-
}
|
|
378
|
-
},
|
|
379
|
-
"figma": {
|
|
380
|
-
"command": "npx",
|
|
381
|
-
"args": ["-y", "@anthropic/figma-mcp-server"],
|
|
382
|
-
"_bridge": {
|
|
383
|
-
"category": "design"
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
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.
|
|
391
|
-
|
|
392
|
-
### Authentication
|
|
393
|
-
|
|
394
|
-
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`:
|
|
395
|
-
|
|
396
|
-
```bash
|
|
397
|
-
# Store credentials
|
|
398
|
-
crabeye-mcp-bridge credential set github-pat ghp_abc123
|
|
399
|
-
crabeye-mcp-bridge credential set remote-api-key sk_live_xyz
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
```json
|
|
403
|
-
{
|
|
404
|
-
"upstreamMcpServers": {
|
|
405
|
-
"github": {
|
|
406
|
-
"command": "npx",
|
|
407
|
-
"args": ["-y", "@anthropic/github-mcp-server"],
|
|
408
|
-
"env": { "GITHUB_TOKEN": "${credential:github-pat}" }
|
|
409
|
-
},
|
|
410
|
-
"remote-api": {
|
|
411
|
-
"url": "https://mcp.example.com/sse",
|
|
412
|
-
"type": "sse",
|
|
413
|
-
"headers": { "Authorization": "Bearer ${credential:remote-api-key}" }
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
Templates are resolved on every connect and reconnect, so rotating a credential takes effect without restarting the bridge.
|
|
420
|
-
|
|
421
|
-
**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.
|
|
422
|
-
|
|
423
|
-
**CLI commands:**
|
|
424
|
-
|
|
425
|
-
| Command | Description |
|
|
426
|
-
|---------|-------------|
|
|
427
|
-
| `credential set <key> [value]` | Store a plain string secret |
|
|
428
|
-
| `credential set <key> --json '<json>'` | Store a typed credential (bearer/oauth2/secret) |
|
|
429
|
-
| `credential get <key>` | Retrieve a credential (masked by default, `--show-secret` for full) |
|
|
430
|
-
| `credential delete <key>` | Delete a stored credential |
|
|
431
|
-
| `credential list` | List all stored credential keys |
|
|
432
|
-
|
|
433
|
-
Pipe-friendly: `echo "ghp_abc123" | crabeye-mcp-bridge credential set github-pat`
|
|
434
|
-
|
|
435
|
-
### Tool policies
|
|
436
|
-
|
|
437
|
-
Control which tools can run automatically, require confirmation, or are blocked entirely. Three policy values:
|
|
438
|
-
|
|
439
|
-
- `"always"` — tool runs without confirmation (default)
|
|
440
|
-
- `"prompt"` — user is asked to approve each call via MCP elicitation
|
|
441
|
-
- `"never"` — tool is disabled and cannot be called
|
|
442
|
-
|
|
443
|
-
Policies cascade in this order (first match wins):
|
|
444
|
-
|
|
445
|
-
1. Per-tool (`_bridge.tools.<toolName>`)
|
|
446
|
-
2. Per-server (`_bridge.toolPolicy`)
|
|
447
|
-
3. Global (`_bridge.toolPolicy`)
|
|
448
|
-
4. Default: `"always"`
|
|
449
|
-
|
|
450
|
-
```json
|
|
451
|
-
{
|
|
452
|
-
"upstreamMcpServers": {
|
|
453
|
-
"linear": {
|
|
454
|
-
"command": "npx",
|
|
455
|
-
"args": ["-y", "@anthropic/linear-mcp-server"],
|
|
456
|
-
"_bridge": {
|
|
457
|
-
"toolPolicy": "prompt",
|
|
458
|
-
"tools": {
|
|
459
|
-
"list_issues": "always",
|
|
460
|
-
"delete_issue": "never"
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
},
|
|
465
|
-
"_bridge": {
|
|
466
|
-
"toolPolicy": "always"
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
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"`).
|
|
472
|
-
|
|
473
|
-
### Rate limiting
|
|
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.
|
|
474
80
|
|
|
475
|
-
|
|
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.
|
|
476
82
|
|
|
477
|
-
|
|
478
|
-
{
|
|
479
|
-
"upstreamMcpServers": {
|
|
480
|
-
"github": {
|
|
481
|
-
"command": "npx",
|
|
482
|
-
"args": ["-y", "@anthropic/github-mcp-server"],
|
|
483
|
-
"_bridge": {
|
|
484
|
-
"rateLimit": {
|
|
485
|
-
"maxCalls": 30,
|
|
486
|
-
"windowSeconds": 60
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
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.
|
|
495
|
-
|
|
496
|
-
Rate limit configuration is hot-reloadable — changes take effect without restarting the bridge.
|
|
497
|
-
|
|
498
|
-
### Discovery mode
|
|
499
|
-
|
|
500
|
-
Control how searched tools are surfaced to the assistant with `--discovery-mode`:
|
|
501
|
-
|
|
502
|
-
- **`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.
|
|
503
|
-
- **`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.
|
|
504
|
-
- **`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`.
|
|
505
|
-
|
|
506
|
-
```bash
|
|
507
|
-
npx @crabeye-ai/crabeye-mcp-bridge --config config.json --discovery-mode search
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
## CLI
|
|
511
|
-
|
|
512
|
-
```
|
|
513
|
-
npx @crabeye-ai/crabeye-mcp-bridge --config <path>
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
| Flag / Subcommand | Description |
|
|
517
|
-
|-------------------|-------------|
|
|
518
|
-
| `-c, --config <path>` | Path to config file (required, or set `MCP_BRIDGE_CONFIG`) |
|
|
519
|
-
| `--validate` | Validate config and list upstream servers, then exit |
|
|
520
|
-
| `--stats` | Include `session_stats` in `search_tools` responses (always logged to stderr) |
|
|
521
|
-
| `--discovery-mode <mode>` | How searched tools are surfaced: `search`, `tools_list`, or `both` (default) |
|
|
522
|
-
| `-V, --version` | Print version |
|
|
523
|
-
| `-h, --help` | Print help |
|
|
524
|
-
| `credential set <key> [value]` | Store a credential (plain string, `--json` for typed, or pipe stdin) |
|
|
525
|
-
| `credential get <key>` | Retrieve a credential (`--show-secret` to unmask) |
|
|
526
|
-
| `credential delete <key>` | Delete a stored credential |
|
|
527
|
-
| `credential list` | List all stored credential keys |
|
|
528
|
-
|
|
529
|
-
### Validating your config
|
|
530
|
-
|
|
531
|
-
Use `--validate` to check your config file without starting the bridge:
|
|
532
|
-
|
|
533
|
-
```
|
|
534
|
-
$ npx @crabeye-ai/crabeye-mcp-bridge --config config.json --validate
|
|
535
|
-
Config OK — 3 upstream servers
|
|
536
|
-
linear (stdio) [project management]
|
|
537
|
-
github (stdio)
|
|
538
|
-
sentry (streamable-http)
|
|
539
|
-
```
|
|
83
|
+
## Features
|
|
540
84
|
|
|
541
|
-
|
|
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).
|
|
542
91
|
|
|
543
92
|
## License
|
|
544
93
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// src/config/discovery.ts
|
|
2
|
+
import { access } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { homedir, platform } from "os";
|
|
5
|
+
function getKnownLocations() {
|
|
6
|
+
const home = homedir();
|
|
7
|
+
const os = platform();
|
|
8
|
+
const vscodeSettingsPath = os === "darwin" ? join(home, "Library", "Application Support", "Code", "User", "settings.json") : os === "win32" ? join(home, "AppData", "Roaming", "Code", "User", "settings.json") : join(home, ".config", "Code", "User", "settings.json");
|
|
9
|
+
return [
|
|
10
|
+
{
|
|
11
|
+
clientName: "Claude Desktop",
|
|
12
|
+
paths: [
|
|
13
|
+
join(home, ".claude", "claude_desktop_config.json"),
|
|
14
|
+
join(home, ".claude.json")
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
clientName: "Cursor",
|
|
19
|
+
paths: [join(home, ".cursor", "mcp.json")]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
clientName: "VS Code Copilot",
|
|
23
|
+
paths: [vscodeSettingsPath]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
clientName: "Windsurf",
|
|
27
|
+
paths: [join(home, ".codeium", "windsurf", "mcp_config.json")]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
clientName: "Zed",
|
|
31
|
+
paths: [join(home, ".config", "zed", "settings.json")]
|
|
32
|
+
}
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
async function fileExists(path) {
|
|
36
|
+
try {
|
|
37
|
+
await access(path);
|
|
38
|
+
return true;
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function discoverMcpConfigs() {
|
|
44
|
+
const locations = getKnownLocations();
|
|
45
|
+
const results = [];
|
|
46
|
+
for (const { clientName, paths } of locations) {
|
|
47
|
+
for (const path of paths) {
|
|
48
|
+
if (await fileExists(path)) {
|
|
49
|
+
results.push({ clientName, path });
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return results;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
discoverMcpConfigs
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=chunk-4P5BLKL7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/discovery.ts"],"sourcesContent":["import { access } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { homedir, platform } from \"node:os\";\n\nexport interface McpConfigEntry {\n clientName: string;\n path: string;\n}\n\nfunction getKnownLocations(): Array<{ clientName: string; paths: string[] }> {\n const home = homedir();\n const os = platform();\n\n const vscodeSettingsPath =\n os === \"darwin\"\n ? join(home, \"Library\", \"Application Support\", \"Code\", \"User\", \"settings.json\")\n : os === \"win32\"\n ? join(home, \"AppData\", \"Roaming\", \"Code\", \"User\", \"settings.json\")\n : join(home, \".config\", \"Code\", \"User\", \"settings.json\");\n\n return [\n {\n clientName: \"Claude Desktop\",\n paths: [\n join(home, \".claude\", \"claude_desktop_config.json\"),\n join(home, \".claude.json\"),\n ],\n },\n {\n clientName: \"Cursor\",\n paths: [join(home, \".cursor\", \"mcp.json\")],\n },\n {\n clientName: \"VS Code Copilot\",\n paths: [vscodeSettingsPath],\n },\n {\n clientName: \"Windsurf\",\n paths: [join(home, \".codeium\", \"windsurf\", \"mcp_config.json\")],\n },\n {\n clientName: \"Zed\",\n paths: [join(home, \".config\", \"zed\", \"settings.json\")],\n },\n ];\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function discoverMcpConfigs(): Promise<McpConfigEntry[]> {\n const locations = getKnownLocations();\n const results: McpConfigEntry[] = [];\n\n for (const { clientName, paths } of locations) {\n for (const path of paths) {\n if (await fileExists(path)) {\n results.push({ clientName, path });\n break; // only first match per client\n }\n }\n }\n\n return results;\n}\n"],"mappings":";AAAA,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAS,SAAS,gBAAgB;AAOlC,SAAS,oBAAoE;AAC3E,QAAM,OAAO,QAAQ;AACrB,QAAM,KAAK,SAAS;AAEpB,QAAM,qBACJ,OAAO,WACH,KAAK,MAAM,WAAW,uBAAuB,QAAQ,QAAQ,eAAe,IAC5E,OAAO,UACL,KAAK,MAAM,WAAW,WAAW,QAAQ,QAAQ,eAAe,IAChE,KAAK,MAAM,WAAW,QAAQ,QAAQ,eAAe;AAE7D,SAAO;AAAA,IACL;AAAA,MACE,YAAY;AAAA,MACZ,OAAO;AAAA,QACL,KAAK,MAAM,WAAW,4BAA4B;AAAA,QAClD,KAAK,MAAM,cAAc;AAAA,MAC3B;AAAA,IACF;AAAA,IACA;AAAA,MACE,YAAY;AAAA,MACZ,OAAO,CAAC,KAAK,MAAM,WAAW,UAAU,CAAC;AAAA,IAC3C;AAAA,IACA;AAAA,MACE,YAAY;AAAA,MACZ,OAAO,CAAC,kBAAkB;AAAA,IAC5B;AAAA,IACA;AAAA,MACE,YAAY;AAAA,MACZ,OAAO,CAAC,KAAK,MAAM,YAAY,YAAY,iBAAiB,CAAC;AAAA,IAC/D;AAAA,IACA;AAAA,MACE,YAAY;AAAA,MACZ,OAAO,CAAC,KAAK,MAAM,WAAW,OAAO,eAAe,CAAC;AAAA,IACvD;AAAA,EACF;AACF;AAEA,eAAe,WAAW,MAAgC;AACxD,MAAI;AACF,UAAM,OAAO,IAAI;AACjB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,qBAAgD;AACpE,QAAM,YAAY,kBAAkB;AACpC,QAAM,UAA4B,CAAC;AAEnC,aAAW,EAAE,YAAY,MAAM,KAAK,WAAW;AAC7C,eAAW,QAAQ,OAAO;AACxB,UAAI,MAAM,WAAW,IAAI,GAAG;AAC1B,gBAAQ,KAAK,EAAE,YAAY,KAAK,CAAC;AACjC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|