From 5327f3348c48a8cc925f84839326ce6a511dd6f9 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Fri, 15 May 2026 16:40:16 +0200 Subject: [PATCH] connector-popup: surface trusted-tools UI in MCP edit view; consolidate disconnect - Slot MCPTrustedTools in mcp-service-config (gated on connector.id > 0) so any connected MCP-backed connector exposes a revoke surface for approve_always grants. - Add new mcp-trusted-tools.tsx (audit + revoke list) and connectorsApiService.untrustMCPTool() that backs it. - Drop the redundant row-level Disconnect from ConnectorAccountsListView: Manage now leads to the edit view whose own Disconnect is the single source of truth. Remove the now-dead onDisconnect prop, confirm-flow state, and handleDisconnectFromList hook callback + return entry. --- .../assistant-ui/connector-popup.tsx | 7 -- .../components/mcp-service-config.tsx | 5 +- .../components/mcp-trusted-tools.tsx | 89 +++++++++++++++++++ .../hooks/use-connector-dialog.ts | 20 ----- .../views/connector-accounts-list-view.tsx | 51 +---------- .../lib/apis/connectors-api.service.ts | 7 ++ 6 files changed, 101 insertions(+), 78 deletions(-) create mode 100644 surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/mcp-trusted-tools.tsx diff --git a/surfsense_web/components/assistant-ui/connector-popup.tsx b/surfsense_web/components/assistant-ui/connector-popup.tsx index 32943142a..84361e25b 100644 --- a/surfsense_web/components/assistant-ui/connector-popup.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup.tsx @@ -124,7 +124,6 @@ export const ConnectorIndicator = forwardRef - handleDisconnectFromList(connector, () => refreshConnectors()) - } onAddAccount={handleAddNewMCPFromList} addButtonText="Add New MCP Server" /> @@ -247,9 +243,6 @@ export const ConnectorIndicator = forwardRef - handleDisconnectFromList(connector, () => refreshConnectors()) - } onAddAccount={() => { // Check both OAUTH_CONNECTORS and COMPOSIO_CONNECTORS const oauthConnector = diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/mcp-service-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/mcp-service-config.tsx index 71d0e31a8..cfa2cde38 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/mcp-service-config.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/mcp-service-config.tsx @@ -3,6 +3,7 @@ import { CheckCircle2 } from "lucide-react"; import type { FC } from "react"; import type { ConnectorConfigProps } from "../index"; +import { MCPTrustedTools } from "./mcp-trusted-tools"; export const MCPServiceConfig: FC = ({ connector }) => { const serviceName = connector.config?.mcp_service as string | undefined; @@ -11,7 +12,7 @@ export const MCPServiceConfig: FC = ({ connector }) => { : "this service"; return ( -
+
@@ -23,6 +24,8 @@ export const MCPServiceConfig: FC = ({ connector }) => {

+ + {connector.id > 0 && }
); }; diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/mcp-trusted-tools.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/mcp-trusted-tools.tsx new file mode 100644 index 000000000..ed01511ca --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/mcp-trusted-tools.tsx @@ -0,0 +1,89 @@ +"use client"; + +import { ShieldCheck, Trash2 } from "lucide-react"; +import type { FC } from "react"; +import { useState } from "react"; +import { toast } from "sonner"; +import { Button } from "@/components/ui/button"; +import type { SearchSourceConnector } from "@/contracts/types/connector.types"; +import { connectorsApiService } from "@/lib/apis/connectors-api.service"; + +interface MCPTrustedToolsProps { + connector: SearchSourceConnector; +} + +/** Audit + revoke surface for tools promoted via in-chat "Always Allow". */ +export const MCPTrustedTools: FC = ({ connector }) => { + const trustedTools = readTrustedTools(connector.config); + const [pending, setPending] = useState>(new Set()); + + const handleRevoke = async (toolName: string) => { + setPending((prev) => new Set(prev).add(toolName)); + try { + await connectorsApiService.untrustMCPTool(connector.id, toolName); + toast.success(`Removed ${toolName} from trusted tools`); + } catch { + toast.error(`Failed to remove ${toolName} from trusted tools`); + } finally { + setPending((prev) => { + const next = new Set(prev); + next.delete(toolName); + return next; + }); + } + }; + + return ( +
+

+ + Trusted Tools +

+ +
+

+ Tools listed here skip the approval prompt during chat. Trust is granted by clicking + "Always Allow" on an approval card; revoke it here to require approval again. +

+ + {trustedTools.length === 0 ? ( +

+ No trusted tools yet for this connector. +

+ ) : ( +
    + {trustedTools.map((toolName) => { + const isPending = pending.has(toolName); + return ( +
  • + {toolName} + +
  • + ); + })} +
+ )} +
+
+ ); +}; + +function readTrustedTools(config: Record | undefined | null): string[] { + const raw = config?.trusted_tools; + if (!Array.isArray(raw)) return []; + return raw.filter((item): item is string => typeof item === "string"); +} diff --git a/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts b/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts index ed9bf70a8..b49bfda96 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts +++ b/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts @@ -1288,25 +1288,6 @@ export const useConnectorDialog = () => { [editingConnector, searchSpaceId, deleteConnector, cameFromMCPList, setIsOpen] ); - const handleDisconnectFromList = useCallback( - async (connector: SearchSourceConnector, refreshConnectors: () => void) => { - if (!searchSpaceId) return; - try { - await deleteConnector({ id: connector.id }); - trackConnectorDeleted(Number(searchSpaceId), connector.connector_type, connector.id); - toast.success(`${connector.name} disconnected successfully`); - refreshConnectors(); - queryClient.invalidateQueries({ - queryKey: cacheKeys.logs.summary(Number(searchSpaceId)), - }); - } catch (error) { - console.error("Error disconnecting connector:", error); - toast.error("Failed to disconnect connector"); - } - }, - [searchSpaceId, deleteConnector] - ); - // Handle quick index (index with selected date range, or backend defaults if none selected) const handleQuickIndexConnector = useCallback( async ( @@ -1480,7 +1461,6 @@ export const useConnectorDialog = () => { handleStartEdit, handleSaveConnector, handleDisconnectConnector, - handleDisconnectFromList, handleBackFromEdit, handleBackFromConnect, handleBackFromYouTube, diff --git a/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx index 8aee7e005..f6291b64d 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx @@ -1,7 +1,7 @@ "use client"; import { useAtomValue } from "jotai"; -import { ArrowLeft, Plus, RefreshCw, Server, Trash2 } from "lucide-react"; +import { ArrowLeft, Plus, RefreshCw, Server } from "lucide-react"; import { type FC, useCallback, useState } from "react"; import { toast } from "sonner"; import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; @@ -24,7 +24,6 @@ interface ConnectorAccountsListViewProps { indexingConnectorIds: Set; onBack: () => void; onManage: (connector: SearchSourceConnector) => void; - onDisconnect?: (connector: SearchSourceConnector) => Promise | void; onAddAccount: () => void; isConnecting?: boolean; addButtonText?: string; @@ -37,15 +36,12 @@ export const ConnectorAccountsListView: FC = ({ indexingConnectorIds, onBack, onManage, - onDisconnect, onAddAccount, isConnecting = false, addButtonText, }) => { const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); const [reauthingId, setReauthingId] = useState(null); - const [confirmDisconnectId, setConfirmDisconnectId] = useState(null); - const [disconnectingId, setDisconnectingId] = useState(null); // Get connector status const { isConnectorEnabled, getConnectorStatusMessage } = useConnectorStatus(); @@ -240,51 +236,6 @@ export const ConnectorAccountsListView: FC = ({ /> Re-authenticate - ) : isLive && onDisconnect ? ( - confirmDisconnectId === connector.id ? ( -
- - -
- ) : ( - - ) ) : (