LFM2-WebGPU / src /hooks /useMCP.ts
shreyask's picture
add mcp support
aad94d8 verified
raw
history blame
6.79 kB
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,
};
};