"use client"; import { ReactNode, useMemo, useState } from "react"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "ui/dialog"; import useSWR from "swr"; import { cn, fetcher } from "lib/utils"; import { useTranslations } from "next-intl"; import { McpServerCustomization, MCPServerInfo, McpToolCustomization, MCPToolInfo, } from "app-types/mcp"; import { Tooltip, TooltipContent, TooltipTrigger } from "ui/tooltip"; import { ArrowLeft, ChevronRight, Info, Loader, Trash2, Wrench, } from "lucide-react"; import { Button } from "ui/button"; import { Textarea } from "ui/textarea"; import { safe } from "ts-safe"; import { z } from "zod"; import { handleErrorWithToast } from "ui/shared-toast"; import { Skeleton } from "ui/skeleton"; import { Alert, AlertDescription, AlertTitle } from "ui/alert"; import { ToolDetailPopupContent } from "./tool-detail-popup"; import { ExamplePlaceholder } from "ui/example-placeholder"; import { Input } from "ui/input"; import { appStore } from "@/app/store"; import { useShallow } from "zustand/shallow"; export function McpCustomizationPopup() { const [mcpCustomizationPopup, appStoreMutate] = appStore( useShallow((state) => [state.mcpCustomizationPopup, state.mutate]), ); return ( { if (!open) { appStoreMutate({ mcpCustomizationPopup: undefined }); } }} > {mcpCustomizationPopup ? ( ) : null} ); } export function McpServerCustomizationContent({ mcpServerInfo: { id, name, toolInfo, error }, title, }: { mcpServerInfo: MCPServerInfo & { id: string }; title?: ReactNode; }) { const t = useTranslations(); const [prompt, setPrompt] = useState(""); const [search, setSearch] = useState(""); const [isProcessing, setIsProcessing] = useState(false); const [selectedTool, setSelectedTool] = useState(null); const handleSave = () => { setIsProcessing(true); safe(() => z .object({ prompt: z.string().min(1).max(3000), }) .parse({ prompt, }), ) .map((body) => fetch(`/api/mcp/server-customizations/${id}`, { method: "POST", body: JSON.stringify(body), }), ) .ifOk(() => refreshMcpServerCustomization()) .ifFail(handleErrorWithToast) .watch(() => { setIsProcessing(false); }); }; const handleDelete = () => { setIsProcessing(true); safe(() => fetch(`/api/mcp/server-customizations/${id}`, { method: "DELETE", }), ) .ifOk(() => refreshMcpServerCustomization()) .ifFail(handleErrorWithToast) .watch(() => { setIsProcessing(false); }); }; const { data: mcpServerCustomization, mutate: refreshMcpServerCustomization, isLoading: isLoadingMcpServerCustomization, } = useSWR( `/api/mcp/server-customizations/${id}`, fetcher, { onSuccess: (data) => { setPrompt(data?.prompt || ""); }, revalidateOnFocus: false, }, ); const { data: mcpToolCustomizations, mutate: refreshMcpToolCustomizations, isLoading: isLoadingMcpToolCustomizations, } = useSWR( `/api/mcp/tool-customizations/${id}`, fetcher, { fallbackData: [], }, ); const toolCustomizations = useMemo(() => { const mcpToolCustomizationsMap = new Map( mcpToolCustomizations?.map((tool) => [tool.toolName, tool]), ); return toolInfo .filter((tool) => tool.name.includes(search)) .map((tool) => { return { name: tool.name, description: tool.description, prompt: mcpToolCustomizationsMap.get(tool.name)?.prompt || "", id: mcpToolCustomizationsMap.get(tool.name)?.id || null, inputSchema: tool.inputSchema, }; }); }, [mcpToolCustomizations, toolInfo, search]); if (selectedTool) { return ( setSelectedTool(null)} className="flex items-center gap-2 text-muted-foreground text-sm hover:text-foreground transition-colors mb-8" > {t("Common.back")} {selectedTool.name} } /> ); } return ( {title || name}{" "} {error ? error : null} {/* */} {t("MCP.mcpServerCustomization")} {t("MCP.mcpServerCustomizationDescription")} {isProcessing || isLoadingMcpServerCustomization ? ( ) : ( <> {mcpServerCustomization?.id && ( {t("Common.delete")} )} {prompt != (mcpServerCustomization?.prompt || "") && ( {t("Common.save")} {t("Common.edit")} )} > )} setPrompt(e.target.value)} /> {!prompt && ( )} {t("MCP.additionalInstructions")} {t("MCP.toolCustomizationInstructions")} setSearch(e.target.value)} placeholder={t("MCP.searchTools")} /> {isLoadingMcpToolCustomizations ? ( Array.from({ length: 6 }).map((_, index) => ( )) ) : ( {toolCustomizations.length === 0 ? ( {t("MCP.noToolsAvailable")} ) : ( toolCustomizations.map((tool) => { return ( setSelectedTool(tool)} className="cursor-pointer hover:bg-input" > {tool.name} {tool.prompt || "None"} ); }) )} )} ); }
error
{t("MCP.mcpServerCustomizationDescription")}
{t("MCP.toolCustomizationInstructions")}
{tool.prompt || "None"}