@aiwerk/mcp-bridge 2.8.4 → 2.8.6

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.
@@ -195,6 +195,26 @@ All logs go to stderr. Stdout is reserved for the MCP protocol (stdio mode).
195
195
  }
196
196
  function cmdInit(logger) {
197
197
  initConfigDir(logger);
198
+ // Check if installed globally
199
+ const isGlobal = __dirname.includes("node_modules") && !__dirname.includes(homedir());
200
+ process.stdout.write(`
201
+ Next step: add mcp-bridge to your MCP client.
202
+
203
+ Add this to your client's MCP server config:
204
+
205
+ {
206
+ "mcp-bridge": {
207
+ "command": "${isGlobal ? "mcp-bridge" : "node"}",
208
+ "args": ${isGlobal ? '["serve"]' : `["${join(__dirname, "..", "bin", "mcp-bridge.js")}", "serve"]`}
209
+ }
210
+ }
211
+
212
+ Supported clients: Claude Code (~/.claude/settings.json),
213
+ Cursor (~/.cursor/mcp.json), Claude Desktop, Windsurf, OpenClaw, etc.
214
+ ${!isGlobal ? "\nTip: Install globally for a cleaner setup:\n npm install -g @aiwerk/mcp-bridge\n" : ""}
215
+ After adding, restart your client. The bridge will appear as an 'mcp' tool
216
+ with search, install, and catalog actions to discover and add MCP servers.
217
+ `);
198
218
  }
