@loop_ouroboros/mcp-hub-lite 1.2.5 → 1.2.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.
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.2.6] - 2026-04-29
6
+
7
+ ### Hub Tools
8
+
9
+ - use runtime connected status for instance selection
10
+ - clarify instance filtering comment
11
+ - fix listMcpResources returning incomplete server resources by using two-pass approach to check all instances connection status
12
+
13
+ ### Utils
14
+
15
+ - add null check to normalizeToolName
16
+
17
+ ---
18
+
5
19
  ## [1.2.5] - 2026-04-28
6
20
 
7
21
  ### Hub Tools
@@ -475,6 +475,12 @@ export declare class McpConnectionManager {
475
475
  * @returns ServerStatus or undefined if not connected
476
476
  */
477
477
  getStatusByName(name: string): ServerStatus | undefined;
478
+ /**
479
+ * Gets all connected instance indexes for a server.
480
+ * @param serverName - Server name
481
+ * @returns Array of connected instance indexes, empty if none connected
482
+ */
483
+ getConnectedIndexes(serverName: string): number[];
478
484
  /**
479
485
  * Gets the composite key of the first connected instance for a server name.
480
486
  * This is a backward compatibility method for code that expects getServerIdByName.
@@ -1 +1 @@
1
- {"version":3,"file":"connection-manager.d.ts","sourceRoot":"","sources":["../../../../../src/services/connection/connection-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"connection-manager.d.ts","sourceRoot":"","sources":["../../../../../src/services/connection/connection-manager.ts"],"names":[],"mappings":"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"}
@@ -995,6 +995,29 @@ export class McpConnectionManager {
995
995
  }
996
996
  return undefined;
997
997
  }
998
+ /**
999
+ * Gets all connected instance indexes for a server.
1000
+ * @param serverName - Server name
1001
+ * @returns Array of connected instance indexes, empty if none connected
1002
+ */
1003
+ getConnectedIndexes(serverName) {
1004
+ // Directly iterate instances via hubManager, check each via getStatus
1005
+ // This bypasses serverNameToCompositeKeys which may have stale/missing entries
1006
+ // Note: We do NOT filter by instance.enabled here because:
1007
+ // - enabled=false means "do not auto-start" (config), not "cannot be connected" (runtime)
1008
+ // - A user can manually start an enabled=false instance, which should still appear in list_servers
1009
+ const instances = hubManager.getServerInstancesByName(serverName);
1010
+ const connectedIndexes = [];
1011
+ for (const instance of instances) {
1012
+ if (instance.index !== undefined) {
1013
+ const status = this.getStatus(serverName, instance.index);
1014
+ if (status?.connected) {
1015
+ connectedIndexes.push(instance.index);
1016
+ }
1017
+ }
1018
+ }
1019
+ return connectedIndexes;
1020
+ }
998
1021
  /**
999
1022
  * Gets the composite key of the first connected instance for a server name.
1000
1023
  * This is a backward compatibility method for code that expects getServerIdByName.
@@ -17,11 +17,18 @@ export declare class InstanceSelector {
17
17
  private static roundRobinCounters;
18
18
  /**
19
19
  * Select best instance based on configured strategy
20
+ *
21
+ * @param serverName - Name of the server
22
+ * @param serverConfig - Server configuration
23
+ * @param requestOptions - Optional request options for instance selection
24
+ * @param statusChecker - Optional function to check instance connection status (for testing)
20
25
  */
21
26
  static selectInstance(serverName: string, serverConfig: ServerConfig, requestOptions?: {
22
27
  sessionId?: string;
23
28
  tags?: Record<string, string>;
24
- }): ServerInstance | undefined;
29
+ }, statusChecker?: (serverName: string, index: number) => {
30
+ connected?: boolean;
31
+ } | undefined): ServerInstance | undefined;
25
32
  private static selectRandomInstance;
26
33
  private static selectRoundRobinInstance;
27
34
  private static selectTagMatchUniqueInstance;
