From 8cb18ac60ac783f47d7c91de90ff17a84f9830cb Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Thu, 18 Jun 2026 20:06:38 +0530 Subject: [PATCH 1/5] 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/5] 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/5] 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/5] 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/5] 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} )}