feat: mcp modal with better instructions (#836)
feat: mcp modal with better instructions add docs screenshots and claude setup
|
|
@ -135,7 +135,15 @@
|
|||
{
|
||||
"anchor": "Supermemory MCP",
|
||||
"icon": "terminal",
|
||||
"pages": ["supermemory-mcp/mcp", "supermemory-mcp/setup"]
|
||||
"pages": [
|
||||
"supermemory-mcp/mcp",
|
||||
"supermemory-mcp/setup",
|
||||
{
|
||||
"group": "Setups",
|
||||
"icon": "layers",
|
||||
"pages": ["supermemory-mcp/claude-desktop"]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tab": "Developer Platform"
|
||||
|
|
|
|||
BIN
apps/docs/images/supermemory-mcp/claude-desktop/step-1.png
Normal file
|
After Width: | Height: | Size: 637 KiB |
BIN
apps/docs/images/supermemory-mcp/claude-desktop/step-2.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
apps/docs/images/supermemory-mcp/claude-desktop/step-3.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
apps/docs/images/supermemory-mcp/claude-desktop/step-4.png
Normal file
|
After Width: | Height: | Size: 870 KiB |
65
apps/docs/supermemory-mcp/claude-desktop.mdx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
title: "Claude Desktop"
|
||||
description: "Manual setup for supermemory MCP in Claude Desktop with step-by-step screenshots"
|
||||
icon: "monitor"
|
||||
sidebarTitle: "Claude Desktop"
|
||||
---
|
||||
|
||||
This guide walks through the **manual** install for [Claude Desktop](https://claude.ai/download): edit `claude_desktop_config.json`, add the supermemory server, then finish in **Connectors**. For a one-line CLI install instead, see [Setup and Usage](/supermemory-mcp/setup).
|
||||
|
||||
<Note>
|
||||
Config file location: **macOS** `~/Library/Application Support/Claude/claude_desktop_config.json` · **Windows** `%APPDATA%\Claude\claude_desktop_config.json`
|
||||
</Note>
|
||||
|
||||
## Step 1 — Copy the configuration
|
||||
|
||||
Copy the supermemory block below. You will paste it inside `mcpServers` in a later step.
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"supermemory": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"mcp-remote@latest",
|
||||
"https://mcp.supermemory.ai/mcp"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is the same configuration shown in the supermemory dashboard when you choose **Claude Desktop** for MCP setup.
|
||||
|
||||
## Step 2 — Open Developer settings and Edit Config
|
||||
|
||||
In Claude Desktop, open **Settings → Developer**, then click **Edit Config**.
|
||||
|
||||

|
||||
|
||||
## Step 3 — Open claude_desktop_config.json
|
||||
|
||||
When `claude_desktop_config.json` opens in your editor, keep it ready for the next step.
|
||||
|
||||

|
||||
|
||||
## Step 4 — Paste under mcpServers and save
|
||||
|
||||
Paste what you copied under `mcpServers` (merge with existing servers if the file already has some), then save.
|
||||
|
||||

|
||||
|
||||
## Step 5 — Restart and configure in Connectors
|
||||
|
||||
Restart Claude Desktop. Open **Settings → Connectors**, find **supermemory**, and click **Configure**.
|
||||
|
||||

|
||||
|
||||
## Step 6 — Done
|
||||
|
||||
supermemory is installed in your Claude Desktop and ready to use.
|
||||
|
||||
---
|
||||
|
||||
**See also:** [Overview](/supermemory-mcp/mcp) · [Setup and Usage](/supermemory-mcp/setup)
|
||||
|
|
@ -70,17 +70,7 @@ This keeps memories organized by project, useful when working on multiple codeba
|
|||
|
||||
### Claude Desktop
|
||||
|
||||
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"supermemory": {
|
||||
"url": "https://mcp.supermemory.ai/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
For a screenshot-backed walkthrough (Developer → Edit Config, `claude_desktop_config.json`, Connectors), see **[Claude Desktop](/supermemory-mcp/claude-desktop)**. The recommended one-line install at the top of this page also supports Claude.
|
||||
|
||||
### Cursor
|
||||
|
||||
|
|
|
|||
|
|
@ -30,11 +30,27 @@ 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",
|
||||
|
|
@ -42,9 +58,6 @@ const clients = {
|
|||
"gemini-cli": "Gemini CLI",
|
||||
"claude-code": "Claude Code",
|
||||
"mcp-url": "MCP URL",
|
||||
"roo-cline": "Roo Cline",
|
||||
witsy: "Witsy",
|
||||
enconvo: "Enconvo",
|
||||
} as const
|
||||
|
||||
const mcpMigrationSchema = z.object({
|
||||
|
|
@ -125,11 +138,8 @@ export function ConnectAIModal({
|
|||
const setIsOpen = onOpenChange || setInternalIsOpen
|
||||
const [isMigrateDialogOpen, setIsMigrateDialogOpen] = useState(false)
|
||||
const [selectedProject, setSelectedProject] = useState<string | null>("none")
|
||||
const [cursorInstallTab, setCursorInstallTab] = useState<
|
||||
"oneClick" | "manual"
|
||||
>("oneClick")
|
||||
const [mcpUrlTab, setMcpUrlTab] = useState<"oneClick" | "manual">(
|
||||
openInitialTab || "oneClick",
|
||||
const [setupTab, setSetupTab] = useState<"oneClick" | "manual">(
|
||||
openInitialTab ?? "manual",
|
||||
)
|
||||
const [manualApiKey, setManualApiKey] = useState<string | null>(null)
|
||||
const [isCopied, setIsCopied] = useState(false)
|
||||
|
|
@ -252,21 +262,38 @@ export function ConnectAIModal({
|
|||
},
|
||||
})
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies(createMcpApiKeyMutation.mutate): we need to mutate the mutation
|
||||
useEffect(() => {
|
||||
if (openInitialClient) {
|
||||
setSelectedClient(openInitialClient as keyof typeof clients)
|
||||
if (openInitialTab) {
|
||||
setMcpUrlTab(openInitialTab)
|
||||
if (org?.id) {
|
||||
createMcpApiKeyMutation.mutate()
|
||||
}
|
||||
}
|
||||
setSetupTab(
|
||||
resolveMcpSetupTabForClient(openInitialClient, openInitialTab),
|
||||
)
|
||||
}
|
||||
}, [openInitialClient, openInitialTab, org?.id])
|
||||
}, [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) return ""
|
||||
if (!selectedClient || selectedClient === "chatgpt") return ""
|
||||
|
||||
let command = `npx -y install-mcp@latest https://mcp.supermemory.ai/mcp --client ${selectedClient} --oauth=yes`
|
||||
|
||||
|
|
@ -290,6 +317,24 @@ export function ConnectAIModal({
|
|||
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 (
|
||||
<Dialog onOpenChange={setIsOpen} open={isOpen}>
|
||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||
|
|
@ -313,414 +358,406 @@ export function ConnectAIModal({
|
|||
</div>
|
||||
|
||||
<div className="space-x-2 space-y-2">
|
||||
{Object.entries(clients)
|
||||
.slice(0, 7)
|
||||
.map(([key, clientName]) => (
|
||||
<button
|
||||
className={`pr-3 pl-1 rounded-full border cursor-pointer transition-all ${
|
||||
selectedClient === key
|
||||
? "border-primary bg-primary/10"
|
||||
: "border-border hover:border-border/60 hover:bg-muted/50"
|
||||
}`}
|
||||
key={key}
|
||||
onClick={() =>
|
||||
setSelectedClient(key as keyof typeof clients)
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-8 h-8 flex items-center justify-center">
|
||||
<Image
|
||||
alt={clientName}
|
||||
className="rounded object-contain"
|
||||
height={20}
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement
|
||||
target.style.display = "none"
|
||||
const parent = target.parentElement
|
||||
if (
|
||||
parent &&
|
||||
!parent.querySelector(".fallback-text")
|
||||
) {
|
||||
const fallback = document.createElement("span")
|
||||
fallback.className =
|
||||
"fallback-text text-sm font-bold text-muted-foreground"
|
||||
fallback.textContent = clientName
|
||||
.substring(0, 2)
|
||||
.toUpperCase()
|
||||
parent.appendChild(fallback)
|
||||
}
|
||||
}}
|
||||
src={
|
||||
key === "mcp-url"
|
||||
? "/mcp-icon.svg"
|
||||
: `/mcp-supported-tools/${key === "claude-code" ? "claude" : key}.png`
|
||||
{Object.entries(clients).map(([key, clientName]) => (
|
||||
<button
|
||||
className={`pr-3 pl-1 rounded-full border cursor-pointer transition-all ${
|
||||
selectedClient === key
|
||||
? "border-primary bg-primary/10"
|
||||
: "border-border hover:border-border/60 hover:bg-muted/50"
|
||||
}`}
|
||||
key={key}
|
||||
onClick={() => {
|
||||
setSelectedClient(key as keyof typeof clients)
|
||||
setSetupTab(defaultMcpSetupTab(getMcpClientSetup(key)))
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-8 h-8 flex items-center justify-center">
|
||||
<Image
|
||||
alt={clientName}
|
||||
className="rounded object-contain"
|
||||
height={20}
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement
|
||||
target.style.display = "none"
|
||||
const parent = target.parentElement
|
||||
if (
|
||||
parent &&
|
||||
!parent.querySelector(".fallback-text")
|
||||
) {
|
||||
const fallback = document.createElement("span")
|
||||
fallback.className =
|
||||
"fallback-text text-sm font-bold text-muted-foreground"
|
||||
fallback.textContent = clientName
|
||||
.substring(0, 2)
|
||||
.toUpperCase()
|
||||
parent.appendChild(fallback)
|
||||
}
|
||||
width={20}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-foreground/80">
|
||||
{clientName}
|
||||
</span>
|
||||
}}
|
||||
src={
|
||||
key === "mcp-url"
|
||||
? "/mcp-icon.svg"
|
||||
: `/mcp-supported-tools/${key === "claude-code" ? "claude" : key}.png`
|
||||
}
|
||||
width={20}
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
<span className="text-sm font-medium text-foreground/80">
|
||||
{clientName}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step 2: One-click Install for Cursor, Project Selection for others, or MCP URL */}
|
||||
{selectedClient && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-accent text-accent-foreground flex items-center justify-center text-sm font-semibold">
|
||||
2
|
||||
</div>
|
||||
<h3 className="text-sm font-medium">
|
||||
{selectedClient === "cursor"
|
||||
? "Install Supermemory MCP"
|
||||
: selectedClient === "mcp-url"
|
||||
? "MCP Server Configuration"
|
||||
: "Select Target Project (Optional)"}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
{selectedClient && selectedClient !== "mcp-url" && (
|
||||
<ManualMCPHelpLink
|
||||
onClick={() => {
|
||||
setSelectedClient("mcp-url")
|
||||
setMcpUrlTab("manual")
|
||||
if (
|
||||
!manualApiKey &&
|
||||
!createMcpApiKeyMutation.isPending
|
||||
) {
|
||||
createMcpApiKeyMutation.mutate()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"flex-col gap-2 hidden",
|
||||
(selectedClient === "cursor" ||
|
||||
selectedClient === "mcp-url") &&
|
||||
"flex",
|
||||
)}
|
||||
>
|
||||
{/* Tabs */}
|
||||
<div className="flex justify-end">
|
||||
<div className="flex bg-muted/50 rounded-full p-1 border border-border">
|
||||
<button
|
||||
className={`px-3 py-1.5 text-xs font-medium rounded-full transition-all ${
|
||||
(
|
||||
selectedClient === "cursor"
|
||||
? cursorInstallTab
|
||||
: mcpUrlTab
|
||||
) === "oneClick"
|
||||
? "bg-background text-foreground border border-border shadow-sm"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
onClick={() =>
|
||||
selectedClient === "cursor"
|
||||
? setCursorInstallTab("oneClick")
|
||||
: setMcpUrlTab("oneClick")
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
{selectedClient === "mcp-url"
|
||||
? "Quick Setup"
|
||||
: "One Click Install"}
|
||||
</button>
|
||||
<button
|
||||
className={`px-3 py-1.5 text-xs font-medium rounded-full transition-all ${
|
||||
(
|
||||
selectedClient === "cursor"
|
||||
? cursorInstallTab
|
||||
: mcpUrlTab
|
||||
) === "manual"
|
||||
? "bg-background text-foreground border border-border shadow-sm"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (selectedClient === "cursor") {
|
||||
setCursorInstallTab("manual")
|
||||
} else {
|
||||
setMcpUrlTab("manual")
|
||||
if (
|
||||
!manualApiKey &&
|
||||
!createMcpApiKeyMutation.isPending
|
||||
) {
|
||||
createMcpApiKeyMutation.mutate()
|
||||
}
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Manual Config
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex size-8 items-center justify-center rounded-full bg-accent text-sm font-semibold text-accent-foreground">
|
||||
2
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedClient === "cursor" ? (
|
||||
<div className="space-y-4">
|
||||
{/* Tab Content */}
|
||||
{cursorInstallTab === "oneClick" ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col items-center gap-4 p-6 border border-green-500/20 rounded-lg bg-green-500/5">
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-foreground/80 mb-2">
|
||||
Click the button below to automatically install and
|
||||
configure Supermemory in Cursor
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href={getCursorDeeplink()}
|
||||
onClick={() => {
|
||||
analytics.mcpInstallCmdCopied()
|
||||
toast.success("Opening Cursor installer...")
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt="Add Supermemory MCP server to Cursor"
|
||||
className="hover:opacity-80 transition-opacity cursor-pointer"
|
||||
height="40"
|
||||
src="https://cursor.com/deeplink/mcp-install-dark.svg"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
{((clientSetup && mcpClientSetupShowsTabs(clientSetup)) ||
|
||||
selectedClient !== "mcp-url") && (
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
|
||||
{clientSetup && mcpClientSetupShowsTabs(clientSetup) ? (
|
||||
<div
|
||||
className="flex w-full max-w-md flex-row gap-1 rounded-full border border-border bg-muted/50 p-1 sm:w-auto sm:min-w-[280px]"
|
||||
role="tablist"
|
||||
aria-label="Setup method"
|
||||
>
|
||||
<button
|
||||
className={cn(
|
||||
"min-h-9 flex-1 rounded-full px-3 py-2 text-center text-xs font-medium transition-all",
|
||||
effectiveSetupTab === "manual"
|
||||
? "border border-border bg-background text-foreground shadow-sm"
|
||||
: "text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
onClick={() => setSetupTab("manual")}
|
||||
role="tab"
|
||||
type="button"
|
||||
aria-selected={effectiveSetupTab === "manual"}
|
||||
>
|
||||
Manual instructions
|
||||
</button>
|
||||
<button
|
||||
className={cn(
|
||||
"min-h-9 flex-1 rounded-full px-3 py-2 text-center text-xs font-medium transition-all",
|
||||
effectiveSetupTab === "oneClick"
|
||||
? "border border-border bg-background text-foreground shadow-sm"
|
||||
: "text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
onClick={() => setSetupTab("oneClick")}
|
||||
role="tab"
|
||||
type="button"
|
||||
aria-selected={effectiveSetupTab === "oneClick"}
|
||||
>
|
||||
One click setup
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Choose a project and follow the installation steps below
|
||||
</p>
|
||||
<div className="max-w-md">
|
||||
<Select
|
||||
disabled={isLoadingProjects}
|
||||
onValueChange={setSelectedProject}
|
||||
value={selectedProject || "none"}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select project" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">
|
||||
Auto-select project
|
||||
</SelectItem>
|
||||
<SelectItem value="sm_project_default">
|
||||
Default Project
|
||||
</SelectItem>
|
||||
{projects
|
||||
.filter(
|
||||
(p: Project) =>
|
||||
p.containerTag !== "sm_project_default",
|
||||
)
|
||||
.map((project: Project) => (
|
||||
<SelectItem
|
||||
key={project.id}
|
||||
value={project.containerTag}
|
||||
>
|
||||
{project.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
) : null}
|
||||
{selectedClient !== "mcp-url" && (
|
||||
<div
|
||||
className={cn(
|
||||
"flex justify-end",
|
||||
!(
|
||||
clientSetup && mcpClientSetupShowsTabs(clientSetup)
|
||||
) && "w-full sm:justify-start",
|
||||
)}
|
||||
>
|
||||
<ManualMCPHelpLink
|
||||
onClick={() => {
|
||||
setSelectedClient("mcp-url")
|
||||
setSetupTab("manual")
|
||||
if (
|
||||
!manualApiKey &&
|
||||
!createMcpApiKeyMutation.isPending
|
||||
) {
|
||||
createMcpApiKeyMutation.mutate()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : selectedClient === "mcp-url" ? (
|
||||
<div className="space-y-4">
|
||||
{mcpUrlTab === "oneClick" ? (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Use this URL to quickly configure supermemory in your AI
|
||||
assistant
|
||||
</p>
|
||||
<div className="relative">
|
||||
<Input
|
||||
className="font-mono text-xs w-full pr-10"
|
||||
readOnly
|
||||
value="https://mcp.supermemory.ai/mcp"
|
||||
/>
|
||||
<Button
|
||||
className="absolute top-[-1px] right-0 cursor-pointer"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
"https://mcp.supermemory.ai/mcp",
|
||||
)
|
||||
analytics.mcpInstallCmdCopied()
|
||||
toast.success("Copied to clipboard!")
|
||||
}}
|
||||
variant="ghost"
|
||||
>
|
||||
<CopyIcon className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Add this configuration to your MCP settings file with
|
||||
authentication
|
||||
</p>
|
||||
{createMcpApiKeyMutation.isPending ? (
|
||||
<div className="flex items-center justify-center p-8">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-primary" />
|
||||
)}
|
||||
<div className="min-w-0 space-y-4">
|
||||
{clientSetup &&
|
||||
mcpClientShowsOneClick(clientSetup, effectiveSetupTab) ? (
|
||||
<>
|
||||
{selectedClient === "cursor" && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col items-center gap-4 rounded-lg border border-green-500/20 bg-green-500/5 p-6">
|
||||
<p className="text-center text-sm text-foreground/80">
|
||||
Open Cursor and add supermemory in one step, or
|
||||
switch to Manual instructions to edit{" "}
|
||||
<code className="text-xs">mcp.json</code> yourself.
|
||||
</p>
|
||||
<a
|
||||
href={getCursorDeeplink()}
|
||||
onClick={() => {
|
||||
analytics.mcpInstallCmdCopied()
|
||||
toast.success("Opening Cursor installer...")
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt="Add Supermemory MCP server to Cursor"
|
||||
className="cursor-pointer transition-opacity hover:opacity-80"
|
||||
height="40"
|
||||
src="https://cursor.com/deeplink/mcp-install-dark.svg"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="relative">
|
||||
<pre className="bg-muted border border-border rounded-lg p-4 pr-12 text-xs overflow-x-auto max-w-full">
|
||||
<code className="font-mono block whitespace-pre-wrap break-all">
|
||||
{`{
|
||||
"supermemory-mcp": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "mcp-remote", "https://mcp.supermemory.ai/mcp"],
|
||||
"env": {},
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${manualApiKey || "your-api-key-here"}"
|
||||
}
|
||||
}
|
||||
}`}
|
||||
</code>
|
||||
</pre>
|
||||
<Button
|
||||
className="absolute top-2 right-2 cursor-pointer h-8 w-8 p-0 bg-muted/80 hover:bg-muted"
|
||||
onClick={() => {
|
||||
const config = `{
|
||||
"supermemory-mcp": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "mcp-remote", "https://mcp.supermemory.ai/mcp"],
|
||||
"env": {},
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${manualApiKey || "your-api-key-here"}"
|
||||
}
|
||||
}
|
||||
}`
|
||||
navigator.clipboard.writeText(config)
|
||||
analytics.mcpInstallCmdCopied()
|
||||
toast.success("Copied to clipboard!")
|
||||
setIsCopied(true)
|
||||
setTimeout(() => setIsCopied(false), 2000)
|
||||
}}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Alternatively, pick another client and use the install
|
||||
command, or use Manual instructions for a JSON
|
||||
snippet.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{selectedClient === "mcp-url" && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Paste this URL into clients that support remote MCP
|
||||
over HTTPS (OAuth).
|
||||
</p>
|
||||
<div className="relative max-w-xl">
|
||||
<Input
|
||||
className="w-full pr-10 font-mono text-xs"
|
||||
readOnly
|
||||
value={CHATGPT_REMOTE_MCP_URL}
|
||||
/>
|
||||
<Button
|
||||
className="absolute -top-px right-0 cursor-pointer"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
CHATGPT_REMOTE_MCP_URL,
|
||||
)
|
||||
analytics.mcpInstallCmdCopied()
|
||||
toast.success("Copied to clipboard!")
|
||||
}}
|
||||
variant="ghost"
|
||||
>
|
||||
<CopyIcon className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{selectedClient !== "cursor" &&
|
||||
selectedClient !== "mcp-url" && (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Optional: scope installs to a project. Then copy and
|
||||
run the command in your terminal.
|
||||
</p>
|
||||
<div className="max-w-md">
|
||||
<Select
|
||||
disabled={isLoadingProjects}
|
||||
onValueChange={setSelectedProject}
|
||||
value={selectedProject || "none"}
|
||||
>
|
||||
{isCopied ? (
|
||||
<CheckIcon className="size-3.5 text-green-600" />
|
||||
) : (
|
||||
<CopyIcon className="size-3.5" />
|
||||
)}
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select project" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">
|
||||
Auto-select project
|
||||
</SelectItem>
|
||||
<SelectItem value="sm_project_default">
|
||||
Default Project
|
||||
</SelectItem>
|
||||
{projects
|
||||
.filter(
|
||||
(p: Project) =>
|
||||
p.containerTag !== "sm_project_default",
|
||||
)
|
||||
.map((project: Project) => (
|
||||
<SelectItem
|
||||
key={project.id}
|
||||
value={project.containerTag}
|
||||
>
|
||||
{project.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="relative max-w-full">
|
||||
<Input
|
||||
className="w-full pr-10 font-mono text-xs"
|
||||
readOnly
|
||||
value={generateInstallCommand()}
|
||||
/>
|
||||
<Button
|
||||
className="absolute -top-px right-0 cursor-pointer"
|
||||
onClick={copyToClipboard}
|
||||
variant="ghost"
|
||||
>
|
||||
<CopyIcon className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
The API key is included as a Bearer token in the
|
||||
Authorization header
|
||||
Requires Node/npx. OAuth runs when the CLI prompts
|
||||
you.
|
||||
</p>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-w-md">
|
||||
<Select
|
||||
disabled={isLoadingProjects}
|
||||
onValueChange={setSelectedProject}
|
||||
value={selectedProject || "none"}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select project" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">Auto-select project</SelectItem>
|
||||
<SelectItem value="sm_project_default">
|
||||
Default Project
|
||||
</SelectItem>
|
||||
{projects
|
||||
.filter(
|
||||
(p: Project) =>
|
||||
p.containerTag !== "sm_project_default",
|
||||
)
|
||||
.map((project: Project) => (
|
||||
<SelectItem
|
||||
key={project.id}
|
||||
value={project.containerTag}
|
||||
</>
|
||||
) : (
|
||||
(() => {
|
||||
const manual = getManualInstallEntry(selectedClient)
|
||||
if (manual.kind === "chatgpt") {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<ol className="list-decimal space-y-2 pl-5 text-sm text-muted-foreground">
|
||||
<li>Open ChatGPT in your browser.</li>
|
||||
<li>
|
||||
Settings → Apps → Advanced settings → enable
|
||||
Developer mode.
|
||||
</li>
|
||||
<li>
|
||||
Create an app and paste the MCP URL when asked.
|
||||
</li>
|
||||
<li>Complete OAuth in ChatGPT.</li>
|
||||
</ol>
|
||||
<div className="relative max-w-xl">
|
||||
<Input
|
||||
className="w-full pr-10 font-mono text-xs"
|
||||
readOnly
|
||||
value={CHATGPT_REMOTE_MCP_URL}
|
||||
/>
|
||||
<Button
|
||||
className="absolute -top-px right-0 cursor-pointer"
|
||||
onClick={() =>
|
||||
copyManualSnippet(CHATGPT_REMOTE_MCP_URL)
|
||||
}
|
||||
variant="ghost"
|
||||
>
|
||||
<CopyIcon className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<a
|
||||
className="text-xs text-primary underline"
|
||||
href="https://developers.openai.com/api/docs/guides/developer-mode/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{project.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
Developer mode docs (OpenAI)
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (manual.kind === "claude-desktop-timeline") {
|
||||
return (
|
||||
<ClaudeDesktopManualTimeline
|
||||
onCopySnippet={() =>
|
||||
copyManualSnippet(CLAUDE_DESKTOP_MCP_SNIPPET)
|
||||
}
|
||||
snippetCopied={isCopied}
|
||||
variant="modal"
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (manual.kind === "generic-remote") {
|
||||
const remoteSnippet = buildMcpUrlRemoteJson(
|
||||
manualApiKey || "your-api-key-here",
|
||||
)
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Paste into your MCP config. We create an API key for
|
||||
you when you open this tab; copy the block after it
|
||||
appears.
|
||||
</p>
|
||||
{createMcpApiKeyMutation.isPending ? (
|
||||
<div className="flex items-center justify-center p-8">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-primary" />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="relative max-w-full">
|
||||
<pre className="max-h-80 overflow-x-auto overflow-y-auto rounded-lg border border-border bg-muted p-4 pr-12 text-xs">
|
||||
<code className="block font-mono whitespace-pre-wrap break-all">
|
||||
{remoteSnippet}
|
||||
</code>
|
||||
</pre>
|
||||
<Button
|
||||
className="absolute top-2 right-2 h-8 w-8 cursor-pointer p-0 bg-muted/80 hover:bg-muted"
|
||||
onClick={() =>
|
||||
copyManualSnippet(remoteSnippet)
|
||||
}
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
{isCopied ? (
|
||||
<CheckIcon className="size-3.5 text-green-600" />
|
||||
) : (
|
||||
<CopyIcon className="size-3.5" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Bearer token uses your supermemory API key.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{manual.paths}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Merge the snippet with your existing config. Restart
|
||||
the client and sign in with OAuth when prompted.
|
||||
</p>
|
||||
<div className="relative max-w-full">
|
||||
<pre className="max-h-80 overflow-x-auto overflow-y-auto rounded-lg border border-border bg-muted p-4 pr-12 text-xs">
|
||||
<code className="block font-mono whitespace-pre-wrap break-all">
|
||||
{manual.snippet}
|
||||
</code>
|
||||
</pre>
|
||||
<Button
|
||||
className="absolute top-2 right-2 h-8 w-8 cursor-pointer p-0 bg-muted/80 hover:bg-muted"
|
||||
onClick={() => copyManualSnippet(manual.snippet)}
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
{isCopied ? (
|
||||
<CheckIcon className="size-3.5 text-green-600" />
|
||||
) : (
|
||||
<CopyIcon className="size-3.5" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})()
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 3: Command Line - Show for manual installation or non-cursor clients */}
|
||||
{selectedClient &&
|
||||
selectedClient !== "mcp-url" &&
|
||||
(selectedClient !== "cursor" || cursorInstallTab === "manual") && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-accent text-accent-foreground flex items-center justify-center text-sm font-semibold">
|
||||
3
|
||||
</div>
|
||||
<h3 className="text-sm font-medium">
|
||||
{selectedClient === "cursor" &&
|
||||
cursorInstallTab === "manual"
|
||||
? "Manual Installation Command"
|
||||
: "Installation Command"}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<Input
|
||||
className="font-mono text-xs w-full pr-10"
|
||||
readOnly
|
||||
value={generateInstallCommand()}
|
||||
/>
|
||||
<Button
|
||||
className="absolute top-[-1px] right-0 cursor-pointer"
|
||||
onClick={copyToClipboard}
|
||||
variant="ghost"
|
||||
>
|
||||
<CopyIcon className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{selectedClient === "cursor" && cursorInstallTab === "manual"
|
||||
? "Copy and run this command in your terminal for manual installation (or switch to the one-click option above)"
|
||||
: "Copy and run this command in your terminal to install the MCP server"}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Blurred Command Placeholder - Only show when no client selected */}
|
||||
{!selectedClient && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-accent text-accent-foreground flex items-center justify-center text-sm font-semibold">
|
||||
3
|
||||
<div className="flex size-8 items-center justify-center rounded-full bg-accent text-sm font-semibold text-accent-foreground">
|
||||
2
|
||||
</div>
|
||||
<h3 className="text-sm font-medium">Installation Command</h3>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<div className="w-full h-10 bg-muted border border-border rounded-md flex items-center px-3">
|
||||
<div className="w-full h-4 bg-muted-foreground/20 rounded animate-pulse blur-sm" />
|
||||
<div className="flex h-10 w-full items-center rounded-md border border-border bg-muted px-3">
|
||||
<div className="h-4 w-full animate-pulse rounded bg-muted-foreground/20 blur-sm" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-muted-foreground/50">
|
||||
Select a client above to see the installation command
|
||||
Select a client for setup instructions
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -740,7 +777,7 @@ export function ConnectAIModal({
|
|||
<div className="p-1 bg-muted rounded-lg border border-border items-center flex px-2">
|
||||
<CopyableCell
|
||||
className="font-mono text-xs text-primary"
|
||||
value="https://mcp.supermemory.ai/mcp"
|
||||
value={CHATGPT_REMOTE_MCP_URL}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
214
apps/web/components/mcp-modal/claude-desktop-manual-timeline.tsx
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
"use client"
|
||||
|
||||
import { CheckIcon, CopyIcon } from "lucide-react"
|
||||
import Image from "next/image"
|
||||
import { Button } from "@ui/components/button"
|
||||
import { CLAUDE_DESKTOP_MCP_SNIPPET } from "@/lib/mcp-manual-instructions"
|
||||
import { cn } from "@lib/utils"
|
||||
import { dmMonoClassName } from "@/lib/fonts"
|
||||
|
||||
const GUIDE_IMAGES = {
|
||||
step2: "/mcp-supported-tools/guides/claude-desktop/step-1.png",
|
||||
step3: "/mcp-supported-tools/guides/claude-desktop/step-2.png",
|
||||
step4: "/mcp-supported-tools/guides/claude-desktop/step-3.png",
|
||||
step5: "/mcp-supported-tools/guides/claude-desktop/step-4.png",
|
||||
} as const
|
||||
|
||||
const STEPS: {
|
||||
step: number
|
||||
body: string
|
||||
imageSrc?: string
|
||||
imageAlt?: string
|
||||
}[] = [
|
||||
{
|
||||
step: 1,
|
||||
body: "Copy the supermemory block below. You will paste it inside mcpServers in a later step.",
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
body: "In Claude Desktop, open Settings → Developer, then click Edit Config.",
|
||||
imageSrc: GUIDE_IMAGES.step2,
|
||||
imageAlt:
|
||||
"Claude Desktop settings: Developer in the sidebar and Edit Config highlighted",
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
body: "When claude_desktop_config.json opens in your editor, keep it ready for the next step.",
|
||||
imageSrc: GUIDE_IMAGES.step3,
|
||||
imageAlt: "File list with claude_desktop_config.json selected",
|
||||
},
|
||||
{
|
||||
step: 4,
|
||||
body: "Paste what you copied under mcpServers (merge with existing servers if the file already has some), then save.",
|
||||
imageSrc: GUIDE_IMAGES.step4,
|
||||
imageAlt: "JSON editor showing supermemory mcpServers configuration",
|
||||
},
|
||||
{
|
||||
step: 5,
|
||||
body: "Restart Claude Desktop. Open Settings → Connectors, find supermemory, and click Configure.",
|
||||
imageSrc: GUIDE_IMAGES.step5,
|
||||
imageAlt:
|
||||
"Claude Desktop Connectors settings with supermemory and Configure highlighted",
|
||||
},
|
||||
{
|
||||
step: 6,
|
||||
body: "supermemory is installed in your Claude Desktop and ready to use.",
|
||||
},
|
||||
]
|
||||
|
||||
type ClaudeDesktopManualTimelineProps = {
|
||||
variant: "detail" | "modal"
|
||||
onCopySnippet: () => void
|
||||
snippetCopied: boolean
|
||||
}
|
||||
|
||||
export function ClaudeDesktopManualTimeline({
|
||||
variant,
|
||||
onCopySnippet,
|
||||
snippetCopied,
|
||||
}: ClaudeDesktopManualTimelineProps) {
|
||||
const isDetail = variant === "detail"
|
||||
|
||||
const textMuted = isDetail ? "text-[#B0B0B0]" : "text-muted-foreground"
|
||||
const border = isDetail ? "border-[#3D434D]" : "border-border"
|
||||
const spine = isDetail ? "bg-[#7C8C9E]" : "bg-muted-foreground/40"
|
||||
const circleBorder = isDetail
|
||||
? "border-[#9CA8B8]"
|
||||
: "border-muted-foreground/50"
|
||||
const circleBg = isDetail ? "bg-[#080B0F]" : "bg-background"
|
||||
const codeBg = isDetail ? "bg-[#050608]" : "bg-muted"
|
||||
const imgBorder = isDetail ? "border-[#3D434D]" : "border-border"
|
||||
const screenshotBed = isDetail ? "bg-[#2A2D35]" : "bg-muted"
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"space-y-0",
|
||||
isDetail
|
||||
? "rounded-2xl border border-[#3D434D] bg-[#080B0F] p-5 sm:p-6"
|
||||
: "rounded-xl border border-border bg-muted/50 p-4 sm:p-5",
|
||||
)}
|
||||
>
|
||||
<div className="relative">
|
||||
<div
|
||||
className={cn(
|
||||
"absolute top-4.5 bottom-4.5 left-4.5 w-0.5 rounded-full",
|
||||
spine,
|
||||
)}
|
||||
aria-hidden
|
||||
/>
|
||||
|
||||
<ol className="relative m-0 list-none space-y-10">
|
||||
{STEPS.map((item) => (
|
||||
<li className="relative flex gap-4 sm:gap-5" key={item.step}>
|
||||
<div className="relative z-1 flex w-9 shrink-0 justify-center pt-0.5">
|
||||
<div
|
||||
className={cn(
|
||||
"flex size-9 items-center justify-center rounded-full border-2 text-sm font-semibold tabular-nums shadow-sm",
|
||||
circleBorder,
|
||||
circleBg,
|
||||
isDetail ? "text-[#FAFAFA]" : "text-foreground",
|
||||
)}
|
||||
>
|
||||
{item.step}
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 space-y-3">
|
||||
<p className={cn("text-sm leading-relaxed", textMuted)}>
|
||||
{item.body}
|
||||
</p>
|
||||
|
||||
{item.step === 1 ? (
|
||||
isDetail ? (
|
||||
<div className="relative">
|
||||
<pre
|
||||
className={cn(
|
||||
"max-w-full overflow-x-auto rounded-lg border p-4 pr-12 text-xs",
|
||||
border,
|
||||
codeBg,
|
||||
)}
|
||||
>
|
||||
<code
|
||||
className={cn(
|
||||
"block font-mono whitespace-pre-wrap break-all text-emerald-400",
|
||||
dmMonoClassName(),
|
||||
)}
|
||||
>
|
||||
{CLAUDE_DESKTOP_MCP_SNIPPET}
|
||||
</code>
|
||||
</pre>
|
||||
<button
|
||||
type="button"
|
||||
className="absolute top-2 right-2 flex size-8 cursor-pointer items-center justify-center rounded-md border border-[#3D434D] bg-[#121820] hover:bg-[#1a2230]"
|
||||
onClick={onCopySnippet}
|
||||
>
|
||||
{snippetCopied ? (
|
||||
<span className="text-xs text-emerald-500">✓</span>
|
||||
) : (
|
||||
<CopyIcon className="size-3.5 text-[#8B8B8B] hover:text-white" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative max-w-full">
|
||||
<pre
|
||||
className={cn(
|
||||
"max-h-80 max-w-full overflow-x-auto overflow-y-auto rounded-lg border p-4 pr-12 text-xs",
|
||||
border,
|
||||
codeBg,
|
||||
)}
|
||||
>
|
||||
<code
|
||||
className={cn(
|
||||
"block font-mono whitespace-pre-wrap break-all",
|
||||
dmMonoClassName(),
|
||||
)}
|
||||
>
|
||||
{CLAUDE_DESKTOP_MCP_SNIPPET}
|
||||
</code>
|
||||
</pre>
|
||||
<Button
|
||||
className="absolute top-2 right-2 h-8 w-8 cursor-pointer p-0 bg-muted/80 hover:bg-muted"
|
||||
onClick={onCopySnippet}
|
||||
size="icon"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
{snippetCopied ? (
|
||||
<CheckIcon className="size-3.5 text-green-600" />
|
||||
) : (
|
||||
<CopyIcon className="size-3.5" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
) : null}
|
||||
|
||||
{item.imageSrc ? (
|
||||
<div
|
||||
className={cn(
|
||||
"overflow-hidden rounded-xl border",
|
||||
imgBorder,
|
||||
screenshotBed,
|
||||
)}
|
||||
>
|
||||
<div className="max-h-[min(420px,70vh)] overflow-auto p-2 sm:p-3">
|
||||
<Image
|
||||
alt={item.imageAlt ?? ""}
|
||||
className="mx-auto h-auto w-full max-w-full rounded-md object-contain object-top"
|
||||
height={720}
|
||||
src={item.imageSrc}
|
||||
unoptimized
|
||||
width={1200}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
import { dmSans125ClassName, dmSansClassName } from "@/lib/fonts"
|
||||
import { Dialog, DialogContent, DialogFooter } from "@repo/ui/components/dialog"
|
||||
import { cn } from "@lib/utils"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import { XIcon } from "lucide-react"
|
||||
import { Button } from "@ui/components/button"
|
||||
import { MCPSteps } from "./mcp-detail-view"
|
||||
import { SpaceSelector } from "../space-selector"
|
||||
import { useProject } from "@/stores"
|
||||
import { useProjectMutations } from "@/hooks/use-project-mutations"
|
||||
|
||||
export function MCPModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
}: {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}) {
|
||||
const { selectedProject } = useProject()
|
||||
const { switchProject } = useProjectMutations()
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
|
||||
<DialogContent
|
||||
className={cn(
|
||||
"w-[90vw]! max-w-[900px]! max-h-[min(75vh,560px)]! border-none bg-[#1B1F24] flex flex-col p-4 gap-3 rounded-[22px]",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
style={{
|
||||
boxShadow:
|
||||
"0 2.842px 14.211px 0 rgba(0, 0, 0, 0.25), 0.711px 0.711px 0.711px 0 rgba(255, 255, 255, 0.10) inset",
|
||||
}}
|
||||
showCloseButton={false}
|
||||
>
|
||||
<div className="flex justify-between h-fit">
|
||||
<div className="pl-1 space-y-1">
|
||||
<p className={cn("font-semibold", dmSans125ClassName())}>
|
||||
Connect your AI to Supermemory
|
||||
</p>
|
||||
<p className={cn("text-[#737373] font-medium")}>
|
||||
Let your AI create and use your memories via MCP.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<DialogPrimitive.Close
|
||||
className="bg-[#0D121A] w-7 h-7 flex items-center justify-center focus:ring-ring rounded-full transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 shadow-[inset_0_2px_4px_rgba(0,0,0,0.3),inset_0_1px_2px_rgba(0,0,0,0.1)]"
|
||||
data-slot="dialog-close"
|
||||
>
|
||||
<XIcon stroke="#737373" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full flex-1 min-h-0 px-4 py-4 rounded-[14px] bg-[#14161A] shadow-inside-out overflow-y-auto">
|
||||
<MCPSteps variant="embedded" />
|
||||
</div>
|
||||
<DialogFooter className="justify-between!">
|
||||
<div className="flex items-center gap-2">
|
||||
<SpaceSelector
|
||||
selectedProjects={[selectedProject]}
|
||||
onValueChange={(projects) =>
|
||||
switchProject(projects[0] ?? selectedProject)
|
||||
}
|
||||
variant="insideOut"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-[#737373] cursor-pointer rounded-full"
|
||||
>
|
||||
Migrate from MCP v1
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
variant="insideOut"
|
||||
className="px-6 py-[10px]"
|
||||
onClick={onClose}
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
68
apps/web/lib/mcp-client-setup.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
export type McpSetupAvailability = {
|
||||
manual: boolean
|
||||
oneClick: boolean
|
||||
}
|
||||
|
||||
const DEFAULT_SETUP: McpSetupAvailability = {
|
||||
manual: true,
|
||||
oneClick: true,
|
||||
}
|
||||
|
||||
/**
|
||||
* Which setup surfaces each MCP client supports. Clients not listed use both
|
||||
* manual instructions and one-click (CLI / deeplink / URL copy).
|
||||
*/
|
||||
export const MCP_CLIENT_SETUP: Record<string, McpSetupAvailability> = {
|
||||
chatgpt: { manual: true, oneClick: false },
|
||||
cline: { manual: false, oneClick: true },
|
||||
}
|
||||
|
||||
export function getMcpClientSetup(clientKey: string): McpSetupAvailability {
|
||||
return MCP_CLIENT_SETUP[clientKey] ?? DEFAULT_SETUP
|
||||
}
|
||||
|
||||
export function mcpClientSetupShowsTabs(setup: McpSetupAvailability): boolean {
|
||||
return setup.manual && setup.oneClick
|
||||
}
|
||||
|
||||
export function defaultMcpSetupTab(
|
||||
setup: McpSetupAvailability,
|
||||
): "manual" | "oneClick" {
|
||||
if (setup.manual && setup.oneClick) return "manual"
|
||||
if (setup.manual) return "manual"
|
||||
return "oneClick"
|
||||
}
|
||||
|
||||
export function resolveMcpSetupTabForClient(
|
||||
clientKey: string,
|
||||
preferred: "manual" | "oneClick" | null | undefined,
|
||||
): "manual" | "oneClick" {
|
||||
const s = getMcpClientSetup(clientKey)
|
||||
if (!s.manual) return "oneClick"
|
||||
if (!s.oneClick) return "manual"
|
||||
return preferred ?? "manual"
|
||||
}
|
||||
|
||||
export function mcpClientShowsOneClick(
|
||||
setup: McpSetupAvailability,
|
||||
tab: "manual" | "oneClick",
|
||||
): boolean {
|
||||
return setup.oneClick && (!setup.manual || tab === "oneClick")
|
||||
}
|
||||
|
||||
export function mcpClientShowsManual(
|
||||
setup: McpSetupAvailability,
|
||||
tab: "manual" | "oneClick",
|
||||
): boolean {
|
||||
return setup.manual && (!setup.oneClick || tab === "manual")
|
||||
}
|
||||
|
||||
export function setupInstructionsSubtitle(setup: McpSetupAvailability): string {
|
||||
if (mcpClientSetupShowsTabs(setup)) {
|
||||
return "Choose manual instructions or one click setup (CLI, deeplink, or paste URL)."
|
||||
}
|
||||
if (setup.manual) {
|
||||
return "Follow the steps below to finish setup."
|
||||
}
|
||||
return "Use the command or link below, then complete OAuth if prompted."
|
||||
}
|
||||
113
apps/web/lib/mcp-manual-instructions.ts
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
export const CHATGPT_REMOTE_MCP_URL = "https://mcp.supermemory.ai/mcp"
|
||||
|
||||
export const SUPERMEMORY_MCP_OAUTH_JSON = `{
|
||||
"mcpServers": {
|
||||
"supermemory": {
|
||||
"url": "${CHATGPT_REMOTE_MCP_URL}"
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
export function buildMcpUrlRemoteJson(apiKeyPlaceholder: string) {
|
||||
return `{
|
||||
"supermemory-mcp": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "mcp-remote", "https://mcp.supermemory.ai/mcp"],
|
||||
"env": {},
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${apiKeyPlaceholder}"
|
||||
}
|
||||
}
|
||||
}`
|
||||
}
|
||||
|
||||
export const CODEX_MCP_TOML = `[mcp_servers.supermemory]
|
||||
command = "npx"
|
||||
args = ["-y", "mcp-remote@latest", "https://mcp.supermemory.ai/mcp"]
|
||||
`
|
||||
|
||||
/** Full file merge target: Claude Desktop `claude_desktop_config.json` → `mcpServers`. */
|
||||
export const CLAUDE_DESKTOP_MCP_SNIPPET = `{
|
||||
"mcpServers": {
|
||||
"supermemory": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"mcp-remote@latest",
|
||||
"https://mcp.supermemory.ai/mcp"
|
||||
]
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
export type ManualInstallEntry =
|
||||
| { kind: "file"; paths: string; snippet: string; format: "json" | "toml" }
|
||||
| { kind: "chatgpt" }
|
||||
| { kind: "generic-remote" }
|
||||
| { kind: "claude-desktop-timeline" }
|
||||
|
||||
export function getManualInstallEntry(clientKey: string): ManualInstallEntry {
|
||||
switch (clientKey) {
|
||||
case "chatgpt":
|
||||
return { kind: "chatgpt" }
|
||||
case "mcp-url":
|
||||
return { kind: "generic-remote" }
|
||||
case "codex":
|
||||
return {
|
||||
kind: "file",
|
||||
paths:
|
||||
"Codex: ~/.codex/config.toml (or $CODEX_HOME/config.toml). Merge the block into your existing [mcp_servers] section.",
|
||||
snippet: CODEX_MCP_TOML,
|
||||
format: "toml",
|
||||
}
|
||||
case "cursor":
|
||||
return {
|
||||
kind: "file",
|
||||
paths:
|
||||
"Cursor: ~/.cursor/mcp.json globally, or .cursor/mcp.json in your project.",
|
||||
snippet: SUPERMEMORY_MCP_OAUTH_JSON,
|
||||
format: "json",
|
||||
}
|
||||
case "vscode":
|
||||
return {
|
||||
kind: "file",
|
||||
paths:
|
||||
"VS Code: macOS ~/Library/Application Support/Code/User/mcp.json · Windows %APPDATA%\\Code\\User\\mcp.json · Linux ~/.config/Code/User/mcp.json.",
|
||||
snippet: SUPERMEMORY_MCP_OAUTH_JSON,
|
||||
format: "json",
|
||||
}
|
||||
case "cline":
|
||||
return {
|
||||
kind: "file",
|
||||
paths:
|
||||
"Cline: VS Code user folder → globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json.",
|
||||
snippet: SUPERMEMORY_MCP_OAUTH_JSON,
|
||||
format: "json",
|
||||
}
|
||||
case "claude":
|
||||
return { kind: "claude-desktop-timeline" }
|
||||
case "claude-code":
|
||||
return {
|
||||
kind: "file",
|
||||
paths:
|
||||
"Claude Code: ~/.claude.json (user) or .mcp.json in the project root.",
|
||||
snippet: SUPERMEMORY_MCP_OAUTH_JSON,
|
||||
format: "json",
|
||||
}
|
||||
case "gemini-cli":
|
||||
return {
|
||||
kind: "file",
|
||||
paths:
|
||||
"Gemini CLI: ~/.gemini/settings.json (or project .gemini/settings.json).",
|
||||
snippet: SUPERMEMORY_MCP_OAUTH_JSON,
|
||||
format: "json",
|
||||
}
|
||||
default:
|
||||
return {
|
||||
kind: "file",
|
||||
paths: "Open your client's MCP settings file (see its documentation).",
|
||||
snippet: SUPERMEMORY_MCP_OAUTH_JSON,
|
||||
format: "json",
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
apps/web/public/mcp-supported-tools/chatgpt.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
apps/web/public/mcp-supported-tools/codex.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 637 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 870 KiB |