BusyBee3333 4e6467ffb0 Add CRESync CRM application with Setup page
- Build complete Next.js CRM for commercial real estate
- Add authentication with JWT sessions and role-based access
- Add GoHighLevel API integration for contacts, conversations, opportunities
- Add AI-powered Control Center with tool calling
- Add Setup page with onboarding checklist (/setup)
- Add sidebar navigation with Setup menu item
- Fix type errors in onboarding API, GHL services, and control center tools
- Add Prisma schema with SQLite for local development
- Add UI components with clay morphism design system

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 17:30:55 -05:00

215 lines
5.4 KiB
TypeScript

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<boolean> {
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<void> {
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<boolean> {
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<ToolDefinition[]> {
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<string, unknown>): Promise<ToolResult> {
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<MCPClient | null> {
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<void> {
if (mcpClientInstance) {
await mcpClientInstance.disconnect();
mcpClientInstance = null;
}
}