@aiwerk/mcp-bridge 2.8.30 → 2.8.32

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.
@@ -14,6 +14,7 @@ export declare class StandaloneServer {
14
14
  private readonly requestIdState;
15
15
  private directTools;
16
16
  private directConnections;
17
+ private stdoutRef;
17
18
  constructor(config: BridgeConfig, logger: Logger);
18
19
  private isRouterMode;
19
20
  /** Start stdio mode: read JSON-RPC from stdin, write responses to stdout.
@@ -30,6 +31,8 @@ export declare class StandaloneServer {
30
31
  /** Connect to all backend servers and discover their tools (direct mode). */
31
32
  private discoverDirectTools;
32
33
  private _doDiscovery;
34
+ /** Send notifications/tools/list_changed to the client via stdout */
35
+ private sendToolsChanged;
33
36
  /** Extract server name from a tool name like "todoist_call" or "github_call" */
34
37
  private guessServerFromToolName;
35
38
  /** Discover tools from a single server (lazy, per-server) */
@@ -26,6 +26,7 @@ export class StandaloneServer {
26
26
  // Direct mode state
27
27
  directTools = [];
28
28
  directConnections = new Map();
29
+ stdoutRef = null;
29
30
  constructor(config, logger) {
30
31
  this.config = config;
31
32
  this.logger = logger;
@@ -50,6 +51,7 @@ export class StandaloneServer {
50
51
  async startStdio() {
51
52
  const stdin = process.stdin;
52
53
  const stdout = process.stdout;
54
+ this.stdoutRef = stdout;
53
55
  let buffer = Buffer.alloc(0);
54
56
  // LSP framing state
55
57
  let lspContentLength = -1; // -1 means not in LSP mode for current message
@@ -258,9 +260,10 @@ export class StandaloneServer {
258
260
  }));
259
261
  return { jsonrpc: "2.0", id, result: { tools } };
260
262
  }
261
- // Lazy: try cache first, then placeholder
263
+ // Lazy: try cache first, add discover tool for uncached servers
262
264
  const lazyTools = [];
263
265
  const globalNames = new Set();
266
+ const uncachedServers = [];
264
267
  for (const [serverName, serverConfig] of Object.entries(this.config.servers)) {
265
268
  const cached = this.loadToolCache(serverName);
266
269
  if (cached && cached.length > 0) {
@@ -271,7 +274,6 @@ export class StandaloneServer {
271
274
  localNames.add(registeredName);
272
275
  globalNames.add(registeredName);
273
276
  lazyTools.push({ name: registeredName, description: tool.description, inputSchema: tool.inputSchema });
274
- // Also populate directTools so call works with lazy connect
275
277
  this.directTools.push({
276
278
  serverName, originalName: tool.name, registeredName,
277
279
  description: tool.description, inputSchema: tool.inputSchema
@@ -279,21 +281,31 @@ export class StandaloneServer {
279
281
  }
280
282
  }
281
283
  else {
282
- // No cache: single placeholder per server
283
- const desc = serverConfig.description || serverName;
284
- const registeredName = pickRegisteredToolName(serverName, "call", this.config.toolPrefix, new Set(), globalNames, this.logger);
285
- globalNames.add(registeredName);
286
- lazyTools.push({
287
- name: registeredName,
288
- description: `[${serverName}] ${desc} — call this tool to discover all available tools from this server.`,
289
- inputSchema: { type: "object", properties: { _discover: { type: "boolean", description: "Set to true to discover all tools" } } }
290
- });
291
- this.directTools.push({
292
- serverName, originalName: "call", registeredName,
293
- description: `[${serverName}] ${desc}`, inputSchema: {}
294
- });
284
+ uncachedServers.push(serverName);
295
285
  }
296
286
  }
287
+ // Add a single discover tool if there are uncached servers
288
+ if (uncachedServers.length > 0) {
289
+ const serverDescs = uncachedServers.map(name => {
290
+ const desc = this.config.servers[name]?.description || name;
291
+ return `${name} (${desc})`;
292
+ });
293
+ lazyTools.push({
294
+ name: "mcp_discover",
295
+ description: `Connect to MCP servers and discover their tools. Servers not yet connected: ${serverDescs.join(", ")}. Call this before using any of these servers.`,
296
+ inputSchema: {
297
+ type: "object",
298
+ properties: {
299
+ server: {
300
+ type: "string",
301
+ description: `Server to discover. Available: ${uncachedServers.join(", ")}`,
302
+ enum: uncachedServers
303
+ }
304
+ },
305
+ required: ["server"]
306
+ }
307
+ });
308
+ }
297
309
  return { jsonrpc: "2.0", id, result: { tools: lazyTools } };
298
310
  }
299
311
  async handleToolsCall(id, params) {
@@ -337,25 +349,34 @@ export class StandaloneServer {
337
349
  }
338
350
  };
339
351
  }
352
+ // Handle mcp_discover tool
353
+ if (toolName === "mcp_discover") {
354
+ const serverName = toolArgs?.server;
355
+ if (!serverName || !this.config.servers[serverName]) {
356
+ const available = Object.keys(this.config.servers).join(", ");
357
+ return {
358
+ jsonrpc: "2.0", id,
359
+ result: { content: [{ type: "text", text: `Please specify a server. Available: ${available}` }] }
360
+ };
361
+ }
362
+ this.logger.info(`[mcp-bridge] Discovering server: ${serverName}`);
363
+ await this.discoverSingleServer(serverName);
364
+ const serverTools = this.directTools.filter(t => t.serverName === serverName);
365
+ const discovered = serverTools.map(t => `${t.registeredName}: ${t.description}`);
366
+ return {
367
+ jsonrpc: "2.0", id,
368
+ result: { content: [{ type: "text", text: `Connected to "${serverName}". Discovered ${serverTools.length} tools:\n\n${discovered.join("\n")}\n\nThese tools are now available. Call them directly by name.` }] }
369
+ };
370
+ }
340
371
  // Direct mode: find and call the tool
341
372
  let entry = this.directTools.find(t => t.registeredName === toolName);
342
- // Lazy discovery: if tool not found, discover only the relevant server (not all)
373
+ // Lazy discovery: if tool not found, try to discover the relevant server
343
374
  if (!entry) {
344
- // Extract server name from tool name (prefix_toolname pattern)
345
375
  const serverName = this.guessServerFromToolName(toolName);
346
- if (serverName) {
347
- this.logger.info(`[mcp-bridge] Lazy discovery for server: ${serverName} (triggered by ${toolName})`);
376
+ if (serverName && !this.directConnections.get(serverName)?.initialized) {
377
+ this.logger.info(`[mcp-bridge] Auto-discovering server: ${serverName} (triggered by ${toolName})`);
348
378
  await this.discoverSingleServer(serverName);
349
- // After discovery, the placeholder tool no longer exists - return discovered tools
350
- const serverTools = this.directTools.filter(t => t.serverName === serverName);
351
- const discovered = serverTools.map(t => `${t.registeredName}: ${t.description}`);
352
- return {
353
- jsonrpc: "2.0",
354
- id,
355
- result: {
356
- content: [{ type: "text", text: `Discovered ${serverTools.length} tools from ${serverName}. Available tools:\n\n${discovered.join("\n")}\n\nCall any of these tools directly by name.` }]
357
- }
358
- };
379
+ entry = this.directTools.find(t => t.registeredName === toolName);
359
380
  }
360
381
  }
361
382
  if (!entry) {
@@ -494,6 +515,14 @@ export class StandaloneServer {
494
515
  }
495
516
  }
496
517
  }
518
+ /** Send notifications/tools/list_changed to the client via stdout */
519
+ sendToolsChanged() {
520
+ if (!this.stdoutRef)
521
+ return;
522
+ const notification = { jsonrpc: "2.0", method: "notifications/tools/list_changed" };
523
+ this.writeResponse(this.stdoutRef, notification);
524
+ this.logger.info("[mcp-bridge] Sent notifications/tools/list_changed");
525
+ }
497
526
  /** Extract server name from a tool name like "todoist_call" or "github_call" */
498
527
  guessServerFromToolName(toolName) {
499
528
  // Try exact match with placeholder pattern: serverName_call
@@ -538,6 +567,8 @@ export class StandaloneServer {
538
567
  // Cache tools to disk
539
568
  this.saveToolCache(serverName, tools);
540
569
  this.logger.info(`[mcp-bridge] Discovered ${tools.length} tools from ${serverName}`);
570
+ // Notify client that tool list changed (MCP spec: notifications/tools/list_changed)
571
+ this.sendToolsChanged();
541
572
  }
542
573
  catch (err) {
543
574
  this.logger.error(`[mcp-bridge] Failed to discover ${serverName}:`, err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwerk/mcp-bridge",
3
- "version": "2.8.30",
3
+ "version": "2.8.32",
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",