mcp-quiz-server
Version:
🧠 AI-Powered Quiz Management via Model Context Protocol (MCP) - Create, manage, and take quizzes directly from VS Code, Claude, and other AI agents.
281 lines (280 loc) • 10.1 kB
JavaScript
;
/**
* @fileoverview Transport Service Registry - Clean Architecture Infrastructure
* @version 1.0.0
* @since 2025-07-30
* @lastUpdated 2025-07-30
* @module TransportServiceRegistry
* @description Registry for managing multiple transport services in Clean Architecture.
* Provides protocol-based service resolution and lifecycle management.
* @contributors Claude Code Agent
* @dependencies ITransportService interface
* @requirements SECURITY_001 (Transport Layer Integration)
* @testCoverage Unit and integration tests for service registration and resolution
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.InvalidServiceError = exports.DuplicateProtocolError = exports.InvalidProtocolError = exports.TransportServiceNotFoundError = exports.TransportRegistryError = exports.TransportServiceRegistry = void 0;
/**
* Transport Service Registry Implementation
*
* @description Manages registration and resolution of transport services by protocol.
* Ensures proper lifecycle management and configuration validation.
*
* @example
* ```typescript
* const registry = new TransportServiceRegistry();
* registry.register('sse', new SSETransportService());
* registry.register('stdio', new StdioTransportService());
*
* const sseService = registry.resolve('sse');
* ```
*
* @since 2025-07-30
* @author Claude Code Agent
* @requirements SECURITY_001 (Transport Layer Integration)
*/
class TransportServiceRegistry {
constructor() {
this.services = new Map();
this.configurations = new Map();
this.healthStatus = new Map();
}
/**
* Register a transport service for a specific protocol
*/
register(protocol, service) {
this.validateProtocol(protocol);
this.validateService(service);
// Store service and its configuration
this.services.set(protocol, service);
this.configurations.set(protocol, service.getConfiguration());
this.healthStatus.set(protocol, false); // Will be updated by health checks
console.log(`🚀 Transport service registered: ${protocol}`);
// Start health monitoring for the service
this.startHealthMonitoring(protocol, service);
}
/**
* Resolve transport service by protocol
*/
resolve(protocol) {
const service = this.services.get(protocol);
if (!service) {
throw new TransportServiceNotFoundError(`Transport service not found for protocol: ${protocol}. Available: ${this.getAvailableProtocols().join(', ')}`);
}
// Check if service is healthy
const isHealthy = this.healthStatus.get(protocol);
if (!isHealthy) {
console.warn(`⚠️ Transport service for ${protocol} may be unhealthy`);
}
return service;
}
/**
* Get all available transport protocols
*/
getAvailableProtocols() {
return Array.from(this.services.keys()).sort();
}
/**
* Check if protocol is supported
*/
isSupported(protocol) {
return this.services.has(protocol);
}
/**
* Get configuration for a specific protocol
*/
getConfiguration(protocol) {
return this.configurations.get(protocol) || null;
}
/**
* Get health status for a specific protocol
*/
isHealthy(protocol) {
return this.healthStatus.get(protocol) || false;
}
/**
* Get health status for all registered services
*/
getHealthStatus() {
const status = {};
for (const [protocol, healthy] of this.healthStatus.entries()) {
status[protocol] = healthy;
}
return status;
}
/**
* Perform health check on all registered services
*/
async performHealthChecks() {
const results = {};
for (const [protocol, service] of this.services.entries()) {
try {
const isHealthy = await service.healthCheck();
this.healthStatus.set(protocol, isHealthy);
results[protocol] = isHealthy;
if (isHealthy) {
console.log(`✅ Transport service healthy: ${protocol}`);
}
else {
console.warn(`⚠️ Transport service unhealthy: ${protocol}`);
}
}
catch (error) {
this.healthStatus.set(protocol, false);
results[protocol] = false;
console.error(`❌ Health check failed for ${protocol}:`, error);
}
}
return results;
}
/**
* Gracefully shut down all transport services
*/
async shutdown() {
console.log(`🔄 Shutting down ${this.services.size} transport services...`);
const shutdownPromises = [];
for (const [protocol, service] of this.services.entries()) {
const shutdownPromise = service
.shutdown()
.then(() => {
console.log(`✅ Transport service shut down: ${protocol}`);
})
.catch(error => {
console.error(`❌ Error shutting down ${protocol}:`, error);
});
shutdownPromises.push(shutdownPromise);
}
// Wait for all services to shut down (with timeout)
await Promise.allSettled(shutdownPromises);
// Clear registry
this.services.clear();
this.configurations.clear();
this.healthStatus.clear();
console.log('🔄 All transport services shut down');
}
/**
* Get registry statistics
*/
getStatistics() {
const totalServices = this.services.size;
const healthyServices = Array.from(this.healthStatus.values()).filter(h => h).length;
const protocols = this.getAvailableProtocols();
return {
totalServices,
healthyServices,
unhealthyServices: totalServices - healthyServices,
protocols,
configurations: Array.from(this.configurations.values()),
};
}
/**
* Validate protocol name
*/
validateProtocol(protocol) {
if (!protocol || typeof protocol !== 'string') {
throw new InvalidProtocolError('Protocol must be a non-empty string');
}
if (protocol.includes(' ') || protocol.includes('/')) {
throw new InvalidProtocolError('Protocol cannot contain spaces or slashes');
}
if (this.services.has(protocol)) {
throw new DuplicateProtocolError(`Protocol ${protocol} is already registered`);
}
}
/**
* Validate transport service
*/
validateService(service) {
if (!service) {
throw new InvalidServiceError('Service cannot be null or undefined');
}
// Verify service implements required interface methods
const requiredMethods = [
'authenticate',
'authorize',
'routeToHandler',
'auditLog',
'healthCheck',
'getConfiguration',
'shutdown',
];
for (const method of requiredMethods) {
if (typeof service[method] !== 'function') {
throw new InvalidServiceError(`Service must implement method: ${method}`);
}
}
}
/**
* Start health monitoring for a service
*/
startHealthMonitoring(protocol, service) {
// Perform initial health check
service
.healthCheck()
.then(isHealthy => {
this.healthStatus.set(protocol, isHealthy);
console.log(`🔍 Initial health check for ${protocol}: ${isHealthy ? 'healthy' : 'unhealthy'}`);
})
.catch(error => {
this.healthStatus.set(protocol, false);
console.error(`❌ Initial health check failed for ${protocol}:`, error);
});
// Set up periodic health checks (every 30 seconds)
const healthCheckInterval = setInterval(async () => {
try {
const isHealthy = await service.healthCheck();
const wasHealthy = this.healthStatus.get(protocol);
this.healthStatus.set(protocol, isHealthy);
// Log status changes
if (wasHealthy !== isHealthy) {
console.log(`🔄 Health status changed for ${protocol}: ${isHealthy ? 'healthy' : 'unhealthy'}`);
}
}
catch (error) {
this.healthStatus.set(protocol, false);
console.error(`❌ Periodic health check failed for ${protocol}:`, error);
}
}, 30000); // 30 seconds
// Store interval for cleanup (in a real implementation, we'd need proper cleanup)
// For now, this is a simplified version
}
}
exports.TransportServiceRegistry = TransportServiceRegistry;
/**
* Transport Registry Error Types
*/
class TransportRegistryError extends Error {
constructor(message) {
super(message);
this.name = 'TransportRegistryError';
}
}
exports.TransportRegistryError = TransportRegistryError;
class TransportServiceNotFoundError extends TransportRegistryError {
constructor(message) {
super(message);
this.name = 'TransportServiceNotFoundError';
}
}
exports.TransportServiceNotFoundError = TransportServiceNotFoundError;
class InvalidProtocolError extends TransportRegistryError {
constructor(message) {
super(message);
this.name = 'InvalidProtocolError';
}
}
exports.InvalidProtocolError = InvalidProtocolError;
class DuplicateProtocolError extends TransportRegistryError {
constructor(message) {
super(message);
this.name = 'DuplicateProtocolError';
}
}
exports.DuplicateProtocolError = DuplicateProtocolError;
class InvalidServiceError extends TransportRegistryError {
constructor(message) {
super(message);
this.name = 'InvalidServiceError';
}
}
exports.InvalidServiceError = InvalidServiceError;