From 3157b987fc490ebf5e51bdd71c02242076409160 Mon Sep 17 00:00:00 2001 From: MaheshtheDev <38828053+MaheshtheDev@users.noreply.github.com> Date: Mon, 1 Sep 2025 04:00:45 +0000 Subject: [PATCH] feat: redesign MCP connection flow with step-based UI and v1 migration (#399) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Screen Recording 2025-08-31 at 12.03.49 PM.mov (uploaded via Graphite) ](https://app.graphite.dev/user-attachments/video/f6c88b1f-9f16-47aa-8d5f-6cde61b61e4f.mov) --- apps/web/components/connect-ai-modal.tsx | 550 +++++++++++++++++------ apps/web/components/menu.tsx | 17 +- 2 files changed, 422 insertions(+), 145 deletions(-) diff --git a/apps/web/components/connect-ai-modal.tsx b/apps/web/components/connect-ai-modal.tsx index ff5f1064..5099deed 100644 --- a/apps/web/components/connect-ai-modal.tsx +++ b/apps/web/components/connect-ai-modal.tsx @@ -1,6 +1,8 @@ "use client"; -import { useIsMobile } from "@hooks/use-mobile"; +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, @@ -19,10 +21,12 @@ import { SelectValue, } from "@ui/components/select"; import { CopyableCell } from "@ui/copyable-cell"; -import { CopyIcon, ExternalLink } from "lucide-react"; +import { CopyIcon, ExternalLink, Loader2 } from "lucide-react"; import Image from "next/image"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { toast } from "sonner"; +import { z } from "zod/v4"; +import { analytics } from "@/lib/analytics"; const clients = { cursor: "Cursor", @@ -36,24 +40,138 @@ const clients = { enconvo: "Enconvo", } as const; -interface ConnectAIModalProps { - children: React.ReactNode; +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 Project { + id: string; + name: string; + containerTag: string; + createdAt: string; + updatedAt: string; + isExperimental?: boolean; } -export function ConnectAIModal({ children }: ConnectAIModalProps) { - const [client, setClient] = useState("cursor"); - const [isOpen, setIsOpen] = useState(false); - const [showAllTools, setShowAllTools] = useState(false); - const isMobile = useIsMobile(); - const installCommand = `npx -y install-mcp@latest https://api.supermemory.ai/mcp --client ${client} --oauth=yes`; +interface ConnectAIModalProps { + children: React.ReactNode; + open?: boolean; + onOpenChange?: (open: boolean) => void; +} + +export function ConnectAIModal({ + children, + open, + onOpenChange, +}: ConnectAIModalProps) { + const [selectedClient, setSelectedClient] = useState< + keyof typeof clients | null + >(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 projectId = localStorage.getItem("selectedProject") ?? "default"; + + 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 || []; + }, + staleTime: 30 * 1000, + }); + + 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/memories/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", + }); + }, + }); + + function generateInstallCommand() { + if (!selectedClient) return ""; + + let command = `npx -y install-mcp@latest https://api.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; + } const copyToClipboard = () => { - navigator.clipboard.writeText(installCommand); + const command = generateInstallCommand(); + navigator.clipboard.writeText(command); + analytics.mcpInstallCmdCopied(); toast.success("Copied to clipboard!"); }; return ( - + {children} @@ -65,144 +183,194 @@ export function ConnectAIModal({ children }: ConnectAIModalProps) { -
- -
- -
-

- Click URL to copy to clipboard. Use this URL to configure - supermemory in your AI assistant. -

-
-
-
-

Supported AI Tools

-
+ {/* Step 1: Client Selection */} +
+
+
+ 1 +
+

Select Your AI Client

+
+ +
{Object.entries(clients) - .slice(0, showAllTools ? undefined : isMobile ? 4 : 6) + .slice(0, 6) .map(([key, clientName]) => ( -
+ setSelectedClient(key as keyof typeof clients) + } + type="button" > -
- {clientName} { - 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-xs font-bold text-muted-foreground"; - fallback.textContent = clientName - .substring(0, 2) - .toUpperCase(); - parent.appendChild(fallback); - } - }} - /> +
+
+ {clientName} { + 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-white/40"; + fallback.textContent = clientName + .substring(0, 2) + .toUpperCase(); + parent.appendChild(fallback); + } + }} + src={`/mcp-supported-tools/${key === "claude-code" ? "claude" : key}.png`} + width={20} + /> +
+ + {clientName} +
- {clientName} -
+ ))}
- {Object.entries(clients).length > 6 && ( -
- -
- )}
-
-

Quick Installation

-
- + {/* Step 2: Project Selection */} + {selectedClient && ( +
+
+
+ 2 +
+

+ Select Target Project (Optional) +

+
-
+
+ +
+
+ )} + + {/* Step 3: Command Line */} + {selectedClient && ( +
+
+
+ 3 +
+

Installation Command

+
+ +
-
+ +

+ Copy and run this command in your terminal to install the MCP + server +

+
+ )} + + {/* Blurred Command Placeholder */} + {!selectedClient && ( +
+
+
+ 3 +
+

Installation Command

+
+ +
+
+
+
+
+ +

+ Select a client above to see the installation command +

+
+ )} + +
+
+ +

+ Use this URL to configure supermemory in your AI assistant +

+
+
+
@@ -217,22 +385,120 @@ export function ConnectAIModal({ children }: ConnectAIModalProps) {
- +
+ + + +
+ + {/* 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 + +

+
+
+
+ + +
+
+
+
+
+ )}
); } diff --git a/apps/web/components/menu.tsx b/apps/web/components/menu.tsx index f59617c0..6eb81128 100644 --- a/apps/web/components/menu.tsx +++ b/apps/web/components/menu.tsx @@ -6,6 +6,7 @@ import { fetchMemoriesFeature, } from "@repo/lib/queries"; import { Button } from "@repo/ui/components/button"; +import { ConnectAIModal } from "./connect-ai-modal"; import { HeadingH2Bold } from "@repo/ui/text/heading/heading-h2-bold"; import { GlassMenuEffect } from "@ui/other/glass-effect"; import { useCustomer } from "autumn-js/react"; @@ -20,7 +21,6 @@ import { ProjectSelector } from "./project-selector"; import { useTour } from "./tour"; import { AddMemoryExpandedView, AddMemoryView } from "./views/add-memory"; import { IntegrationsView } from "./views/integrations"; -import { MCPView } from "./views/mcp"; import { ProfileView } from "./views/profile"; const MCPIcon = ({ className }: { className?: string }) => { @@ -47,6 +47,7 @@ function Menu({ id }: { id?: string }) { const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isCollapsing, setIsCollapsing] = useState(false); const [showAddMemoryView, setShowAddMemoryView] = useState(false); + const [showConnectAIModal, setShowConnectAIModal] = useState(false); const isMobile = useIsMobile(); const { activePanel, setActivePanel } = useMobilePanel(); const { setMenuExpanded } = useTour(); @@ -125,6 +126,11 @@ function Menu({ id }: { id?: string }) { if (isMobile) { setActivePanel("chat"); } + } else if (key === "mcp") { + // Open ConnectAIModal directly for MCP + setIsMobileMenuOpen(false); + setExpandedView(null); + setShowConnectAIModal(true); } else { if (expandedView === key) { setIsCollapsing(true); @@ -392,7 +398,6 @@ function Menu({ id }: { id?: string }) { ease: [0.4, 0, 0.2, 1], }} > - {expandedView === "mcp" && } {expandedView === "profile" && } {expandedView === "integrations" && ( @@ -608,7 +613,6 @@ function Menu({ id }: { id?: string }) { {expandedView === "addUrl" && ( )} - {expandedView === "mcp" && } {expandedView === "profile" && } {expandedView === "integrations" && ( @@ -631,6 +635,13 @@ function Menu({ id }: { id?: string }) { onClose={() => setShowAddMemoryView(false)} /> )} + + + + ); }