@loop_ouroboros/mcp-hub-lite 1.3.2 → 1.3.3

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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.3.3] - 2026-06-10
6
+
7
+ ### Hub
8
+
9
+ - fix tag-match-unique error when reading server-level resources
10
+ - return per-instance tags array instead of merged object
11
+ - remove dead strictMode parameter from server-selector
12
+ - remove L1 fallback in call-tool, route directly to RANDOM
13
+ - remove gateway mode in call-tool, simplify tool validation
14
+
15
+ ### Docs
16
+
17
+ - update hub-tools CLAUDE.md with current architecture
18
+
5
19
  ## [1.3.2] - 2026-06-09
6
20
 
7
21
  ### Search
@@ -5,4 +5,5 @@ export { getSystemTools } from './system-tool-definitions.js';
5
5
  export type { ToolAnnotations, SystemToolDefinition } from './system-tool-definitions.js';
6
6
  export { generateDynamicResources, readResource } from './resource-generator.js';
7
7
  export type { ServerMetadata } from './resource-generator.js';
8
+ export { serverMetadataCache } from './server-metadata-cache.js';
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/services/hub-tools/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG3E,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAClF,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5F,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EAAE,wBAAwB,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACjF,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/services/hub-tools/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG3E,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAClF,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5F,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EAAE,wBAAwB,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACjF,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC"}
@@ -3,3 +3,4 @@ export { HubToolsService, hubToolsService } from '../hub-tools.service.js';
3
3
  export { hasValidId, selectBestInstance, getServerDescription } from './server-selector.js';
4
4
  export { getSystemTools } from './system-tool-definitions.js';
5
5
  export { generateDynamicResources, readResource } from './resource-generator.js';
