@aiwerk/mcp-bridge 2.8.25 → 2.8.27

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.
@@ -69,7 +69,7 @@ export class McpRouter {
69
69
  static generateDescription(servers) {
70
70
  const serverNames = Object.keys(servers);
71
71
  if (serverNames.length === 0) {
72
- return "MCP server multiplexer with no servers configured yet. To add MCP servers, always use this tool first (not npm install): action='search' to find servers in the verified catalog (100+ signed, security-audited recipes), action='install' to add a server by name, action='catalog' to browse all available servers.";
72
+ return "MCP server manager with no servers configured yet. To add MCP servers, always use this tool first (not npm install): action='search' to find servers in the verified catalog (100+ signed, security-audited recipes), action='install' to add a server by name, action='catalog' to browse all available servers.";
73
73
  }
74
74
  const serverList = serverNames
75
75
  .map((name) => {
@@ -77,7 +77,7 @@ export class McpRouter {
77
77
  return desc ? `${name} (${desc})` : name;
78
78
  })
79
79
  .join(", ");
80
- return `MCP server multiplexer with ${serverNames.length} connected servers: ${serverList}. Actions: 'call' to execute a tool, 'list' to discover tools on a server, 'batch' for multiple calls in one round-trip, 'status' to check connections, 'refresh' to re-discover tools. To add new MCP servers, always use this tool first (not npm install): 'search' to find servers in the verified catalog (100+ signed, security-audited recipes), 'install' to add a server by name. Use 'set-mode' with params.mode='direct' to expose all tools individually (requires restart). If the user mentions a specific tool by name, the call action auto-connects and works without listing first.`;
80
+ return `MCP server manager with ${serverNames.length} connected servers: ${serverList}. Actions: 'call' to execute a tool, 'list' to discover tools on a server, 'batch' for multiple calls in one round-trip, 'status' to check connections, 'refresh' to re-discover tools. To add new MCP servers, always use this tool first (not npm install): 'search' to find servers in the verified catalog (100+ signed, security-audited recipes), 'install' to add a server by name. Use 'set-mode' with params.mode='direct' to expose all tools individually (requires restart). If the user mentions a specific tool by name, the call action auto-connects and works without listing first.`;
81
81
  }
82
82
  async dispatch(server, action = "call", tool, params) {
83
83
  try {
@@ -244,14 +244,31 @@ export class StandaloneServer {
244
244
  }
245
245
  };
246
246
  }
247
- // Direct mode: discover all tools from all servers
248
- await this.discoverDirectTools();
249
- const tools = this.directTools.map(t => ({
250
- name: t.registeredName,
251
- description: t.description,
252
- inputSchema: t.inputSchema
253
- }));
254
- return { jsonrpc: "2.0", id, result: { tools } };
247
+ // Direct mode: return tools lazily
248
+ // If already discovered, use real tool list. Otherwise, generate placeholder
249
+ // tools from config descriptions (no child process startup on tools/list).
250
+ if (this.directTools.length > 0) {
251
+ const tools = this.directTools.map(t => ({
252
+ name: t.registeredName,
253
+ description: t.description,
254
+ inputSchema: t.inputSchema
255
+ }));
256
+ return { jsonrpc: "2.0", id, result: { tools } };
257
+ }
258
+ // Lazy: generate tool entries from server config (no connect yet)
259
+ const placeholderTools = [];
260
+ const globalNames = new Set();
261
+ for (const [serverName, serverConfig] of Object.entries(this.config.servers)) {
262
+ const desc = serverConfig.description || serverName;
263
+ const registeredName = pickRegisteredToolName(serverName, "call", this.config.toolPrefix, new Set(), globalNames, this.logger);
264
+ globalNames.add(registeredName);
265
+ placeholderTools.push({
266
+ name: registeredName,
267
+ description: `[${serverName}] ${desc} — tools will be discovered on first call. Use this tool to trigger discovery.`,
268
+ inputSchema: { type: "object", properties: { _discover: { type: "boolean", description: "Set to true to discover all tools from this server" } } }
269
+ });
270
+ }
271
+ return { jsonrpc: "2.0", id, result: { tools: placeholderTools } };
255
272
  }
256
273
  async handleToolsCall(id, params) {
257
274
  const toolName = params?.name;
@@ -295,7 +312,28 @@ export class StandaloneServer {
295
312
  };
296
313
  }
297
314
  // Direct mode: find and call the tool
298
- const entry = this.directTools.find(t => t.registeredName === toolName);
315
+ let entry = this.directTools.find(t => t.registeredName === toolName);
316
+ // Lazy discovery: if tool not found, maybe we haven't connected yet
317
+ if (!entry) {
318
+ // Check if this looks like a placeholder tool (serverName_call pattern) or just unknown
319
+ // Try full discovery if we haven't done it yet
320
+ if (this.directTools.length === 0 || toolArgs?._discover) {
321
+ this.logger.info(`[mcp-bridge] Lazy discovery triggered by tool call: ${toolName}`);
322
+ await this.discoverDirectTools();
323
+ entry = this.directTools.find(t => t.registeredName === toolName);
324
+ // If the original call was a placeholder, send back the discovered tools list
325
+ if (!entry && toolArgs?._discover) {
326
+ const discovered = this.directTools.map(t => `${t.registeredName}: ${t.description}`);
327
+ return {
328
+ jsonrpc: "2.0",
329
+ id,
330
+ result: {
331
+ content: [{ type: "text", text: `Discovered ${this.directTools.length} tools:\n${discovered.join("\n")}` }]
332
+ }
333
+ };
334
+ }
335
+ }
336
+ }
299
337
  if (!entry) {
300
338
  return {
301
339
  jsonrpc: "2.0",
@@ -308,7 +346,32 @@ export class StandaloneServer {
308
346
  };
309
347
  }
310
348
  try {
311
- const conn = this.directConnections.get(entry.serverName);
349
+ let conn = this.directConnections.get(entry.serverName);
350
+ // Lazy connect: if server not connected yet, connect now
351
+ if (!conn || !conn.transport.isConnected()) {
352
+ const serverConfig = this.config.servers[entry.serverName];
353
+ if (serverConfig) {
354
+ try {
355
+ this.logger.info(`[mcp-bridge] Lazy connecting to ${entry.serverName}...`);
356
+ const transport = this.createTransport(entry.serverName, serverConfig);
357
+ await transport.connect();
358
+ await initializeProtocol(transport, PACKAGE_VERSION);
359
+ conn = { transport, initialized: true };
360
+ this.directConnections.set(entry.serverName, conn);
361
+ }
362
+ catch (connErr) {
363
+ return {
364
+ jsonrpc: "2.0",
365
+ id,
366
+ error: {
367
+ code: -32001,
368
+ message: `Failed to connect to ${entry.serverName}: ${connErr instanceof Error ? connErr.message : String(connErr)}`,
369
+ data: { errorType: "connection_failed", server: entry.serverName, retriable: true }
370
+ }
371
+ };
372
+ }
373
+ }
374
+ }
312
375
  if (!conn || !conn.transport.isConnected()) {
313
376
  return {
314
377
  jsonrpc: "2.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwerk/mcp-bridge",
3
- "version": "2.8.25",
3
+ "version": "2.8.27",
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",