import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'; import { ToolDefinition, ToolResult } from './types'; /** * MCP Client for connecting to the GoHighLevel MCP Server. * Uses the official MCP SDK with SSE transport. */ export class MCPClient { private serverUrl: string; private client: Client | null = null; private transport: SSEClientTransport | null = null; private connected: boolean = false; private toolsCache: ToolDefinition[] | null = null; constructor(serverUrl: string) { this.serverUrl = serverUrl; } /** * Connect to the MCP server */ async connect(): Promise { if (this.connected && this.client) { return true; } try { // Create SSE transport this.transport = new SSEClientTransport( new URL(`${this.serverUrl}/sse`) ); // Create MCP client this.client = new Client( { name: 'cresync-control-center', version: '1.0.0' }, { capabilities: {} } ); // Connect await this.client.connect(this.transport); this.connected = true; console.log('[MCP Client] Connected to server'); return true; } catch (error) { console.error('[MCP Client] Connection failed:', error); this.connected = false; return false; } } /** * Disconnect from the MCP server */ async disconnect(): Promise { if (this.client) { await this.client.close(); this.client = null; this.transport = null; this.connected = false; } } /** * Health check - try to connect and list tools */ async healthCheck(): Promise { try { // Simple HTTP check to /health endpoint const response = await fetch(`${this.serverUrl}/health`); return response.ok; } catch { return false; } } /** * Get available tools from MCP server * Uses REST endpoint for listing (faster than SSE for just getting list) */ async getTools(): Promise { if (this.toolsCache) { return this.toolsCache; } try { // Use REST endpoint for tool listing const response = await fetch(`${this.serverUrl}/tools`); if (!response.ok) { throw new Error(`Failed to fetch tools: ${response.status}`); } const data = await response.json(); const tools = (data.tools || []).map((tool: any) => ({ name: tool.name, description: tool.description || '', inputSchema: tool.inputSchema || { type: 'object', properties: {} } })); this.toolsCache = tools; console.log(`[MCP Client] Loaded ${tools.length} tools`); return tools; } catch (error) { console.error('[MCP Client] Failed to fetch tools:', error); return []; } } /** * Execute a tool via MCP protocol */ async executeTool(toolName: string, input: Record): Promise { try { // Ensure connected if (!this.connected) { const connected = await this.connect(); if (!connected) { return { toolCallId: '', success: false, error: 'Failed to connect to MCP server' }; } } if (!this.client) { return { toolCallId: '', success: false, error: 'MCP client not initialized' }; } // Call tool via MCP protocol const result = await this.client.callTool({ name: toolName, arguments: input }); // Parse result - MCP returns content array let resultData: unknown; if (result.content && Array.isArray(result.content)) { // Extract text content const textContent = result.content.find((c: any) => c.type === 'text'); if (textContent && 'text' in textContent) { try { resultData = JSON.parse(textContent.text); } catch { resultData = textContent.text; } } else { resultData = result.content; } } else { resultData = result; } return { toolCallId: '', success: !result.isError, result: resultData, error: result.isError ? String(resultData) : undefined }; } catch (error) { console.error(`[MCP Client] Tool execution failed for ${toolName}:`, error); return { toolCallId: '', success: false, error: error instanceof Error ? error.message : 'Tool execution failed' }; } } /** * Clear tool cache */ clearCache(): void { this.toolsCache = null; } } // Singleton instance let mcpClientInstance: MCPClient | null = null; /** * Get or create MCP client instance */ export async function createMCPClient(): Promise { if (mcpClientInstance) { return mcpClientInstance; } // Import settings service const { settingsService } = await import('@/lib/settings/settings-service'); const serverUrl = await settingsService.get('mcpServerUrl'); if (!serverUrl) { console.warn('[MCP Client] Server URL not configured'); return null; } mcpClientInstance = new MCPClient(serverUrl); return mcpClientInstance; } /** * Close and reset MCP client */ export async function closeMCPClient(): Promise { if (mcpClientInstance) { await mcpClientInstance.disconnect(); mcpClientInstance = null; } }