From 8cb18ac60ac783f47d7c91de90ff17a84f9830cb Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Thu, 18 Jun 2026 20:06:38 +0530 Subject: [PATCH 1/8] Show MCP connection status in integrations --- apps/web/components/integrations-view.tsx | 200 +++++++++++++++++++++- 1 file changed, 197 insertions(+), 3 deletions(-) diff --git a/apps/web/components/integrations-view.tsx b/apps/web/components/integrations-view.tsx index fe68d1539..09ee9026d 100644 --- a/apps/web/components/integrations-view.tsx +++ b/apps/web/components/integrations-view.tsx @@ -93,6 +93,19 @@ interface ConnectedKey { createdAt?: string | null } +interface ConnectedMcpKey { + keyId: string + keyStart: string | null + lastRequest?: string | null + createdAt?: string | null +} + +function isMcpAuthMetadata(metadata: { sm_source?: string; sm_kind?: string }) { + return ( + metadata.sm_source === "mcp" || metadata.sm_kind === "mcp_oauth_exchange" + ) +} + function toIsoDate(value: string | Date | null | undefined): string | null { if (!value) return null const d = value instanceof Date ? value : new Date(value) @@ -142,6 +155,34 @@ function parsePluginAuthKeys( return { active, setup } } +function parseMcpAuthKeys( + apiKeys: ListedApiKey[], + keyPrefix: (key: ListedApiKey) => string | null, +): ConnectedMcpKey[] { + const keys: ConnectedMcpKey[] = [] + for (const key of apiKeys) { + if (key.enabled === false) continue + if (!key.metadata) continue + try { + const metadata = + typeof key.metadata === "string" + ? (JSON.parse(key.metadata) as { + sm_source?: string + sm_kind?: string + }) + : (key.metadata as { sm_source?: string; sm_kind?: string }) + if (!isMcpAuthMetadata(metadata)) continue + keys.push({ + keyId: key.id, + keyStart: keyPrefix(key), + lastRequest: toIsoDate(key.lastRequest), + createdAt: toIsoDate(key.createdAt), + }) + } catch {} + } + return keys +} + type ListedApiKey = { id: string name?: string | null @@ -1036,6 +1077,31 @@ function ActiveButton({ ) } +function McpConnectedPill({ + connectedAt, + lastActive, +}: { + connectedAt?: string | null + lastActive?: string | null +}) { + return ( + + + Connected + {(lastActive ?? connectedAt) && ( + + · {formatRelativeTime(lastActive ?? connectedAt)} + + )} + + ) +} + function FinishSetupButton({ onClick }: { onClick: () => void }) { return ( @@ -1137,7 +1203,18 @@ interface ConnectorEntry { onReconnect: () => void } -type RailEntry = PluginEntry | ConnectorEntry +interface McpEntry { + kind: "mcp" + id: string + name: string + icon: ReactNode + connectionCount: number + createdAt: string | null + lastActive: string | null + onManage: () => void +} + +type RailEntry = PluginEntry | ConnectorEntry | McpEntry function railConnectionMeta(connection: Connection) { const m = connection.metadata as Record | undefined @@ -1423,6 +1500,60 @@ function ConnectorRailRow({ entry }: { entry: ConnectorEntry }) { ) } +function McpRailRow({ entry }: { entry: McpEntry }) { + const [expanded, setExpanded] = useState(false) + const lastTime = entry.lastActive ?? entry.createdAt + const suffix = [ + entry.connectionCount > 1 ? `${entry.connectionCount} connections` : null, + lastTime ? formatRelativeTime(lastTime) : null, + ] + .filter(Boolean) + .join(" · ") + return ( + setExpanded((v) => !v)} + statusLine={ +
+ + {suffix && ( + + · {suffix} + + )} +
+ } + > + {entry.createdAt && ( + + )} + {entry.lastActive && ( + + )} + +
+ +
+
+ ) +} + const SKELETON_KEYS = ["s1", "s2", "s3", "s4", "s5"] function RailSkeleton({ rows }: { rows: number }) { @@ -1521,6 +1652,8 @@ function ActiveConnectionsRail({ {entries.map((entry) => entry.kind === "plugin" ? ( + ) : entry.kind === "mcp" ? ( + ) : ( ), @@ -1879,6 +2012,8 @@ function MobileActivityPanel({ {entries.map((entry) => entry.kind === "plugin" ? ( + ) : entry.kind === "mcp" ? ( + ) : ( ), @@ -2524,6 +2659,11 @@ export function IntegrationsView({ [apiKeys, keyPrefix], ) + const activeMcpKeys = useMemo( + () => parseMcpAuthKeys(apiKeys, keyPrefix), + [apiKeys, keyPrefix], + ) + const activePluginById = useMemo(() => { const map = new Map() for (const key of activePlugins) { @@ -2541,6 +2681,20 @@ export function IntegrationsView({ return map }, [activePlugins]) + const activeMcpKey = useMemo(() => { + let latest: ConnectedMcpKey | null = null + for (const key of activeMcpKeys) { + if (!latest) { + latest = key + continue + } + const a = toMs(key.lastRequest ?? key.createdAt) + const b = toMs(latest.lastRequest ?? latest.createdAt) + if (a >= b) latest = key + } + return latest + }, [activeMcpKeys]) + const activeCountByPlugin = useMemo(() => { const map = new Map() for (const key of activePlugins) { @@ -2727,12 +2881,21 @@ export function IntegrationsView({ if (item.kind === "connector") { return connectionsByProvider[item.provider].length > 0 } + if (item.kind === "mcp-client") { + return !!activeMcpKey + } if (item.kind === "import") { return tweetCount > 0 } return false }, - [activePluginById, connectionsByProvider, publicMode, tweetCount], + [ + activeMcpKey, + activePluginById, + connectionsByProvider, + publicMode, + tweetCount, + ], ) const counts = useMemo>( @@ -2792,6 +2955,24 @@ export function IntegrationsView({ }, }) } + if (activeMcpKey) { + rows.push({ + ts: toMs(activeMcpKey.lastRequest ?? activeMcpKey.createdAt), + entry: { + kind: "mcp", + id: "mcp", + name: "Supermemory MCP", + icon: , + connectionCount: activeMcpKeys.length, + createdAt: activeMcpKey.createdAt ?? null, + lastActive: activeMcpKey.lastRequest ?? null, + onManage: () => { + void setMcpClient("mcp-url") + setMcpModalOpen(true) + }, + }, + }) + } for (const provider of [ "google-drive", "notion", @@ -2829,11 +3010,14 @@ export function IntegrationsView({ rows.sort((a, b) => b.ts - a.ts) return rows.map((r) => r.entry) }, [ + activeMcpKey, + activeMcpKeys.length, activePluginById, activeCountByPlugin, connectionsByProvider, allProjects, setAddDoc, + setMcpClient, addConnectionMutation, ]) @@ -2886,6 +3070,7 @@ export function IntegrationsView({ const claudeCodeConnected = activePluginById.has("claude_code") const claudeCodeNeedsPro = !isAutumnLoading && !hasProProduct && !isFreeTierPlugin("claude_code") + const mcpConnected = !!activeMcpKey const featuredPicks: FeaturedPick[] = [ { @@ -2939,7 +3124,7 @@ export function IntegrationsView({ /> ), docsUrl: "https://supermemory.ai/docs/supermemory-mcp/introduction", - ctaLabel: "Connect", + ctaLabel: mcpConnected ? "Connected" : "Connect", onCta: () => { if (publicMode) { redirectToLogin() @@ -3341,6 +3526,15 @@ export function IntegrationsView({ if (count <= 0) return null return } + case "mcp-client": { + if (!activeMcpKey) return null + return ( + + ) + } case "import": { if (tweetCount <= 0) return null return From 74d2ae2efe9ce325571eefda4e472fca0d4661ca Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Sat, 20 Jun 2026 18:59:37 +0530 Subject: [PATCH 2/8] Move MCP connected state to section header --- apps/web/components/integrations-view.tsx | 49 ++++++++++++----------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/apps/web/components/integrations-view.tsx b/apps/web/components/integrations-view.tsx index 09ee9026d..dcdec1ac8 100644 --- a/apps/web/components/integrations-view.tsx +++ b/apps/web/components/integrations-view.tsx @@ -2456,9 +2456,11 @@ function CategoryFilterToggle({ function SectionRail({ label, children, + headerSlot, }: { label: string children: ReactNode + headerSlot?: ReactNode }) { const scrollRef = useRef(null) const [canScrollLeft, setCanScrollLeft] = useState(false) @@ -2509,6 +2511,7 @@ function SectionRail({ {label}
+ {headerSlot} ) case "import": return ( @@ -3526,15 +3527,6 @@ export function IntegrationsView({ if (count <= 0) return null return } - case "mcp-client": { - if (!activeMcpKey) return null - return ( - - ) - } case "import": { if (tweetCount <= 0) return null return @@ -3679,7 +3671,18 @@ export function IntegrationsView({ ) if (items.length === 0) return null return ( - + + ) : null + } + > {items.map((item) => (
Date: Mon, 22 Jun 2026 17:01:51 +0530 Subject: [PATCH 3/8] Update apps/web/components/integrations-view.tsx Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --- apps/web/components/integrations-view.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/components/integrations-view.tsx b/apps/web/components/integrations-view.tsx index dcdec1ac8..354656cd5 100644 --- a/apps/web/components/integrations-view.tsx +++ b/apps/web/components/integrations-view.tsx @@ -1095,7 +1095,7 @@ function McpConnectedPill({ Connected {(lastActive ?? connectedAt) && ( - · {formatRelativeTime(lastActive ?? connectedAt)} + · {formatRelativeTime(lastActive ?? connectedAt)} )} From 0e2b21acedd704e9268a1d4edf29cd2991f0a5b2 Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Mon, 22 Jun 2026 17:11:12 +0530 Subject: [PATCH 4/8] Update apps/web/components/integrations-view.tsx Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --- apps/web/components/integrations-view.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/components/integrations-view.tsx b/apps/web/components/integrations-view.tsx index 354656cd5..8a32a4eec 100644 --- a/apps/web/components/integrations-view.tsx +++ b/apps/web/components/integrations-view.tsx @@ -1508,7 +1508,7 @@ function McpRailRow({ entry }: { entry: McpEntry }) { lastTime ? formatRelativeTime(lastTime) : null, ] .filter(Boolean) - .join(" · ") + .join(" · ") return ( Date: Mon, 22 Jun 2026 17:11:22 +0530 Subject: [PATCH 5/8] Update apps/web/components/integrations-view.tsx Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --- apps/web/components/integrations-view.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/components/integrations-view.tsx b/apps/web/components/integrations-view.tsx index 8a32a4eec..8b60ba897 100644 --- a/apps/web/components/integrations-view.tsx +++ b/apps/web/components/integrations-view.tsx @@ -1525,7 +1525,7 @@ function McpRailRow({ entry }: { entry: McpEntry }) { "min-w-0 truncate text-[11px] text-[#737373]", )} > - · {suffix} + · {suffix} )}
From 12e77ab5ae7a2b22c216960103b48665b0d296fc Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Sat, 27 Jun 2026 20:49:46 +0530 Subject: [PATCH 6/8] Fix MCP icon rendering --- apps/web/components/integrations-view.tsx | 87 +++++++++++++++++++---- 1 file changed, 75 insertions(+), 12 deletions(-) diff --git a/apps/web/components/integrations-view.tsx b/apps/web/components/integrations-view.tsx index 8b60ba897..1d6a8491f 100644 --- a/apps/web/components/integrations-view.tsx +++ b/apps/web/components/integrations-view.tsx @@ -19,13 +19,7 @@ import { AppleShortcutsIcon, RaycastIcon, } from "@/components/integration-icons" -import { - GoogleDrive, - Notion, - OneDrive, - MCPIcon, - Granola, -} from "@ui/assets/icons" +import { GoogleDrive, Notion, OneDrive, Granola } from "@ui/assets/icons" import * as DialogPrimitive from "@radix-ui/react-dialog" import { ArrowLeft, @@ -57,6 +51,7 @@ import { addDocumentParam, docParam } from "@/lib/search-params" import { useCallback, useEffect, + useId, useMemo, useRef, useState, @@ -412,7 +407,7 @@ const SECTIONS: Array<{ dev: c.dev, icon: c.key === "mcp-url" ? ( - + ) : ( + + + + + + + + + + + + + + + + + + + + + + ) +} + type InfoUseCase = { title: string description: string @@ -1736,7 +1799,7 @@ function resolveDocSource( return { label: cc.label, icon: pluginIconNode(cc.iconSrc) } } if (doc.source === "mcp") { - return { label: "MCP", icon: } + return { label: "MCP", icon: } } const type = (doc.type ?? "").toLowerCase() if (type.includes("notion")) { @@ -2956,7 +3019,7 @@ export function IntegrationsView({ kind: "mcp", id: "mcp", name: "Supermemory MCP", - icon: , + icon: , connectionCount: activeMcpKeys.length, createdAt: activeMcpKey.createdAt ?? null, lastActive: activeMcpKey.lastRequest ?? null, @@ -3107,7 +3170,7 @@ export function IntegrationsView({ headline: "Your AI tools forget everything between chats.", support: "one setup gives Cursor, Claude & ChatGPT your memory", tagline: "Plug your memory into any MCP client.", - icon: , + icon: , backdrop: ( ) : ( - + )}
From 7f68618962f4a2859ca2ac2679ec65f49b6e4bf0 Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Sat, 27 Jun 2026 20:53:44 +0530 Subject: [PATCH 7/8] Use shared MCP icon asset --- apps/web/components/integrations-view.tsx | 75 +++-------------------- 1 file changed, 8 insertions(+), 67 deletions(-) diff --git a/apps/web/components/integrations-view.tsx b/apps/web/components/integrations-view.tsx index 1d6a8491f..6cdb1c36b 100644 --- a/apps/web/components/integrations-view.tsx +++ b/apps/web/components/integrations-view.tsx @@ -19,7 +19,13 @@ import { AppleShortcutsIcon, RaycastIcon, } from "@/components/integration-icons" -import { GoogleDrive, Notion, OneDrive, Granola } from "@ui/assets/icons" +import { + GoogleDrive, + Notion, + OneDrive, + MCPIcon, + Granola, +} from "@ui/assets/icons" import * as DialogPrimitive from "@radix-ui/react-dialog" import { ArrowLeft, @@ -51,7 +57,6 @@ import { addDocumentParam, docParam } from "@/lib/search-params" import { useCallback, useEffect, - useId, useMemo, useRef, useState, @@ -633,71 +638,7 @@ function IconBox({ } function SupermemoryMcpIcon({ className }: { className?: string }) { - const gradientIdBase = useId().replace(/:/g, "") - const gradientA = `${gradientIdBase}-mcp-wordmark-a` - const gradientB = `${gradientIdBase}-mcp-wordmark-b` - const gradientC = `${gradientIdBase}-mcp-wordmark-c` - - return ( - - ) + return } type InfoUseCase = { From 0728c2ba12248336ebab7ce79834c2fed49d0945 Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Sat, 27 Jun 2026 20:57:29 +0530 Subject: [PATCH 8/8] Use direct MCP icon sizing --- apps/web/components/integrations-view.tsx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/apps/web/components/integrations-view.tsx b/apps/web/components/integrations-view.tsx index 6cdb1c36b..8b60ba897 100644 --- a/apps/web/components/integrations-view.tsx +++ b/apps/web/components/integrations-view.tsx @@ -412,7 +412,7 @@ const SECTIONS: Array<{ dev: c.dev, icon: c.key === "mcp-url" ? ( - + ) : ( -} - type InfoUseCase = { title: string description: string @@ -1740,7 +1736,7 @@ function resolveDocSource( return { label: cc.label, icon: pluginIconNode(cc.iconSrc) } } if (doc.source === "mcp") { - return { label: "MCP", icon: } + return { label: "MCP", icon: } } const type = (doc.type ?? "").toLowerCase() if (type.includes("notion")) { @@ -2960,7 +2956,7 @@ export function IntegrationsView({ kind: "mcp", id: "mcp", name: "Supermemory MCP", - icon: , + icon: , connectionCount: activeMcpKeys.length, createdAt: activeMcpKey.createdAt ?? null, lastActive: activeMcpKey.lastRequest ?? null, @@ -3111,7 +3107,7 @@ export function IntegrationsView({ headline: "Your AI tools forget everything between chats.", support: "one setup gives Cursor, Claude & ChatGPT your memory", tagline: "Plug your memory into any MCP client.", - icon: , + icon: , backdrop: ( ) : ( - + )}