import type { OpenGeniBrowserClient } from "@opengeni/sdk/browser"; import type { CapabilityCatalogItem, ConnectionMetadata, McpConnectionAccountSelection, McpServerConnectionRef, Session, } from "@opengeni/sdk"; import { normalizeProviderDomain } from "../../lib/capabilities"; function canonicalResource(value: string): string { try { const url = new URL(value); url.hash = ""; url.pathname = url.pathname.replace(/\/+$/u, "") || "/"; return url.toString(); } catch { return value.trim(); } } /** Credential-free projection of the backend-authorized inventory, including inactive accounts. */ export function matchingConnectionAccounts( ref: NativeConnectorAccountRef["connectionRef"] | null | undefined, connections: readonly ConnectionMetadata[], endpoint?: string | null, ) { if (!ref || ref.authoritySource === "host") return []; // Inventory is authorized by the backend. This projection is not a grant, // and must never synthesize accounts from shared catalog metadata. const matches = connections.filter( (entry) => (entry.subjectId === null || entry.authorityId != null) && (!ref.kind || entry.kind === ref.kind) && (ref.connectionId === undefined || entry.id === ref.connectionId) && (!ref.resource || (typeof entry.metadata?.resource === "string" && canonicalResource(entry.metadata.resource) === canonicalResource(ref.resource))) && // An OAuth audience can differ from the transport endpoint. Compare a // saved endpoint only to the catalog endpoint, not to its resource. (!endpoint || typeof entry.metadata?.mcpUrl !== "string" || canonicalResource(entry.metadata.mcpUrl) === canonicalResource(endpoint)) && normalizeProviderDomain(entry.providerDomain) === normalizeProviderDomain(ref.providerDomain), ); return matches; } export type ConnectedAccountGroup = { serverId: string; name: string; accounts: ConnectionMetadata[]; }; /** Missing key follows eligible defaults; an array is an exact, explicit set. */ export type ConnectionAccountChoices = Record; export type NativeConnectorAccountRef = { serverId: string; name: string; connectionRef: Pick< McpServerConnectionRef, | "connectionId" | "authoritySource" | "providerDomain" | "subjectScope" | "resource" | "selectedResources" > & { kind?: string | undefined }; }; export function selectedNativeConnectorRefs( items: readonly CapabilityCatalogItem[], ): NativeConnectorAccountRef[] { return items.flatMap((item) => item.enabled && item.runtime.mcpServerId && item.connectionRef && item.connectionRef.authoritySource !== "host" ? [{ serverId: item.runtime.mcpServerId, name: item.name, connectionRef: item.connectionRef }] : [], ); } export function connectionAccountChoices(selections: McpConnectionAccountSelection[]) { const choices: ConnectionAccountChoices = {}; for (const { serverId, connectionId } of selections) { const ids = (choices[serverId] ??= []); if (!ids.includes(connectionId)) ids.push(connectionId); } return choices; } export function connectedAccountGroups( refs: readonly NativeConnectorAccountRef[], connections: readonly ConnectionMetadata[], ): ConnectedAccountGroup[] { const groups = new Map(); for (const ref of refs) { if (ref.connectionRef.authoritySource === "host") continue; const serverId = ref.serverId; const group = groups.get(serverId) ?? { serverId, name: ref.name, accounts: [] }; for (const account of matchingConnectionAccounts(ref.connectionRef, connections).filter( (entry) => entry.status === "active", )) { if (!group.accounts.some((existing) => existing.id === account.id)) group.accounts.push(account); } groups.set(serverId, group); } return [...groups.values()]; } export async function sessionConnectedAccounts( client: OpenGeniBrowserClient, workspaceId: string, items: CapabilityCatalogItem[], knownConnections?: ConnectionMetadata[], ): Promise { const native = selectedNativeConnectorRefs(items); if (native.length === 0) return []; const connections = knownConnections ?? (await client.listOwnConnectionAccounts(workspaceId)); return connectedAccountGroups(native, connections); } export function selectedConnectionAccounts( groups: ConnectedAccountGroup[], choices: ConnectionAccountChoices, ): { selections: McpConnectionAccountSelection[]; unresolved: ConnectedAccountGroup[] } { const selections: McpConnectionAccountSelection[] = []; const unresolved: ConnectedAccountGroup[] = []; for (const group of groups) { // A workspace default can name a connector the sender has never connected. // No eligible account means no access, not an account choice to resolve. // Keep explicit empty/stale choices blocked: omission must not widen them. if (choices[group.serverId] === undefined && group.accounts.length === 0) continue; const chosen = choices[group.serverId] ?? group.accounts.map((account) => account.id); // Until an explicit empty-set wire representation exists, fail closed: // never submit an omission that the backend might interpret as defaults. if ( !chosen.length || chosen.some((id) => !group.accounts.some((account) => account.id === id)) ) { unresolved.push(group); continue; } for (const connectionId of new Set(chosen)) selections.push({ serverId: group.serverId, connectionId }); } return { selections, unresolved }; } export async function sessionConnectionAccounts( client: OpenGeniBrowserClient, session: Pick, items: CapabilityCatalogItem[], knownConnections?: ConnectionMetadata[], ): Promise { const groups = await sessionConnectedAccounts( client, session.workspaceId, items, knownConnections, ); const { selections, unresolved } = selectedConnectionAccounts(groups, {}); if (unresolved.length) throw new Error(`Check accounts for ${unresolved[0]!.name} before sending.`); return selections; }