@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 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
- ```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
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)
16
10
 
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
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
- 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
15
+ ```bash
16
+ npx @crabeye-ai/crabeye-mcp-bridge init
26
17
  ```
27
18
 
28
- ## Quick start
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
- Say your MCP client config looks like this today:
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. On duplicate names, earlier sources win: `upstreamMcpServers` > `upstreamServers` > `servers` > `context_servers` > `mcpServers`. Self-exclusion applies to `mcpServers` and `context_servers`.
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
- 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.
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
- ```json
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
- 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).
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":[]}