Spaces:
Paused
Paused
| import React, { useState } from 'react'; | |
| import { useQuery, useMutation } from '@tanstack/react-query'; | |
| import { DataTable } from '../view_logs/table'; | |
| import { columns, ToolTestPanel } from './columns'; | |
| import { MCPTool, MCPToolsViewerProps, CallMCPToolResponse } from './types'; | |
| import { listMCPTools, callMCPTool } from '../networking'; | |
| // Wrapper to handle the type mismatch between MCPTool and DataTable's expected type | |
| function DataTableWrapper({ | |
| columns, | |
| data, | |
| isLoading, | |
| }: { | |
| columns: any; | |
| data: MCPTool[]; | |
| isLoading: boolean; | |
| }) { | |
| // Create a dummy renderSubComponent and getRowCanExpand function | |
| const renderSubComponent = () => <div />; | |
| const getRowCanExpand = () => false; | |
| return ( | |
| <DataTable | |
| columns={columns as any} | |
| data={data as any} | |
| isLoading={isLoading} | |
| renderSubComponent={renderSubComponent} | |
| getRowCanExpand={getRowCanExpand} | |
| loadingMessage="🚅 Loading tools..." | |
| noDataMessage="No tools found" | |
| /> | |
| ); | |
| } | |
| export default function MCPToolsViewer({ | |
| accessToken, | |
| userRole, | |
| userID, | |
| }: MCPToolsViewerProps) { | |
| const [searchTerm, setSearchTerm] = useState(''); | |
| const [selectedTool, setSelectedTool] = useState<MCPTool | null>(null); | |
| const [toolResult, setToolResult] = useState<CallMCPToolResponse | null>(null); | |
| const [toolError, setToolError] = useState<Error | null>(null); | |
| // Query to fetch MCP tools | |
| const { data: mcpTools, isLoading: isLoadingTools } = useQuery({ | |
| queryKey: ['mcpTools'], | |
| queryFn: () => { | |
| if (!accessToken) throw new Error('Access Token required'); | |
| return listMCPTools(accessToken); | |
| }, | |
| enabled: !!accessToken, | |
| }); | |
| // Mutation for calling a tool | |
| const { mutate: executeTool, isPending: isCallingTool } = useMutation({ | |
| mutationFn: (args: { tool: MCPTool; arguments: Record<string, any> }) => { | |
| if (!accessToken) throw new Error('Access Token required'); | |
| return callMCPTool( | |
| accessToken, | |
| args.tool.name, | |
| args.arguments | |
| ); | |
| }, | |
| onSuccess: (data) => { | |
| setToolResult(data); | |
| setToolError(null); | |
| }, | |
| onError: (error: Error) => { | |
| setToolError(error); | |
| setToolResult(null); | |
| }, | |
| }); | |
| // Add onToolSelect handler to each tool | |
| const toolsData = React.useMemo(() => { | |
| if (!mcpTools) return []; | |
| return mcpTools.map((tool: MCPTool) => ({ | |
| ...tool, | |
| onToolSelect: (tool: MCPTool) => { | |
| setSelectedTool(tool); | |
| setToolResult(null); | |
| setToolError(null); | |
| } | |
| })); | |
| }, [mcpTools]); | |
| // Filter tools based on search term | |
| const filteredTools = React.useMemo(() => { | |
| return toolsData.filter((tool: MCPTool) => { | |
| const searchLower = searchTerm.toLowerCase(); | |
| return ( | |
| tool.name.toLowerCase().includes(searchLower) || | |
| tool.description.toLowerCase().includes(searchLower) || | |
| tool.mcp_info.server_name.toLowerCase().includes(searchLower) | |
| ); | |
| }); | |
| }, [toolsData, searchTerm]); | |
| // Handle tool call submission | |
| const handleToolSubmit = (args: Record<string, any>) => { | |
| if (!selectedTool) return; | |
| executeTool({ | |
| tool: selectedTool, | |
| arguments: args, | |
| }); | |
| }; | |
| if (!accessToken || !userRole || !userID) { | |
| return <div className="p-6 text-center text-gray-500">Missing required authentication parameters.</div>; | |
| } | |
| return ( | |
| <div className="w-full p-6"> | |
| <div className="flex items-center justify-between mb-4"> | |
| <h1 className="text-xl font-semibold">MCP Tools</h1> | |
| </div> | |
| <div className="bg-white rounded-lg shadow"> | |
| <div className="border-b px-6 py-4"> | |
| <div className="flex items-center justify-between"> | |
| <div className="relative w-64"> | |
| <input | |
| type="text" | |
| placeholder="Search tools..." | |
| className="w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" | |
| value={searchTerm} | |
| onChange={(e) => setSearchTerm(e.target.value)} | |
| /> | |
| <svg | |
| className="absolute left-2.5 top-2.5 h-4 w-4 text-gray-500" | |
| fill="none" | |
| stroke="currentColor" | |
| viewBox="0 0 24 24" | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth={2} | |
| d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" | |
| /> | |
| </svg> | |
| </div> | |
| <div className="text-sm text-gray-500"> | |
| {filteredTools.length} tool{filteredTools.length !== 1 ? "s" : ""} available | |
| </div> | |
| </div> | |
| </div> | |
| <DataTableWrapper | |
| columns={columns} | |
| data={filteredTools} | |
| isLoading={isLoadingTools} | |
| /> | |
| </div> | |
| {/* Tool Test Panel - Show when a tool is selected */} | |
| {selectedTool && ( | |
| <div className="fixed inset-0 bg-gray-800 bg-opacity-75 flex items-center justify-center z-50 p-4"> | |
| <ToolTestPanel | |
| tool={selectedTool} | |
| onSubmit={handleToolSubmit} | |
| isLoading={isCallingTool} | |
| result={toolResult} | |
| error={toolError} | |
| onClose={() => setSelectedTool(null)} | |
| /> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } |