"use client"; import { Loader2Icon, PlugZapIcon, WrenchIcon } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; import { DashboardPage, DashboardTableShell, DashboardToolbar, } from "@/components/dashboard-page"; import { JsonCodeEditor } from "@/components/json-code-editor"; import { JsonViewer } from "@/components/json-viewer"; import { OptionCombobox } from "@/components/option-combobox"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Drawer, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, } from "@/components/ui/drawer"; import { Label } from "@/components/ui/label"; import { callMCPTool, listMCPServers, listMCPTools, testMCPConnection, type MCPConnectionResult, type MCPServerInfo, type MCPToolCallResult, type MCPToolInfo, } from "@/lib/api/admin"; import { useI18n } from "@/i18n/provider"; const defaultServerCode = ""; export default function MCPDashboardPage() { const t = useI18n(); const [serverCode, setServerCode] = useState(defaultServerCode); const [servers, setServers] = useState([]); const [connection, setConnection] = useState( null, ); const [tools, setTools] = useState([]); const [loadingServers, setLoadingServers] = useState(true); const [testing, setTesting] = useState(false); const [loadingTools, setLoadingTools] = useState(false); const [callingTool, setCallingTool] = useState(false); const [drawerOpen, setDrawerOpen] = useState(false); const [activeTool, setActiveTool] = useState(null); const [argumentsText, setArgumentsText] = useState("{}"); const [toolResult, setToolResult] = useState(null); const [argumentsError, setArgumentsError] = useState(null); const serverOptions = useMemo( () => servers.map((server) => ({ value: server.code, label: server.enabled ? `${server.code} (${server.endpoint})` : `${server.code} (${t("mcp.disabled")})`, })), [servers, t], ); useEffect(() => { async function loadServers() { setLoadingServers(true); try { const result = await listMCPServers(); setServers(result); const firstServer = result[0]; if (firstServer) { setServerCode((current) => current || firstServer.code); } } catch (error) { toast.error( error instanceof Error ? error.message : t("mcp.loadServersFailed"), ); } finally { setLoadingServers(false); } } void loadServers(); }, [t]); async function handleTestConnection() { setTesting(true); try { const result = await testMCPConnection(serverCode.trim()); setConnection(result); toast.success(t("mcp.connectSuccess")); } catch (error) { toast.error(error instanceof Error ? error.message : t("mcp.connectFailed")); } finally { setTesting(false); } } async function handleListTools() { setLoadingTools(true); try { const result = await listMCPTools(serverCode.trim()); setTools(result); toast.success(t("mcp.toolsLoaded", { count: result.length })); } catch (error) { toast.error(error instanceof Error ? error.message : t("mcp.loadToolsFailed")); } finally { setLoadingTools(false); } } async function handleCallTool() { if (!activeTool) { toast.error(t("mcp.selectToolFirst")); return; } if (argumentsError) { toast.error(t("mcp.argumentsInvalid")); return; } let parsedArguments: Record = {}; try { parsedArguments = argumentsText.trim() ? (JSON.parse(argumentsText) as Record) : {}; } catch { toast.error(t("mcp.argumentsMustBeJson")); return; } setCallingTool(true); try { const result = await callMCPTool({ serverCode: serverCode.trim(), toolName: activeTool.name, arguments: parsedArguments, }); setToolResult(result); toast.success(result.isError ? t("mcp.toolReturnedError") : t("mcp.toolCallSuccess")); } catch (error) { toast.error(error instanceof Error ? error.message : t("mcp.toolCallFailed")); } finally { setCallingTool(false); } } function openToolDrawer(tool: MCPToolInfo) { setActiveTool(tool); setToolResult(null); if (tool.name === "lorem") { setArgumentsText('{\n "wordCount": 8\n}'); } else if (tool.name === "ping") { setArgumentsText('{\n "message": "hello from dashboard"\n}'); } else { setArgumentsText("{}"); } setDrawerOpen(true); } return ( <> } >
{t("mcp.serverConfig")}
{ setServerCode(value); setConnection(null); setTools([]); setToolResult(null); setActiveTool(null); }} />
{connection ? (
{t("mcp.connected")} {connection.serverName || "-"}
serverCode: {connection.serverCode}
protocol: {connection.protocol || "-"}
endpoint: {connection.endpoint}
version: {connection.version || "-"}
) : null}
{tools.length === 0 ? (
{t("mcp.emptyToolsHint")}
) : (
{t("mcp.toolName")}
{t("mcp.description")}
{t("mcp.actions")}
{tools.map((tool) => (
{tool.name}
{tool.description || "-"}
))}
)}
{activeTool?.name || t("mcp.toolDetail")} {t("mcp.detailDescription")}
{activeTool ? ( <>
{activeTool.name} {activeTool.title ? ( {activeTool.title} ) : null}

{activeTool.description || t("mcp.noDescription")}

Input Schema

Output Schema

{toolResult ? (
{toolResult.isError ? t("mcp.returnedError") : t("mcp.callSuccess")} {toolResult.toolName}

Content

Structured Content

) : null}
) : null}
); }