"use client"; import { ChevronRight, FlaskConical, ShieldAlertIcon, Loader, RotateCw, Settings, Settings2, Wrench, } from "lucide-react"; import { Alert, AlertDescription, AlertTitle } from "ui/alert"; import { Button } from "ui/button"; import { Card, CardContent, CardHeader } from "ui/card"; import JsonView from "ui/json-view"; import { Tooltip, TooltipContent, TooltipTrigger } from "ui/tooltip"; import { memo, useCallback, useMemo, useState } from "react"; import Link from "next/link"; import { useSWRConfig } from "swr"; import { safe } from "ts-safe"; import { handleErrorWithToast } from "ui/shared-toast"; import { refreshMcpClientAction, removeMcpClientAction, shareMcpServerAction, } from "@/app/api/mcp/actions"; import { ShareableActions, type Visibility } from "./shareable-actions"; import type { MCPServerInfo, MCPToolInfo } from "app-types/mcp"; import { ToolDetailPopup } from "./tool-detail-popup"; import { useTranslations } from "next-intl"; import { Separator } from "ui/separator"; import { Avatar, AvatarFallback, AvatarImage } from "ui/avatar"; import { appStore } from "@/app/store"; import { isString } from "lib/utils"; import { redriectMcpOauth } from "lib/ai/mcp/oauth-redirect"; import { BasicUser } from "app-types/user"; import { canChangeVisibilityMCP } from "lib/auth/client-permissions"; // Main MCPCard component export const MCPCard = memo(function MCPCard({ id, config, error, status, name, toolInfo, visibility, enabled, userId, user, userName, userAvatar, }: MCPServerInfo & { user: BasicUser }) { const [isProcessing, setIsProcessing] = useState(false); const [visibilityChangeLoading, setVisibilityChangeLoading] = useState(false); const t = useTranslations("MCP"); const appStoreMutate = appStore((state) => state.mutate); const { mutate } = useSWRConfig(); const isOwner = userId === user?.id; const canChangeVisibility = useMemo( () => canChangeVisibilityMCP(user?.role), [user?.role], ); const isLoading = useMemo(() => { return isProcessing || status === "loading"; }, [isProcessing, status]); const needsAuthorization = status === "authorizing"; const isDisabled = isLoading || needsAuthorization; // Check permissions (kept for potential future use) const errorMessage = useMemo(() => { if (error) { return isString(error) ? error : JSON.stringify(error); } return null; }, [error]); const pipeProcessing = useCallback( async (fn: () => Promise) => safe(() => setIsProcessing(true)) .ifOk(fn) .ifOk(() => mutate("/api/mcp/list")) .ifFail(handleErrorWithToast) .watch(() => setIsProcessing(false)), [], ); const handleRefresh = useCallback( () => pipeProcessing(() => refreshMcpClientAction(id)), [id], ); const handleDelete = useCallback(async () => { await pipeProcessing(() => removeMcpClientAction(id)); }, [id]); const handleAuthorize = useCallback( () => pipeProcessing(() => redriectMcpOauth(id)), [id], ); const handleVisibilityChange = useCallback( async (newVisibility: Visibility) => { // Map visibility for MCP (public becomes featured) const mcpVisibility = newVisibility === "public" ? "public" : "private"; safe(() => setVisibilityChangeLoading(true)) .map(async () => shareMcpServerAction(id, mcpVisibility)) .ifOk(() => { mutate("/api/mcp/list"); }) .ifFail((e) => { handleErrorWithToast(e); }) .watch(() => setVisibilityChangeLoading(false)); }, [id], ); return ( {isLoading && (
)} {isLoading && }

{name}

{needsAuthorization && ( <>

Authorize

)}

{t("mcpServerCustomization")}

{isDisabled ? (
) : ( )}

{t("toolsTest")}

{t("refresh")}

{/* Add sharing actions for owners or visibility indicator for featured servers */} null} /> {/* Show user info for featured servers */} {!isOwner && userName && ( <>
{userName[0]?.toUpperCase()} {userName}
)} {errorMessage && } {needsAuthorization && (
{ if (e.key === "Enter" || e.key === " ") { e.preventDefault(); handleAuthorize(); } }} > Authorization Required Click here to authorize this MCP server and access its tools.
)}
{/* Only show config to owners to prevent credential exposure */} {isOwner && config && (
{t("configuration")}
)}
{t("availableTools")}
{toolInfo.length > 0 ? ( ) : (

{t("noToolsAvailable")}

)}
); }); // Tools list component const ToolsList = memo( ({ tools, serverId }: { tools: MCPToolInfo[]; serverId: string }) => (
{tools.map((tool) => (

{tool.name}

{tool.description}

))}
), ); ToolsList.displayName = "ToolsList"; // Error alert component const ErrorAlert = memo(({ error }: { error: string }) => (
Error {error}
)); ErrorAlert.displayName = "ErrorAlert";