@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 +40 -31
- package/dist/bin/mcp-bridge.js +65 -40
- package/dist/src/catalog-client.d.ts +1 -0
- package/dist/src/mcp-router.js +4 -0
- package/package.json +1 -1
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.
|
|
52
|
-
mcp-bridge
|
|
51
|
+
# 4. Restart Claude Code — bridge 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
|
|
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
|
|
566
|
-
mcp-bridge
|
|
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
|
-
|
|
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
|
|
600
|
-
mcp-bridge
|
|
601
|
-
mcp-bridge
|
|
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
|
-
|
|
|
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
|
|
package/dist/bin/mcp-bridge.js
CHANGED
|
@@ -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
|
|
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
|
|
334
|
-
|
|
335
|
-
|
|
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
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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);
|
package/dist/src/mcp-router.js
CHANGED
|
@@ -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
|
}
|