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
text/typescript
/**
* 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();