diff --git a/apps/web/components/connect-ai-modal.tsx b/apps/web/components/connect-ai-modal.tsx index b576934f..eb1597b5 100644 --- a/apps/web/components/connect-ai-modal.tsx +++ b/apps/web/components/connect-ai-modal.tsx @@ -1,6 +1,9 @@ "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" @@ -21,7 +24,7 @@ import { SelectValue, } from "@ui/components/select" import { CopyableCell } from "@ui/copyable-cell" -import { CopyIcon, ExternalLink, Loader2 } from "lucide-react" +import { CheckIcon, CopyIcon, ExternalLink, Loader2 } from "lucide-react" import Image from "next/image" import { useEffect, useState } from "react" import { toast } from "sonner" @@ -65,16 +68,21 @@ interface ConnectAIModalProps { children: React.ReactNode open?: boolean onOpenChange?: (open: boolean) => void + openInitialClient?: "mcp-url" | null + openInitialTab?: "oneClick" | "manual" | null } export function ConnectAIModal({ children, open, onOpenChange, + openInitialClient, + openInitialTab, }: ConnectAIModalProps) { + const { org } = useAuth() const [selectedClient, setSelectedClient] = useState< keyof typeof clients | null - >(null) + >(openInitialClient || null) const [internalIsOpen, setInternalIsOpen] = useState(false) const isOpen = open !== undefined ? open : internalIsOpen const setIsOpen = onOpenChange || setInternalIsOpen @@ -83,6 +91,11 @@ export function ConnectAIModal({ const [cursorInstallTab, setCursorInstallTab] = useState< "oneClick" | "manual" >("oneClick") + const [mcpUrlTab, setMcpUrlTab] = useState<"oneClick" | "manual">( + openInitialTab || "oneClick", + ) + const [manualApiKey, setManualApiKey] = useState(null) + const [isCopied, setIsCopied] = useState(false) const [projectId, setProjectId] = useState("default") @@ -163,6 +176,46 @@ export function ConnectAIModal({ }, }) + 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", + }) + }, + }) + + // 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() + } + } + } + }, [openInitialClient, openInitialTab, org?.id]) + function generateInstallCommand() { if (!selectedClient) return "" @@ -279,7 +332,7 @@ export function ConnectAIModal({ {selectedClient === "cursor" ? "Install Supermemory MCP" : selectedClient === "mcp-url" - ? "MCP Server URL" + ? "MCP Server Configuration" : "Select Target Project (Optional)"} @@ -287,7 +340,9 @@ export function ConnectAIModal({
{/* Tabs */} @@ -295,25 +350,51 @@ export function ConnectAIModal({
@@ -396,30 +477,98 @@ export function ConnectAIModal({ )} ) : selectedClient === "mcp-url" ? ( -
-
- - -
-

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

+
+ {mcpUrlTab === "oneClick" ? ( +
+

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

+
+ + +
+
+ ) : ( +
+

+ Add this configuration to your MCP settings file with + authentication +

+ {createMcpApiKeyMutation.isPending ? ( +
+ +
+ ) : ( + <> +
+
+															
+																{`{
+  "supermemory-mcp": {
+    "command": "npx",
+    "args": ["-y", "mcp-remote", "https://api.supermemory.ai/mcp"],
+    "env": {},
+    "headers": {
+      "Authorization": "Bearer ${manualApiKey || "your-api-key-here"}"
+    }
+  }
+}`}
+															
+														
+ +
+

+ The API key is included as a Bearer token in the + Authorization header +

+ + )} +
+ )}
) : (
diff --git a/apps/web/components/header.tsx b/apps/web/components/header.tsx index 23fb5cf3..19a6ec3a 100644 --- a/apps/web/components/header.tsx +++ b/apps/web/components/header.tsx @@ -28,7 +28,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@ui/components/tooltip" import { useAuth } from "@lib/auth-context" import { ConnectAIModal } from "./connect-ai-modal" import { useTheme } from "next-themes" -import { usePathname, useRouter } from "next/navigation" +import { usePathname, useRouter, useSearchParams } from "next/navigation" import { MCPIcon } from "./menu" import { authClient } from "@lib/auth" import { analytics } from "@/lib/analytics" @@ -44,10 +44,11 @@ import { import { ScrollArea } from "@ui/components/scroll-area" import { formatDistanceToNow } from "date-fns" import { cn } from "@lib/utils" -import { useMemo, useState } from "react" +import { useEffect, useMemo, useState } from "react" export function Header({ onAddMemory }: { onAddMemory?: () => void }) { const { user } = useAuth() + const searchParams = useSearchParams() const { theme, setTheme } = useTheme() const router = useRouter() const { setIsOpen: setGraphModalOpen } = useGraphModal() @@ -61,6 +62,13 @@ export function Header({ onAddMemory }: { onAddMemory?: () => void }) { const { selectedProject } = useProject() const pathname = usePathname() const [isDialogOpen, setIsDialogOpen] = useState(false) + const [mcpModalOpen, setMcpModalOpen] = useState(false) + const [mcpInitialClient, setMcpInitialClient] = useState<"mcp-url" | null>( + null, + ) + const [mcpInitialTab, setMcpInitialTab] = useState< + "oneClick" | "manual" | null + >(null) const sorted = useMemo(() => { return [...conversations].sort((a, b) => @@ -68,6 +76,22 @@ export function Header({ onAddMemory }: { onAddMemory?: () => void }) { ) }, [conversations]) + useEffect(() => { + console.log("searchParams", searchParams.get("mcp")) + const mcpParam = searchParams.get("mcp") + if (mcpParam === "manual") { + setMcpInitialClient("mcp-url") + setMcpInitialTab("manual") + setMcpModalOpen(true) + const newSearchParams = new URLSearchParams(searchParams.toString()) + newSearchParams.delete("mcp") + const newUrl = `${ + window.location.pathname + }${newSearchParams.toString() ? `?${newSearchParams.toString()}` : ""}` + window.history.replaceState({}, "", newUrl) + } + }, [searchParams]) + function handleNewChat() { analytics.newChatStarted() const newId = crypto.randomUUID() @@ -240,18 +264,28 @@ export function Header({ onAddMemory }: { onAddMemory?: () => void }) {

Graph View

- - + + - - -

Connect to AI (MCP)

-
-
-
+ + +

Connect to AI (MCP)

+
+