"use client" import { $fetch } from "@lib/api" import { authClient } from "@lib/auth" import { useAuth } from "@lib/auth-context" import { generateId } from "@lib/generate-id" 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 = { 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 { org } = useAuth() 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 [manualApiKey, setManualApiKey] = useState(null) 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", }) }, }) const createMcpApiKeyMutation = useMutation({ mutationFn: async () => { if (!org?.id) { throw new Error("Organization ID is required") } const res = await authClient.apiKey.create({ metadata: { organizationId: org?.id, type: "mcp-manual", }, name: `mcp-manual-${generateId().slice(0, 8)}`, prefix: `sm_${org?.id}_`, }) return res.key }, onSuccess: (apiKey) => { setManualApiKey(apiKey) toast.success("API key created successfully!") }, onError: (error) => { toast.error("Failed to create API key", { 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]) useEffect(() => { if (selectedClient !== "mcp-url" || setupTab !== "manual" || !org?.id) return if (manualApiKey || createMcpApiKeyMutation.isPending) return createMcpApiKeyMutation.mutate() }, [ selectedClient, setupTab, org?.id, manualApiKey, createMcpApiKeyMutation.isPending, createMcpApiKeyMutation.mutate, ]) function generateInstallCommand() { if (!selectedClient || selectedClient === "chatgpt") return "" let command = `npx -y install-mcp@latest https://mcp.supermemory.ai/mcp --client ${selectedClient} --oauth=yes` if (selectedProject && selectedProject !== "none") { // Remove the "sm_project_" prefix from the containerTag const projectIdForCommand = selectedProject.replace(/^sm_project_/, "") command += ` --project ${projectIdForCommand}` } return command } function getCursorDeeplink() { return "cursor://anysphere.cursor-deeplink/mcp/install?name=supermemory&config=eyJ1cmwiOiJodHRwczovL2FwaS5zdXBlcm1lbW9yeS5haS9tY3AifQ%3D%3D" } const copyToClipboard = () => { const command = generateInstallCommand() navigator.clipboard.writeText(command) 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") if ( !manualApiKey && !createMcpApiKeyMutation.isPending ) { createMcpApiKeyMutation.mutate() } }} />
)}
)}
{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" && (

Optional: scope installs to a project. Then copy and run the command in your terminal.

Requires Node/npx. OAuth runs when the CLI prompts you.

)} ) : ( (() => { const manual = getManualInstallEntry(selectedClient) if (manual.kind === "chatgpt") { return (
  1. Open ChatGPT in your browser.
  2. Settings → Apps → Advanced settings → enable Developer mode.
  3. Create an app and paste the MCP URL when asked.
  4. Complete OAuth in ChatGPT.
Developer mode docs (OpenAI)
) } if (manual.kind === "claude-desktop-timeline") { return ( copyManualSnippet(CLAUDE_DESKTOP_MCP_SNIPPET) } snippetCopied={isCopied} variant="modal" /> ) } if (manual.kind === "generic-remote") { const remoteSnippet = buildMcpUrlRemoteJson( manualApiKey || "your-api-key-here", ) return (

Paste into your MCP config. We create an API key for you when you open this tab; copy the block after it appears.

{createMcpApiKeyMutation.isPending ? (
) : ( <>
																	
																		{remoteSnippet}
																	
																

Bearer token uses your supermemory API key.

)}
) } return (

{manual.paths}

Merge the snippet with your existing config. Restart the client and sign in with OAuth when prompted.

														
															{manual.snippet}
														
													
) })() )}
)} {!selectedClient && (
2

Select a client for setup instructions

)}

Use this URL to configure supermemory in your AI assistant

Connection Status

{isCheckingConnection ? ( <> Checking… ) : connectionStatus?.previousLogin ? ( <>
Connected ) : ( <>
Waiting for connection… )}

What You Can Do

  • • Ask your AI to save important information as memories
  • • Search through your saved memories during conversations
  • • Get contextual information from your knowledge base
{/* Migration Dialog */} {isMigrateDialogOpen && (
Migrate from MCP v1 Migrate your MCP documents from the legacy system.
{ e.preventDefault() e.stopPropagation() mcpMigrationForm.handleSubmit() }} >
{({ state, handleChange, handleBlur }) => ( <> , ) => handleChange(e.target.value)} placeholder="https://mcp.supermemory.ai/your-user-id/sse" value={state.value} /> {state.meta.errors.length > 0 && (

{state.meta.errors.join(", ")}

)} )}

Enter your old MCP Link in the format:
https://mcp.supermemory.ai/userId/sse

)}
) }