@wundr.io/mcp-registry 1.0.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.
@@ -0,0 +1,708 @@
1
+ "use strict";
2
+ /**
3
+ * @wundr.io/mcp-registry - MCP Aggregator (Super MCP)
4
+ *
5
+ * Implements the Super MCP pattern for routing requests to appropriate
6
+ * servers based on capabilities, health, and configured routing strategies.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.MCPAggregator = exports.RetryExhaustedError = exports.CircuitBreakerOpenError = exports.ToolInvocationTimeoutError = exports.NoServerAvailableError = void 0;
12
+ exports.createMCPAggregator = createMCPAggregator;
13
+ const eventemitter3_1 = require("eventemitter3");
14
+ const discovery_1 = require("./discovery");
15
+ const types_1 = require("./types");
16
+ // =============================================================================
17
+ // Aggregator Error Types
18
+ // =============================================================================
19
+ /**
20
+ * Error thrown when no server can handle a tool invocation
21
+ */
22
+ class NoServerAvailableError extends Error {
23
+ toolName;
24
+ constructor(toolName, message) {
25
+ super(message ?? `No server available for tool: ${toolName}`);
26
+ this.toolName = toolName;
27
+ this.name = 'NoServerAvailableError';
28
+ }
29
+ }
30
+ exports.NoServerAvailableError = NoServerAvailableError;
31
+ /**
32
+ * Error thrown when a tool invocation times out
33
+ */
34
+ class ToolInvocationTimeoutError extends Error {
35
+ toolName;
36
+ timeoutMs;
37
+ constructor(toolName, timeoutMs, message) {
38
+ super(message ?? `Tool invocation timed out after ${timeoutMs}ms: ${toolName}`);
39
+ this.toolName = toolName;
40
+ this.timeoutMs = timeoutMs;
41
+ this.name = 'ToolInvocationTimeoutError';
42
+ }
43
+ }
44
+ exports.ToolInvocationTimeoutError = ToolInvocationTimeoutError;
45
+ /**
46
+ * Error thrown when circuit breaker is open for a server
47
+ */
48
+ class CircuitBreakerOpenError extends Error {
49
+ serverId;
50
+ constructor(serverId, message) {
51
+ super(message ?? `Circuit breaker open for server: ${serverId}`);
52
+ this.serverId = serverId;
53
+ this.name = 'CircuitBreakerOpenError';
54
+ }
55
+ }
56
+ exports.CircuitBreakerOpenError = CircuitBreakerOpenError;
57
+ /**
58
+ * Error thrown when all retry attempts are exhausted
59
+ */
60
+ class RetryExhaustedError extends Error {
61
+ toolName;
62
+ attempts;
63
+ lastError;
64
+ constructor(toolName, attempts, lastError, message) {
65
+ super(message ??
66
+ `All ${attempts} retry attempts exhausted for tool: ${toolName}`);
67
+ this.toolName = toolName;
68
+ this.attempts = attempts;
69
+ this.lastError = lastError;
70
+ this.name = 'RetryExhaustedError';
71
+ }
72
+ }
73
+ exports.RetryExhaustedError = RetryExhaustedError;
74
+ // =============================================================================
75
+ // MCPAggregator Class
76
+ // =============================================================================
77
+ /**
78
+ * MCP Aggregator (Super MCP Pattern)
79
+ *
80
+ * Routes tool invocation requests to appropriate servers based on
81
+ * capabilities, health status, and configured routing strategies.
82
+ * Implements circuit breaker pattern for fault tolerance.
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * const aggregator = new MCPAggregator(registry, {
87
+ * defaultStrategy: 'health-aware',
88
+ * enableRetries: true,
89
+ * maxRetries: 3,
90
+ * enableCircuitBreaker: true,
91
+ * });
92
+ *
93
+ * // Invoke a tool
94
+ * const response = await aggregator.invoke({
95
+ * name: 'drift_detection',
96
+ * arguments: { action: 'detect' },
97
+ * });
98
+ *
99
+ * // Use specific routing strategy
100
+ * const response = await aggregator.invokeWithStrategy(
101
+ * { name: 'my-tool' },
102
+ * 'least-latency',
103
+ * );
104
+ * ```
105
+ */
106
+ class MCPAggregator extends eventemitter3_1.EventEmitter {
107
+ registry;
108
+ /** Configuration */
109
+ config;
110
+ /** Discovery service */
111
+ discovery;
112
+ /** Circuit breaker states by server ID */
113
+ circuitBreakers;
114
+ /** Round-robin index by tool name */
115
+ roundRobinIndex;
116
+ /** Request counter for generating IDs */
117
+ requestCounter;
118
+ /** Tool handler registry for direct invocation */
119
+ toolHandlers;
120
+ /**
121
+ * Creates a new MCPAggregator
122
+ *
123
+ * @param registry - The server registry
124
+ * @param config - Aggregator configuration
125
+ */
126
+ constructor(registry, config = {}) {
127
+ super();
128
+ this.registry = registry;
129
+ // Validate and merge config with defaults
130
+ const validation = types_1.AggregatorConfigSchema.safeParse(config);
131
+ if (!validation.success) {
132
+ throw new Error(`Invalid aggregator config: ${validation.error.message}`);
133
+ }
134
+ this.config = {
135
+ defaultStrategy: config.defaultStrategy ?? 'health-aware',
136
+ requestTimeout: config.requestTimeout ?? 30000,
137
+ enableRetries: config.enableRetries ?? true,
138
+ maxRetries: config.maxRetries ?? 3,
139
+ retryDelay: config.retryDelay ?? 1000,
140
+ enableCircuitBreaker: config.enableCircuitBreaker ?? true,
141
+ circuitBreakerThreshold: config.circuitBreakerThreshold ?? 5,
142
+ circuitBreakerResetTimeout: config.circuitBreakerResetTimeout ?? 60000,
143
+ healthMonitor: config.healthMonitor ?? {},
144
+ };
145
+ this.discovery = (0, discovery_1.createServerDiscoveryService)(registry);
146
+ this.circuitBreakers = new Map();
147
+ this.roundRobinIndex = new Map();
148
+ this.requestCounter = 0;
149
+ this.toolHandlers = new Map();
150
+ }
151
+ // ===========================================================================
152
+ // Tool Invocation Methods
153
+ // ===========================================================================
154
+ /**
155
+ * Invoke a tool using the default routing strategy
156
+ *
157
+ * @param request - Tool invocation request
158
+ * @returns Tool invocation response
159
+ * @throws {NoServerAvailableError} If no server can handle the tool
160
+ * @throws {ToolInvocationTimeoutError} If the invocation times out
161
+ * @throws {RetryExhaustedError} If all retries are exhausted
162
+ */
163
+ async invoke(request) {
164
+ return this.invokeWithStrategy(request, this.config.defaultStrategy);
165
+ }
166
+ /**
167
+ * Invoke a tool with a specific routing strategy
168
+ *
169
+ * @param request - Tool invocation request
170
+ * @param strategy - Routing strategy to use
171
+ * @returns Tool invocation response
172
+ */
173
+ async invokeWithStrategy(request, strategy) {
174
+ const requestId = this.generateRequestId();
175
+ const startTime = Date.now();
176
+ let retryAttempts = 0;
177
+ let lastError;
178
+ // Emit request started event
179
+ this.emitRequestEvent('request:started', requestId, request.name);
180
+ while (retryAttempts <= (this.config.enableRetries ? this.config.maxRetries : 0)) {
181
+ try {
182
+ // Select server based on strategy
183
+ const server = await this.selectServer(request.name, strategy, request.preferredServer);
184
+ if (!server) {
185
+ throw new NoServerAvailableError(request.name);
186
+ }
187
+ // Check circuit breaker
188
+ if (this.config.enableCircuitBreaker && this.isCircuitOpen(server.id)) {
189
+ // Try next server
190
+ throw new CircuitBreakerOpenError(server.id);
191
+ }
192
+ // Execute the tool
193
+ const result = await this.executeWithTimeout(request, server, request.timeout ?? this.config.requestTimeout);
194
+ const durationMs = Date.now() - startTime;
195
+ // Record success
196
+ this.recordSuccess(server.id);
197
+ // Emit completion event
198
+ this.emitRequestEvent('request:completed', requestId, request.name, server.id, durationMs);
199
+ return {
200
+ result,
201
+ serverId: server.id,
202
+ latencyMs: durationMs,
203
+ retried: retryAttempts > 0,
204
+ retryAttempts,
205
+ };
206
+ }
207
+ catch (error) {
208
+ lastError = error instanceof Error ? error : new Error(String(error));
209
+ // Record failure for circuit breaker
210
+ const failedServerId = this.getLastAttemptedServerId();
211
+ if (failedServerId) {
212
+ this.recordFailure(failedServerId);
213
+ }
214
+ // Check if we should retry
215
+ if (this.config.enableRetries &&
216
+ retryAttempts < this.config.maxRetries) {
217
+ retryAttempts++;
218
+ // Emit retry event
219
+ this.emitRequestEvent('request:retried', requestId, request.name, undefined, undefined, lastError, retryAttempts);
220
+ // Wait before retry
221
+ await this.delay(this.config.retryDelay * retryAttempts);
222
+ }
223
+ else {
224
+ break;
225
+ }
226
+ }
227
+ }
228
+ // All retries exhausted
229
+ const durationMs = Date.now() - startTime;
230
+ this.emitRequestEvent('request:failed', requestId, request.name, undefined, durationMs, lastError);
231
+ if (retryAttempts > 0) {
232
+ throw new RetryExhaustedError(request.name, retryAttempts, lastError ?? new Error('Unknown error'));
233
+ }
234
+ throw lastError ?? new NoServerAvailableError(request.name);
235
+ }
236
+ /**
237
+ * Invoke multiple tools in parallel
238
+ *
239
+ * @param requests - Array of tool invocation requests
240
+ * @returns Array of tool invocation responses
241
+ */
242
+ async invokeParallel(requests) {
243
+ return Promise.all(requests.map(req => this.invoke(req)));
244
+ }
245
+ /**
246
+ * Invoke tools in sequence
247
+ *
248
+ * @param requests - Array of tool invocation requests
249
+ * @returns Array of tool invocation responses
250
+ */
251
+ async invokeSequential(requests) {
252
+ const results = [];
253
+ for (const request of requests) {
254
+ const response = await this.invoke(request);
255
+ results.push(response);
256
+ }
257
+ return results;
258
+ }
259
+ // ===========================================================================
260
+ // Tool Handler Registration
261
+ // ===========================================================================
262
+ /**
263
+ * Register a direct tool handler (for local execution)
264
+ *
265
+ * @param toolName - Tool name
266
+ * @param handler - Tool handler function
267
+ */
268
+ registerToolHandler(toolName, handler) {
269
+ this.toolHandlers.set(toolName, handler);
270
+ }
271
+ /**
272
+ * Unregister a tool handler
273
+ *
274
+ * @param toolName - Tool name
275
+ */
276
+ unregisterToolHandler(toolName) {
277
+ this.toolHandlers.delete(toolName);
278
+ }
279
+ // ===========================================================================
280
+ // Server Selection Methods
281
+ // ===========================================================================
282
+ /**
283
+ * Select a server for a tool using the specified strategy
284
+ *
285
+ * @param toolName - Tool name
286
+ * @param strategy - Routing strategy
287
+ * @param preferredServer - Optional preferred server ID
288
+ * @returns Selected server or undefined
289
+ */
290
+ async selectServer(toolName, strategy, preferredServer) {
291
+ // Check preferred server first
292
+ if (preferredServer) {
293
+ const server = this.registry.get(preferredServer);
294
+ if (server && this.serverProvidesTool(server, toolName)) {
295
+ const health = this.registry.getHealthStatus(server.id);
296
+ if (health?.status !== 'unhealthy' && !this.isCircuitOpen(server.id)) {
297
+ return server;
298
+ }
299
+ }
300
+ }
301
+ // Get all servers that provide the tool
302
+ const candidates = this.registry.findByTool(toolName);
303
+ if (candidates.length === 0) {
304
+ return undefined;
305
+ }
306
+ // Filter out unhealthy servers and open circuits
307
+ const availableServers = candidates.filter(server => {
308
+ const health = this.registry.getHealthStatus(server.id);
309
+ const isHealthy = health?.status !== 'unhealthy';
310
+ const circuitClosed = !this.isCircuitOpen(server.id);
311
+ return isHealthy && circuitClosed;
312
+ });
313
+ if (availableServers.length === 0) {
314
+ // Fall back to any server if all are unhealthy
315
+ return candidates[0];
316
+ }
317
+ // Apply routing strategy
318
+ switch (strategy) {
319
+ case 'priority':
320
+ return this.selectByPriority(availableServers);
321
+ case 'round-robin':
322
+ return this.selectRoundRobin(toolName, availableServers);
323
+ case 'least-latency':
324
+ return this.selectByLeastLatency(availableServers);
325
+ case 'random':
326
+ return this.selectRandom(availableServers);
327
+ case 'health-aware':
328
+ return this.selectHealthAware(availableServers);
329
+ default:
330
+ return availableServers[0];
331
+ }
332
+ }
333
+ /**
334
+ * Select server by highest priority
335
+ */
336
+ selectByPriority(servers) {
337
+ return [...servers].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))[0];
338
+ }
339
+ /**
340
+ * Select server using round-robin
341
+ */
342
+ selectRoundRobin(toolName, servers) {
343
+ const currentIndex = this.roundRobinIndex.get(toolName) ?? 0;
344
+ const server = servers[currentIndex % servers.length];
345
+ this.roundRobinIndex.set(toolName, (currentIndex + 1) % servers.length);
346
+ return server;
347
+ }
348
+ /**
349
+ * Select server with lowest latency
350
+ */
351
+ selectByLeastLatency(servers) {
352
+ return [...servers].sort((a, b) => {
353
+ const aHealth = this.registry.getHealthStatus(a.id);
354
+ const bHealth = this.registry.getHealthStatus(b.id);
355
+ const aLatency = aHealth?.avgLatencyMs ?? Number.MAX_VALUE;
356
+ const bLatency = bHealth?.avgLatencyMs ?? Number.MAX_VALUE;
357
+ return aLatency - bLatency;
358
+ })[0];
359
+ }
360
+ /**
361
+ * Select random server
362
+ */
363
+ selectRandom(servers) {
364
+ const index = Math.floor(Math.random() * servers.length);
365
+ return servers[index];
366
+ }
367
+ /**
368
+ * Select server using health-aware strategy
369
+ */
370
+ selectHealthAware(servers) {
371
+ // Score each server based on health metrics
372
+ const scored = servers.map(server => {
373
+ const health = this.registry.getHealthStatus(server.id);
374
+ let score = 0;
375
+ // Health status score
376
+ switch (health?.status) {
377
+ case 'healthy':
378
+ score += 100;
379
+ break;
380
+ case 'degraded':
381
+ score += 50;
382
+ break;
383
+ case 'unhealthy':
384
+ score += 0;
385
+ break;
386
+ default:
387
+ score += 25;
388
+ }
389
+ // Latency score (lower is better)
390
+ if (health?.avgLatencyMs !== undefined) {
391
+ score += Math.max(0, 50 - health.avgLatencyMs / 20);
392
+ }
393
+ // Error rate penalty
394
+ if (health?.errorRate !== undefined) {
395
+ score -= health.errorRate * 100;
396
+ }
397
+ // Priority bonus
398
+ score += (server.priority ?? 0) * 5;
399
+ return { server, score };
400
+ });
401
+ // Sort by score descending and return best
402
+ scored.sort((a, b) => b.score - a.score);
403
+ return scored[0].server;
404
+ }
405
+ // ===========================================================================
406
+ // Circuit Breaker Methods
407
+ // ===========================================================================
408
+ /**
409
+ * Check if circuit is open for a server
410
+ *
411
+ * @param serverId - Server ID
412
+ * @returns True if circuit is open
413
+ */
414
+ isCircuitOpen(serverId) {
415
+ const state = this.circuitBreakers.get(serverId);
416
+ if (!state) {
417
+ return false;
418
+ }
419
+ if (state.state === 'open') {
420
+ // Check if reset timeout has passed
421
+ if (state.openedAt) {
422
+ const elapsed = Date.now() - state.openedAt.getTime();
423
+ if (elapsed >= this.config.circuitBreakerResetTimeout) {
424
+ // Transition to half-open
425
+ this.transitionCircuit(serverId, 'half-open');
426
+ return false;
427
+ }
428
+ }
429
+ return true;
430
+ }
431
+ return false;
432
+ }
433
+ /**
434
+ * Get circuit breaker status for a server
435
+ *
436
+ * @param serverId - Server ID
437
+ * @returns Circuit breaker status
438
+ */
439
+ getCircuitStatus(serverId) {
440
+ const state = this.circuitBreakers.get(serverId);
441
+ if (!state) {
442
+ return {
443
+ serverId,
444
+ state: 'closed',
445
+ failureCount: 0,
446
+ };
447
+ }
448
+ let timeUntilClose;
449
+ if (state.state === 'open' && state.openedAt) {
450
+ const elapsed = Date.now() - state.openedAt.getTime();
451
+ timeUntilClose = Math.max(0, this.config.circuitBreakerResetTimeout - elapsed);
452
+ }
453
+ return {
454
+ serverId,
455
+ state: state.state,
456
+ failureCount: state.failureCount,
457
+ lastFailure: state.lastFailure,
458
+ timeUntilClose,
459
+ };
460
+ }
461
+ /**
462
+ * Get all circuit breaker statuses
463
+ *
464
+ * @returns Array of circuit breaker statuses
465
+ */
466
+ getAllCircuitStatuses() {
467
+ const statuses = [];
468
+ for (const serverId of this.registry.getAll().map(s => s.id)) {
469
+ statuses.push(this.getCircuitStatus(serverId));
470
+ }
471
+ return statuses;
472
+ }
473
+ /**
474
+ * Manually reset a circuit breaker
475
+ *
476
+ * @param serverId - Server ID
477
+ */
478
+ resetCircuit(serverId) {
479
+ this.circuitBreakers.delete(serverId);
480
+ }
481
+ /**
482
+ * Manually open a circuit breaker
483
+ *
484
+ * @param serverId - Server ID
485
+ */
486
+ openCircuit(serverId) {
487
+ this.transitionCircuit(serverId, 'open');
488
+ }
489
+ /**
490
+ * Record a successful request
491
+ */
492
+ recordSuccess(serverId) {
493
+ const state = this.getOrCreateCircuitState(serverId);
494
+ state.successCount++;
495
+ state.lastSuccess = new Date();
496
+ if (state.state === 'half-open') {
497
+ // Success in half-open state closes the circuit
498
+ this.transitionCircuit(serverId, 'closed');
499
+ }
500
+ }
501
+ /**
502
+ * Record a failed request
503
+ */
504
+ recordFailure(serverId) {
505
+ const state = this.getOrCreateCircuitState(serverId);
506
+ state.failureCount++;
507
+ state.lastFailure = new Date();
508
+ if (state.state === 'half-open') {
509
+ // Failure in half-open state re-opens the circuit
510
+ this.transitionCircuit(serverId, 'open');
511
+ }
512
+ else if (state.failureCount >= this.config.circuitBreakerThreshold) {
513
+ // Threshold exceeded, open the circuit
514
+ this.transitionCircuit(serverId, 'open');
515
+ }
516
+ }
517
+ /**
518
+ * Transition circuit to a new state
519
+ */
520
+ transitionCircuit(serverId, newState) {
521
+ const state = this.getOrCreateCircuitState(serverId);
522
+ const previousState = state.state;
523
+ state.state = newState;
524
+ if (newState === 'open') {
525
+ state.openedAt = new Date();
526
+ }
527
+ else if (newState === 'closed') {
528
+ state.failureCount = 0;
529
+ state.openedAt = undefined;
530
+ }
531
+ // Emit circuit event
532
+ const event = {
533
+ serverId,
534
+ previousState,
535
+ newState,
536
+ timestamp: new Date(),
537
+ };
538
+ switch (newState) {
539
+ case 'open':
540
+ this.emit('circuit:opened', event);
541
+ break;
542
+ case 'closed':
543
+ this.emit('circuit:closed', event);
544
+ break;
545
+ case 'half-open':
546
+ this.emit('circuit:half-open', event);
547
+ break;
548
+ }
549
+ }
550
+ /**
551
+ * Get or create circuit breaker state
552
+ */
553
+ getOrCreateCircuitState(serverId) {
554
+ let state = this.circuitBreakers.get(serverId);
555
+ if (!state) {
556
+ state = {
557
+ state: 'closed',
558
+ failureCount: 0,
559
+ successCount: 0,
560
+ };
561
+ this.circuitBreakers.set(serverId, state);
562
+ }
563
+ return state;
564
+ }
565
+ // ===========================================================================
566
+ // Private Helper Methods
567
+ // ===========================================================================
568
+ /** Track last attempted server for failure recording */
569
+ lastAttemptedServerId;
570
+ /**
571
+ * Execute tool with timeout
572
+ */
573
+ async executeWithTimeout(request, server, timeoutMs) {
574
+ this.lastAttemptedServerId = server.id;
575
+ // Check for local handler first
576
+ const handler = this.toolHandlers.get(request.name);
577
+ if (handler) {
578
+ return this.executeWithTimeoutInternal(() => handler(request.arguments ?? {}), timeoutMs, request.name);
579
+ }
580
+ // Default implementation returns a placeholder result
581
+ // In real implementation, this would use transport to call the server
582
+ return this.executeWithTimeoutInternal(async () => this.createPlaceholderResult(request, server), timeoutMs, request.name);
583
+ }
584
+ /**
585
+ * Execute a function with timeout
586
+ */
587
+ async executeWithTimeoutInternal(fn, timeoutMs, toolName) {
588
+ return new Promise((resolve, reject) => {
589
+ const timer = setTimeout(() => {
590
+ reject(new ToolInvocationTimeoutError(toolName, timeoutMs));
591
+ }, timeoutMs);
592
+ fn()
593
+ .then(result => {
594
+ clearTimeout(timer);
595
+ resolve(result);
596
+ })
597
+ .catch(error => {
598
+ clearTimeout(timer);
599
+ reject(error);
600
+ });
601
+ });
602
+ }
603
+ /**
604
+ * Create a placeholder result for demonstration
605
+ */
606
+ createPlaceholderResult(request, server) {
607
+ return {
608
+ content: [
609
+ {
610
+ type: 'text',
611
+ text: `Tool "${request.name}" would be executed on server "${server.name}"`,
612
+ },
613
+ ],
614
+ isError: false,
615
+ serverId: server.id,
616
+ toolName: request.name,
617
+ };
618
+ }
619
+ /**
620
+ * Get the last attempted server ID
621
+ */
622
+ getLastAttemptedServerId() {
623
+ return this.lastAttemptedServerId;
624
+ }
625
+ /**
626
+ * Check if a server provides a tool
627
+ */
628
+ serverProvidesTool(server, toolName) {
629
+ return server.tools.some(tool => tool.name === toolName);
630
+ }
631
+ /**
632
+ * Generate a unique request ID
633
+ */
634
+ generateRequestId() {
635
+ return `req-${++this.requestCounter}-${Date.now()}`;
636
+ }
637
+ /**
638
+ * Delay for specified milliseconds
639
+ */
640
+ delay(ms) {
641
+ return new Promise(resolve => setTimeout(resolve, ms));
642
+ }
643
+ /**
644
+ * Emit a request event
645
+ */
646
+ emitRequestEvent(type, requestId, toolName, serverId, durationMs, error, retryAttempt) {
647
+ const event = {
648
+ requestId,
649
+ toolName,
650
+ serverId,
651
+ durationMs,
652
+ error,
653
+ retryAttempt,
654
+ timestamp: new Date(),
655
+ };
656
+ this.emit(type, event);
657
+ }
658
+ // ===========================================================================
659
+ // Utility Methods
660
+ // ===========================================================================
661
+ /**
662
+ * Get aggregator statistics
663
+ *
664
+ * @returns Aggregator statistics
665
+ */
666
+ getStats() {
667
+ const circuitStatuses = this.getAllCircuitStatuses();
668
+ return {
669
+ openCircuits: circuitStatuses.filter(s => s.state === 'open').length,
670
+ halfOpenCircuits: circuitStatuses.filter(s => s.state === 'half-open')
671
+ .length,
672
+ closedCircuits: circuitStatuses.filter(s => s.state === 'closed').length,
673
+ totalRequests: this.requestCounter,
674
+ registeredHandlers: this.toolHandlers.size,
675
+ };
676
+ }
677
+ /**
678
+ * Get the current configuration
679
+ *
680
+ * @returns Current aggregator configuration
681
+ */
682
+ getConfig() {
683
+ return { ...this.config };
684
+ }
685
+ }
686
+ exports.MCPAggregator = MCPAggregator;
687
+ // =============================================================================
688
+ // Factory Function
689
+ // =============================================================================
690
+ /**
691
+ * Create a new MCPAggregator
692
+ *
693
+ * @param registry - The server registry
694
+ * @param config - Optional aggregator configuration
695
+ * @returns New aggregator instance
696
+ *
697
+ * @example
698
+ * ```typescript
699
+ * const aggregator = createMCPAggregator(registry, {
700
+ * defaultStrategy: 'health-aware',
701
+ * enableRetries: true,
702
+ * });
703
+ * ```
704
+ */
705
+ function createMCPAggregator(registry, config) {
706
+ return new MCPAggregator(registry, config);
707
+ }
708
+ //# sourceMappingURL=aggregator.js.map