feat(web): add staff custom MCP connection cards (#1199)

---
**Session Details**
- Session: [View Session](https://supermemory.us1.vorflux.com/agent-sessions/cd042b68-db46-431f-982c-3070096a37bd)
- Requested by: Unknown
- Address comments on this PR. Add `(aside)` to your comment to have me ignore it.
This commit is contained in:
MaheshtheDev 2026-07-06 06:11:09 +00:00
parent d7050ed332
commit af61880da1
2 changed files with 206 additions and 31 deletions

View file

@ -15,14 +15,16 @@ export function PillButton({
children,
onClick,
disabled,
type = "button",
}: {
children: ReactNode
onClick?: () => void
disabled?: boolean
type?: "button" | "submit"
}) {
return (
<button
type="button"
type={type}
onClick={onClick}
disabled={disabled}
className={cn(

View file

@ -8,6 +8,7 @@ import { useCallback, useEffect, useState } from "react"
import { toast } from "sonner"
import { dmSans125ClassName } from "@/lib/fonts"
import { useHasCompanyBrain } from "@/hooks/use-company-brain"
import { useAuth } from "@lib/auth-context"
import { brainConnectorIcon, SlackMark } from "../brain-connector-icons"
import { PillButton } from "../integrations/install-steps"
@ -26,6 +27,7 @@ type CatalogEntry = {
}
type ConnRow = {
serverSlug: string
serverUrl?: string
authType: AuthType
status: "active" | "pending" | "error"
userId: string | null
@ -37,6 +39,15 @@ function titleCase(s: string) {
return s.replace(/\b\w/g, (c) => c.toUpperCase())
}
function slugifyMcpName(value: string) {
return value
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "")
.slice(0, 63)
}
function SecondaryButton({
children,
href,
@ -63,7 +74,7 @@ function StatusDot({ connected }: { connected: boolean }) {
<span
className={cn(
dmSans125ClassName(),
"flex items-center gap-1.5 text-[13px] font-medium",
"flex shrink-0 items-center gap-1.5 whitespace-nowrap text-[12px] font-medium",
connected ? "text-[#FAFAFA]" : "text-[#737373]",
)}
>
@ -78,7 +89,7 @@ function StatusDot({ connected }: { connected: boolean }) {
)
}
function AppRow({
function AppCard({
name,
subtitle,
icon,
@ -102,16 +113,16 @@ function AppRow({
onDisconnect: () => void
}) {
return (
<div className="flex items-center justify-between gap-4 rounded-xl bg-[#14161A] px-4 py-3 shadow-[inset_2.42px_2.42px_4.263px_rgba(11,15,21,0.7)]">
<div className="flex min-w-0 items-center gap-3">
<div className="flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-[9px] bg-[#080B0F] shadow-[inset_1.5px_1.5px_4.5px_rgba(0,0,0,0.6)]">
<div className="flex min-h-[152px] min-w-0 flex-col justify-between gap-4 rounded-xl bg-[#14161A] p-4 shadow-[inset_2.42px_2.42px_4.263px_rgba(11,15,21,0.7)]">
<div className="flex min-w-0 items-start gap-3">
<div className="flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-[10px] bg-[#080B0F] shadow-[inset_1.5px_1.5px_4.5px_rgba(0,0,0,0.6)]">
{icon}
</div>
<div className="min-w-0">
<div className="min-w-0 pt-0.5">
<p
className={cn(
dmSans125ClassName(),
"font-semibold text-[14px] tracking-[-0.15px] text-[#FAFAFA]",
"truncate font-semibold text-[14px] tracking-[-0.15px] text-[#FAFAFA]",
)}
>
{name}
@ -119,14 +130,14 @@ function AppRow({
<p
className={cn(
dmSans125ClassName(),
"truncate text-[12px] font-medium text-[#737373]",
"mt-1 line-clamp-2 break-words text-[12px] font-medium leading-5 text-[#737373]",
)}
>
{subtitle}
</p>
</div>
</div>
<div className="flex shrink-0 items-center gap-3">
<div className="flex min-h-9 items-center justify-between gap-3 border-[#1E293B]/50 border-t pt-3">
<StatusDot connected={connected} />
{connected && canDisconnect ? (
<PillButton onClick={onDisconnect} disabled={busy}>
@ -187,23 +198,30 @@ function ScopeToggle({
function RowSkeleton() {
return (
<div className="flex items-center gap-3 rounded-xl bg-[#14161A] px-4 py-3 shadow-[inset_2.42px_2.42px_4.263px_rgba(11,15,21,0.7)]">
<div className="size-9 animate-pulse rounded-[9px] bg-[#1c1f24]" />
<div className="space-y-2">
<div className="h-3 w-24 animate-pulse rounded bg-[#1c1f24]" />
<div className="h-2.5 w-36 animate-pulse rounded bg-[#1c1f24]" />
<div className="min-h-[152px] rounded-xl bg-[#14161A] p-4 shadow-[inset_2.42px_2.42px_4.263px_rgba(11,15,21,0.7)]">
<div className="flex items-center gap-3">
<div className="size-10 animate-pulse rounded-[10px] bg-[#1c1f24]" />
<div className="space-y-2">
<div className="h-3 w-24 animate-pulse rounded bg-[#1c1f24]" />
<div className="h-2.5 w-32 animate-pulse rounded bg-[#1c1f24]" />
</div>
</div>
<div className="mt-8 h-8 w-28 animate-pulse rounded-full bg-[#1c1f24] ml-auto" />
</div>
)
}
export default function CompanyBrainConnections() {
const isCompanyBrain = useHasCompanyBrain()
const { user } = useAuth()
const [catalog, setCatalog] = useState<CatalogEntry[] | null>(null)
const [catalogLoaded, setCatalogLoaded] = useState(false)
const [rows, setRows] = useState<ConnRow[]>([])
const [slackStatus, setSlackStatus] = useState<SlackStatus | null>(null)
const [busy, setBusy] = useState<string | null>(null)
const [scope, setScope] = useState<Scope>("user")
const [customName, setCustomName] = useState("")
const [customServerUrl, setCustomServerUrl] = useState("")
const roleQuery = useQuery({
queryKey: ["company-brain-connections", "role"],
@ -224,8 +242,10 @@ export default function CompanyBrainConnections() {
if (catRes.ok) {
const data = (await catRes.json()) as { catalog?: CatalogEntry[] }
setCatalog(Array.isArray(data.catalog) ? data.catalog : [])
setCatalogLoaded(true)
} else {
setCatalog([])
setCatalogLoaded(false)
toast.error("Couldn't load the app catalog.")
}
if (connRes.ok) {
@ -259,6 +279,9 @@ export default function CompanyBrainConnections() {
(shared ? r.userId === null : r.userId !== null),
)
const isStaff =
user?.email?.toLowerCase().endsWith("@supermemory.com") ?? false
const connect = async (entry: CatalogEntry, shared: boolean) => {
const key = `${entry.slug}:${shared ? "org" : "user"}`
setBusy(key)
@ -320,6 +343,66 @@ export default function CompanyBrainConnections() {
}
}
const connectCustom = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
const slug = slugifyMcpName(customName)
const serverUrl = customServerUrl.trim()
if (!slug) {
toast.error("Enter a custom MCP name.")
return
}
if (!serverUrl) {
toast.error("Enter an OAuth MCP URL.")
return
}
if (apps.some((entry) => entry.slug === slug)) {
toast.error("That name is already used by a catalog app.")
return
}
const key = `custom:${slug}`
setBusy(key)
try {
const res = await fetch(`${MCP_BASE}/${slug}/connect`, {
method: "POST",
credentials: "include",
headers: { "content-type": "application/json" },
body: JSON.stringify({
serverUrl,
shared: false,
redirectUrl: window.location.href,
}),
})
if (res.status === 403) {
toast.error("Custom MCP URLs are staff-only.")
return
}
const data = (await res.json().catch(() => ({}))) as {
authUrl?: string
ok?: boolean
error?: string
}
if (!res.ok) {
toast.error(data.error ?? "Couldn't start the custom connection.")
return
}
if (data.authUrl) {
window.open(data.authUrl, "_blank", "noopener")
} else if (data.ok) {
toast.success(`${slug} connected.`)
setCustomName("")
setCustomServerUrl("")
await load()
} else {
toast.error(data.error ?? "Couldn't start the custom connection.")
}
} catch {
toast.error("Couldn't start the custom connection.")
} finally {
setBusy(null)
}
}
const disconnect = async (entry: CatalogEntry, shared: boolean) => {
if (
!window.confirm(
@ -366,6 +449,18 @@ export default function CompanyBrainConnections() {
const loading = catalog === null
const apps = catalog ?? []
const catalogSlugs = new Set(apps.map((entry) => entry.slug))
const canClassifyCustomRows = catalogLoaded && apps.length > 0
const customRows = canClassifyCustomRows
? rows.filter(
(row) =>
row.userId !== null &&
row.status === "active" &&
typeof row.serverUrl === "string" &&
row.serverUrl.length > 0 &&
!catalogSlugs.has(row.serverSlug),
)
: []
const shared = scope === "org"
const description = shared
? "Connected by admins. Used for reads when you haven't connected your own."
@ -402,7 +497,7 @@ export default function CompanyBrainConnections() {
{description}
</p>
<div className="space-y-2.5">
<div className="grid gap-3 md:grid-cols-2">
{loading ? (
<>
<RowSkeleton />
@ -410,21 +505,99 @@ export default function CompanyBrainConnections() {
<RowSkeleton />
</>
) : (
apps.map((entry) => (
<AppRow
key={`${scope}-${entry.slug}`}
name={entry.name}
subtitle={titleCase(entry.category)}
icon={brainConnectorIcon(entry.slug, entry.name)}
connected={isConnected(entry.slug, shared)}
canConnect={shared ? isAdmin : true}
canDisconnect={shared ? isAdmin : true}
lockedHint={shared ? "Admin only" : undefined}
busy={busy === `${entry.slug}:${scope}`}
onConnect={() => connect(entry, shared)}
onDisconnect={() => disconnect(entry, shared)}
/>
))
<>
{!shared && isStaff ? (
<form
onSubmit={connectCustom}
className="flex min-h-[152px] min-w-0 flex-col justify-between gap-3 rounded-xl bg-[#14161A] p-4 shadow-[inset_2.42px_2.42px_4.263px_rgba(11,15,21,0.7)]"
>
<div>
<p
className={cn(
dmSans125ClassName(),
"text-[14px] font-semibold text-[#FAFAFA]",
)}
>
Custom MCP server
</p>
<p
className={cn(
dmSans125ClassName(),
"mt-1 line-clamp-2 text-[12px] font-medium text-[#737373]",
)}
>
Add a personal OAuth MCP server by URL.
</p>
</div>
<div className="space-y-2">
<input
value={customName}
onChange={(event) => setCustomName(event.target.value)}
placeholder="Name"
className="h-8 w-full rounded-full border border-[#1E293B] bg-[#0D121A] px-3 text-[12px] font-medium text-[#FAFAFA] outline-none placeholder:text-[#5F6673] focus:border-[#334155]"
/>
<input
value={customServerUrl}
onChange={(event) => setCustomServerUrl(event.target.value)}
placeholder="https://example.com/mcp"
className="h-8 w-full rounded-full border border-[#1E293B] bg-[#0D121A] px-3 text-[12px] font-medium text-[#FAFAFA] outline-none placeholder:text-[#5F6673] focus:border-[#334155]"
/>
<div className="flex justify-end border-[#1E293B]/50 border-t pt-3">
<PillButton
type="submit"
disabled={busy?.startsWith("custom:") ?? false}
>
{busy?.startsWith("custom:") && (
<Loader2 className="size-3.5 animate-spin" />
)}
Connect
</PillButton>
</div>
</div>
</form>
) : null}
{apps.map((entry) => (
<AppCard
key={`${scope}-${entry.slug}`}
name={entry.name}
subtitle={titleCase(entry.category)}
icon={brainConnectorIcon(entry.slug, entry.name)}
connected={isConnected(entry.slug, shared)}
canConnect={shared ? isAdmin : true}
canDisconnect={shared ? isAdmin : true}
lockedHint={shared ? "Admin only" : undefined}
busy={busy === `${entry.slug}:${scope}`}
onConnect={() => connect(entry, shared)}
onDisconnect={() => disconnect(entry, shared)}
/>
))}
{!shared &&
customRows.map((row) => (
<AppCard
key={`custom-${row.serverSlug}`}
name={titleCase(row.serverSlug.replace(/-/g, " "))}
subtitle={row.serverUrl ?? "Custom OAuth MCP"}
icon={brainConnectorIcon(row.serverSlug, row.serverSlug)}
connected
canConnect={false}
canDisconnect
busy={busy === `${row.serverSlug}:user`}
onConnect={() => {}}
onDisconnect={() =>
disconnect(
{
slug: row.serverSlug,
name: titleCase(row.serverSlug.replace(/-/g, " ")),
category: "Custom OAuth MCP",
authType: "oauth",
},
false,
)
}
/>
))}
</>
)}
</div>
</div>