Spaces:
Running
Running
| 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<MCPClientState>({ | |
| servers: {}, | |
| isLoading: false, | |
| error: undefined | |
| }); | |
| const mcpClient = getMCPClient(); | |
| // Subscribe to MCP state changes | |
| useEffect(() => { | |
| const handleStateChange = (state: MCPClientState) => { | |
| setMCPState(state); | |
| }; | |
| mcpClient.addStateListener(handleStateChange); | |
| // Get initial state | |
| setMCPState(mcpClient.getState()); | |
| return () => { | |
| mcpClient.removeStateListener(handleStateChange); | |
| }; | |
| }, [mcpClient]); | |
| // Add a new MCP server | |
| const addServer = useCallback(async (config: MCPServerConfig): Promise<void> => { | |
| return mcpClient.addServer(config); | |
| }, [mcpClient]); | |
| // Remove an MCP server | |
| const removeServer = useCallback(async (serverId: string): Promise<void> => { | |
| return mcpClient.removeServer(serverId); | |
| }, [mcpClient]); | |
| // Connect to a server | |
| const connectToServer = useCallback(async (serverId: string): Promise<void> => { | |
| return mcpClient.connectToServer(serverId); | |
| }, [mcpClient]); | |
| // Disconnect from a server | |
| const disconnectFromServer = useCallback(async (serverId: string): Promise<void> => { | |
| return mcpClient.disconnectFromServer(serverId); | |
| }, [mcpClient]); | |
| // Test connection to a server | |
| const testConnection = useCallback(async (config: MCPServerConfig): Promise<boolean> => { | |
| return mcpClient.testConnection(config); | |
| }, [mcpClient]); | |
| // Call a tool on an MCP server | |
| const callMCPTool = useCallback(async (serverId: string, toolName: string, args: Record<string, unknown>) => { | |
| return mcpClient.callTool(serverId, toolName, args); | |
| }, [mcpClient]); | |
| // Get all available MCP tools | |
| const getMCPTools = useCallback((): ExtendedTool[] => { | |
| 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 = Date.now(); // Use timestamp to force tool refresh | |
| Object.entries(mcpState.servers).forEach(([serverId, connection]) => { | |
| if (connection.isConnected && connection.config.enabled) { | |
| connection.tools.forEach((mcpTool) => { | |
| // Convert tool name to valid JavaScript identifier | |
| const jsToolName = mcpTool.name.replace(/[-\s]/g, '_').replace(/[^a-zA-Z0-9_]/g, ''); | |
| // Create a JavaScript function that calls the MCP tool | |
| const safeDescription = (mcpTool.description || `MCP tool from ${connection.config.name}`).replace(/[`${}\\]/g, ''); | |
| const serverName = connection.config.name; | |
| const safeParams = Object.entries(mcpTool.inputSchema.properties || {}).map(([name, prop]) => { | |
| const p = prop as { type?: string; description?: string }; | |
| const safeType = (p.type || 'any').replace(/[`${}\\]/g, ''); | |
| const safeDesc = (p.description || '').replace(/[`${}\\]/g, ''); | |
| return `@param {${safeType}} ${name} - ${safeDesc}`; | |
| }).join('\n * '); | |
| const code = `/** | |
| * ${safeDescription} | |
| * ${safeParams} | |
| * @returns {Promise<any>} 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: ${JSON.stringify(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-2" }, | |
| 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} (MCP)" | |
| ), | |
| ), | |
| React.createElement( | |
| "div", | |
| { className: "text-sm space-y-1" }, | |
| React.createElement( | |
| "p", | |
| { className: "text-blue-700 font-medium" }, | |
| "Server: " + ${JSON.stringify(serverName)} | |
| ), | |
| React.createElement( | |
| "p", | |
| { className: "text-blue-700 font-medium" }, | |
| "Input: " + JSON.stringify(input) | |
| ), | |
| React.createElement( | |
| "div", | |
| { className: "mt-3" }, | |
| React.createElement( | |
| "h4", | |
| { className: "text-blue-800 font-medium mb-2" }, | |
| "Result:" | |
| ), | |
| React.createElement( | |
| "pre", | |
| { | |
| className: "text-gray-800 text-xs bg-gray-50 p-3 rounded border overflow-x-auto max-w-full", | |
| style: { whiteSpace: "pre-wrap", wordBreak: "break-word" } | |
| }, | |
| (() => { | |
| // Try to parse and format JSON content from text fields | |
| if (output && output.content && Array.isArray(output.content)) { | |
| const textContent = output.content.find(item => item.type === 'text' && item.text); | |
| if (textContent && textContent.text) { | |
| try { | |
| const parsed = JSON.parse(textContent.text); | |
| return JSON.stringify(parsed, null, 2); | |
| } catch { | |
| // If not JSON, return the original text | |
| return textContent.text; | |
| } | |
| } | |
| } | |
| // Fallback to original output | |
| return JSON.stringify(output, null, 2); | |
| })() | |
| ) | |
| ), | |
| ), | |
| );`; | |
| 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<void> => { | |
| return mcpClient.connectAll(); | |
| }, [mcpClient]); | |
| // Disconnect from all servers | |
| const disconnectAll = useCallback(async (): Promise<void> => { | |
| return mcpClient.disconnectAll(); | |
| }, [mcpClient]); | |
| return { | |
| mcpState, | |
| addServer, | |
| removeServer, | |
| connectToServer, | |
| disconnectFromServer, | |
| testConnection, | |
| callMCPTool, | |
| getMCPTools, | |
| getMCPToolsAsOriginalTools, | |
| connectAll, | |
| disconnectAll | |
| }; | |
| }; | |