@@ -1 +1 @@
1
- {"version":3,"file":"instance-selector.d.ts","sourceRoot":"","sources":["../../../../../src/services/hub-tools/instance-selector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAGnF;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,aAAa,EAAE,MAAM;aACrB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;aACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBAF3D,aAAa,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YAAA,EACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,YAAA;CAmB9E;AAED;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAA6B;IAE9D;;OAEG;IACH,MAAM,CAAC,cAAc,CACnB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,YAAY,EAC1B,cAAc,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GACrE,cAAc,GAAG,SAAS;IA+B7B,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAKnC,OAAO,CAAC,MAAM,CAAC,wBAAwB;IAUvC,OAAO,CAAC,MAAM,CAAC,4BAA4B;CAiC5C"}
1
+ {"version":3,"file":"instance-selector.d.ts","sourceRoot":"","sources":["../../../../../src/services/hub-tools/instance-selector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAInF;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,aAAa,EAAE,MAAM;aACrB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;aACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBAF3D,aAAa,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YAAA,EACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,YAAA;CAmB9E;AAED;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAA6B;IAE9D;;;;;;;OAOG;IACH,MAAM,CAAC,cAAc,CACnB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,YAAY,EAC1B,cAAc,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,EACtE,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,GACzF,cAAc,GAAG,SAAS;IAwC7B,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAKnC,OAAO,CAAC,MAAM,CAAC,wBAAwB;IAUvC,OAAO,CAAC,MAAM,CAAC,4BAA4B;CAiC5C"}
@@ -1,4 +1,5 @@
1
1
  import { InstanceSelectionStrategy } from '../../models/server.model.js';
2
+ import { mcpConnectionManager } from '../mcp-connection-manager.js';
2
3
  /**
3
4
  * Error thrown when tag-match-unique instance selection fails.
4
5
  * Passes raw data so the error class itself can format the message.
@@ -39,28 +40,41 @@ export class InstanceSelector {
39
40
  static roundRobinCounters = new Map();
40
41
  /**
41
42
  * Select best instance based on configured strategy
43
+ *
44
+ * @param serverName - Name of the server
45
+ * @param serverConfig - Server configuration
46
+ * @param requestOptions - Optional request options for instance selection
47
+ * @param statusChecker - Optional function to check instance connection status (for testing)
42
48
  */
43
- static selectInstance(serverName, serverConfig, requestOptions) {
49
+ static selectInstance(serverName, serverConfig, requestOptions, statusChecker) {
44
50
  const { instances } = serverConfig;
45
51
  const instanceSelectionStrategy = serverConfig.template.instanceSelectionStrategy || InstanceSelectionStrategy.RANDOM;
46
- // Filter instances - only use enabled instances
47
- const enabledInstances = instances.filter((instance) => instance.enabled !== false);
48
- if (enabledInstances.length === 0) {
52
+ // Use provided statusChecker or default to mcpConnectionManager.getStatus
53
+ const checkStatus = statusChecker || ((name, idx) => mcpConnectionManager.getStatus(name, idx));
54
+ // Filter instances - use runtime connected status, NOT config enabled flag
55
+ // enabled=false means "do not auto-start" but user can manually start it
56
+ const connectedInstances = instances.filter((instance) => {
57
+ if (instance.index === undefined)
58
+ return false;
59
+ const status = checkStatus(serverName, instance.index);
60
+ return status?.connected;
61
+ });
62
+ if (connectedInstances.length === 0) {
49
63
  return undefined;
50
64
  }
51
65
  // Single instance case - return directly
52
- if (enabledInstances.length === 1) {
53
- return enabledInstances[0];
66
+ if (connectedInstances.length === 1) {
67
+ return connectedInstances[0];
54
68
  }
55
69
  switch (instanceSelectionStrategy) {
56
70
  case InstanceSelectionStrategy.RANDOM:
57
- return this.selectRandomInstance(enabledInstances);
71
+ return this.selectRandomInstance(connectedInstances);
58
72
  case InstanceSelectionStrategy.ROUND_ROBIN:
59
- return this.selectRoundRobinInstance(serverName, enabledInstances);
73
+ return this.selectRoundRobinInstance(serverName, connectedInstances);
60
74
  case InstanceSelectionStrategy.TAG_MATCH_UNIQUE:
61
- return this.selectTagMatchUniqueInstance(enabledInstances, requestOptions?.tags);
75
+ return this.selectTagMatchUniqueInstance(connectedInstances, requestOptions?.tags);
62
76
  default:
63
- return enabledInstances[0]; // Fallback to first instance
77
+ return connectedInstances[0]; // Fallback to first instance
64
78
  }
65
79
  }
66
80
  static selectRandomInstance(instances) {
@@ -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;AAUlE;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AA6ID;;GAEG;AACH,eAAO,MAAM,aAAa,oBAAoB,CAAC;AAE/C;;GAEG;AACH,eAAO,MAAM,cAAc,2BAA2B,CAAC;AAEvD;;GAEG;AACH,eAAO,MAAM,qBAAqB,uEACoC,CAAC;AAEvE;;GAEG;AACH,eAAO,MAAM,mBAAmB,kBAAkB,CAAC;AAEnD;;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,CAuFrD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,GACV,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;AAUlE;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AA6ID;;GAEG;AACH,eAAO,MAAM,aAAa,oBAAoB,CAAC;AAE/C;;GAEG;AACH,eAAO,MAAM,cAAc,2BAA2B,CAAC;AAEvD;;GAEG;AACH,eAAO,MAAM,qBAAqB,uEACoC,CAAC;AAEvE;;GAEG;AACH,eAAO,MAAM,mBAAmB,kBAAkB,CAAC;AAEnD;;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,GACV,OAAO,CAAC,cAAc,GAAG,QAAQ,EAAE,GAAG,MAAM,GAAG,OAAO,CAAC,CAyGzD"}
@@ -178,24 +178,9 @@ export function generateDynamicResources() {
178
178
  if (!hasValidId(server)) {
179
179
  continue;
180
180
  }
181
- let hasAnyConnectedInstance = false;
182
- let firstConnectedInstanceIndex;
183
- // First pass: check which instances are connected (consistent with /web/mcp/status API)
184
- for (const instance of server.config.instances) {
185
- const idx = instance.index;
186
- if (idx === undefined) {
187
- continue;
188
- }
189
- const instanceStatus = mcpConnectionManager.getStatus(server.name, idx);
190
- if (instanceStatus?.connected) {
191
- hasAnyConnectedInstance = true;
192
- if (firstConnectedInstanceIndex === undefined) {
193
- firstConnectedInstanceIndex = idx;
194
- }
195
- }
196
- }
197
- // Skip server if no instances are connected
198
- if (!hasAnyConnectedInstance) {
181
+ // Check if any instances are connected using getConnectedIndexes
182
+ const connectedIndexes = mcpConnectionManager.getConnectedIndexes(server.name);
183
+ if (connectedIndexes.length === 0) {
199
184
  continue;
200
185
  }
201
186
  // Second pass: generate resources for each connected instance
@@ -210,7 +195,7 @@ export function generateDynamicResources() {
210
195
  }
211
196
  // Server metadata resource (one per server, using first connected instance's index)
212
197
  // Only generate once when we hit the first connected instance
213
- if (idx === firstConnectedInstanceIndex) {
198
+ if (idx === connectedIndexes[0]) {
214
199
  resources.push({
215
200
  uri: `hub://servers/${server.name}`,
216
201
  name: `Server: ${server.name}`,
@@ -1 +1 @@
1
- {"version":3,"file":"hub-tools.service.d.ts","sourceRoot":"","sources":["../../../../src/services/hub-tools.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hub-tools.service.d.ts","sourceRoot":"","sources":["../../../../src/services/hub-tools.service.ts"],"names":[],"mappings":"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"}
@@ -91,9 +91,9 @@ export class HubToolsService {
91
91
  const servers = hubManager.getAllServers();
92
92
  const result = {};
93
93
  for (const server of servers.filter(hasValidId)) {
94
- // Check if server is actually connected
95
- const status = mcpConnectionManager.getStatusByName(server.name);
96
- if (!status?.connected) {
94
+ // Use getConnectedIndexes for reliable multi-instance support
95
+ const indexes = mcpConnectionManager.getConnectedIndexes(server.name);
96
+ if (indexes.length === 0) {
97
97
  continue;
98
98
  }
99
99
  // Use non-strict mode for management operations to avoid tag-match-unique errors
@@ -139,6 +139,11 @@ export class HubToolsService {
139
139
  tools: toolSummaries
140
140
  };
141
141
  }
142
+ // Check if server has any connected instances before trying to get tools
143
+ const indexes = mcpConnectionManager.getConnectedIndexes(args.serverName);
144
+ if (indexes.length === 0) {
145
+ throw new Error(`Server not found: ${args.serverName}`);
146
+ }
142
147
  // Use server name level cache to get tools directly without triggering instance selection
143
148
  // This avoids tag-match-unique errors for multi-instance servers when listing tools
144
149
  const tools = mcpConnectionManager.getToolsByServerName(args.serverName);
@@ -180,6 +185,11 @@ export class HubToolsService {
180
185
  }
181
186
  return undefined;
182
187
  }
188
+ // Check if server has any connected instances before trying to get tools
189
+ const indexes = mcpConnectionManager.getConnectedIndexes(args.serverName);
190
+ if (indexes.length === 0) {
191
+ throw new Error(`Server not found: ${args.serverName}`);
192
+ }
183
193
  // Use server name level cache to get tools directly without triggering instance selection
184
194
  // This avoids tag-match-unique errors for multi-instance servers when getting tool details
185
195
  const tools = mcpConnectionManager.getToolsByServerName(args.serverName);
@@ -405,8 +415,15 @@ export class HubToolsService {
405
415
  logger.debug(`selectBestInstance returned undefined for ${serverName}, trying direct instance selection with RANDOM strategy`, LOG_MODULES.HUB_TOOLS);
406
416
  const serverConfig = hubManager.getServerByName(serverName);
407
417
  if (serverConfig && serverConfig.instances.length > 0) {
408
- const enabledInstances = serverConfig.instances.filter((instance) => instance.enabled !== false);
409
- if (enabledInstances.length > 0) {
418
+ // Filter: use runtime connected status, NOT config enabled flag
419
+ // enabled=false means "do not auto-start" but user can manually start it
420
+ const connectedInstances = serverConfig.instances.filter((instance) => {
421
+ if (instance.index === undefined)
422
+ return false;
423
+ const status = mcpConnectionManager.getStatus(serverName, instance.index);
424
+ return status?.connected;
425
+ });
426
+ if (connectedInstances.length > 0) {
410
427
  // Use RANDOM strategy regardless of server's configured strategy
411
428
  const selectedInstance = InstanceSelector.selectInstance(serverName, {
412
429
  ...serverConfig,
@@ -17,8 +17,13 @@ describe('InstanceSelector', () => {
17
17
  headers: {},
18
18
  tags: {}
19
19
  };
20
+ // Mock statusChecker that simulates connected instances
21
+ const mockConnectedStatus = () => ({ connected: true });
22
+ const mockDisconnectedStatus = () => ({ connected: false });
20
23
  describe('random strategy', () => {
21
- it('should select random instance from enabled instances', () => {
24
+ it('should select random instance from connected instances', () => {
25
+ // Local mock that simulates idx=2 being disconnected
26
+ const localMockConnectedStatus = (_name, idx) => idx === 2 ? { connected: false } : { connected: true };
22
27
  const config = {
23
28
  template: {
24
29
  ...baseTemplate,
@@ -31,11 +36,11 @@ describe('InstanceSelector', () => {
31
36
  ],
32
37
  tagDefinitions: []
33
38
  };
34
- const selected = InstanceSelector.selectInstance('test-server', config);
39
+ const selected = InstanceSelector.selectInstance('test-server', config, undefined, localMockConnectedStatus);
35
40
  expect(selected).toBeDefined();
36
41
  expect(['1', '2']).toContain(selected.id);
37
42
  });
38
- it('should return undefined when no enabled instances', () => {
43
+ it('should return undefined when no connected instances', () => {
39
44
  const config = {
40
45
  template: {
41
46
  ...baseTemplate,
@@ -47,12 +52,12 @@ describe('InstanceSelector', () => {
47
52
  ],
48
53
  tagDefinitions: []
49
54
  };
50
- const selected = InstanceSelector.selectInstance('test-server', config);
55
+ const selected = InstanceSelector.selectInstance('test-server', config, undefined, mockDisconnectedStatus);
51
56
  expect(selected).toBeUndefined();
52
57
  });
53
58
  });
54
59
  describe('round-robin strategy', () => {
55
- it('should cycle through enabled instances', () => {
60
+ it('should cycle through connected instances', () => {
56
61
  const config = {
57
62
  template: {
58
63
  ...baseTemplate,
@@ -65,10 +70,10 @@ describe('InstanceSelector', () => {
65
70
  ],
66
71
  tagDefinitions: []
67
72
  };
68
- const selected1 = InstanceSelector.selectInstance('test-server-rr', config);
69
- const selected2 = InstanceSelector.selectInstance('test-server-rr', config);
70
- const selected3 = InstanceSelector.selectInstance('test-server-rr', config);
71
- const selected4 = InstanceSelector.selectInstance('test-server-rr', config);
73
+ const selected1 = InstanceSelector.selectInstance('test-server-rr', config, undefined, mockConnectedStatus);
74
+ const selected2 = InstanceSelector.selectInstance('test-server-rr', config, undefined, mockConnectedStatus);
75
+ const selected3 = InstanceSelector.selectInstance('test-server-rr', config, undefined, mockConnectedStatus);
76
+ const selected4 = InstanceSelector.selectInstance('test-server-rr', config, undefined, mockConnectedStatus);
72
77
  expect(selected1.id).toBe('1');
73
78
  expect(selected2.id).toBe('2');
74
79
  expect(selected3.id).toBe('3');
@@ -91,7 +96,7 @@ describe('InstanceSelector', () => {
91
96
  };
92
97
  const selected = InstanceSelector.selectInstance('test-server', config, {
93
98
  tags: { env: 'prod', region: 'us' }
94
- });
99
+ }, mockConnectedStatus);
95
100
  expect(selected).toBeDefined();
96
101
  expect(selected.id).toBe('2');
97
102
  });
@@ -110,7 +115,7 @@ describe('InstanceSelector', () => {
110
115
  expect(() => {
111
116
  InstanceSelector.selectInstance('test-server', config, {
112
117
  tags: { env: 'staging' }
113
- });
118
+ }, mockConnectedStatus);
114
119
  }).toThrow('No instance found matching tags');
115
120
  });
116
121
  it('should throw error when multiple instances match tags', () => {
@@ -128,7 +133,7 @@ describe('InstanceSelector', () => {
128
133
  expect(() => {
129
134
  InstanceSelector.selectInstance('test-server', config, {
130
135
  tags: { env: 'prod' }
131
- });
136
+ }, mockConnectedStatus);
132
137
  }).toThrow('Multiple instances match tags');
133
138
  });
134
139
  it('should return the single instance when no tags provided and only one instance exists', () => {
@@ -140,7 +145,7 @@ describe('InstanceSelector', () => {
140
145
  instances: [{ ...baseInstance, id: '1', index: 0 }],
141
146
  tagDefinitions: []
142
147
  };
143
- const selected = InstanceSelector.selectInstance('test-server', config);
148
+ const selected = InstanceSelector.selectInstance('test-server', config, undefined, mockConnectedStatus);
144
149
  expect(selected).toBeDefined();
145
150
  expect(selected.id).toBe('1');
146
151
  });
@@ -157,7 +162,7 @@ describe('InstanceSelector', () => {
157
162
  tagDefinitions: []
158
163
  };
159
164
  expect(() => {
160
- InstanceSelector.selectInstance('test-server', config);
165
+ InstanceSelector.selectInstance('test-server', config, undefined, mockConnectedStatus);
161
166
  }).toThrow('No tags provided for tag-match-unique strategy with 2 instances. Available: [0:{}, 1:{}]. Pass matching tags to select.');
162
167
  });
163
168
  });
@@ -171,7 +176,7 @@ describe('InstanceSelector', () => {
171
176
  instances: [{ ...baseInstance, id: '1', index: 0 }],
172
177
  tagDefinitions: []
173
178
  };
174
- const selected = InstanceSelector.selectInstance('test-server', config);
179
+ const selected = InstanceSelector.selectInstance('test-server', config, undefined, mockConnectedStatus);
175
180
  expect(selected).toBeDefined();
176
181
  expect(selected.id).toBe('1');
177
182
  });
@@ -187,7 +192,7 @@ describe('InstanceSelector', () => {
187
192
  tagDefinitions: []
188
193
  // No instanceSelectionStrategy field
189
194
  };
190
- const selected = InstanceSelector.selectInstance('test-server', config);
195
+ const selected = InstanceSelector.selectInstance('test-server', config, undefined, mockConnectedStatus);
191
196
  expect(selected).toBeDefined();
192
197
  expect(['1', '2']).toContain(selected.id);
193
198
  });
@@ -36,6 +36,7 @@ describe('HubToolsService', () => {
36
36
  instances: [
37
37
  {
38
38
  id: 'test-server-1-instance',
39
+ index: 0,
39
40
  enabled: true,
40
41
  args: [],
41
42
  env: {},
@@ -62,6 +63,7 @@ describe('HubToolsService', () => {
62
63
  instances: [
63
64
  {
64
65
  id: 'test-server-2-instance',
66
+ index: 0,
65
67
  enabled: true,
66
68
  args: [],
67
69
  env: {},
@@ -82,13 +84,11 @@ describe('HubToolsService', () => {
82
84
  const server = mockServers.find((s) => s.name === name);
83
85
  return server?.config;
84
86
  });
85
- vi.mocked(mcpConnectionManager.getStatusByName).mockImplementation(() => {
86
- return {
87
- connected: true,
88
- lastCheck: Date.now(),
89
- toolsCount: 0,
90
- resourcesCount: 0
91
- };
87
+ vi.mocked(mcpConnectionManager.getConnectedIndexes).mockImplementation(() => {
88
+ return [0];
89
+ });
90
+ vi.mocked(mcpConnectionManager.getStatus).mockImplementation(() => {
91
+ return { connected: true, lastCheck: Date.now(), toolsCount: 0, resourcesCount: 0 };
92
92
  });
93
93
  // Act
94
94
  const servers = await hubToolsService.listServers();
@@ -118,6 +118,7 @@ describe('HubToolsService', () => {
118
118
  instances: [
119
119
  {
120
120
  id: 'server1-instance',
121
+ index: 0,
121
122
  enabled: true,
122
123
  args: [],
123
124
  env: {},
@@ -138,13 +139,11 @@ describe('HubToolsService', () => {
138
139
  const server = mockServers.find((s) => s.name === name);
139
140
  return server?.config;
140
141
  });
141
- vi.mocked(mcpConnectionManager.getStatusByName).mockImplementation(() => {
142
- return {
143
- connected: true,
144
- lastCheck: Date.now(),
145
- toolsCount: 0,
146
- resourcesCount: 0
147
- };
142
+ vi.mocked(mcpConnectionManager.getConnectedIndexes).mockImplementation(() => {
143
+ return [0];
144
+ });
145
+ vi.mocked(mcpConnectionManager.getStatus).mockImplementation(() => {
146
+ return { connected: true, lastCheck: Date.now(), toolsCount: 0, resourcesCount: 0 };
148
147
  });
149
148
  // Act
150
149
  const servers = await hubToolsService.listServers();
@@ -173,6 +172,7 @@ describe('HubToolsService', () => {
173
172
  instances: [
174
173
  {
175
174
  id: 'filesystem-instance',
175
+ index: 0,
176
176
  enabled: true,
177
177
  args: [],
178
178
  env: {},
@@ -200,6 +200,7 @@ describe('HubToolsService', () => {
200
200
  instances: [
201
201
  {
202
202
  id: 'time-instance',
203
+ index: 0,
203
204
  enabled: true,
204
205
  args: [],
205
206
  env: {},
@@ -220,13 +221,11 @@ describe('HubToolsService', () => {
220
221
  const server = mockServers.find((s) => s.name === name);
221
222
  return server?.config;
222
223
  });
223
- vi.mocked(mcpConnectionManager.getStatusByName).mockImplementation(() => {
224
- return {
225
- connected: true,
226
- lastCheck: Date.now(),
227
- toolsCount: 0,
228
- resourcesCount: 0
229
- };
224
+ vi.mocked(mcpConnectionManager.getConnectedIndexes).mockImplementation(() => {
225
+ return [0];
226
+ });
227
+ vi.mocked(mcpConnectionManager.getStatus).mockImplementation(() => {
228
+ return { connected: true, lastCheck: Date.now(), toolsCount: 0, resourcesCount: 0 };
230
229
  });
231
230
  // Act
232
231
  const servers = await hubToolsService.listServers();
@@ -256,6 +255,7 @@ describe('HubToolsService', () => {
256
255
  instances: [
257
256
  {
258
257
  id: 'connected-server-instance',
258
+ index: 0,
259
259
  enabled: true,
260
260
  args: [],
261
261
  env: {},
@@ -303,21 +303,14 @@ describe('HubToolsService', () => {
303
303
  const server = mockServers.find((s) => s.name === name);
304
304
  return server?.config;
305
305
  });
306
- vi.mocked(mcpConnectionManager.getStatusByName).mockImplementation((name) => {
306
+ vi.mocked(mcpConnectionManager.getConnectedIndexes).mockImplementation((name) => {
307
307
  if (name === 'Connected Server') {
308
- return {
309
- connected: true,
310
- lastCheck: Date.now(),
311
- toolsCount: 0,
312
- resourcesCount: 0
313
- };
308
+ return [0];
314
309
  }
315
- return {
316
- connected: false,
317
- lastCheck: Date.now(),
318
- toolsCount: 0,
319
- resourcesCount: 0
320
- };
310
+ return [];
311
+ });
312
+ vi.mocked(mcpConnectionManager.getStatus).mockImplementation(() => {
313
+ return { connected: true, lastCheck: Date.now(), toolsCount: 0, resourcesCount: 0 };
321
314
  });
322
315
  // Act
323
316
  const servers = await hubToolsService.listServers();
@@ -380,6 +373,7 @@ describe('HubToolsService', () => {
380
373
  tagDefinitions: []
381
374
  });
382
375
  vi.mocked(mcpConnectionManager.getToolsByServerName).mockReturnValue(mockTools);
376
+ vi.mocked(mcpConnectionManager.getConnectedIndexes).mockReturnValue([0]);
383
377
  // Act
384
378
  const result = await hubToolsService.listToolsInServer({ serverName });
385
379
  // Assert
@@ -392,7 +386,7 @@ describe('HubToolsService', () => {
392
386
  it('should throw error if server not found', async () => {
393
387
  // Arrange
394
388
  const serverName = 'Non-existent Server';
395
- vi.mocked(mcpConnectionManager.getToolsByServerName).mockReturnValue([]);
389
+ vi.mocked(mcpConnectionManager.getConnectedIndexes).mockReturnValue([]);
396
390
  // Act & Assert
397
391
  await expect(hubToolsService.listToolsInServer({ serverName })).rejects.toThrow(`Server not found: ${serverName}`);
398
392
  });
@@ -440,6 +434,7 @@ describe('HubToolsService', () => {
440
434
  tagDefinitions: []
441
435
  });
442
436
  vi.mocked(mcpConnectionManager.getToolsByServerName).mockReturnValue(mockTools);
437
+ vi.mocked(mcpConnectionManager.getConnectedIndexes).mockReturnValue([0]);
443
438
  // Act
444
439
  const tool = await hubToolsService.getTool({ serverName, toolName });
445
440
  // Assert
@@ -480,6 +475,8 @@ describe('HubToolsService', () => {
480
475
  instances: [mockInstance],
481
476
  tagDefinitions: []
482
477
  });
478
+ vi.mocked(mcpConnectionManager.getToolsByServerName).mockReturnValue(mockTools);
479
+ vi.mocked(mcpConnectionManager.getConnectedIndexes).mockReturnValue([0]);
483
480
  vi.mocked(mcpConnectionManager.getTools).mockReturnValue(mockTools);
484
481
  // Act
485
482
  const tool = await hubToolsService.getTool({ serverName, toolName });
@@ -681,6 +678,7 @@ describe('HubToolsService', () => {
681
678
  vi.mocked(mcpConnectionManager.getResources).mockReturnValue([
682
679
  { uri: 'test://resource', name: 'Test Resource' }
683
680
  ]);
681
+ vi.mocked(mcpConnectionManager.getConnectedIndexes).mockReturnValue([0]);
684
682
  // Act
685
683
  const resources = await hubToolsService.listResources();
686
684
  // Assert - the resource list should include use-guide and at least the server resource
@@ -747,6 +745,7 @@ describe('HubToolsService', () => {
747
745
  instances: [
748
746
  {
749
747
  id: 'test-instance',
748
+ index: 0,
750
749
  enabled: true,
751
750
  args: [],
752
751
  env: {},
@@ -792,6 +791,7 @@ describe('HubToolsService', () => {
792
791
  const serverName = 'Test Server';
793
792
  const mockInstance = {
794
793
  id: '1',
794
+ index: 0,
795
795
  enabled: true,
796
796
  args: [],
797
797
  env: {},
@@ -817,6 +817,7 @@ describe('HubToolsService', () => {
817
817
  const serverName = 'Test Server';
818
818
  const mockInstance = {
819
819
  id: '1',
820
+ index: 0,
820
821
  enabled: true,
821
822
  args: [],
822
823
  env: {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loop_ouroboros/mcp-hub-lite",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "description": "A lightweight MCP management platform designed for independent developers",
5
5
  "license": "MIT",
6
6
  "author": "loop_ouroboros",