UNPKG

capacitor-cors-bypass-enhanced

Version:

Enhanced Capacitor plugin for CORS bypass with HTTP/2, HTTP/3, gRPC, GraphQL, file operations, and advanced networking features. Modular TypeScript definitions for better maintainability.

245 lines (213 loc) 6.53 kB
/** * MCP Client Manager (Web Implementation) * Manages MCP connections with StreamableHTTP transport */ import { StreamableHTTPTransport } from './streamable-http'; import type { MCPClientOptions, MCPClient } from '../types/mcp'; export class MCPClientManager { private connections: Map<string, { transport: StreamableHTTPTransport; info: MCPClient; listeners: Map<string, Set<Function>>; }> = new Map(); private connectionCounter = 0; /** * Create a new MCP client connection */ async createClient(options: MCPClientOptions): Promise<MCPClient> { this.connectionCounter++; const connectionId = `mcp_web_${this.connectionCounter}`; // Determine transport type const transport = options.transport || 'streamablehttp'; // Get endpoint URL let url = options.url; if (!url && options.sseUrl) { // Backward compatibility url = options.sseUrl; } if (!url) { throw new Error('URL is required for MCP client'); } if (transport === 'streamablehttp') { return this.createStreamableHTTPClient(connectionId, url, options); } else if (transport === 'sse') { throw new Error('Legacy SSE transport not yet implemented. Use StreamableHTTP instead.'); } else { throw new Error(`Unsupported transport: ${transport}`); } } /** * Create a StreamableHTTP MCP client */ private async createStreamableHTTPClient( connectionId: string, url: string, options: MCPClientOptions ): Promise<MCPClient> { const listeners = new Map<string, Set<Function>>(); const streamableTransport = new StreamableHTTPTransport( url, { onMessage: (message: any) => { this.notifyListeners(connectionId, 'message', { connectionId, message }); }, onError: (error: string) => { this.notifyListeners(connectionId, 'error', { connectionId, error }); }, onConnectionStateChange: (state: string) => { this.notifyListeners(connectionId, 'stateChange', { connectionId, state }); }, }, options.resumable || false, options.sessionId, options.lastSequence ); const clientInfo: MCPClient = { connectionId, status: 'connecting', }; this.connections.set(connectionId, { transport: streamableTransport, info: clientInfo, listeners, }); // Send initialize request try { const initRequest = { jsonrpc: '2.0' as const, id: 1, method: 'initialize', params: { protocolVersion: options.protocolVersion || '2025-03-26', clientInfo: options.clientInfo, capabilities: options.capabilities, }, }; await streamableTransport.sendMessage(initRequest, true); clientInfo.status = 'connected'; } catch (error: any) { clientInfo.status = 'error'; throw error; } return clientInfo; } /** * Send a message to an MCP server */ async sendMessage(connectionId: string, message: any, expectStream: boolean = false): Promise<void> { const connection = this.connections.get(connectionId); if (!connection) { throw new Error(`MCP connection not found: ${connectionId}`); } await connection.transport.sendMessage(message, expectStream); } /** * Open a listen stream for server-initiated messages */ async openListenStream(connectionId: string): Promise<void> { const connection = this.connections.get(connectionId); if (!connection) { throw new Error(`MCP connection not found: ${connectionId}`); } await connection.transport.openListenStream(); } /** * Close an MCP client connection */ closeClient(connectionId: string): void { const connection = this.connections.get(connectionId); if (connection) { connection.transport.close(); connection.info.status = 'disconnected'; this.connections.delete(connectionId); } } /** * Get session information for a connection */ getSessionInfo(connectionId: string): { sessionId: string | null; lastSequence: number; resumable: boolean; } | null { const connection = this.connections.get(connectionId); if (!connection) { return null; } return { sessionId: connection.transport.getSessionId(), lastSequence: connection.transport.getLastSequence(), resumable: connection.transport.isResumable(), }; } /** * Get client info */ getClientInfo(connectionId: string): MCPClient | null { const connection = this.connections.get(connectionId); return connection ? connection.info : null; } /** * Add event listener */ addEventListener( connectionId: string, event: string, callback: Function ): void { const connection = this.connections.get(connectionId); if (!connection) { throw new Error(`MCP connection not found: ${connectionId}`); } if (!connection.listeners.has(event)) { connection.listeners.set(event, new Set()); } connection.listeners.get(event)!.add(callback); } /** * Remove event listener */ removeEventListener( connectionId: string, event: string, callback: Function ): void { const connection = this.connections.get(connectionId); if (!connection) { return; } const listeners = connection.listeners.get(event); if (listeners) { listeners.delete(callback); } } /** * Notify listeners */ private notifyListeners(connectionId: string, event: string, data: any): void { const connection = this.connections.get(connectionId); if (!connection) { return; } const listeners = connection.listeners.get(event); if (listeners) { listeners.forEach((callback) => { try { callback(data); } catch (error) { console.error('Error in MCP event listener:', error); } }); } } /** * Close all connections */ closeAll(): void { this.connections.forEach((connection, connectionId) => { this.closeClient(connectionId); }); } } // Export singleton instance export const mcpClientManager = new MCPClientManager();