@aiwerk/mcp-bridge 2.8.5 → 2.8.7

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,12 +195,21 @@ 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());
198
+ // Detect global install: check if we're in a global node_modules (not under a project)
199
+ // Global paths: ~/.nvm/.../lib/node_modules, /usr/lib/node_modules, etc.
200
+ // Local paths: ~/projects/.../node_modules, ~/node_modules/
201
+ const isGlobal = __dirname.includes("node_modules") && (__dirname.includes("/lib/node_modules/") || __dirname.includes("\\lib\\node_modules\\"));
202
+ const cmd = isGlobal ? "mcp-bridge" : `node ${join(__dirname, "..", "bin", "mcp-bridge.js")}`;
200
203
  process.stdout.write(`
201
204
  Next step: add mcp-bridge to your MCP client.
202
205
 
203
- Add this to your client's MCP server config:
206
+ Claude Code: claude mcp add -s user mcp-bridge -- ${cmd} serve
207
+ Cursor: Add to ~/.cursor/mcp.json
208
+ Claude Desktop: Add to claude_desktop_config.json
209
+ Windsurf: Add to ~/.windsurf/mcp.json
210
+ OpenClaw: openclaw plugins install @aiwerk/openclaw-mcp-bridge
211
+
212
+ For Cursor/Claude Desktop/Windsurf, add this JSON block:
204
213
 
205
214
  {
206
215
  "mcp-bridge": {
@@ -208,9 +217,6 @@ Add this to your client's MCP server config:
208
217
  "args": ${isGlobal ? '["serve"]' : `["${join(__dirname, "..", "bin", "mcp-bridge.js")}", "serve"]`}
209
218
  }
210
219
  }
211
-
212
- Supported clients: Claude Code (~/.claude/settings.json),
213
- Cursor (~/.cursor/mcp.json), Claude Desktop, Windsurf, OpenClaw, etc.
214
220
  ${!isGlobal ? "\nTip: Install globally for a cleaner setup:\n npm install -g @aiwerk/mcp-bridge\n" : ""}
215
221
  After adding, restart your client. The bridge will appear as an 'mcp' tool
216
222
  with search, install, and catalog actions to discover and add MCP servers.
@@ -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.5",
3
+ "version": "2.8.7",
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",