From 73d15ac9f86cedc6dfee281a4d6478307ea4e286 Mon Sep 17 00:00:00 2001 From: MaheshtheDev <38828053+MaheshtheDev@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:02:21 +0000 Subject: [PATCH] feat(web): add API key and extra headers to custom MCP dialog (#1419) Header-auth MCP servers like Plane need an API key plus a second header, which the custom connector dialog had no way to collect. - Restructured on the Claude connector pattern: name + URL up front, credentials behind collapsed Advanced settings - Adds API key, header name, and repeatable extra header rows --- .../settings/company-brain-connections.tsx | 200 ++++++++++++++++-- 1 file changed, 185 insertions(+), 15 deletions(-) diff --git a/apps/web/components/settings/company-brain-connections.tsx b/apps/web/components/settings/company-brain-connections.tsx index d7969d3e..55a01228 100644 --- a/apps/web/components/settings/company-brain-connections.tsx +++ b/apps/web/components/settings/company-brain-connections.tsx @@ -94,6 +94,9 @@ function ScopeChip({ const menuItemClass = "gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-white/85 hover:bg-white/[0.06] focus:bg-white/[0.06] focus:text-white cursor-pointer" +const customInputClass = + "h-9 w-full rounded-full border border-[#1E293B] bg-[#0D121A] px-3.5 text-[13px] font-medium text-[#FAFAFA] outline-none placeholder:text-[#5F6673] focus:border-[#334155]" + function AppCard({ name, subtitle, @@ -360,6 +363,12 @@ export default function CompanyBrainConnections() { const [customOpen, setCustomOpen] = useState(false) const [customName, setCustomName] = useState("") const [customServerUrl, setCustomServerUrl] = useState("") + const [customToken, setCustomToken] = useState("") + const [customHeaderName, setCustomHeaderName] = useState("") + const [customExtraHeaders, setCustomExtraHeaders] = useState< + { name: string; value: string }[] + >([]) + const [customAdvancedOpen, setCustomAdvancedOpen] = useState(false) const { isAdmin } = useOrgMemberRole(isCompanyBrain) @@ -470,6 +479,16 @@ export default function CompanyBrainConnections() { } } + const resetCustomForm = () => { + setCustomOpen(false) + setCustomName("") + setCustomServerUrl("") + setCustomToken("") + setCustomHeaderName("") + setCustomExtraHeaders([]) + setCustomAdvancedOpen(false) + } + const connectCustom = async (event: React.FormEvent) => { event.preventDefault() const slug = slugifyMcpName(customName) @@ -479,7 +498,7 @@ export default function CompanyBrainConnections() { return } if (!serverUrl) { - toast.error("Enter an OAuth MCP URL.") + toast.error("Enter an MCP URL.") return } if (apps.some((entry) => entry.slug === slug)) { @@ -490,6 +509,45 @@ export default function CompanyBrainConnections() { const key = `custom:${slug}` setBusy(key) try { + const token = customToken.trim() + if (token) { + const rows = customExtraHeaders + .map((h) => [h.name.trim(), h.value.trim()] as const) + .filter(([name, value]) => name && value) + const duplicate = rows.find( + ([name], i) => + rows.findIndex(([n]) => n.toLowerCase() === name.toLowerCase()) !== + i, + ) + if (duplicate) { + toast.error(`Duplicate header: ${duplicate[0]}`) + return + } + const res = await fetch(`${MCP_BASE}/${slug}/connect-static`, { + method: "POST", + credentials: "include", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + serverUrl, + token, + headerName: customHeaderName.trim() || undefined, + extraHeaders: Object.fromEntries(rows), + shared: false, + }), + }) + const data = (await res.json().catch(() => ({}))) as { + ok?: boolean + error?: string + } + if (!res.ok || !data.ok) { + toast.error(data.error ?? "Couldn't connect.") + return + } + toast.success(`${slug} connected.`) + resetCustomForm() + await load() + return + } const res = await fetch(`${MCP_BASE}/${slug}/connect`, { method: "POST", credentials: "include", @@ -511,14 +569,10 @@ export default function CompanyBrainConnections() { } if (data.authUrl) { window.open(data.authUrl, "_blank", "noopener") - setCustomOpen(false) - setCustomName("") - setCustomServerUrl("") + resetCustomForm() } else if (data.ok) { toast.success(`${slug} connected.`) - setCustomOpen(false) - setCustomName("") - setCustomServerUrl("") + resetCustomForm() await load() } else { toast.error(data.error ?? "Couldn't start the custom connection.") @@ -680,7 +734,13 @@ export default function CompanyBrainConnections() { )} - + {/* Reset on every close path so the API key never lingers in state. */} + + open ? setCustomOpen(true) : resetCustomForm() + } + > - Custom MCP server + Add custom connector

- Add a personal OAuth MCP server by URL. + Connect your Brain to any remote MCP server. Signs in with OAuth + unless you add an API key below.

setCustomName(event.target.value)} placeholder="Name" - className="h-9 w-full rounded-full border border-[#1E293B] bg-[#0D121A] px-3.5 text-[13px] font-medium text-[#FAFAFA] outline-none placeholder:text-[#5F6673] focus:border-[#334155]" + className={customInputClass} /> setCustomServerUrl(event.target.value)} - placeholder="https://example.com/mcp" - className="h-9 w-full rounded-full border border-[#1E293B] bg-[#0D121A] px-3.5 text-[13px] font-medium text-[#FAFAFA] outline-none placeholder:text-[#5F6673] focus:border-[#334155]" + placeholder="Remote MCP server URL" + className={customInputClass} /> -
+ + + + {customAdvancedOpen && ( +
+ setCustomToken(event.target.value)} + type="password" + placeholder="API key (optional)" + className={customInputClass} + /> + setCustomHeaderName(event.target.value)} + placeholder="Send key as header (default: Authorization)" + className={customInputClass} + /> + {customExtraHeaders.length > 0 && ( +

+ Extra headers +

+ )} + {customExtraHeaders.map((header, index) => ( +
+ + setCustomExtraHeaders((prev) => + prev.map((h, i) => + i === index + ? { ...h, name: event.target.value } + : h, + ), + ) + } + placeholder="Name" + className={customInputClass} + /> + + setCustomExtraHeaders((prev) => + prev.map((h, i) => + i === index + ? { ...h, value: event.target.value } + : h, + ), + ) + } + placeholder="Value" + className={customInputClass} + /> + +
+ ))} + +
+ )} + +

+ Only connect servers you trust. Supermemory can't verify which + tools a server exposes or that they won't change. +

+ +
+ )} - Connect + Add