"use client"
import { useState, useEffect } from "react"
import {
useQueryState,
parseAsString,
parseAsStringLiteral,
parseAsInteger,
} from "nuqs"
import { Button } from "@ui/components/button"
import { CopyIcon, Check, ChevronRight } from "lucide-react"
import Image from "next/image"
import { toast } from "sonner"
import { analytics } from "@/lib/analytics"
import { cn } from "@lib/utils"
import { dmMonoClassName, dmSansClassName } from "@/lib/fonts"
import { SyncLogoIcon } from "@ui/assets/icons"
import {
defaultMcpSetupTab,
getMcpClientSetup,
mcpClientSetupShowsTabs,
mcpClientShowsManual,
mcpClientShowsOneClick,
setupInstructionsSubtitle,
} from "@/lib/mcp-client-setup"
import {
buildMcpUrlRemoteJson,
CHATGPT_REMOTE_MCP_URL,
CLAUDE_DESKTOP_MCP_SNIPPET,
getManualInstallEntry,
} from "@/lib/mcp-manual-instructions"
import { ClaudeDesktopManualTimeline } from "@/components/mcp-modal/claude-desktop-manual-timeline"
import { INSET } from "@/components/integrations/install-steps"
function McpCodeBlock({
code,
multiline,
onCopy,
}: {
code: string
multiline?: boolean
onCopy?: () => void
}) {
const [copied, setCopied] = useState(false)
const copy = () => {
navigator.clipboard.writeText(code)
setCopied(true)
toast.success("Copied to clipboard!")
setTimeout(() => setCopied(false), 2000)
onCopy?.()
}
return (
{code}
{!multiline && (
)}
{copied ? (
) : (
)}
)
}
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
type ClientKey = keyof typeof clients
const MCP_CATEGORIES: {
label: string
items: { key: ClientKey; title: string; subtitle: string }[]
}[] = [
{
label: "OpenAI",
items: [
{
key: "chatgpt",
title: "ChatGPT",
subtitle: "Developer mode · Apps",
},
{ key: "codex", title: "Codex", subtitle: "CLI" },
],
},
{
label: "Claude",
items: [
{ key: "claude", title: "Claude Desktop", subtitle: "Desktop app" },
{ key: "claude-code", title: "Claude Code", subtitle: "CLI" },
],
},
{
label: "IDEs",
items: [
{ key: "antigravity", title: "Antigravity", subtitle: "AI IDE" },
{ key: "cursor", title: "Cursor", subtitle: "AI IDE" },
{ key: "vscode", title: "VS Code", subtitle: "IDE" },
{ key: "cline", title: "Cline", subtitle: "VS Code extension" },
{ key: "gemini-cli", title: "Gemini CLI", subtitle: "Terminal" },
],
},
{
label: "MCP",
items: [{ key: "mcp-url", title: "MCP URL", subtitle: "Custom clients" }],
},
]
function clientIconSrc(key: ClientKey) {
if (key === "mcp-url") return "/mcp-icon.svg"
if (key === "antigravity") return "/mcp-supported-tools/antigravity.png"
const file = key === "claude-code" ? "claude" : key
return `/mcp-supported-tools/${file}.png`
}
function ClientToolIcon({
clientKey,
title,
}: {
clientKey: ClientKey
title: string
}) {
const [failed, setFailed] = useState(false)
const src = clientIconSrc(clientKey)
return (
{failed ? (
{title.slice(0, 2).toUpperCase()}
) : (
setFailed(true)}
/>
)}
)
}
interface MCPStepsProps {
variant?: "full" | "embedded"
}
export function MCPSteps({ variant = "full" }: MCPStepsProps) {
const [selectedClient, setSelectedClient] = useQueryState(
"mcpClient",
parseAsString,
)
const [selectedProject] = useState("sm_project_default")
const [setupTab, setSetupTab] = useQueryState(
"mcpTab",
parseAsStringLiteral(["oneClick", "manual"] as const).withDefault("manual"),
)
const [isManualCopied, setIsManualCopied] = useState(false)
const [activeStep, setActiveStep] = useQueryState(
"mcpStep",
parseAsInteger.withDefault(1),
)
useEffect(() => {
analytics.mcpViewOpened()
}, [])
useEffect(() => {
if (!selectedClient) return
const s = getMcpClientSetup(selectedClient)
if (!s.oneClick && setupTab === "oneClick") setSetupTab("manual")
if (!s.manual && setupTab === "manual") setSetupTab("oneClick")
}, [selectedClient, setupTab, setSetupTab])
function getCursorDeeplink() {
return "cursor://anysphere.cursor-deeplink/mcp/install?name=supermemory&config=eyJ1cmwiOiJodHRwczovL2FwaS5zdXBlcm1lbW9yeS5haS9tY3AifQ%3D%3D"
}
function getMcpServerUrl() {
return "https://mcp.supermemory.ai/mcp"
}
function getMcpConfigSnippet() {
const projectId = selectedProject.replace(/^sm_project_/, "")
const config: {
mcpServers: {
supermemory: {
url: string
headers?: { "x-sm-project": string }
}
}
} = {
mcpServers: {
supermemory: {
url: getMcpServerUrl(),
},
},
}
if (projectId && projectId !== "default" && selectedProject !== "none") {
config.mcpServers.supermemory.headers = {
"x-sm-project": projectId,
}
}
return JSON.stringify(config, null, 2)
}
const copyManualSnippet = (text: string) => {
navigator.clipboard.writeText(text)
analytics.mcpInstallCmdCopied()
toast.success("Copied to clipboard!")
setIsManualCopied(true)
setActiveStep(3)
setTimeout(() => setIsManualCopied(false), 2000)
}
const isEmbedded = variant === "embedded"
const isValidClient = Boolean(selectedClient && selectedClient in clients)
const selectedKey = isValidClient ? (selectedClient as ClientKey) : null
const detailSetup =
selectedKey != null ? getMcpClientSetup(selectedKey) : null
const effectiveSetupTab: "manual" | "oneClick" =
detailSetup == null
? "manual"
: !detailSetup.manual
? "oneClick"
: !detailSetup.oneClick
? "manual"
: setupTab
return (
{!isValidClient || selectedKey == null ? (
{MCP_CATEGORIES.map((category, rowIndex) => (
0 && "border-t border-white/[0.06]",
)}
>
{category.items.map((item) => (
{
setSelectedClient(item.key)
setSetupTab(
defaultMcpSetupTab(getMcpClientSetup(item.key)),
)
setActiveStep(2)
}}
className={cn(
"group flex w-full min-w-0 items-center gap-3 rounded-[12px] bg-[#0D121A] p-3.5 text-left transition-colors",
"shadow-[inset_1.5px_1.5px_4.5px_rgba(0,0,0,0.5)] hover:bg-[#10151D]",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#4BA0FA]/40",
)}
>
{item.title}
{item.subtitle}
))}
))}
You can connect several clients; setup is different for each one.
) : (
{!isEmbedded && (
<>
{
setSelectedClient(null)
setActiveStep(1)
}}
className="text-sm font-medium text-[#A1A1AA] transition-colors hover:text-[#FAFAFA]"
>
← All clients
{clients[selectedKey]}
{detailSetup ? setupInstructionsSubtitle(detailSetup) : ""}
>
)}
{detailSetup && mcpClientSetupShowsTabs(detailSetup) ? (
setSetupTab("manual")}
role="tab"
type="button"
aria-selected={effectiveSetupTab === "manual"}
>
Manual instructions
setSetupTab("oneClick")}
role="tab"
type="button"
aria-selected={effectiveSetupTab === "oneClick"}
>
One click setup
) : null}
{detailSetup &&
mcpClientShowsOneClick(detailSetup, effectiveSetupTab) ? (
<>
{selectedClient === "mcp-url" && (
Paste this URL into any MCP client that supports remote
servers over HTTPS.
{
analytics.mcpInstallCmdCopied()
toast.success("Copied to clipboard!")
setActiveStep(3)
}}
/>
)}
{selectedClient === "cursor" && (
Open the link below to add supermemory in Cursor, or use
Manual instructions to edit{" "}
~/.cursor/mcp.json
.
)}
{selectedClient !== "mcp-url" &&
selectedClient !== "cursor" && (
Add this remote MCP server URL in{" "}
{clients[selectedKey]}. Your client will prompt for
OAuth when you connect.
{
analytics.mcpInstallCmdCopied()
setActiveStep(3)
}}
/>
Or merge this into your client's MCP config:
{
analytics.mcpInstallCmdCopied()
setActiveStep(3)
}}
/>
)}
>
) : (
(() => {
const manual = getManualInstallEntry(selectedKey)
if (manual.kind === "chatgpt") {
return (
Write-capable custom MCP apps are only supported on
Business & Enterprise plans.
Open ChatGPT in your browser.
Go to Settings → Security and Login → scroll to the
bottom to enable Developer mode.
Go to{" "}
chatgpt.com/plugins
.
Paste the URL below.
Complete OAuth in ChatGPT.
{
analytics.mcpInstallCmdCopied()
setActiveStep(3)
}}
/>
Developer mode docs (OpenAI)
)
}
if (manual.kind === "claude-desktop-timeline") {
return (
copyManualSnippet(CLAUDE_DESKTOP_MCP_SNIPPET)
}
snippetCopied={isManualCopied}
variant="detail"
/>
)
}
if (manual.kind === "generic-remote") {
const snippet = buildMcpUrlRemoteJson()
return (
Add this to your client's MCP config. Your client
will open Supermemory OAuth when it first connects.
{
analytics.mcpInstallCmdCopied()
setActiveStep(3)
}}
/>
)
}
return (
{manual.paths}
Merge the snippet with your existing file (create it if
needed). Restart the client and complete OAuth when
prompted.
{
analytics.mcpInstallCmdCopied()
setActiveStep(3)
}}
/>
)
})()
)}
{!isEmbedded && (
{detailSetup != null &&
mcpClientShowsManual(detailSetup, effectiveSetupTab)
? selectedClient === "chatgpt"
? "Finish in ChatGPT"
: selectedClient === "claude"
? "Finish in Claude Desktop"
: "Save and restart"
: selectedClient === "cursor"
? "Finish in Cursor"
: "Run in terminal"}
{activeStep === 3 && (
{detailSetup != null &&
mcpClientShowsManual(detailSetup, effectiveSetupTab)
? selectedClient === "chatgpt"
? "Complete app setup in ChatGPT, then enable it for your chat."
: selectedClient === "claude"
? "After Connectors, complete any prompts so supermemory is enabled for chat."
: "Save your config, restart the client, and sign in if prompted."
: selectedClient === "cursor"
? "Complete the install in Cursor and sign in with OAuth if asked."
: "Waiting for installation"}
)}
)}
)}
)
}
interface MCPDetailViewProps {
onBack: () => void
}
export function MCPDetailView({ onBack }: MCPDetailViewProps) {
return (
← Back
Connect Supermemory MCP to your AI Tools
Connect Cursor, Claude, VS Code, and more via MCP.
)
}