@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 +14 -0
- package/dist/server/src/services/connection/connection-manager.d.ts +6 -0
- package/dist/server/src/services/connection/connection-manager.d.ts.map +1 -1
- package/dist/server/src/services/connection/connection-manager.js +23 -0
- package/dist/server/src/services/hub-tools/instance-selector.d.ts +8 -1
- package/dist/server/src/services/hub-tools/instance-selector.d.ts.map +1 -1
- package/dist/server/src/services/hub-tools/instance-selector.js +24 -10
- package/dist/server/src/services/hub-tools/resource-generator.d.ts.map +1 -1
- package/dist/server/src/services/hub-tools/resource-generator.js +4 -19
- package/dist/server/src/services/hub-tools.service.d.ts.map +1 -1
- package/dist/server/src/services/hub-tools.service.js +22 -5
- package/dist/server/tests/unit/services/hub-tools/instance-selector.test.js +21 -16
- package/dist/server/tests/unit/services/hub-tools.service.test.js +36 -35
- package/package.json +1 -1
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
|
-
}
|
|
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;
|
|
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
|
-
//
|
|
47
|
-
const
|
|
48
|
-
|
|
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 (
|
|
53
|
-
return
|
|
66
|
+
if (connectedInstances.length === 1) {
|
|
67
|
+
return connectedInstances[0];
|
|
54
68
|
}
|
|
55
69
|
switch (instanceSelectionStrategy) {
|
|
56
70
|
case InstanceSelectionStrategy.RANDOM:
|
|
57
|
-
return this.selectRandomInstance(
|
|
71
|
+
return this.selectRandomInstance(connectedInstances);
|
|
58
72
|
case InstanceSelectionStrategy.ROUND_ROBIN:
|
|
59
|
-
return this.selectRoundRobinInstance(serverName,
|
|
73
|
+
return this.selectRoundRobinInstance(serverName, connectedInstances);
|
|
60
74
|
case InstanceSelectionStrategy.TAG_MATCH_UNIQUE:
|
|
61
|
-
return this.selectTagMatchUniqueInstance(
|
|
75
|
+
return this.selectTagMatchUniqueInstance(connectedInstances, requestOptions?.tags);
|
|
62
76
|
default:
|
|
63
|
-
return
|
|
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,
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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 ===
|
|
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":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAOjE,OAAO,EAEL,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,cAAc,EACd,8BAA8B,EAC9B,cAAc,EAEf,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,uBAAuB,EACvB,aAAa,EACb,cAAc,EACd,6BAA6B,EAC7B,cAAc,EACf,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;IAyBpD;;;;;;;;;;;OAWG;IACG,iBAAiB,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC;QAC9D,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,WAAW,EAAE,CAAC;KACtB,CAAC;
|
|
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
|
-
//
|
|
95
|
-
const
|
|
96
|
-
if (
|
|
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
|
-
|
|
409
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
86
|
-
return
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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.
|
|
142
|
-
return
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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.
|
|
224
|
-
return
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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.
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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.
|
|
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: {},
|