"use client" import { $fetch } from "@lib/api" import { useForm } from "@tanstack/react-form" import { useMutation, useQuery } from "@tanstack/react-query" import { Button } from "@ui/components/button" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "@ui/components/dialog" import { Input } from "@ui/components/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@ui/components/select" import { CopyableCell } from "@ui/copyable-cell" import { CheckIcon, CopyIcon, ExternalLink, Loader2 } from "lucide-react" import Image from "next/image" import { useEffect, useState } from "react" import { toast } from "sonner" import { z } from "zod/v4" import { analytics } from "@/lib/analytics" import { defaultMcpSetupTab, getMcpClientSetup, mcpClientSetupShowsTabs, mcpClientShowsOneClick, resolveMcpSetupTabForClient, } from "@/lib/mcp-client-setup" import { ClaudeDesktopManualTimeline } from "@/components/mcp-modal/claude-desktop-manual-timeline" import { buildMcpUrlRemoteJson, CHATGPT_REMOTE_MCP_URL, CLAUDE_DESKTOP_MCP_SNIPPET, getManualInstallEntry, } from "@/lib/mcp-manual-instructions" import { cn } from "@lib/utils" import type { Project } from "@lib/types" import { motion, AnimatePresence } from "motion/react" const clients = { antigravity: "Antigravity", chatgpt: "ChatGPT", codex: "Codex", cursor: "Cursor", claude: "Claude Desktop", vscode: "VSCode", cline: "Cline", "gemini-cli": "Gemini CLI", "claude-code": "Claude Code", "mcp-url": "MCP URL", } as const const mcpMigrationSchema = z.object({ url: z .string() .min(1, "MCP Link is required") .regex( /^https:\/\/mcp\.supermemory\.ai\/[^/]+\/sse$/, "Link must be in format: https://mcp.supermemory.ai/userId/sse", ), }) interface ConnectAIModalProps { children: React.ReactNode open?: boolean onOpenChange?: (open: boolean) => void openInitialClient?: "mcp-url" | null openInitialTab?: "oneClick" | "manual" | null } interface ManualMCPHelpLinkProps { onClick: () => void } function ManualMCPHelpLink({ onClick }: ManualMCPHelpLinkProps) { const [isHovered, setIsHovered] = useState(false) return ( ) } export function ConnectAIModal({ children, open, onOpenChange, openInitialClient, openInitialTab, }: ConnectAIModalProps) { const [selectedClient, setSelectedClient] = useState< keyof typeof clients | null >(openInitialClient || null) const [internalIsOpen, setInternalIsOpen] = useState(false) const isOpen = open !== undefined ? open : internalIsOpen const setIsOpen = onOpenChange || setInternalIsOpen const [isMigrateDialogOpen, setIsMigrateDialogOpen] = useState(false) const [selectedProject, setSelectedProject] = useState("none") const [setupTab, setSetupTab] = useState<"oneClick" | "manual">( openInitialTab ?? "manual", ) const [isCopied, setIsCopied] = useState(false) const [projectId, setProjectId] = useState("default") useEffect(() => { if (typeof window !== "undefined") { const storedProjectId = localStorage.getItem("selectedProject") ?? "default" setProjectId(storedProjectId) } }, []) useEffect(() => { analytics.mcpViewOpened() }, []) const { data: projects = [], isLoading: isLoadingProjects } = useQuery({ queryKey: ["projects"], queryFn: async () => { const response = await $fetch("@get/projects") if (response.error) { throw new Error(response.error?.message || "Failed to load projects") } return (response.data?.projects || []) as Project[] }, staleTime: 30 * 1000, }) const { data: connectionStatus, isLoading: isCheckingConnection } = useQuery({ queryKey: ["mcp-connection"], queryFn: async () => { const response = await $fetch("@get/mcp/has-login") if (response.error) { throw new Error(response.error?.message || "Failed to check connection") } return response.data }, refetchInterval: 5000, }) const mcpMigrationForm = useForm({ defaultValues: { url: "" }, onSubmit: async ({ value, formApi }) => { const userId = extractUserIdFromMCPUrl(value.url) if (userId) { migrateMCPMutation.mutate({ userId, projectId }) formApi.reset() } }, validators: { onChange: mcpMigrationSchema, }, }) const extractUserIdFromMCPUrl = (url: string): string | null => { const regex = /^https:\/\/mcp\.supermemory\.ai\/([^/]+)\/sse$/ const match = url.trim().match(regex) return match?.[1] || null } const migrateMCPMutation = useMutation({ mutationFn: async ({ userId, projectId, }: { userId: string projectId: string }) => { const response = await $fetch("@post/documents/migrate-mcp", { body: { userId, projectId }, }) if (response.error) { throw new Error( response.error?.message || "Failed to migrate documents", ) } return response.data }, onSuccess: (data) => { toast.success("Migration completed!", { description: `Successfully migrated ${data?.migratedCount} documents`, }) setIsMigrateDialogOpen(false) }, onError: (error) => { toast.error("Migration failed", { description: error instanceof Error ? error.message : "Unknown error", }) }, }) useEffect(() => { if (openInitialClient) { setSelectedClient(openInitialClient as keyof typeof clients) setSetupTab( resolveMcpSetupTabForClient(openInitialClient, openInitialTab), ) } }, [openInitialClient, openInitialTab]) useEffect(() => { if (!selectedClient) return const s = getMcpClientSetup(selectedClient) if (!s.oneClick && setupTab === "oneClick") setSetupTab("manual") if (!s.manual && setupTab === "manual") setSetupTab("oneClick") }, [selectedClient, setupTab]) function getMcpServerUrl() { return "https://mcp.supermemory.ai/mcp" } function getMcpConfigSnippet() { const config: { mcpServers: { supermemory: { url: string headers?: { "x-sm-project": string } } } } = { mcpServers: { supermemory: { url: getMcpServerUrl(), }, }, } if (selectedProject && selectedProject !== "none") { const projectIdForCommand = selectedProject.replace(/^sm_project_/, "") if (projectIdForCommand) { config.mcpServers.supermemory.headers = { "x-sm-project": projectIdForCommand, } } } return JSON.stringify(config, null, 2) } function getCursorDeeplink() { return "cursor://anysphere.cursor-deeplink/mcp/install?name=supermemory&config=eyJ1cmwiOiJodHRwczovL2FwaS5zdXBlcm1lbW9yeS5haS9tY3AifQ%3D%3D" } const copyToClipboard = () => { navigator.clipboard.writeText(getMcpServerUrl()) analytics.mcpInstallCmdCopied() toast.success("Copied to clipboard!") } const copyConfigSnippet = () => { navigator.clipboard.writeText(getMcpConfigSnippet()) analytics.mcpInstallCmdCopied() toast.success("Copied to clipboard!") } const copyManualSnippet = (text: string) => { navigator.clipboard.writeText(text) analytics.mcpInstallCmdCopied() toast.success("Copied to clipboard!") setIsCopied(true) setTimeout(() => setIsCopied(false), 2000) } const clientSetup = selectedClient ? getMcpClientSetup(selectedClient) : null const effectiveSetupTab: "manual" | "oneClick" = clientSetup == null ? "manual" : !clientSetup.manual ? "oneClick" : !clientSetup.oneClick ? "manual" : setupTab return ( {children} Connect supermemory to Your AI Enable your AI assistant to create, search, and access your memories directly using the Model Context Protocol (MCP).
{/* Step 1: Client Selection */}
1

Select Your AI Client

{Object.entries(clients).map(([key, clientName]) => ( ))}
{selectedClient && (
2
{((clientSetup && mcpClientSetupShowsTabs(clientSetup)) || selectedClient !== "mcp-url") && (
{clientSetup && mcpClientSetupShowsTabs(clientSetup) ? (
) : null} {selectedClient !== "mcp-url" && (
{ setSelectedClient("mcp-url") setSetupTab("manual") }} />
)}
)}
{clientSetup && mcpClientShowsOneClick(clientSetup, effectiveSetupTab) ? ( <> {selectedClient === "cursor" && (

Open Cursor and add supermemory in one step, or switch to Manual instructions to edit{" "} mcp.json yourself.

{ analytics.mcpInstallCmdCopied() toast.success("Opening Cursor installer…") }} > Add Supermemory MCP server to Cursor

Alternatively, pick another client and use the install command, or use Manual instructions for a JSON snippet.

)} {selectedClient === "mcp-url" && (

Paste this URL into clients that support remote MCP over HTTPS (OAuth).

)} {selectedClient !== "cursor" && selectedClient !== "mcp-url" && (

Add this remote MCP server URL in your client. Optional: scope to a project for the config snippet.