UNPKG

anon-identity

Version:

Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure

564 lines 19.4 kB
"use strict"; /** * MCP (Model Context Protocol) Client Implementation * * Core client for connecting to MCP servers and managing LLM provider interactions */ Object.defineProperty(exports, "__esModule", { value: true }); exports.MCPConnectionImpl = exports.MCPConnectionManager = exports.MCPClient = void 0; const events_1 = require("events"); const types_1 = require("./types"); /** * Connection manager for individual MCP connections */ class MCPConnectionManager extends events_1.EventEmitter { constructor(config) { super(); this.config = config; this.connections = new Map(); this.connectionAttempts = new Map(); this.lastHeartbeat = new Map(); this.startHeartbeatMonitoring(); } /** * Create connection to MCP server */ async connect(providerId, endpoint) { try { const connection = new MCPConnectionImpl(providerId, endpoint, this.config); await connection.connect(); this.connections.set(providerId, connection); this.connectionAttempts.set(providerId, 0); this.lastHeartbeat.set(providerId, new Date()); // Set up connection event handlers connection.on('disconnect', () => this.handleDisconnection(providerId)); connection.on('error', (error) => this.handleConnectionError(providerId, error)); connection.on('heartbeat', () => this.lastHeartbeat.set(providerId, new Date())); this.emit('connected', providerId); return connection; } catch (error) { const attempts = this.connectionAttempts.get(providerId) || 0; this.connectionAttempts.set(providerId, attempts + 1); if (attempts < this.config.retryAttempts) { // Exponential backoff retry const delay = Math.min(this.config.retryDelay * Math.pow(this.config.backoffMultiplier, attempts), this.config.maxRetryDelay); setTimeout(() => this.connect(providerId, endpoint), delay); } throw new types_1.MCPError({ code: types_1.MCPErrorCode.NETWORK_ERROR, message: `Failed to connect to provider ${providerId}: ${error}`, timestamp: new Date(), provider: providerId, retryable: attempts < this.config.retryAttempts }); } } /** * Get connection by provider ID */ getConnection(providerId) { return this.connections.get(providerId); } /** * Get all active connections */ getAllConnections() { return new Map(this.connections); } /** * Disconnect from provider */ async disconnect(providerId) { const connection = this.connections.get(providerId); if (connection) { await connection.disconnect(); this.connections.delete(providerId); this.connectionAttempts.delete(providerId); this.lastHeartbeat.delete(providerId); this.emit('disconnected', providerId); } } /** * Disconnect from all providers */ async disconnectAll() { const disconnectPromises = Array.from(this.connections.keys()).map((providerId) => this.disconnect(providerId)); await Promise.all(disconnectPromises); } /** * Handle connection disconnection */ handleDisconnection(providerId) { this.connections.delete(providerId); this.emit('disconnected', providerId); // Attempt reconnection if configured if (this.config.keepAlive) { this.emit('reconnecting', providerId); } } /** * Handle connection errors */ handleConnectionError(providerId, error) { this.emit('error', providerId, error); } /** * Monitor connection health via heartbeats */ startHeartbeatMonitoring() { if (!this.config.heartbeatInterval) return; setInterval(() => { const now = new Date(); for (const [providerId, lastHeartbeat] of this.lastHeartbeat.entries()) { const timeSinceHeartbeat = now.getTime() - lastHeartbeat.getTime(); if (timeSinceHeartbeat > this.config.heartbeatInterval * 2) { // Connection appears dead this.handleDisconnection(providerId); } } }, this.config.heartbeatInterval); } } exports.MCPConnectionManager = MCPConnectionManager; /** * Concrete implementation of MCPConnection */ class MCPConnectionImpl extends events_1.EventEmitter { constructor(providerId, endpoint, config) { super(); this.endpoint = endpoint; this.config = config; this.status = types_1.ConnectionStatus.DISCONNECTED; this.lastHeartbeat = new Date(); this.createdAt = new Date(); this.ws = null; this.requestId = 0; this.pendingRequests = new Map(); this.id = `conn-${providerId}-${Date.now()}`; this.providerId = providerId; this.metadata = { endpoint: this.endpoint, version: '1.0.0', features: [], retryCount: 0 }; } /** * Connect to MCP server */ async connect() { return new Promise((resolve, reject) => { try { this.ws = new WebSocket(this.endpoint); this.ws.onopen = () => { this.status = types_1.ConnectionStatus.CONNECTED; this.startHeartbeat(); resolve(); }; this.ws.onmessage = (event) => { this.handleMessage(JSON.parse(event.data)); }; this.ws.onclose = () => { this.status = types_1.ConnectionStatus.DISCONNECTED; this.emit('disconnect'); }; this.ws.onerror = (error) => { reject(new Error(`WebSocket error: ${error}`)); }; // Connection timeout setTimeout(() => { if (this.ws?.readyState !== WebSocket.OPEN) { reject(new Error('Connection timeout')); } }, this.config.timeout); } catch (error) { reject(error); } }); } /** * Disconnect from MCP server */ async disconnect() { if (this.ws) { this.ws.close(); this.ws = null; } this.status = types_1.ConnectionStatus.DISCONNECTED; // Clear pending requests for (const [requestId, { reject, timeout }] of this.pendingRequests.entries()) { clearTimeout(timeout); reject(new types_1.MCPError({ code: types_1.MCPErrorCode.NETWORK_ERROR, message: 'Connection closed', timestamp: new Date(), requestId, retryable: false })); } this.pendingRequests.clear(); } /** * Send LLM request and await response */ async sendRequest(request) { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { throw new types_1.MCPError({ code: types_1.MCPErrorCode.NETWORK_ERROR, message: 'Connection not available', timestamp: new Date(), provider: this.providerId, retryable: true }); } const requestId = `${this.providerId}-${++this.requestId}`; const message = { id: requestId, type: types_1.MCPMessageType.REQUEST, timestamp: new Date(), sender: 'client', recipient: this.providerId, payload: request, metadata: { requestType: request.type, agentDID: request.agentDID, sessionId: request.sessionId } }; return new Promise((resolve, reject) => { // Set up timeout const timeout = setTimeout(() => { this.pendingRequests.delete(requestId); reject(new types_1.MCPError({ code: types_1.MCPErrorCode.TIMEOUT, message: `Request timeout after ${this.config.timeout}ms`, timestamp: new Date(), requestId, provider: this.providerId, retryable: true })); }, this.config.timeout); // Store pending request this.pendingRequests.set(requestId, { resolve, reject, timeout }); // Send message this.ws.send(JSON.stringify(message)); }); } /** * Send streaming LLM request */ async *streamRequest(request) { // Implementation for streaming would depend on the MCP server protocol // This is a placeholder that would need to be implemented based on // the actual MCP streaming specification throw new Error('Streaming not yet implemented'); } /** * Check connection health */ async health() { const start = Date.now(); try { await this.sendHeartbeat(); const latency = Date.now() - start; return { status: 'healthy', latency }; } catch (error) { return { status: 'unhealthy' }; } } /** * Handle incoming messages from MCP server */ handleMessage(message) { switch (message.type) { case types_1.MCPMessageType.RESPONSE: this.handleResponse(message); break; case types_1.MCPMessageType.ERROR: this.handleError(message); break; case types_1.MCPMessageType.HEARTBEAT: this.lastHeartbeat = new Date(); this.emit('heartbeat'); break; case types_1.MCPMessageType.NOTIFICATION: this.emit('notification', message.payload); break; } } /** * Handle response messages */ handleResponse(message) { const pending = this.pendingRequests.get(message.id); if (pending) { clearTimeout(pending.timeout); this.pendingRequests.delete(message.id); pending.resolve(message.payload); } } /** * Handle error messages */ handleError(message) { const pending = this.pendingRequests.get(message.id); if (pending) { clearTimeout(pending.timeout); this.pendingRequests.delete(message.id); pending.reject(message.payload); } } /** * Send heartbeat to maintain connection */ async sendHeartbeat() { if (this.ws && this.ws.readyState === WebSocket.OPEN) { const heartbeat = { id: `heartbeat-${Date.now()}`, type: types_1.MCPMessageType.HEARTBEAT, timestamp: new Date(), sender: 'client', recipient: this.providerId, payload: {} }; this.ws.send(JSON.stringify(heartbeat)); } } /** * Start periodic heartbeat */ startHeartbeat() { if (this.config.heartbeatInterval) { setInterval(() => { this.sendHeartbeat(); }, this.config.heartbeatInterval); } } } exports.MCPConnectionImpl = MCPConnectionImpl; /** * Main MCP Client class */ class MCPClient extends events_1.EventEmitter { constructor(config) { super(); this.config = config; this.providers = new Map(); this.requestMetrics = new Map(); this.connectionManager = new MCPConnectionManager(config.client); // Set up connection manager event handlers this.connectionManager.on('connected', (providerId) => this.emit('connected', providerId)); this.connectionManager.on('disconnected', (providerId) => this.emit('disconnected', providerId)); this.connectionManager.on('error', (providerId, error) => this.emit('error', providerId, error)); } /** * Initialize MCP client and connect to providers */ async initialize() { // Register and connect to all configured providers for (const providerConfig of this.config.providers) { if (providerConfig.enabled) { await this.addProvider(providerConfig); } } // Set default provider if not set if (!this.defaultProvider && this.providers.size > 0) { this.defaultProvider = Array.from(this.providers.keys())[0]; } } /** * Add and connect to a provider */ async addProvider(providerConfig) { const provider = { id: providerConfig.id, name: providerConfig.id, version: '1.0.0', description: `${providerConfig.id} provider`, capabilities: { completion: true, streaming: false, functionCalling: true, embeddings: false, moderation: false, multimodal: false, codeGeneration: true, jsonMode: true }, models: [], rateLimits: providerConfig.rateLimits || { requestsPerMinute: 60, tokensPerMinute: 100000, requestsPerDay: 1000, tokensPerDay: 1000000, concurrentRequests: 10 }, config: providerConfig, status: types_1.ProviderStatus.AVAILABLE }; this.providers.set(provider.id, provider); await this.connectionManager.connect(provider.id, providerConfig.endpoint); } /** * Send request to specific provider */ async sendRequest(request, providerId) { const targetProvider = providerId || this.defaultProvider; if (!targetProvider) { throw new types_1.MCPError({ code: types_1.MCPErrorCode.PROVIDER_UNAVAILABLE, message: 'No provider available', timestamp: new Date(), retryable: false }); } const connection = this.connectionManager.getConnection(targetProvider); if (!connection) { throw new types_1.MCPError({ code: types_1.MCPErrorCode.PROVIDER_UNAVAILABLE, message: `Provider ${targetProvider} not connected`, timestamp: new Date(), provider: targetProvider, retryable: true }); } // Add request metadata const enhancedRequest = { ...request, metadata: { ...request.metadata, requestId: `${targetProvider}-${Date.now()}`, timestamp: new Date(), source: 'mcp-client' } }; try { const response = await connection.sendRequest(enhancedRequest); // Track usage metrics if (response.usage) { const providerMetrics = this.requestMetrics.get(targetProvider) || []; providerMetrics.push(response.usage); this.requestMetrics.set(targetProvider, providerMetrics); } return response; } catch (error) { // Handle provider failover if enabled if (error instanceof types_1.MCPError && error.retryable && this.config.providers.length > 1) { const alternativeProviders = Array.from(this.providers.keys()) .filter(id => id !== targetProvider); if (alternativeProviders.length > 0) { return this.sendRequest(request, alternativeProviders[0]); } } throw error; } } /** * Send streaming request */ async *streamRequest(request, providerId) { const targetProvider = providerId || this.defaultProvider; if (!targetProvider) { throw new types_1.MCPError({ code: types_1.MCPErrorCode.PROVIDER_UNAVAILABLE, message: 'No provider available', timestamp: new Date(), retryable: false }); } const connection = this.connectionManager.getConnection(targetProvider); if (!connection) { throw new types_1.MCPError({ code: types_1.MCPErrorCode.PROVIDER_UNAVAILABLE, message: `Provider ${targetProvider} not connected`, timestamp: new Date(), provider: targetProvider, retryable: true }); } yield* connection.streamRequest(request); } /** * Get available providers */ getAvailableProviders() { return Array.from(this.providers.keys()).filter((providerId) => this.connectionManager.getConnection(providerId) !== undefined); } /** * Get provider information */ getProvider(providerId) { return this.providers.get(providerId); } /** * Get usage statistics for provider */ getUsageStats(providerId) { return this.requestMetrics.get(providerId) || []; } /** * Set default provider */ setDefaultProvider(providerId) { if (this.providers.has(providerId)) { this.defaultProvider = providerId; } else { throw new Error(`Provider ${providerId} not available`); } } /** * Health check for all providers */ async healthCheck() { const results = new Map(); for (const providerId of this.providers.keys()) { const connection = this.connectionManager.getConnection(providerId); if (connection) { try { const health = await connection.health(); results.set(providerId, health); } catch (error) { results.set(providerId, { status: 'unhealthy', error: error.message }); } } else { results.set(providerId, { status: 'disconnected' }); } } return results; } /** * Get connection for provider */ async getConnection(providerId) { return this.connectionManager.getConnection(providerId) || null; } /** * Get provider capabilities */ async getProviderCapabilities(providerId) { const provider = this.providers.get(providerId); return provider?.capabilities || {}; } /** * Shutdown MCP client */ async shutdown() { await this.connectionManager.disconnectAll(); this.removeAllListeners(); } } exports.MCPClient = MCPClient; //# sourceMappingURL=client.js.map