From 72ad558240e105f4b409212e7ec9a3ffadbfb516 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 19 Jan 2026 19:50:07 +0530 Subject: [PATCH] refactor: consolidate MCP connector handling in UI components - Replaced MCPConnectorListView with ConnectorAccountsListView for better integration and UI consistency. - Updated ConnectorCard to display connector counts for MCP connectors. - Enhanced MCPConnectForm to improve connection testing feedback and error handling. - Streamlined MCPConfig validation logic and improved user feedback for configuration errors. - Adjusted AllConnectorsTab to count and display MCP connectors accurately. - Removed redundant MCP-specific components to simplify the codebase. --- .../assistant-ui/connector-popup.tsx | 23 ++- .../components/connector-card.tsx | 24 ++- .../components/mcp-connect-form.tsx | 96 +++--------- .../components/mcp-config.tsx | 69 +++++---- .../views/connector-edit-view.tsx | 2 +- .../hooks/use-connector-dialog.ts | 4 +- .../tabs/all-connectors-tab.tsx | 9 +- .../utils/mcp-config-validator.ts | 17 +- .../views/connector-accounts-list-view.tsx | 49 +++++- .../views/mcp-connector-list-view.tsx | 145 ------------------ 10 files changed, 148 insertions(+), 290 deletions(-) delete mode 100644 surfsense_web/components/assistant-ui/connector-popup/views/mcp-connector-list-view.tsx diff --git a/surfsense_web/components/assistant-ui/connector-popup.tsx b/surfsense_web/components/assistant-ui/connector-popup.tsx index 90113707e..a366bd37f 100644 --- a/surfsense_web/components/assistant-ui/connector-popup.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup.tsx @@ -22,7 +22,6 @@ import { useIndexingConnectors } from "./connector-popup/hooks/use-indexing-conn import { ActiveConnectorsTab } from "./connector-popup/tabs/active-connectors-tab"; import { AllConnectorsTab } from "./connector-popup/tabs/all-connectors-tab"; import { ConnectorAccountsListView } from "./connector-popup/views/connector-accounts-list-view"; -import { MCPConnectorListView } from "./connector-popup/views/mcp-connector-list-view"; import { YouTubeCrawlerView } from "./connector-popup/views/youtube-crawler-view"; export const ConnectorIndicator: FC = () => { @@ -177,18 +176,16 @@ export const ConnectorIndicator: FC = () => { {isYouTubeView && searchSpaceId ? ( ) : viewingMCPList ? ( -
- c.connector_type === "MCP_CONNECTOR" - ) as SearchSourceConnector[] - } - onAddNew={handleAddNewMCPFromList} - onManageConnector={handleStartEdit} - onBack={handleBackFromMCPList} - /> -
+ ) : viewingAccountsType ? ( void; onManage?: () => void; @@ -46,10 +48,12 @@ export const ConnectorCard: FC = ({ isConnecting = false, documentCount, accountCount, + connectorCount, isIndexing = false, onConnect, onManage, }) => { + const isMCP = connectorType === EnumConnectorName.MCP_CONNECTOR; // Get connector status const { getConnectorStatus, isConnectorEnabled, getConnectorStatusMessage, shouldShowWarnings } = useConnectorStatus(); @@ -112,13 +116,21 @@ export const ConnectorCard: FC = ({

) : isConnected ? (

- {formatDocumentCount(documentCount)} - {accountCount !== undefined && accountCount > 0 && ( + {isMCP && connectorCount !== undefined ? ( + + {connectorCount} {connectorCount === 1 ? "server" : "servers"} + + ) : ( <> - - - {accountCount} {accountCount === 1 ? "Account" : "Accounts"} - + {formatDocumentCount(documentCount)} + {accountCount !== undefined && accountCount > 0 && ( + <> + + + {accountCount} {accountCount === 1 ? "Account" : "Accounts"} + + + )} )}

diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/mcp-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/mcp-connect-form.tsx index 68ec12610..17c6245bd 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/mcp-connect-form.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/mcp-connect-form.tsx @@ -1,20 +1,18 @@ "use client"; -import { CheckCircle2, ChevronDown, ChevronUp, Server, XCircle } from "lucide-react"; +import { Server } from "lucide-react"; import { type FC, useRef, useState } from "react"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { toast } from "sonner"; +import { Alert, AlertDescription } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { EnumConnectorName } from "@/contracts/enums/connector"; -import type { MCPToolDefinition } from "@/contracts/types/mcp.types"; import type { ConnectFormProps } from ".."; import { extractServerName, parseMCPConfig, testMCPConnection, - type MCPConnectionTestResult, } from "../../utils/mcp-config-validator"; export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) => { @@ -22,8 +20,6 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) const [configJson, setConfigJson] = useState(""); const [jsonError, setJsonError] = useState(null); const [isTesting, setIsTesting] = useState(false); - const [showDetails, setShowDetails] = useState(false); - const [testResult, setTestResult] = useState(null); const DEFAULT_CONFIG = JSON.stringify( { @@ -69,19 +65,26 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) const handleTestConnection = async () => { const serverConfig = parseConfig(); if (!serverConfig) { - setTestResult({ - status: "error", - message: jsonError || "Invalid configuration", - tools: [], + toast.error("Invalid configuration", { + description: jsonError || "Please check your MCP server configuration JSON.", }); return; } setIsTesting(true); - setTestResult(null); const result = await testMCPConnection(serverConfig); - setTestResult(result); + + if (result.status === "success") { + toast.success("Connection Successful", { + description: result.message, + }); + } else { + toast.error("Connection Failed", { + description: result.message, + }); + } + setIsTesting(false); }; @@ -143,7 +146,7 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) className={`font-mono text-xs ${jsonError ? "border-red-500" : ""}`} /> {jsonError && ( -

JSON Error: {jsonError}

+

{jsonError}

)}

Paste a single MCP server configuration. Must include: name, command, args (optional), env (optional), transport (optional). @@ -158,72 +161,9 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) variant="outline" className="w-full" > - {isTesting ? "Testing Connection..." : "Test Connection"} + {isTesting ? "Testing Connection" : "Test Connection"} - - {testResult && ( - - {testResult.status === "success" ? ( - - ) : ( - - )} -

-
- - {testResult.status === "success" ? "Connection Successful" : "Connection Failed"} - - {testResult.tools.length > 0 && ( - - )} -
- - {testResult.message} - {showDetails && testResult.tools.length > 0 && ( -
-

- Available tools: -

-
    - {testResult.tools.map((tool, i) => ( -
  • {tool.name}
  • - ))} -
-
- )} -
-
- - )} diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/mcp-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/mcp-config.tsx index a0868e2f5..3e306f303 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/mcp-config.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/mcp-config.tsx @@ -22,23 +22,6 @@ interface MCPConfigProps extends ConnectorConfigProps { } export const MCPConfig: FC = ({ connector, onConfigChange, onNameChange }) => { - // Validate that this is an MCP connector - if (connector.connector_type !== EnumConnectorName.MCP_CONNECTOR) { - console.error( - "MCPConfig received non-MCP connector:", - connector.connector_type - ); - return ( - - - Invalid Connector Type - - This component can only be used with MCP connectors. - - - ); - } - const [name, setName] = useState(""); const [configJson, setConfigJson] = useState(""); const [jsonError, setJsonError] = useState(null); @@ -63,8 +46,24 @@ export const MCPConfig: FC = ({ connector, onConfigChange, onNam }; setConfigJson(JSON.stringify(configObj, null, 2)); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); // Only run on mount to preserve user edits + }, []); + + // Validate that this is an MCP connector (after hooks) + if (connector.connector_type !== EnumConnectorName.MCP_CONNECTOR) { + console.error( + "MCPConfig received non-MCP connector:", + connector.connector_type + ); + return ( + + + Invalid Connector Type + + This component can only be used with MCP connectors. + + + ); + } const handleNameChange = (value: string) => { setName(value); @@ -126,15 +125,21 @@ export const MCPConfig: FC = ({ connector, onConfigChange, onNam return (
{/* Server Name */} -
- - handleNameChange(e.target.value)} - placeholder="e.g., Filesystem Server" - required - /> +
+
+ + handleNameChange(e.target.value)} + placeholder="e.g., Filesystem Server" + className="border-slate-400/20 focus-visible:border-slate-400/40" + required + /> +

+ A friendly name to identify this connector. +

+
{/* Server Configuration */} @@ -168,8 +173,8 @@ export const MCPConfig: FC = ({ connector, onConfigChange, onNam type="button" onClick={handleTestConnection} disabled={isTesting} - variant="outline" - className="w-full" + variant="secondary" + className="w-full h-8 text-[13px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80" > {isTesting ? "Testing Connection..." : "Test Connection"} @@ -228,8 +233,8 @@ export const MCPConfig: FC = ({ connector, onConfigChange, onNam Available tools:

    - {testResult.tools.map((tool, i) => ( -
  • {tool.name}
  • + {testResult.tools.map((tool) => ( +
  • {tool.name}
  • ))}
diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx index 515a3a47b..80d364f27 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx @@ -151,7 +151,7 @@ export const ConnectorEditView: FC = ({

- {connector.connector_type === "MCP_CONNECTOR" ? "MCP Server" : connector.name} + {connector.name}

Manage your connector settings and sync configuration 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 7ac0d3e0f..b7fb9880a 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 @@ -645,7 +645,7 @@ export const useConnectorDialog = () => { }); const successMessage = currentConnectorType === "MCP_CONNECTOR" - ? `${connector.name} MCP server added successfully` + ? `${connector.name} added successfully` : `${connectorTitle} connected and indexing started!`; toast.success(successMessage, { description: periodicEnabledForIndexing @@ -709,7 +709,7 @@ export const useConnectorDialog = () => { } else { // Other non-indexable connectors - just show success message and close const successMessage = currentConnectorType === "MCP_CONNECTOR" - ? `${connector.name} MCP server added successfully` + ? `${connector.name} added successfully` : `${connectorTitle} connected successfully!`; toast.success(successMessage); diff --git a/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx b/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx index d358037c2..260d6c926 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx @@ -1,6 +1,7 @@ "use client"; import type { FC } from "react"; +import { EnumConnectorName } from "@/contracts/enums/connector"; import type { SearchSourceConnector } from "@/contracts/types/connector.types"; import { ConnectorCard } from "../components/connector-card"; import { CRAWLERS, OAUTH_CONNECTORS, OTHER_CONNECTORS } from "../constants/connector-constants"; @@ -162,6 +163,12 @@ export const AllConnectorsTab: FC = ({ ); const isIndexing = actualConnector && indexingConnectorIds?.has(actualConnector.id); + // For MCP connectors, count total MCP connectors instead of document count + const isMCP = connector.connectorType === EnumConnectorName.MCP_CONNECTOR; + const mcpConnectorCount = isMCP && allConnectors + ? allConnectors.filter((c: SearchSourceConnector) => c.connector_type === EnumConnectorName.MCP_CONNECTOR).length + : undefined; + const handleConnect = onConnectNonOAuth ? () => onConnectNonOAuth(connector.connectorType) : () => {}; // Fallback - connector popup should handle all connector types @@ -176,7 +183,7 @@ export const AllConnectorsTab: FC = ({ isConnected={isConnected} isConnecting={isConnecting} documentCount={documentCount} - + connectorCount={mcpConnectorCount} isIndexing={isIndexing} onConnect={handleConnect} onManage={ diff --git a/surfsense_web/components/assistant-ui/connector-popup/utils/mcp-config-validator.ts b/surfsense_web/components/assistant-ui/connector-popup/utils/mcp-config-validator.ts index dea547be7..36afc44f0 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/utils/mcp-config-validator.ts +++ b/surfsense_web/components/assistant-ui/connector-popup/utils/mcp-config-validator.ts @@ -131,19 +131,24 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult => // Replace technical error messages with user-friendly ones if (errorMsg.includes("expected string, received undefined")) { - errorMsg = "This field is required"; + errorMsg = fieldPath + ? `The '${fieldPath}' field is required` + : "This field is required"; } else if (errorMsg.includes("Invalid input")) { - errorMsg = "Invalid value"; + errorMsg = fieldPath + ? `The '${fieldPath}' field has an invalid value` + : "Invalid value"; + } else if (fieldPath && !errorMsg.toLowerCase().includes(fieldPath.toLowerCase())) { + // If error message doesn't mention the field name, prepend it + errorMsg = `The '${fieldPath}' field: ${errorMsg}`; } - const formattedError = fieldPath ? `${fieldPath}: ${errorMsg}` : errorMsg; - - console.error('[MCP Validator] ❌ Validation error:', formattedError); + console.error('[MCP Validator] ❌ Validation error:', errorMsg); console.error('[MCP Validator] Full Zod errors:', result.error.issues); return { config: null, - error: formattedError, + error: errorMsg, }; } 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 5f8c1f3ed..2ded83284 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,9 +1,10 @@ "use client"; import { differenceInDays, differenceInMinutes, format, isToday, isYesterday } from "date-fns"; -import { ArrowLeft, Loader2, Plus } from "lucide-react"; +import { ArrowLeft, Loader2, Plus, Server } from "lucide-react"; import type { FC } from "react"; import { Button } from "@/components/ui/button"; +import { EnumConnectorName } from "@/contracts/enums/connector"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import type { SearchSourceConnector } from "@/contracts/types/connector.types"; import { cn } from "@/lib/utils"; @@ -19,6 +20,7 @@ interface ConnectorAccountsListViewProps { onManage: (connector: SearchSourceConnector) => void; onAddAccount: () => void; isConnecting?: boolean; + addButtonText?: string; } /** @@ -70,6 +72,7 @@ export const ConnectorAccountsListView: FC = ({ onManage, onAddAccount, isConnecting = false, + addButtonText, }) => { // Get connector status const { isConnectorEnabled, getConnectorStatusMessage } = useConnectorStatus(); @@ -79,6 +82,20 @@ export const ConnectorAccountsListView: FC = ({ // Filter connectors to only show those of this type const typeConnectors = connectors.filter((c) => c.connector_type === connectorType); + + // Determine button text - default to "Add Account" unless specified + const buttonText = addButtonText || (connectorType === EnumConnectorName.MCP_CONNECTOR ? "Add New MCP Server" : "Add Account"); + const isMCP = connectorType === EnumConnectorName.MCP_CONNECTOR; + + // Helper to get display name for connector (handles MCP server name extraction) + const getDisplayName = (connector: SearchSourceConnector): string => { + if (isMCP) { + // For MCP, extract server name from config if available + const serverName = connector.config?.server_config?.name || connector.name; + return serverName; + } + return getConnectorDisplayName(connector.name); + }; return (

@@ -130,7 +147,7 @@ export const ConnectorAccountsListView: FC = ({ )}
- {isConnecting ? "Connecting" : "Add Account"} + {isConnecting ? "Connecting" : buttonText}
@@ -139,8 +156,27 @@ export const ConnectorAccountsListView: FC = ({ {/* Content */}
{/* Connected Accounts Grid */} -
- {typeConnectors.map((connector) => { + {typeConnectors.length === 0 ? ( +
+
+ {isMCP ? ( + + ) : ( + getConnectorIcon(connectorType, "size-8") + )} +
+

+ {isMCP ? "No MCP Servers" : `No ${connectorTitle} Accounts`} +

+

+ {isMCP + ? "Get started by adding your first Model Context Protocol server" + : `Get started by connecting your first ${connectorTitle} account`} +

+
+ ) : ( +
+ {typeConnectors.map((connector) => { const isIndexing = indexingConnectorIds.has(connector.id); return ( @@ -165,7 +201,7 @@ export const ConnectorAccountsListView: FC = ({

- {getConnectorDisplayName(connector.name)} + {getDisplayName(connector)}

{isIndexing ? (

@@ -193,7 +229,8 @@ export const ConnectorAccountsListView: FC = ({

); })} -
+
+ )} ); diff --git a/surfsense_web/components/assistant-ui/connector-popup/views/mcp-connector-list-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/views/mcp-connector-list-view.tsx deleted file mode 100644 index c826e9fc4..000000000 --- a/surfsense_web/components/assistant-ui/connector-popup/views/mcp-connector-list-view.tsx +++ /dev/null @@ -1,145 +0,0 @@ -"use client"; - -import { Plus, Server, XCircle } from "lucide-react"; -import type { FC } from "react"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { Button } from "@/components/ui/button"; -import { EnumConnectorName } from "@/contracts/enums/connector"; -import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; -import type { SearchSourceConnector } from "@/contracts/types/connector.types"; -import { cn } from "@/lib/utils"; - -interface MCPConnectorListViewProps { - mcpConnectors: SearchSourceConnector[]; - onAddNew: () => void; - onManageConnector: (connector: SearchSourceConnector) => void; - onBack: () => void; -} - -export const MCPConnectorListView: FC = ({ - mcpConnectors, - onAddNew, - onManageConnector, - onBack, -}) => { - // Validate that all connectors are MCP connectors - const invalidConnectors = mcpConnectors.filter( - (c) => c.connector_type !== EnumConnectorName.MCP_CONNECTOR - ); - - if (invalidConnectors.length > 0) { - console.error( - "MCPConnectorListView received non-MCP connectors:", - invalidConnectors.map((c) => c.connector_type) - ); - return ( - - - Invalid Connector Type - - This view can only display MCP connectors. Found {invalidConnectors.length} invalid - connector(s). - - - ); - } - return ( -
- {/* Header */} -
-
- -
-

MCP Connectors

-

- Manage your Model Context Protocol servers -

-
-
-
- - {/* Add New Button */} -
- -
- - {/* MCP Connectors List */} -
- {mcpConnectors.length === 0 ? ( -
-
- -
-

No MCP Servers

-

- Get started by adding your first Model Context Protocol server -

-
- ) : ( - mcpConnectors.map((connector) => { - // Extract server name from config - const serverName = connector.config?.server_config?.name || connector.name; - - return ( -
-
- {getConnectorIcon("MCP_CONNECTOR", "size-6")} -
-
-

- {serverName} -

-
- -
- ); - }) - )} -
-
- ); -};