199
219
  function cmdCatalog(logger) {
200
220
  const catalogPath = join(PACKAGE_ROOT, "servers", "index.json");
@@ -74,6 +74,32 @@ export type RouterDispatchResponse = {
74
74
  callCount: number;
75
75
  lastCall: string;
76
76
  }>;
77
+ } | {
78
+ action: "search";
79
+ query: string;
80
+ results: Array<{
81
+ id: string;
82
+ name: string;
83
+ description: string;
84
+ category?: string;
85
+ auth?: string;
86
+ }>;
87
+ } | {
88
+ action: "catalog";
89
+ recipes: Array<{
90
+ id: string;
91
+ name: string;
92
+ description: string;
93
+ category?: string;
94
+ auth?: string;
95
+ }>;
96
+ } | {
97
+ action: "install";
98
+ server: string;
99
+ installed: boolean;
100
+ message: string;
101
+ missingEnvVars?: string[];
102
+ credentialsUrl?: string;
77
103
  } | {
78
104
  action: "intent";
79
105
  intent: string;
@@ -122,6 +148,7 @@ export declare class McpRouter {
122
148
  private readonly tokenManager;
123
149
  private readonly rateLimiter;
124
150
  private readonly requestIdState;
151
+ private readonly catalogClient;
125
152
  private intentRouter;
126
153
  private promotion;
127
154
  constructor(servers: Record<string, McpServerConfig>, clientConfig: McpClientConfig, logger: Logger, transportRefs?: Partial<RouterTransportRefs>);
@@ -13,6 +13,8 @@ import { ToolResolver } from "./tool-resolution.js";
13
13
  import { OAuth2TokenManager } from "./oauth2-token-manager.js";
14
14
  import { FileTokenStore } from "./token-store.js";
15
15
  import { RateLimiter } from "./rate-limiter.js";
16
+ import { CatalogClient } from "./catalog-client.js";
17
+ import { recipeToServerConfig, collectRequiredEnvVars } from "./config.js";
16
18
  const DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
17
19
  const DEFAULT_CONNECT_ERROR_COOLDOWN_MS = 10 * 1000;
18
20
  const DEFAULT_MAX_CONCURRENT = 5;
@@ -33,6 +35,7 @@ export class McpRouter {
33
35
  tokenManager;
34
36
  rateLimiter;
35
37
  requestIdState = { value: 0 };
38
+ catalogClient;
36
39
  intentRouter = null;
37
40
  promotion = null;
38
41
  constructor(servers, clientConfig, logger, transportRefs) {
@@ -58,6 +61,7 @@ export class McpRouter {
58
61
  this.toolResolver = new ToolResolver(Object.keys(servers));
59
62
  this.tokenManager = new OAuth2TokenManager(logger, new FileTokenStore());
60
63
  this.rateLimiter = new RateLimiter();
64
+ this.catalogClient = new CatalogClient({ logger });
61
65
  if (clientConfig.adaptivePromotion?.enabled) {
62
66
  this.promotion = new AdaptivePromotion(clientConfig.adaptivePromotion, logger);
63
67
  }
@@ -94,6 +98,100 @@ export class McpRouter {
94
98
  }
95
99
  return this.resolveIntent(intent);
96
100
  }
101
+ // Search catalog
102
+ if (normalizedAction === "search") {
103
+ const query = params?.query || server || tool;
104
+ if (!query) {
105
+ return this.error("invalid_params", "query is required for action=search (pass as params.query or server field)");
106
+ }
107
+ if (!this.catalogClient) {
108
+ return this.error("mcp_error", "Catalog client not available");
109
+ }
110
+ try {
111
+ const results = await this.catalogClient.search(query);
112
+ return {
113
+ action: "search",
114
+ query,
115
+ results: results.map(r => ({
116
+ id: r.name,
117
+ name: r.name,
118
+ description: r.description || "",
119
+ category: r.category,
120
+ auth: "none", // Search results don't include auth info
121
+ }))
122
+ };
123
+ }
124
+ catch (err) {
125
+ return this.error("mcp_error", `Catalog search failed: ${err instanceof Error ? err.message : String(err)}`);
126
+ }
127
+ }
128
+ // Browse catalog
129
+ if (normalizedAction === "catalog") {
130
+ if (!this.catalogClient) {
131
+ return this.error("mcp_error", "Catalog client not available");
132
+ }
133
+ try {
134
+ const recipeList = await this.catalogClient.list();
135
+ return {
136
+ action: "catalog",
137
+ recipes: recipeList.results.map(r => ({
138
+ id: r.name,
139
+ name: r.name,
140
+ description: r.description || "",
141
+ category: r.category,
142
+ auth: "none", // List results don't include auth info
143
+ }))
144
+ };
145
+ }
146
+ catch (err) {
147
+ return this.error("mcp_error", `Catalog browse failed: ${err instanceof Error ? err.message : String(err)}`);
148
+ }
149
+ }
150
+ // Install server from catalog (runtime only, not persisted to config file)
151
+ if (normalizedAction === "install") {
152
+ const serverName = server || params?.server;
153
+ if (!serverName) {
154
+ return this.error("invalid_params", "server name is required for action=install");
155
+ }
156
+ if (this.servers[serverName]) {
157
+ return { action: "install", server: serverName, installed: true, message: `Server "${serverName}" is already configured.` };
158
+ }
159
+ if (!this.catalogClient) {
160
+ return this.error("mcp_error", "Catalog client not available");
161
+ }
162
+ try {
163
+ const recipe = await this.catalogClient.resolve(serverName);
164
+ const serverConfig = recipeToServerConfig(recipe);
165
+ if (!serverConfig) {
166
+ return this.error("mcp_error", `Unsupported recipe format for "${serverName}"`);
167
+ }
168
+ // Check env vars
169
+ const requiredVars = collectRequiredEnvVars(recipe);
170
+ const missing = requiredVars.filter(v => !process.env[v]);
171
+ // Add to runtime config
172
+ this.servers[serverName] = serverConfig;
173
+ // Also update clientConfig.servers so generateDescription includes it
174
+ this.clientConfig.servers[serverName] = serverConfig;
175
+ if (missing.length > 0) {
176
+ return {
177
+ action: "install",
178
+ server: serverName,
179
+ installed: true,
180
+ message: `Server "${serverName}" added (runtime). Missing env vars: ${missing.join(", ")}. Set them before calling.`,
181
+ missingEnvVars: missing,
182
+ };
183
+ }
184
+ return {
185
+ action: "install",
186
+ server: serverName,
187
+ installed: true,
188
+ message: `Server "${serverName}" installed and ready to use.`
189
+ };
190
+ }
191
+ catch (err) {
192
+ return this.error("mcp_error", `Install failed: ${err instanceof Error ? err.message : String(err)}`);
193
+ }
194
+ }
97
195
  if (normalizedAction === "batch") {
98
196
  const calls = params?.calls;
99
197
  if (!Array.isArray(calls) || calls.length === 0) {
@@ -206,7 +304,7 @@ export class McpRouter {
206
304
  }
207
305
  }
208
306
  if (normalizedAction !== "call") {
209
- return this.error("invalid_params", `action must be one of: list, call, batch, refresh, schema, intent, status, promotions`);
307
+ return this.error("invalid_params", `action must be one of: list, call, batch, refresh, schema, intent, status, promotions, search, catalog, install`);
210
308
  }
211
309
  if (!tool) {
212
310
  return this.error("invalid_params", "tool is required for action=call");
@@ -221,7 +221,7 @@ export class StandaloneServer {
221
221
  type: "object",
222
222
  properties: {
223
223
  server: { type: "string", description: "Server name" },
224
- action: { type: "string", description: "list | call | batch | refresh | status | intent | schema | promotions" },
224
+ action: { type: "string", description: "list | call | batch | refresh | status | intent | schema | promotions | search | catalog | install" },
225
225
  tool: { type: "string", description: "Tool name for action=call/schema" },
226
226
  params: { type: "object", description: "Tool arguments" },
227
227
  calls: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwerk/mcp-bridge",
3
- "version": "2.8.4",
3
+ "version": "2.8.6",
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",