6
+ export { serverMetadataCache } from './server-metadata-cache.js';
@@ -9,7 +9,7 @@ export interface ServerMetadata {
9
9
  toolsCount: number;
10
10
  tools: Record<string, string>;
11
11
  resourcesCount: number;
12
- tags: Record<string, string>;
12
+ tags: Array<Record<string, string>>;
13
13
  lastHeartbeat: number;
14
14
  uptime: number;
15
15
  description: string;
@@ -1 +1 @@
1
- {"version":3,"file":"resource-generator.d.ts","sourceRoot":"","sources":["../../../../../src/services/hub-tools/resource-generator.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAmLlE;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,IAAI,QAAQ,EAAE,CAqErD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,GAAG,QAAQ,EAAE,GAAG,MAAM,GAAG,OAAO,CAAC,CAyGzD"}
1
+ {"version":3,"file":"resource-generator.d.ts","sourceRoot":"","sources":["../../../../../src/services/hub-tools/resource-generator.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAoLlE;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,IAAI,QAAQ,EAAE,CAqErD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,GAAG,QAAQ,EAAE,GAAG,MAAM,GAAG,OAAO,CAAC,CAkFzD"}
@@ -3,7 +3,8 @@ import { dirname, join } from 'path';
3
3
  import fs from 'fs';
4
4
  import { hubManager } from '../hub-manager.service.js';
5
5
  import { mcpConnectionManager } from '../connection/index.js';
6
- import { hasValidId, selectBestInstance, getServerDescription } from './server-selector.js';
6
+ import { hasValidId, getServerDescription } from './server-selector.js';
7
+ import { serverMetadataCache } from './server-metadata-cache.js';
7
8
  /**
8
9
  * Maps Hub URI to original MCP URI for resource forwarding.
9
10
  * Key: Hub URI (e.g., "hub://servers/exa-ai/0/tools/list")
@@ -280,43 +281,24 @@ export async function readResource(uri, language) {
280
281
  if (instanceIndex === undefined) {
281
282
  // Handle list requests first
282
283
  if (listType) {
283
- // Use selectBestInstance to get an instance for the list
284
- const serverInfo = selectBestInstance(serverName);
285
- if (!serverInfo) {
284
+ // Read from aggregated cache for server-level list queries
285
+ const metadata = serverMetadataCache.get(serverName);
286
+ if (!metadata) {
286
287
  throw new Error(`Server not found or not connected: ${serverName}`);
287
288
  }
288
- const instanceIndex = serverInfo.instance.index;
289
289
  if (listType === 'tools') {
290
- return mcpConnectionManager.getTools(serverName, instanceIndex);
290
+ return mcpConnectionManager.getToolsByServerName(serverName);
291
291
  }
292
292
  else {
293
- return mcpConnectionManager.getResources(serverName, instanceIndex);
293
+ return mcpConnectionManager.getResourcesByName(serverName);
294
294
  }
295
295
  }
296
- // Server metadata request - use selectBestInstance to get runtime properties
297
- const serverInfo = selectBestInstance(serverName);
298
- if (!serverInfo) {
296
+ // Server metadata request read from aggregated cache (no instance selection needed)
297
+ const metadata = serverMetadataCache.get(serverName);
298
+ if (!metadata) {
299
299
  throw new Error(`Server not found or not connected: ${serverName}`);
300
300
  }
301
- const instanceIndex = serverInfo.instance.index;
302
- const tools = mcpConnectionManager.getTools(serverName, instanceIndex);
303
- const resources = mcpConnectionManager.getResources(serverName, instanceIndex);
304
- // Build tool name to description map
305
- const toolsMap = {};
306
- for (const tool of tools) {
307
- toolsMap[tool.name] = tool.description || '';
308
- }
309
- return {
310
- name: serverName,
311
- status: serverInfo.instance.status,
312
- toolsCount: tools.length,
313
- tools: toolsMap,
314
- resourcesCount: resources.length,
315
- tags: serverInfo.instance.tags || {},
316
- lastHeartbeat: serverInfo.instance.lastHeartbeat,
317
- uptime: serverInfo.instance.uptime,
318
- description: getServerDescription(serverConfig, serverName)
319
- };
301
+ return metadata;
320
302
  }
321
303
  // MCP native resource forwarding: hub://servers/{name}/{instanceIndex}/{mcpPath}
322
304
  // Find the specific instance by index
@@ -0,0 +1,19 @@
1
+ import type { ServerMetadata } from './resource-generator.js';
2
+ /**
3
+ * Cached server-level metadata aggregated across all connected instances.
4
+ * Updated reactively via EventBus subscriptions — no I/O, all synchronous cache lookups.
5
+ */
6
+ declare class ServerMetadataCache {
7
+ private cache;
8
+ private unsubscribers;
9
+ private initialized;
10
+ initialize(): void;
11
+ destroy(): void;
12
+ get(serverName: string): ServerMetadata | undefined;
13
+ refresh(serverName: string): void;
14
+ refreshAll(): void;
15
+ private buildMetadata;
16
+ }
17
+ export declare const serverMetadataCache: ServerMetadataCache;
18
+ export {};
19
+ //# sourceMappingURL=server-metadata-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-metadata-cache.d.ts","sourceRoot":"","sources":["../../../../../src/services/hub-tools/server-metadata-cache.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAO9D;;;GAGG;AACH,cAAM,mBAAmB;IACvB,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,WAAW,CAAS;IAE5B,UAAU,IAAI,IAAI;IA4BlB,OAAO,IAAI,IAAI;IASf,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAYnD,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IASjC,UAAU,IAAI,IAAI;IASlB,OAAO,CAAC,aAAa;CAkDtB;AAED,eAAO,MAAM,mBAAmB,qBAA4B,CAAC"}
@@ -0,0 +1,117 @@
1
+ import { hubManager } from '../hub-manager.service.js';
2
+ import { mcpConnectionManager } from '../connection/index.js';
3
+ import { eventBus, EventTypes } from '../event-bus.service.js';
4
+ import { getServerDescription } from './server-selector.js';
5
+ /**
6
+ * Cached server-level metadata aggregated across all connected instances.
7
+ * Updated reactively via EventBus subscriptions — no I/O, all synchronous cache lookups.
8
+ */
9
+ class ServerMetadataCache {
10
+ cache = new Map();
11
+ unsubscribers = [];
12
+ initialized = false;
13
+ initialize() {
14
+ if (this.initialized)
15
+ return;
16
+ this.initialized = true;
17
+ this.unsubscribers.push(eventBus.subscribe(EventTypes.SERVER_CONNECTED, (data) => {
18
+ this.refresh(data.serverName);
19
+ }));
20
+ this.unsubscribers.push(eventBus.subscribe(EventTypes.SERVER_DISCONNECTED, (data) => {
21
+ this.refresh(data.serverName);
22
+ }));
23
+ this.unsubscribers.push(eventBus.subscribe(EventTypes.TOOLS_UPDATED, (data) => {
24
+ this.refresh(data.serverName);
25
+ }));
26
+ this.unsubscribers.push(eventBus.subscribe(EventTypes.RESOURCES_UPDATED, (data) => {
27
+ this.refresh(data.serverName);
28
+ }));
29
+ // Cache populates lazily on first get() — avoids init-order issues in tests
30
+ }
31
+ destroy() {
32
+ for (const unsub of this.unsubscribers) {
33
+ unsub();
34
+ }
35
+ this.unsubscribers = [];
36
+ this.cache.clear();
37
+ this.initialized = false;
38
+ }
39
+ get(serverName) {
40
+ // Lazy build on first access — no pre-population needed
41
+ if (!this.cache.has(serverName)) {
42
+ const metadata = this.buildMetadata(serverName);
43
+ if (metadata) {
44
+ this.cache.set(serverName, metadata);
45
+ }
46
+ return metadata;
47
+ }
48
+ return this.cache.get(serverName);
49
+ }
50
+ refresh(serverName) {
51
+ const metadata = this.buildMetadata(serverName);
52
+ if (metadata) {
53
+ this.cache.set(serverName, metadata);
54
+ }
55
+ else {
56
+ this.cache.delete(serverName);
57
+ }
58
+ }
59
+ refreshAll() {
60
+ const servers = hubManager.getAllServers();
61
+ for (const server of servers) {
62
+ if (server.name) {
63
+ this.refresh(server.name);
64
+ }
65
+ }
66
+ }
67
+ buildMetadata(serverName) {
68
+ const serverConfig = hubManager.getServerByName(serverName);
69
+ if (!serverConfig)
70
+ return undefined;
71
+ const connectedIndexes = mcpConnectionManager.getConnectedIndexes(serverName);
72
+ if (connectedIndexes.length === 0)
73
+ return undefined;
74
+ // Aggregate tools across all instances (ToolCache already deduplicates by name)
75
+ const tools = mcpConnectionManager.getToolsByServerName(serverName) || [];
76
+ const toolsMap = {};
77
+ for (const tool of tools) {
78
+ toolsMap[tool.name] = tool.description || '';
79
+ }
80
+ // Aggregate resources across all instances
81
+ const resources = mcpConnectionManager.getResourcesByName(serverName) || [];
82
+ // Collect tags from all connected instances
83
+ const tags = [];
84
+ const instances = hubManager.getServerInstancesByName(serverName);
85
+ for (const instance of instances) {
86
+ if (instance.index !== undefined && connectedIndexes.includes(instance.index)) {
87
+ tags.push(instance.tags || {});
88
+ }
89
+ }
90
+ // Aggregate heartbeat (max) and uptime (min startTime) across instances
91
+ let lastHeartbeat = 0;
92
+ let uptime = Infinity;
93
+ for (const idx of connectedIndexes) {
94
+ const status = mcpConnectionManager.getStatus(serverName, idx);
95
+ if (status) {
96
+ if (status.lastCheck > lastHeartbeat)
97
+ lastHeartbeat = status.lastCheck;
98
+ if (status.startTime && status.startTime < uptime)
99
+ uptime = status.startTime;
100
+ }
101
+ }
102
+ if (uptime === Infinity)
103
+ uptime = 0;
104
+ return {
105
+ name: serverName,
106
+ status: 'online',
107
+ toolsCount: tools.length,
108
+ tools: toolsMap,
109
+ resourcesCount: resources.length,
110
+ tags,
111
+ lastHeartbeat,
112
+ uptime,
113
+ description: getServerDescription(serverConfig, serverName)
114
+ };
115
+ }
116
+ }
117
+ export const serverMetadataCache = new ServerMetadataCache();
@@ -72,5 +72,5 @@ export declare function hasValidId(server: unknown): server is ValidServer;
72
72
  * const serverInfoNonStrict = selectBestInstance('my-mcp-server', undefined, false);
73
73
  * ```
74
74
  */
75
- export declare function selectBestInstance(serverName: string, requestOptions?: RequestOptions, strictMode?: boolean): ServerInstanceInfo | undefined;
75
+ export declare function selectBestInstance(serverName: string, requestOptions?: RequestOptions): ServerInstanceInfo | undefined;
76
76
  //# sourceMappingURL=server-selector.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server-selector.d.ts","sourceRoot":"","sources":["../../../../../src/services/hub-tools/server-selector.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAElF;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE;IAAE,QAAQ,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG,SAAS,EACjE,UAAU,EAAE,MAAM,GACjB,MAAM,CAKR;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI,WAAW,CAMjE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,GAAE,OAAc,GACzB,kBAAkB,GAAG,SAAS,CA8ChC"}
1
+ {"version":3,"file":"server-selector.d.ts","sourceRoot":"","sources":["../../../../../src/services/hub-tools/server-selector.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAElF;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE;IAAE,QAAQ,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG,SAAS,EACjE,UAAU,EAAE,MAAM,GACjB,MAAM,CAKR;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI,WAAW,CAMjE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,cAAc,GAC9B,kBAAkB,GAAG,SAAS,CAyChC"}
@@ -80,19 +80,16 @@ export function hasValidId(server) {
80
80
  * const serverInfoNonStrict = selectBestInstance('my-mcp-server', undefined, false);
81
81
  * ```
82
82
  */
83
- export function selectBestInstance(serverName, requestOptions, strictMode = true) {
84
- // Get all instances of the server
83
+ export function selectBestInstance(serverName, requestOptions) {
85
84
  const instances = hubManager.getServerInstancesByName(serverName);
86
85
  if (instances.length === 0) {
87
86
  return undefined;
88
87
  }
89
- // Get server configuration
90
88
  const serverConfig = hubManager.getServerByName(serverName);
91
89
  if (!serverConfig) {
92
90
  return undefined;
93
91
  }
94
92
  try {
95
- // Use the new instance selector
96
93
  const selectedInstance = InstanceSelector.selectInstance(serverName, serverConfig, requestOptions);
97
94
  if (!selectedInstance) {
98
95
  return undefined;
@@ -104,12 +101,10 @@ export function selectBestInstance(serverName, requestOptions, strictMode = true
104
101
  };
105
102
  }
106
103
  catch (error) {
107
- // In strict mode, re-throw tag matching errors to maintain correct semantics
108
- if (strictMode && error instanceof TagMatchUniqueError) {
109
- // Re-throw with server context added to message
104
+ // Re-throw tag matching errors with server context
105
+ if (error instanceof TagMatchUniqueError) {
110
106
  throw new Error(`[${serverName}] ${error.message}`);
111
107
  }
112
- // Handle other errors or non-strict mode gracefully
113
108
  logger.error(`Instance selection failed for server ${serverName}:`, error, LOG_MODULES.SERVER_SELECTOR);
114
109
  return undefined;
115
110
  }
@@ -1 +1 @@
1
- {"version":3,"file":"system-tool-definitions.d.ts","sourceRoot":"","sources":["../../../../../src/services/hub-tools/system-tool-definitions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAY/D;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC;IACxB,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,cAAc,IAAI,oBAAoB,EAAE,CA+LvD"}
1
+ {"version":3,"file":"system-tool-definitions.d.ts","sourceRoot":"","sources":["../../../../../src/services/hub-tools/system-tool-definitions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAY/D;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC;IACxB,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,cAAc,IAAI,oBAAoB,EAAE,CAgMvD"}
@@ -111,7 +111,7 @@ export function getSystemTools() {
111
111
  properties: {
112
112
  serverName: {
113
113
  type: 'string',
114
- description: 'Name of the MCP server (must be an external server, not mcp-hub-lite)'
114
+ description: 'Name of the target MCP server. Use the exact server name from list_servers results. If you already know the server name (e.g., from previous list_tools or search_tools calls), call directly without re-searching.'
115
115
  },
116
116
  toolName: { type: 'string', description: 'Name of the tool to call' },
117
117
  toolArgs: { type: 'object', description: 'Arguments to pass to the tool' },
@@ -1 +1 @@
1
- {"version":3,"file":"hub-tools.service.d.ts","sourceRoot":"","sources":["../../../../src/services/hub-tools.service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAQjE,OAAO,EAEL,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,cAAc,EACd,8BAA8B,EAC9B,cAAc,EACd,iBAAiB,EAElB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,uBAAuB,EACvB,aAAa,EACb,cAAc,EACd,6BAA6B,EAC7B,cAAc,EACd,iBAAiB,EAClB,MAAM,mCAAmC,CAAC;AAa3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,qBAAa,eAAe;;IAM1B;;;;;;;;;OASG;IACH,cAAc;IAId;;;;;;;;;OASG;IACG,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAkBpD;;;;;;;;;;;OAWG;IACG,iBAAiB,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC;QAC9D,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,WAAW,EAAE,CAAC;KACtB,CAAC;IAqDF;;;;;;;;;;OAUG;IACG,OAAO,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;IA+B7D;;;;;;;;;;OAUG;IACG,uBAAuB,CAAC,IAAI,EAAE,6BAA6B,GAAG,OAAO,CAAC;QAC1E,OAAO,EAAE,OAAO,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IAkCF;;;;;;;;;;OAUG;IACG,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC;QAC5C,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,KAAK,CAAC;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;SAAE,CAAC,CAAC;KAC/E,CAAC;IAmBF;;;;;;;;;;;OAWG;IACG,cAAc,CAAC,CAAC,SAAS,cAAc,EAC3C,QAAQ,EAAE,CAAC,EACX,QAAQ,EAAE,CAAC,SAAS,OAAO,iBAAiB,GACxC,iBAAiB,GACjB,CAAC,SAAS,OAAO,eAAe,GAC9B,uBAAuB,GACvB,CAAC,SAAS,OAAO,aAAa,GAC5B,aAAa,GACb,CAAC,SAAS,OAAO,cAAc,GAC7B,cAAc,GACd,CAAC,SAAS,OAAO,8BAA8B,GAC7C,6BAA6B,GAC7B,CAAC,SAAS,OAAO,cAAc,GAC7B,cAAc,GACd,CAAC,SAAS,OAAO,iBAAiB,GAChC,iBAAiB,GACjB,KAAK,GACpB,OAAO,CACR,CAAC,SAAS,OAAO,iBAAiB,GAC9B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACtB,CAAC,SAAS,OAAO,eAAe,GAC9B;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,WAAW,EAAE,CAAA;KAAE,GAC5C,CAAC,SAAS,OAAO,aAAa,GAC5B,IAAI,GAAG,SAAS,GAChB,CAAC,SAAS,OAAO,cAAc,GAC7B,OAAO,GACP,CAAC,SAAS,OAAO,8BAA8B,GAC7C;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAC7D,CAAC,SAAS,OAAO,cAAc,GAC7B;QACE,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,KAAK,CAAC;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;SAAE,CAAC,CAAC;KAC/E,GACD,CAAC,SAAS,OAAO,iBAAiB,GAChC,MAAM,CAAC,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,WAAW,EAAE,CAAA;KAAE,CAAC,GAC7D,KAAK,CACtB;IAkFD;;;;;;;;;;;OAWG;IACG,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;IAgTtD;;;;;;;;OAQG;IACG,YAAY,IAAI,OAAO,CAC3B,MAAM,CACJ,MAAM,EACN;QACE,KAAK,EAAE,WAAW,EAAE,CAAC;KACtB,CACF,CACF;IAuCD;;;;;;;;;OASG;IACG,WAAW,CACf,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAU,GAChB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,WAAW,EAAE,CAAA;KAAE,CAAC,CAAC;IAsDzE;;;;;;;;OAQG;IACG,aAAa,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAK1C;;;;;;;;;;;;;OAaG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CACpC;QACE,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,OAAO,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC9B,cAAc,EAAE,MAAM,CAAC;QACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC7B,aAAa,EAAE,MAAM,CAAC;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,CAAC;KACrB,GACD,IAAI,EAAE,GACN,QAAQ,EAAE,GACV,MAAM,CACT;CAkBF;AAED,eAAO,MAAM,eAAe,iBAAwB,CAAC"}
1
+ {"version":3,"file":"hub-tools.service.d.ts","sourceRoot":"","sources":["../../../../src/services/hub-tools.service.ts"],"names":[],"mappings":"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"}
@@ -10,7 +10,7 @@ import { countMatchingTokens } from '../utils/search-matcher.js';
10
10
  import { McpError } from '@modelcontextprotocol/sdk/types.js';
11
11
  import { MCP_HUB_LITE_SERVER, LIST_SERVERS_TOOL, LIST_TOOLS_TOOL, GET_TOOL_TOOL, CALL_TOOL_TOOL, UPDATE_SERVER_DESCRIPTION_TOOL, LIST_TAGS_TOOL, SEARCH_TOOLS_TOOL, SYSTEM_TOOL_NAMES } from '../models/system-tools.constants.js';
12
12
  import { ToolArgsParser } from '../utils/tool-args-parser.js';
13
- import { hasValidId, selectBestInstance, getServerDescription, getSystemTools, generateDynamicResources, readResource as readResourceUtil } from './hub-tools/index.js';
13
+ import { hasValidId, selectBestInstance, getServerDescription, getSystemTools, generateDynamicResources, readResource as readResourceUtil, serverMetadataCache } from './hub-tools/index.js';
14
14
  import { InstanceSelector } from './hub-tools/instance-selector.js';
15
15
  import { InstanceSelectionStrategy } from '../models/server.model.js';
16
16
  /**
@@ -64,7 +64,7 @@ import { InstanceSelectionStrategy } from '../models/server.model.js';
64
64
  export class HubToolsService {
65
65
  // Cache removed - listResources() now calls generateDynamicResources() directly
66
66
  constructor() {
67
- // No cache-related initialization needed
67
+ serverMetadataCache.initialize();
68
68
  }
69
69
  /**
70
70
  * Retrieves the complete list of system tools provided by this service.
@@ -372,86 +372,54 @@ export class HubToolsService {
372
372
  throw new McpError(-32801, `System tools cannot be called via 'call_tool'. Use 'tools/call' with the system tool name directly. ` +
373
373
  `Example: use 'list_servers' directly instead of call_tool(serverName: "mcp-hub-lite", toolName: "list_servers").`);
374
374
  }
375
- // Not a system tool - find it in all connected servers
376
- logger.info(`Looking for tool '${toolName}' in all connected servers (gateway mode)`, LOG_MODULES.HUB_TOOLS);
377
- // Find all servers that have this tool
378
- const matchingServers = [];
379
- const servers = hubManager.getAllServers();
380
- for (const server of servers) {
381
- if (!hasValidId(server)) {
382
- continue;
383
- }
384
- const serverInfo = selectBestInstance(server.name, requestOptions, true);
385
- if (serverInfo && serverInfo.instance.id) {
386
- const instanceIndex = serverInfo.instance.index;
387
- const tools = mcpConnectionManager.getTools(server.name, instanceIndex);
388
- if (tools.some((tool) => normalizeToolName(tool.name) === normalizeToolName(toolName))) {
389
- matchingServers.push(server.name);
390
- }
391
- }
392
- }
393
- if (matchingServers.length === 0) {
394
- logger.error(`Tool '${toolName}' not found in any connected server`, LOG_MODULES.HUB_TOOLS);
395
- throw new Error(`Tool '${toolName}' not found`);
396
- }
397
- if (matchingServers.length > 1) {
398
- logger.warn(`Tool '${toolName}' found in multiple servers: ${matchingServers.join(', ')}. Using first match.`, LOG_MODULES.HUB_TOOLS);
399
- }
400
- // Use the first matching server
401
- serverName = matchingServers[0];
375
+ // Not a system tool reject with actionable guidance
376
+ // The aggregated gateway tools (wrapped by tool-list-generator) already hardcode
377
+ // the correct serverName, so this path should only be hit when LLM manually fills
378
+ // "mcp-hub-lite" incorrectly. Guide them to use search_tools instead.
379
+ throw new McpError(-32602, `Cannot call external tool '${toolName}' with serverName "mcp-hub-lite". ` +
380
+ `Use 'search_tools' to find which server provides this tool, ` +
381
+ `then call it with the correct serverName.`);
402
382
  }
403
383
  logger.debug(`Tool call received: serverName=${serverName}, toolName=${toolName}, args=${stringifyForLogging(toolArgs)}`, LOG_MODULES.HUB_TOOLS);
404
- // Validate tool exists before doing strict instance selection
405
- // Use strictMode=false to get serverInfo without triggering tag-match-unique errors
406
- const validationServerInfo = selectBestInstance(serverName, requestOptions, false);
407
- let actualToolName;
408
- if (validationServerInfo && validationServerInfo.instance.id) {
409
- const instanceIndex = validationServerInfo.instance.index;
410
- const tools = mcpConnectionManager.getTools(serverName, instanceIndex);
411
- const matchedTool = tools.find((tool) => normalizeToolName(tool.name) === normalizeToolName(toolName));
412
- if (!matchedTool) {
413
- throw new Error(`Tool '${toolName}' not found in server '${serverName}'. ` +
414
- `Use list_tools(serverName: "${serverName}") to see available tools.`);
415
- }
416
- actualToolName = matchedTool.name;
384
+ // Validate tool exists using server-name-level aggregation (no instance selection needed)
385
+ const aggregatedTools = mcpConnectionManager.getToolsByServerName(serverName);
386
+ const matchedTool = aggregatedTools.find((tool) => normalizeToolName(tool.name) === normalizeToolName(toolName));
387
+ if (!matchedTool) {
388
+ throw new Error(`Tool '${toolName}' not found in server '${serverName}'. ` +
389
+ `Use list_tools(serverName: "${serverName}") to see available tools.`);
417
390
  }
418
- const serverInfo = selectBestInstance(serverName, requestOptions, true);
391
+ let actualToolName = matchedTool.name;
392
+ const serverInfo = selectBestInstance(serverName, requestOptions);
419
393
  const requestId = `tool-call-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
420
394
  if (!serverInfo) {
421
395
  // Server not found in hubManager, try direct call by name through mcpConnectionManager
422
396
  logger.debug(`Server not found in hubManager, trying direct call by name: ${serverName}`, LOG_MODULES.HUB_TOOLS);
423
- // Use selectBestInstance with non-strict mode to find an available instance
424
- let fallbackServerInfo = selectBestInstance(serverName, undefined, false);
425
- // If selectBestInstance returns undefined (e.g., TAG_MATCH_UNIQUE strategy without tags),
426
- // fall back to directly selecting an enabled instance with RANDOM strategy
427
- if (!fallbackServerInfo) {
428
- logger.debug(`selectBestInstance returned undefined for ${serverName}, trying direct instance selection with RANDOM strategy`, LOG_MODULES.HUB_TOOLS);
429
- const serverConfig = hubManager.getServerByName(serverName);
430
- if (serverConfig && serverConfig.instances.length > 0) {
431
- // Filter: use runtime connected status, NOT config enabled flag
432
- // enabled=false means "do not auto-start" but user can manually start it
433
- const connectedInstances = serverConfig.instances.filter((instance) => {
434
- if (instance.index === undefined)
435
- return false;
436
- const status = mcpConnectionManager.getStatus(serverName, instance.index);
437
- return status?.connected;
438
- });
439
- if (connectedInstances.length > 0) {
440
- // Use RANDOM strategy regardless of server's configured strategy
441
- const selectedInstance = InstanceSelector.selectInstance(serverName, {
442
- ...serverConfig,
443
- template: {
444
- ...serverConfig.template,
445
- instanceSelectionStrategy: InstanceSelectionStrategy.RANDOM
446
- }
447
- }, undefined);
448
- if (selectedInstance) {
449
- fallbackServerInfo = {
450
- name: serverName,
451
- config: serverConfig,
452
- instance: selectedInstance
453
- };
397
+ // Fallback: force RANDOM strategy on any connected instance
398
+ // (selectBestInstance may fail for TAG_MATCH_UNIQUE without tags)
399
+ let fallbackServerInfo;
400
+ const serverConfig = hubManager.getServerByName(serverName);
401
+ if (serverConfig && serverConfig.instances.length > 0) {
402
+ // Filter: use runtime connected status, NOT config enabled flag
403
+ const connectedInstances = serverConfig.instances.filter((instance) => {
404
+ if (instance.index === undefined)
405
+ return false;
406
+ const status = mcpConnectionManager.getStatus(serverName, instance.index);
407
+ return status?.connected;
408
+ });
409
+ if (connectedInstances.length > 0) {
410
+ const selectedInstance = InstanceSelector.selectInstance(serverName, {
411
+ ...serverConfig,
412
+ template: {
413
+ ...serverConfig.template,
414
+ instanceSelectionStrategy: InstanceSelectionStrategy.RANDOM
454
415
  }
416
+ }, undefined);
417
+ if (selectedInstance) {
418
+ fallbackServerInfo = {
419
+ name: serverName,
420
+ config: serverConfig,
421
+ instance: selectedInstance
422
+ };
455
423
  }
456
424
  }
457
425
  }
@@ -1084,15 +1084,17 @@ describe('HubToolsService', () => {
1084
1084
  });
1085
1085
  it('should throw error for non-existent server', async () => {
1086
1086
  // Arrange
1087
+ vi.mocked(hubManager.getServerByName).mockReturnValue(undefined);
1087
1088
  vi.mocked(hubManager.getServerInstancesByName).mockReturnValue([]);
1088
1089
  // Act & Assert
1089
- await expect(hubToolsService.readResource('hub://servers/NonExistent')).rejects.toThrow('Server not found or not connected');
1090
+ await expect(hubToolsService.readResource('hub://servers/NonExistent')).rejects.toThrow('Server not found');
1090
1091
  });
1091
1092
  it('should return server metadata for server URI with tools field', async () => {
1092
1093
  // Arrange
1093
1094
  const serverName = 'Test Server';
1094
1095
  const mockInstance = {
1095
1096
  id: 'test-instance',
1097
+ index: 0,
1096
1098
  enabled: true,
1097
1099
  args: [],
1098
1100
  env: {},
@@ -1134,13 +1136,20 @@ describe('HubToolsService', () => {
1134
1136
  const mockTools = [
1135
1137
  { name: 'testTool', description: 'Test tool description', serverName: 'test-server' }
1136
1138
  ];
1139
+ const mockResources = [{ uri: 'test://resource', name: 'Test Resource' }];
1137
1140
  // @ts-expect-error - Mocking for test purposes with extra fields
1138
1141
  vi.mocked(hubManager.getServerInstancesByName).mockReturnValue([mockInstance]);
1139
1142
  vi.mocked(hubManager.getServerByName).mockReturnValue(mockConfig);
1140
- vi.mocked(mcpConnectionManager.getTools).mockReturnValue(mockTools);
1141
- vi.mocked(mcpConnectionManager.getResources).mockReturnValue([
1142
- { uri: 'test://resource', name: 'Test Resource' }
1143
- ]);
1143
+ vi.mocked(mcpConnectionManager.getConnectedIndexes).mockReturnValue([0]);
1144
+ vi.mocked(mcpConnectionManager.getToolsByServerName).mockReturnValue(mockTools);
1145
+ vi.mocked(mcpConnectionManager.getResourcesByName).mockReturnValue(mockResources);
1146
+ vi.mocked(mcpConnectionManager.getStatus).mockReturnValue({
1147
+ connected: true,
1148
+ lastCheck: mockInstance.lastHeartbeat,
1149
+ startTime: mockInstance.uptime,
1150
+ toolsCount: 1,
1151
+ resourcesCount: 1
1152
+ });
1144
1153
  // Act
1145
1154
  const result = await hubToolsService.readResource(`hub://servers/${serverName}`);
1146
1155
  // Assert
@@ -1150,7 +1159,7 @@ describe('HubToolsService', () => {
1150
1159
  toolsCount: 1,
1151
1160
  tools: { testTool: 'Test tool description' },
1152
1161
  resourcesCount: 1,
1153
- tags: { env: 'test' },
1162
+ tags: [{ env: 'test' }],
1154
1163
  // @ts-expect-error - Accessing extra fields on mock
1155
1164
  lastHeartbeat: mockInstance.lastHeartbeat,
1156
1165
  // @ts-expect-error - Accessing extra fields on mock
@@ -1176,9 +1185,32 @@ describe('HubToolsService', () => {
1176
1185
  uptime: 1000
1177
1186
  };
1178
1187
  const mockTools = [{ name: 'testTool', description: 'Test tool', serverName: 'test-server' }];
1188
+ const mockConfig = {
1189
+ template: {
1190
+ type: 'stdio',
1191
+ command: '',
1192
+ args: [],
1193
+ env: {},
1194
+ headers: {},
1195
+ aggregatedTools: [],
1196
+ timeout: 30000
1197
+ },
1198
+ instances: [mockInstance],
1199
+ tagDefinitions: []
1200
+ };
1179
1201
  // @ts-expect-error - Mocking for test purposes with extra fields
1180
1202
  vi.mocked(hubManager.getServerInstancesByName).mockReturnValue([mockInstance]);
1181
- vi.mocked(mcpConnectionManager.getTools).mockReturnValue(mockTools);
1203
+ // @ts-expect-error - Mock config with simplified mock instance
1204
+ vi.mocked(hubManager.getServerByName).mockReturnValue(mockConfig);
1205
+ vi.mocked(mcpConnectionManager.getConnectedIndexes).mockReturnValue([0]);
1206
+ vi.mocked(mcpConnectionManager.getStatus).mockReturnValue({
1207
+ connected: true,
1208
+ lastCheck: mockInstance.lastHeartbeat,
1209
+ startTime: mockInstance.uptime,
1210
+ toolsCount: 1,
1211
+ resourcesCount: 0
1212
+ });
1213
+ vi.mocked(mcpConnectionManager.getToolsByServerName).mockReturnValue(mockTools);
1182
1214
  // Act
1183
1215
  const result = await hubToolsService.readResource(`hub://servers/${serverName}/tools`);
1184
1216
  // Assert
@@ -1202,9 +1234,32 @@ describe('HubToolsService', () => {
1202
1234
  uptime: 1000
1203
1235
  };
1204
1236
  const mockResources = [{ uri: 'test://resource', name: 'Test Resource' }];
1237
+ const mockConfig = {
1238
+ template: {
1239
+ type: 'stdio',
1240
+ command: '',
1241
+ args: [],
1242
+ env: {},
1243
+ headers: {},
1244
+ aggregatedTools: [],
1245
+ timeout: 30000
1246
+ },
1247
+ instances: [mockInstance],
1248
+ tagDefinitions: []
1249
+ };
1205
1250
  // @ts-expect-error - Mocking for test purposes with extra fields
1206
1251
  vi.mocked(hubManager.getServerInstancesByName).mockReturnValue([mockInstance]);
1207
- vi.mocked(mcpConnectionManager.getResources).mockReturnValue(mockResources);
1252
+ // @ts-expect-error - Mock config with simplified mock instance
1253
+ vi.mocked(hubManager.getServerByName).mockReturnValue(mockConfig);
1254
+ vi.mocked(mcpConnectionManager.getConnectedIndexes).mockReturnValue([0]);
1255
+ vi.mocked(mcpConnectionManager.getStatus).mockReturnValue({
1256
+ connected: true,
1257
+ lastCheck: mockInstance.lastHeartbeat,
1258
+ startTime: mockInstance.uptime,
1259
+ toolsCount: 0,
1260
+ resourcesCount: 1
1261
+ });
1262
+ vi.mocked(mcpConnectionManager.getResourcesByName).mockReturnValue(mockResources);
1208
1263
  // Act
1209
1264
  const result = await hubToolsService.readResource(`hub://servers/${serverName}/resources`);
1210
1265
  // Assert
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loop_ouroboros/mcp-hub-lite",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "A lightweight MCP management platform designed for independent developers",
5
5
  "license": "MIT",
6
6
  "author": "loop_ouroboros",