'use client' import type { ComponentProps } from 'react' import type { ToolsContentInset } from '../content-inset' import type { Collection } from '@/app/components/tools/types' import type { ToolWithProvider } from '@/app/components/workflow/types' import { AlertDialog, AlertDialogActions, AlertDialogCancelButton, AlertDialogConfirmButton, AlertDialogContent, AlertDialogDescription, AlertDialogTitle, } from '@langgenius/dify-ui/alert-dialog' import { cn } from '@langgenius/dify-ui/cn' import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { STEP_BY_STEP_TOUR_TARGETS } from '@/app/components/step-by-step-tour/target-registry' import { useCanManageMCP } from '@/app/components/tools/hooks/use-tool-permissions' import ToolCardSkeletonGrid from '@/app/components/tools/provider/tool-card-skeleton' import { useAllMCPTools, useDeleteMCP, useUpdateMCP } from '@/service/use-tools' import { toolsContentInsetClassNames, toolsUnifiedContentFrameClassName } from '../content-inset' import NewMCPCard from './create-card' import MCPDetailPanel from './detail/provider-detail' import MCPModal from './modal' import MCPCard from './provider-card' type Props = Readonly<{ providers?: Collection[] isLoading?: boolean searchText: string contentInset?: ToolsContentInset createdProviderId?: string onRefresh?: () => Promise onCreatedProviderHandled?: () => void showCreateCard?: boolean }> type MCPModalConfirmPayload = Parameters['onConfirm']>[0] type MutationResult = { result?: string } const EMPTY_MCP_TOOLS: ToolWithProvider[] = [] const MCPList = ({ providers, isLoading: isLoadingProviders, searchText, contentInset = 'default', createdProviderId, onRefresh, onCreatedProviderHandled, showCreateCard = true, }: Props) => { const { t } = useTranslation() const canManageMCP = useCanManageMCP() const fallbackMCPToolsQuery = useAllMCPTools(providers === undefined) const list = providers ?? fallbackMCPToolsQuery.data ?? EMPTY_MCP_TOOLS const isLoading = isLoadingProviders ?? fallbackMCPToolsQuery.isLoading const refetch = onRefresh ?? fallbackMCPToolsQuery.refetch const [isTriggerAuthorize, setIsTriggerAuthorize] = useState(false) const filteredList = useMemo(() => { return list.filter((collection) => { if (collection.type !== 'mcp') return false if (searchText) return collection.name.toLowerCase().includes(searchText.toLowerCase()) return true }) as ToolWithProvider[] }, [list, searchText]) const [currentProviderID, setCurrentProviderID] = useState() const [editingProviderID, setEditingProviderID] = useState() const [deletingProviderID, setDeletingProviderID] = useState() const currentProvider = list.find((provider) => provider.id === currentProviderID) const editingProvider = list.find((provider) => provider.id === editingProviderID) const deletingProvider = list.find((provider) => provider.id === deletingProviderID) const detailProvider = editingProvider || deletingProvider ? undefined : currentProvider const { mutateAsync: updateMCP } = useUpdateMCP({}) const { mutateAsync: deleteMCP, isPending: isDeleting } = useDeleteMCP({}) const handleCreate = async (provider: ToolWithProvider) => { if (!canManageMCP) return await refetch() // update list setCurrentProviderID(provider.id) setIsTriggerAuthorize(true) } useEffect(() => { if (!canManageMCP || !createdProviderId) return let isActive = true const openCreatedProvider = async () => { try { await refetch() if (!isActive) return setCurrentProviderID(createdProviderId) setIsTriggerAuthorize(true) } finally { if (isActive) onCreatedProviderHandled?.() } } void openCreatedProvider() return () => { isActive = false } }, [canManageMCP, createdProviderId, onCreatedProviderHandled, refetch]) const handleEdit = (providerID: string) => { if (!canManageMCP) return setEditingProviderID(providerID) } const handleEditConfirm = async (form: MCPModalConfirmPayload) => { if (!canManageMCP || !editingProvider) return const res = (await updateMCP({ ...form, provider_id: editingProvider.id, })) as MutationResult if (res.result !== 'success') return await refetch() // update list setCurrentProviderID(editingProvider.id) setIsTriggerAuthorize(true) setEditingProviderID(undefined) } const handleDelete = (providerID: string) => { if (!canManageMCP) return setDeletingProviderID(providerID) } const handleDeleteConfirm = async () => { if (!canManageMCP || !deletingProvider) return const res = (await deleteMCP(deletingProvider.id)) as MutationResult if (res.result !== 'success') return await refetch() setCurrentProviderID(undefined) setDeletingProviderID(undefined) } const contentPaddingClassName = toolsContentInsetClassNames[contentInset] const contentFrameClassName = cn(contentPaddingClassName, toolsUnifiedContentFrameClassName) return ( <>
{!isLoading && canManageMCP && showCreateCard && } {isLoading ? ( ) : ( filteredList.map((provider, index) => (
)) )}
{detailProvider && ( setCurrentProviderID(undefined)} onUpdate={refetch} onEdit={handleEdit} onDelete={handleDelete} isTriggerAuthorize={isTriggerAuthorize} onFirstCreate={() => setIsTriggerAuthorize(false)} /> )} {editingProvider && ( setEditingProviderID(undefined)} /> )} {deletingProvider && ( !open && setDeletingProviderID(undefined)}>
{t(($) => $['mcp.delete'], { ns: 'tools' })} {t(($) => $['mcp.deleteConfirmTitle'], { ns: 'tools', mcp: deletingProvider.name })}
{t(($) => $['operation.cancel'], { ns: 'common' })} {t(($) => $['operation.confirm'], { ns: 'common' })}
)} ) } export default MCPList