import { useState, useEffect, useCallback } from "react"; import { MCPClientService } from "../services/mcpClient"; import type { MCPServerConfig, MCPClientState, ExtendedTool, } from "../types/mcp"; import type { Tool as OriginalTool } from "../components/ToolItem"; // Singleton instance let mcpClientInstance: MCPClientService | null = null; const getMCPClient = (): MCPClientService => { if (!mcpClientInstance) { mcpClientInstance = new MCPClientService(); } return mcpClientInstance; }; export const useMCP = () => { const [mcpState, setMCPState] = useState({ servers: {}, isLoading: false, error: undefined, }); const mcpClient = getMCPClient(); // Subscribe to MCP state changes useEffect(() => { const handleStateChange = (state: MCPClientState) => { console.log("MCP state change triggered:", Object.keys(state.servers)); setMCPState(state); }; mcpClient.addStateListener(handleStateChange); // Get initial state setMCPState(mcpClient.getState()); return () => { mcpClient.removeStateListener(handleStateChange); // Don't cleanup the service itself as it's a singleton used across the app }; }, [mcpClient]); // Add a new MCP server const addServer = useCallback( async (config: MCPServerConfig): Promise => { return mcpClient.addServer(config); }, [mcpClient] ); // Remove an MCP server const removeServer = useCallback( async (serverId: string): Promise => { return mcpClient.removeServer(serverId); }, [mcpClient] ); // Connect to a server const connectToServer = useCallback( async (serverId: string): Promise => { return mcpClient.connectToServer(serverId); }, [mcpClient] ); // Disconnect from a server const disconnectFromServer = useCallback( async (serverId: string): Promise => { return mcpClient.disconnectFromServer(serverId); }, [mcpClient] ); // Test connection to a server const testConnection = useCallback( async (config: MCPServerConfig): Promise => { return mcpClient.testConnection(config); }, [mcpClient] ); // Call a tool on an MCP server const callMCPTool = useCallback( async ( serverId: string, toolName: string, args: Record ) => { console.log("MCP tool called:", { serverId, toolName, args, timestamp: new Date().toISOString(), }); console.trace("MCP tool call stack trace:"); return mcpClient.callTool(serverId, toolName, args); }, [mcpClient] ); // Get all available MCP tools const getMCPTools = useCallback((): ExtendedTool[] => { console.log( "getMCPTools called - checking if this is being called frequently" ); const mcpTools: ExtendedTool[] = []; Object.entries(mcpState.servers).forEach(([serverId, connection]) => { if (connection.isConnected && connection.config.enabled) { connection.tools.forEach((mcpTool) => { mcpTools.push({ id: `${serverId}:${mcpTool.name}`, name: mcpTool.name, enabled: true, isCollapsed: false, mcpServerId: serverId, mcpTool: mcpTool, isRemote: true, }); }); } }); return mcpTools; }, [mcpState.servers]); // Convert MCP tools to the format expected by the existing tool system const getMCPToolsAsOriginalTools = useCallback((): OriginalTool[] => { const mcpTools: OriginalTool[] = []; let globalId = 10000; // Start with high numbers to avoid conflicts Object.entries(mcpState.servers).forEach(([serverId, connection]) => { if (connection.isConnected && connection.config.enabled) { connection.tools.forEach((mcpTool) => { // Convert tool name to valid JavaScript identifier and include server name for uniqueness const serverName = connection.config.name .replace(/[-\s]/g, "_") .replace(/[^a-zA-Z0-9_]/g, ""); const toolName = mcpTool.name .replace(/[-\s]/g, "_") .replace(/[^a-zA-Z0-9_]/g, ""); const jsToolName = `${serverName}_${toolName}`; // Create a JavaScript function that calls the MCP tool const code = `/** * ${mcpTool.description || `MCP tool from ${connection.config.name}`} * Server: ${connection.config.name} * ${Object.entries(mcpTool.inputSchema.properties || {}) .map(([name, prop]) => { const p = prop as { type?: string; description?: string }; return `@param {${p.type || "any"}} ${name} - ${p.description || ""}`; }) .join("\n * ")} * @returns {Promise} Tool execution result */ export async function ${jsToolName}(${Object.keys( mcpTool.inputSchema.properties || {} ).join(", ")}) { // This is an MCP tool - execution is handled by the MCP client return { mcpServerId: "${serverId}", toolName: "${ mcpTool.name }", arguments: arguments }; } export default (input, output) => React.createElement( "div", { className: "bg-blue-50 border border-blue-200 rounded-lg p-4" }, React.createElement( "div", { className: "flex items-center mb-3" }, React.createElement( "div", { className: "w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center mr-3", }, "🌐", ), React.createElement( "h3", { className: "text-blue-900 font-semibold" }, "${mcpTool.name} (${connection.config.name})" ), ), React.createElement( "div", { className: "text-sm mb-2" }, React.createElement( "p", { className: "text-blue-700 font-medium mb-1" }, \`Server: ${connection.config.name}\` ), ), );`; mcpTools.push({ id: globalId++, name: jsToolName, // Use JavaScript-safe name for function calls code: code, enabled: true, isCollapsed: false, }); }); } }); return mcpTools; }, [mcpState.servers]); // Connect to all enabled servers const connectAll = useCallback(async (): Promise => { return mcpClient.connectAll(); }, [mcpClient]); // Disconnect from all servers const disconnectAll = useCallback(async (): Promise => { return mcpClient.disconnectAll(); }, [mcpClient]); return { mcpState, addServer, removeServer, connectToServer, disconnectFromServer, testConnection, callMCPTool, getMCPTools, getMCPToolsAsOriginalTools, connectAll, disconnectAll, }; };