@aiwerk/mcp-bridge 2.8.22 → 2.8.24

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
@@ -8,7 +8,7 @@
8
8
 
9
9
  🌐 **[aiwerkmcp.com](https://aiwerkmcp.com)** — Learn more about the AIWerk MCP Platform
10
10
 
11
- Works with **Claude Desktop**, **Cursor**, **Windsurf**, **Cline**, **OpenClaw**, or any MCP client.
11
+ Works with **Claude Code**, **Codex (OpenAI)**, **Claude Desktop**, **Cursor**, **Windsurf**, **Cline**, **OpenClaw**, or any MCP client.
12
12
 
13
13
  ## Why?
14
14
 
@@ -39,8 +39,8 @@ npm install -g @aiwerk/mcp-bridge
39
39
  ## Quick Start
40
40
 
41
41
  ```bash
42
- # 1. Initialize config
43
- mcp-bridge init
42
+ # 1. Initialize config and register with Claude Code
43
+ mcp-bridge init --register claude-code
44
44
 
45
45
  # 2. Install a server from the catalog
46
46
  mcp-bridge install todoist
@@ -48,8 +48,7 @@ mcp-bridge install todoist
48
48
  # 3. Add your API key
49
49
  echo "TODOIST_API_TOKEN=your-token" >> ~/.mcp-bridge/.env
50
50
 
51
- # 4. Start (stdio modeconnects to any MCP client)
52
- mcp-bridge
51
+ # 4. Restart Claude Codebridge is ready
53
52
  ```
54
53
 
55
54
  ## Use with Claude Desktop
@@ -558,12 +557,16 @@ mcp-bridge # Start in stdio mode (default)
558
557
  mcp-bridge --sse --port 3000 # Start as SSE server
559
558
  mcp-bridge --http --port 3000 # Start as HTTP server
560
559
  mcp-bridge --verbose # Info-level logs to stderr
561
- mcp-bridge --debug # Full protocol logs to stderr
560
+ mcp-bridge --debug # Full debug metadata in tool responses
562
561
  mcp-bridge --config ./my.json # Custom config file
563
562
 
564
- mcp-bridge init # Create ~/.mcp-bridge/ with template
565
- mcp-bridge install <server> # Install from catalog
566
- mcp-bridge catalog # List available servers
563
+ mcp-bridge init # Create ~/.mcp-bridge/ with template config
564
+ mcp-bridge init --register claude-code # Init + register with Claude Code
565
+ mcp-bridge init --register codex # Init + register with Codex
566
+ mcp-bridge init --register cursor # Init + register with Cursor
567
+ mcp-bridge init --register windsurf # Init + register with Windsurf
568
+ mcp-bridge install <server> # Install from online catalog
569
+ mcp-bridge catalog # Browse 100+ available servers
567
570
  mcp-bridge servers # List configured servers
568
571
  mcp-bridge search <query> # Search catalog by keyword
569
572
  mcp-bridge update [--check] # Check for / install updates
@@ -574,33 +577,36 @@ mcp-bridge auth logout <server> # Remove stored token
574
577
  mcp-bridge auth status # Show auth status for all servers
575
578
  ```
576
579
 
580
+ ## Agent Integration
581
+
582
+ When connected to an MCP client (Claude Code, Codex, Cursor, etc.), the bridge exposes a single `mcp` meta-tool. Agents can discover and install servers at runtime:
583
+
584
+ ```
585
+ mcp(action="search", params={query: "task management"}) # Search catalog
586
+ mcp(action="install", params={name: "todoist"}) # Install server (persisted to config)
587
+ mcp(action="catalog") # Browse all servers
588
+ mcp(action="list", server="todoist") # Discover tools on a server
589
+ mcp(action="call", server="todoist", tool="find-tasks", params={query: "today"})
590
+ ```
591
+
592
+ The tool description automatically includes all connected servers with their descriptions, so agents know which server to use for what. New servers installed via the bridge are persisted to `~/.mcp-bridge/config.json` and survive restarts.
593
+
577
594
  ## Server Catalog
578
595
 
579
- Built-in catalog with pre-configured servers:
580
-
581
- | Server | Transport | Description |
582
- |--------|-----------|-------------|
583
- | todoist | stdio | Task management |
584
- | github | stdio | Repos, issues, PRs |
585
- | notion | stdio | Pages and databases |
586
- | stripe | stdio | Payments and billing |
587
- | linear | stdio | Project management |
588
- | google-maps | stdio | Places, geocoding, directions |
589
- | hetzner | stdio | Cloud infrastructure |
590
- | miro | stdio | Collaborative whiteboard |
591
- | wise | stdio | International payments |
592
- | tavily | stdio | AI-optimized web search |
593
- | apify | streamable-http | Web scraping and automation |
594
- | atlassian | stdio | Confluence and Jira |
595
- | chrome-devtools | stdio | Chrome browser automation |
596
- | hostinger | sse | Web hosting management |
596
+ Browse and install from the [AIWerk MCP Catalog](https://catalog.aiwerk.ch) with 100+ verified, signed recipes:
597
597
 
598
598
  ```bash
599
- mcp-bridge install todoist # Interactive setup with API key prompt
600
- mcp-bridge catalog # Full list
601
- mcp-bridge search payments # Search by keyword
599
+ mcp-bridge catalog # Browse all 100+ servers
600
+ mcp-bridge search payments # Search by keyword
601
+ mcp-bridge install todoist # Install from catalog
602
602
  ```
603
603
 
604
+ Popular servers include: todoist, github, notion, stripe, linear, google-maps, slack, supabase, mongodb, playwright, docker, and many more.
605
+
606
+ All catalog recipes are Ed25519 signed and security-audited. The bridge verifies signatures before installation.
607
+
608
+ > **Note**: The bundled `servers/` directory is deprecated. All servers now come from the online catalog.
609
+
604
610
  ## Library Usage
605
611
 
606
612
  Use as a dependency in your own MCP server or OpenClaw plugin:
@@ -667,11 +673,14 @@ For production deployments with high security requirements, consider adding an e
667
673
  | ✅ | OAuth2 Client Credentials | 2.1.0 |
668
674
  | ✅ | OAuth2 Authorization Code + PKCE | 2.5.0 |
669
675
  | ✅ | OAuth2 Device Code flow (headless) | 2.6.0 |
670
- | 🔜 | Auto-discovery (zero-config server registration) | planned |
676
+ | | Agent-driven discovery (search/install at runtime) | 2.8.6 |
671
677
  | 🔜 | Hosted bridge (bridge.aiwerk.ch) | planned |
672
678
  | ✅ | Remote catalog integration | 2.8.0 |
679
+ | ✅ | CLI online catalog | 2.8.23 |
680
+ | ✅ | Debug mode (_debug metadata) | 2.8.4 |
673
681
  | 🔜 | OpenTelemetry / Prometheus metrics | planned |
674
682
  | 🔜 | PII redaction | planned |
683
+ | 🔜 | Skill system (recipe.json skills for agents) | planned |
675
684
 
676
685
  See [docs/hosted-bridge-spec.md](docs/hosted-bridge-spec.md) for the hosted bridge architecture.
677
686
 
@@ -106,6 +106,14 @@ function parseArgs(argv) {
106
106
  }
107
107
  args.register = argv[i];
108
108
  break;
109
+ case "--mode":
110
+ i++;
111
+ if (argv[i] !== "router" && argv[i] !== "direct") {
112
+ process.stderr.write("Error: --mode must be 'router' or 'direct'\n");
113
+ process.exit(1);
114
+ }
115
+ args.mode = argv[i];
116
+ break;
109
117
  case "--daily":
110
118
  i++;
111
119
  args.daily = parseInt(argv[i], 10);
@@ -179,7 +187,7 @@ Usage:
179
187
  mcp-bridge Start in stdio mode (default)
180
188
  mcp-bridge --sse --port 3000 Start as SSE server
181
189
  mcp-bridge --http --port 3000 Start as streamable-http server
182
- mcp-bridge init [--register <client>] Create config + optionally register with a client
190
+ mcp-bridge init [--register <client>] [--mode router|direct] Create config + optionally register
183
191
  mcp-bridge install <server> Install a server from the catalog
184
192
  mcp-bridge catalog [--offline] List available servers
185
193
  mcp-bridge servers List configured servers
@@ -211,8 +219,23 @@ function whichCmd(name) {
211
219
  return false;
212
220
  }
213
221
  }
214
- function cmdInit(logger, register) {
222
+ function cmdInit(logger, register, mode) {
215
223
  initConfigDir(logger);
224
+ // Apply --mode if config exists and mode was specified
225
+ if (mode) {
226
+ const configPath = join(homedir(), ".mcp-bridge", "config.json");
227
+ if (existsSync(configPath)) {
228
+ try {
229
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
230
+ if (raw.mode !== mode) {
231
+ raw.mode = mode;
232
+ writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n", "utf-8");
233
+ process.stdout.write(`Mode set to "${mode}" in ${configPath}\n`);
234
+ }
235
+ }
236
+ catch { /* ignore parse errors */ }
237
+ }
238
+ }
216
239
  const isGlobal = __dirname.includes("node_modules") && (__dirname.includes("/lib/node_modules/") || __dirname.includes("\\lib\\node_modules\\"));
217
240
  const bridgeCmd = isGlobal ? "mcp-bridge" : "node";
218
241
  const bridgeArgs = isGlobal ? ["serve"] : [join(__dirname, "..", "bin", "mcp-bridge.js"), "serve"];
@@ -329,23 +352,27 @@ function registerClient(client, bridgeCmd, bridgeArgs, cmd) {
329
352
  }
330
353
  }
331
354
  }
332
- function cmdCatalog(logger) {
333
- const catalogPath = join(PACKAGE_ROOT, "servers", "index.json");
334
- if (!existsSync(catalogPath)) {
335
- logger.error("Server catalog not found");
355
+ async function cmdCatalog(logger, offline) {
356
+ const client = new CatalogClient({ logger });
357
+ try {
358
+ const result = await client.list({ limit: 200 });
359
+ const recipes = result.results || [];
360
+ process.stdout.write(`\nAvailable servers (${recipes.length} from catalog.aiwerk.ch):\n\n`);
361
+ process.stdout.write(" Server Auth Category Description\n");
362
+ process.stdout.write(" " + "─".repeat(90) + "\n");
363
+ for (const r of recipes) {
364
+ const name = (r.name || "").padEnd(22);
365
+ const auth = (r.authSummary || "none").padEnd(12);
366
+ const cat = (r.category || "").padEnd(17);
367
+ const desc = (r.description || "").slice(0, 60);
368
+ process.stdout.write(` ${name}${auth}${cat}${desc}\n`);
369
+ }
370
+ process.stdout.write("\n");
371
+ }
372
+ catch (err) {
373
+ logger.error(`Failed to fetch catalog: ${err instanceof Error ? err.message : String(err)}`);
336
374
  process.exit(1);
337
375
  }
338
- const catalog = JSON.parse(readFileSync(catalogPath, "utf-8"));
339
- const servers = catalog.recipes || catalog.servers || {};
340
- process.stdout.write("\nAvailable servers:\n\n");
341
- process.stdout.write(" Server Transport Description\n");
342
- process.stdout.write(" " + "─".repeat(60) + "\n");
343
- for (const [name, info] of Object.entries(servers)) {
344
- const padded = name.padEnd(16);
345
- const transport = (info.transport || "stdio").padEnd(13);
346
- process.stdout.write(` ${padded}${transport}${info.description || ""}\n`);
347
- }
348
- process.stdout.write("\n");
349
376
  }
350
377
  function cmdServers(logger, configPath) {
351
378
  try {
@@ -371,28 +398,26 @@ function cmdServers(logger, configPath) {
371
398
  process.exit(1);
372
399
  }
373
400
  }
374
- function cmdSearch(query, logger) {
375
- const catalogPath = join(PACKAGE_ROOT, "servers", "index.json");
376
- if (!existsSync(catalogPath)) {
377
- logger.error("Server catalog not found");
378
- process.exit(1);
401
+ async function cmdSearch(query, logger) {
402
+ const client = new CatalogClient({ logger });
403
+ try {
404
+ const searchResponse = await client.search(query);
405
+ const results = Array.isArray(searchResponse) ? searchResponse : searchResponse.results || [];
406
+ if (results.length === 0) {
407
+ process.stdout.write(`No servers matching "${query}"\n`);
408
+ return;
409
+ }
410
+ process.stdout.write(`\nSearch results for "${query}" (${results.length} found):\n\n`);
411
+ for (const [i, r] of results.entries()) {
412
+ const auth = r.authSummary || (r.authRequired ? "required" : "none");
413
+ process.stdout.write(` ${i + 1} ${(r.name || "").padEnd(22)}[${auth}] ${r.description || ""}\n`);
414
+ }
415
+ process.stdout.write("\n");
379
416
  }
380
- const catalog = JSON.parse(readFileSync(catalogPath, "utf-8"));
381
- const servers = catalog.recipes || catalog.servers || {};
382
- const lowerQuery = query.toLowerCase();
383
- const matches = Object.entries(servers).filter(([name, info]) => {
384
- return name.toLowerCase().includes(lowerQuery) ||
385
- (info.description || "").toLowerCase().includes(lowerQuery);
386
- });
387
- if (matches.length === 0) {
388
- process.stdout.write(`No servers matching "${query}"\n`);
389
- return;
417
+ catch (err) {
418
+ logger.error(`Search failed: ${err instanceof Error ? err.message : String(err)}`);
419
+ process.exit(1);
390
420
  }
391
- process.stdout.write(`\nSearch results for "${query}":\n\n`);
392
- matches.forEach(([name, info], i) => {
393
- process.stdout.write(` ${i + 1} ${name.padEnd(16)}${info.description || ""}\n`);
394
- });
395
- process.stdout.write("\n");
396
421
  }
397
422
  function resolveConfigPath(configPath) {
398
423
  if (!configPath) {
@@ -764,10 +789,10 @@ async function main() {
764
789
  printHelp();
765
790
  break;
766
791
  case "init":
767
- cmdInit(logger, args.register);
792
+ cmdInit(logger, args.register, args.mode);
768
793
  break;
769
794
  case "catalog":
770
- cmdCatalog(logger);
795
+ await cmdCatalog(logger, args.offline);
771
796
  break;
772
797
  case "servers":
773
798
  cmdServers(logger, args.configPath);
@@ -777,7 +802,7 @@ async function main() {
777
802
  process.stderr.write("Usage: mcp-bridge search <query>\n");
778
803
  process.exit(1);
779
804
  }
780
- cmdSearch(args.positional[0], logger);
805
+ await cmdSearch(args.positional[0], logger);
781
806
  break;
782
807
  case "usage":
783
808
  cmdUsage(args.configPath, logger);
@@ -42,6 +42,7 @@ export interface CatalogRecipe {
42
42
  type: string;
43
43
  required?: boolean;
44
44
  envVars?: string[];
45
+ credentialsUrl?: string;
45
46
  };
46
47
  [key: string]: unknown;
47
48
  }
@@ -165,6 +165,10 @@ export class McpRouter {
165
165
  if (!serverName) {
166
166
  return this.error("invalid_params", "server name is required for action=install (pass as server field or params.name)");
167
167
  }
168
+ // Sanitize serverName: only lowercase alphanumeric + hyphens (matches recipe spec id format)
169
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(serverName)) {
170
+ return this.error("invalid_params", `Invalid server name "${serverName}". Must match /^[a-z0-9][a-z0-9-]*$/ (lowercase alphanumeric + hyphens).`);
171
+ }
168
172
  if (this.servers[serverName]) {
169
173
  return { action: "install", server: serverName, installed: true, message: `Server "${serverName}" is already configured.` };
170
174
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwerk/mcp-bridge",
3
- "version": "2.8.22",
3
+ "version": "2.8.24",
4
4
  "description": "Standalone MCP server that multiplexes multiple MCP servers into one interface",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",