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) => { | |
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<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> | |
) => { | |
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<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: "${ | |
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<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, | |
}; | |
}; | |