From 48603b993d77624fefe1f669a184dd002860d890 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 19 Jan 2026 17:59:06 +0530 Subject: [PATCH 01/11] feat: Update MCP connector icon and UI styling --- .../connect-forms/components/mcp-connect-form.tsx | 10 ++++++---- surfsense_web/contracts/enums/connectorIcons.tsx | 2 +- .../public/connectors/modelcontextprotocol.svg | 1 + 3 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 surfsense_web/public/connectors/modelcontextprotocol.svg 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 a671c91e8..68ec12610 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 @@ -122,10 +122,12 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) return (
- - - Connect to an MCP (Model Context Protocol) server. Each MCP server is added as a separate connector. - +
+ + + Connect to an MCP (Model Context Protocol) server. Each MCP server is added as a separate connector. + +
diff --git a/surfsense_web/contracts/enums/connectorIcons.tsx b/surfsense_web/contracts/enums/connectorIcons.tsx index 5f147b63b..9350b6a1e 100644 --- a/surfsense_web/contracts/enums/connectorIcons.tsx +++ b/surfsense_web/contracts/enums/connectorIcons.tsx @@ -65,7 +65,7 @@ export const getConnectorIcon = (connectorType: EnumConnectorName | string, clas case EnumConnectorName.CIRCLEBACK_CONNECTOR: return ; case EnumConnectorName.MCP_CONNECTOR: - return ; + return MCP; // Additional cases for non-enum connector types case "YOUTUBE_CONNECTOR": return YouTube; diff --git a/surfsense_web/public/connectors/modelcontextprotocol.svg b/surfsense_web/public/connectors/modelcontextprotocol.svg new file mode 100644 index 000000000..e9c3fa46e --- /dev/null +++ b/surfsense_web/public/connectors/modelcontextprotocol.svg @@ -0,0 +1 @@ + \ No newline at end of file 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 02/11] 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} -

-
- -
- ); - }) - )} -
-
- ); -}; From 0f3e1b118d4b4b5f03dec7a7823ee1666226571f Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 19 Jan 2026 20:21:02 +0530 Subject: [PATCH 03/11] fix: revert back the alert when clicking "Test Connection" button when adding an MCP, remove toast --- .../components/mcp-connect-form.tsx | 96 +++++++++++++++---- .../components/mcp-config.tsx | 2 +- 2 files changed, 79 insertions(+), 19 deletions(-) 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 17c6245bd..b619b84e1 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,9 +1,8 @@ "use client"; -import { Server } from "lucide-react"; +import { CheckCircle2, ChevronDown, ChevronUp, Server, XCircle } from "lucide-react"; import { type FC, useRef, useState } from "react"; -import { toast } from "sonner"; -import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; @@ -13,6 +12,7 @@ import { extractServerName, parseMCPConfig, testMCPConnection, + type MCPConnectionTestResult, } from "../../utils/mcp-config-validator"; export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) => { @@ -20,6 +20,8 @@ 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( { @@ -65,26 +67,19 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) const handleTestConnection = async () => { const serverConfig = parseConfig(); if (!serverConfig) { - toast.error("Invalid configuration", { - description: jsonError || "Please check your MCP server configuration JSON.", + setTestResult({ + status: "error", + message: jsonError || "Invalid configuration", + tools: [], }); return; } setIsTesting(true); + setTestResult(null); const result = await testMCPConnection(serverConfig); - - if (result.status === "success") { - toast.success("Connection Successful", { - description: result.message, - }); - } else { - toast.error("Connection Failed", { - description: result.message, - }); - } - + setTestResult(result); setIsTesting(false); }; @@ -153,17 +148,82 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting })

+ {/* Test Connection */}
+ + {/* Test Result */} + {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) => ( +
  • {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 3e306f303..f7ca114ca 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 @@ -176,7 +176,7 @@ export const MCPConfig: FC = ({ connector, onConfigChange, onNam 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"} + {isTesting ? "Testing Connection" : "Test Connection"} From 5f672a07f2fab913894dfa4e52d3037831d20ca5 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 19 Jan 2026 20:41:26 +0530 Subject: [PATCH 04/11] refactor: enhance MCP connection forms and account list styling - Updated MCPConnectForm and MCPConfig to improve layout and responsiveness. - Adjusted text sizes and spacing for better readability across different screen sizes. - Enhanced button labels for clarity when showing or hiding details. - Improved styling for the ConnectorAccountsListView to ensure consistent appearance and user experience. --- .../components/mcp-connect-form.tsx | 18 ++++++++++-------- .../components/mcp-config.tsx | 10 ++++++---- .../views/connector-accounts-list-view.tsx | 12 ++++++------ 3 files changed, 22 insertions(+), 18 deletions(-) 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 b619b84e1..d67b9b49c 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 @@ -176,8 +176,8 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) )}
-
- +
+ {testResult.status === "success" ? "Connection Successful" : "Connection Failed"} {testResult.tools.length > 0 && ( @@ -185,7 +185,7 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) type="button" variant="ghost" size="sm" - className="h-6 px-2" + className="h-6 px-2 self-start sm:self-auto text-xs" onClick={(e) => { e.preventDefault(); e.stopPropagation(); @@ -195,25 +195,27 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) {showDetails ? ( <> - Hide Details + Hide Details + Hide ) : ( <> - Show Details + Show Details + Show )} )}
- + {testResult.message} {showDetails && testResult.tools.length > 0 && (
-

+

Available tools:

-
    +
      {testResult.tools.map((tool) => (
    • {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 f7ca114ca..94696581b 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 @@ -195,7 +195,7 @@ export const MCPConfig: FC = ({ connector, onConfigChange, onNam )}
      -
      +
      {testResult.status === "success" ? "Connection Successful" : "Connection Failed"} @@ -204,7 +204,7 @@ export const MCPConfig: FC = ({ connector, onConfigChange, onNam type="button" variant="ghost" size="sm" - className="h-6 px-2" + className="h-6 px-2 self-start sm:self-auto text-xs" onClick={(e) => { e.preventDefault(); e.stopPropagation(); @@ -214,12 +214,14 @@ export const MCPConfig: FC = ({ connector, onConfigChange, onNam {showDetails ? ( <> - Hide Details + Hide Details + Hide ) : ( <> - Show Details + Show Details + Show )} 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 2ded83284..fc1f76f66 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 @@ -132,21 +132,21 @@ export const ConnectorAccountsListView: FC = ({ onClick={onAddAccount} disabled={isConnecting || !isEnabled} className={cn( - "flex items-center gap-1.5 sm:gap-2 px-2 sm:px-3 py-1.5 sm:py-2 rounded-lg border-2 border-dashed text-left transition-all duration-200 shrink-0 self-center sm:self-auto sm:w-auto", + "flex items-center justify-center gap-1.5 h-8 px-3 rounded-md border-2 border-dashed text-xs sm:text-sm transition-all duration-200 shrink-0 w-full sm:w-auto", !isEnabled ? "border-border/30 opacity-50 cursor-not-allowed" - : "border-primary/50 hover:bg-primary/5", + : "border-slate-400/20 dark:border-white/20 hover:bg-primary/5", isConnecting && "opacity-50 cursor-not-allowed" )} > -
      +
      {isConnecting ? ( - + ) : ( - + )}
      - + {isConnecting ? "Connecting" : buttonText} From f0997b0a30a1da7e66bbf0deb83f61d809560a0a Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 19 Jan 2026 21:23:22 +0530 Subject: [PATCH 05/11] chore: ran frontend linting --- .../assistant-ui/connector-popup.tsx | 6 +- .../components/mcp-connect-form.tsx | 36 ++--- .../components/mcp-config.tsx | 34 ++--- .../views/connector-connect-view.tsx | 11 +- .../views/connector-edit-view.tsx | 3 +- .../hooks/use-connector-dialog.ts | 130 +++++++++--------- .../tabs/active-connectors-tab.tsx | 7 +- .../tabs/all-connectors-tab.tsx | 11 +- .../utils/mcp-config-validator.ts | 48 +++---- .../views/connector-accounts-list-view.tsx | 96 ++++++------- .../lib/apis/connectors-api.service.ts | 10 +- 11 files changed, 199 insertions(+), 193 deletions(-) diff --git a/surfsense_web/components/assistant-ui/connector-popup.tsx b/surfsense_web/components/assistant-ui/connector-popup.tsx index a366bd37f..5ad520b05 100644 --- a/surfsense_web/components/assistant-ui/connector-popup.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup.tsx @@ -101,7 +101,8 @@ export const ConnectorIndicator: FC = () => { // Fallback to API if Electric is not available or fails // Use Electric data if: 1) we have data, or 2) still loading without error // Use API data if: Electric failed (has error) or finished loading with no data - const useElectricData = connectorsFromElectric.length > 0 || (connectorsLoading && !connectorsError); + const useElectricData = + connectorsFromElectric.length > 0 || (connectorsLoading && !connectorsError); const connectors = useElectricData ? connectorsFromElectric : allConnectors || []; // Manual refresh function that works with both Electric and API @@ -129,7 +130,7 @@ export const ConnectorIndicator: FC = () => { const hasConnectors = connectors.length > 0; const hasSources = hasConnectors || activeDocumentTypes.length > 0; const totalSourceCount = connectors.length + activeDocumentTypes.length; - + const activeConnectorsCount = connectors.length; // Check which connectors are already connected @@ -226,7 +227,6 @@ export const ConnectorIndicator: FC = () => { isDisconnecting={isDisconnecting} isIndexing={indexingConnectorIds.has(editingConnector.id)} searchSpaceId={searchSpaceId?.toString()} - onStartDateChange={setStartDate} onEndDateChange={setEndDate} onPeriodicEnabledChange={setPeriodicEnabled} 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 d67b9b49c..92b87f124 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 @@ -49,12 +49,12 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) const handleConfigChange = (value: string) => { setConfigJson(value); - + // Clear previous error if (jsonError) { setJsonError(null); } - + // Validate immediately to show errors as user types (with debouncing via parseMCPConfig cache) if (value.trim()) { const result = parseMCPConfig(value); @@ -120,13 +120,14 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) return (
      -
      - - - Connect to an MCP (Model Context Protocol) server. Each MCP server is added as a separate connector. - -
      -
      +
      + + + Connect to an MCP (Model Context Protocol) server. Each MCP server is added as a + separate connector. + +
      +
      @@ -140,11 +141,10 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) rows={16} className={`font-mono text-xs ${jsonError ? "border-red-500" : ""}`} /> - {jsonError && ( -

      {jsonError}

      - )} + {jsonError &&

      {jsonError}

      }

      - Paste a single MCP server configuration. Must include: name, command, args (optional), env (optional), transport (optional). + Paste a single MCP server configuration. Must include: name, command, args (optional), + env (optional), transport (optional).

      @@ -178,7 +178,9 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting })
      - {testResult.status === "success" ? "Connection Successful" : "Connection Failed"} + {testResult.status === "success" + ? "Connection Successful" + : "Connection Failed"} {testResult.tools.length > 0 && (
      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 80d364f27..5433acbf7 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.name} + {connector.name}

      Manage your connector settings and sync configuration @@ -200,7 +200,6 @@ export const ConnectorEditView: FC = ({ onConfigChange={onConfigChange} onNameChange={onNameChange} searchSpaceId={searchSpaceId} - /> )} 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 b7fb9880a..4f56f588d 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 @@ -540,18 +540,18 @@ export const useConnectorDialog = () => { data: { ...connectorData, connector_type: connectorData.connector_type as EnumConnectorName, - is_active: true, - next_scheduled_at: connectorData.next_scheduled_at as string | null, - }, - queryParams: { - search_space_id: searchSpaceId, - }, - }); + is_active: true, + next_scheduled_at: connectorData.next_scheduled_at as string | null, + }, + queryParams: { + search_space_id: searchSpaceId, + }, + }); - // Refetch connectors to get the new one - const result = await refetchAllConnectors(); - if (result.data) { - const connector = result.data.find( + // Refetch connectors to get the new one + const result = await refetchAllConnectors(); + if (result.data) { + const connector = result.data.find( (c: SearchSourceConnector) => c.id === newConnector.id ); if (connector) { @@ -644,34 +644,35 @@ export const useConnectorDialog = () => { }, }); - const successMessage = currentConnectorType === "MCP_CONNECTOR" - ? `${connector.name} added successfully` - : `${connectorTitle} connected and indexing started!`; - toast.success(successMessage, { - description: periodicEnabledForIndexing - ? `Periodic sync enabled every ${getFrequencyLabel(frequencyMinutesForIndexing)}.` - : "You can continue working while we sync your data.", - }); + const successMessage = + currentConnectorType === "MCP_CONNECTOR" + ? `${connector.name} added successfully` + : `${connectorTitle} connected and indexing started!`; + toast.success(successMessage, { + description: periodicEnabledForIndexing + ? `Periodic sync enabled every ${getFrequencyLabel(frequencyMinutesForIndexing)}.` + : "You can continue working while we sync your data.", + }); - const url = new URL(window.location.href); - url.searchParams.delete("modal"); - url.searchParams.delete("tab"); - url.searchParams.delete("view"); - url.searchParams.delete("connectorType"); - router.replace(url.pathname + url.search, { scroll: false }); + const url = new URL(window.location.href); + url.searchParams.delete("modal"); + url.searchParams.delete("tab"); + url.searchParams.delete("view"); + url.searchParams.delete("connectorType"); + router.replace(url.pathname + url.search, { scroll: false }); - // Clear indexing config state since we're not showing the view - setIndexingConfig(null); - setIndexingConnector(null); - setIndexingConnectorConfig(null); + // Clear indexing config state since we're not showing the view + setIndexingConfig(null); + setIndexingConnector(null); + setIndexingConnectorConfig(null); - // Invalidate queries to refresh data - queryClient.invalidateQueries({ - queryKey: cacheKeys.logs.summary(Number(searchSpaceId)), - }); + // Invalidate queries to refresh data + queryClient.invalidateQueries({ + queryKey: cacheKeys.logs.summary(Number(searchSpaceId)), + }); - // Refresh connectors list - await refetchAllConnectors(); + // Refresh connectors list + await refetchAllConnectors(); } else { // Non-indexable connector // For Circleback, transition to edit view to show webhook URL @@ -708,9 +709,10 @@ export const useConnectorDialog = () => { await refetchAllConnectors(); } else { // Other non-indexable connectors - just show success message and close - const successMessage = currentConnectorType === "MCP_CONNECTOR" - ? `${connector.name} added successfully` - : `${connectorTitle} connected successfully!`; + const successMessage = + currentConnectorType === "MCP_CONNECTOR" + ? `${connector.name} added successfully` + : `${connectorTitle} connected successfully!`; toast.success(successMessage); // Refresh connectors list before closing modal @@ -758,7 +760,7 @@ export const useConnectorDialog = () => { const handleBackFromConnect = useCallback(() => { const url = new URL(window.location.href); url.searchParams.set("modal", "connectors"); - + // If we're connecting an MCP and came from list view, go back to list if (connectingConnectorType === "MCP_CONNECTOR" && viewingMCPList) { url.searchParams.set("view", "mcp-list"); @@ -766,7 +768,7 @@ export const useConnectorDialog = () => { url.searchParams.set("tab", "all"); url.searchParams.delete("view"); } - + url.searchParams.delete("connectorType"); router.replace(url.pathname + url.search, { scroll: false }); }, [router, connectingConnectorType, viewingMCPList]); @@ -1252,33 +1254,33 @@ export const useConnectorDialog = () => { ); } - // Generate toast message based on connector type - const toastTitle = `${editingConnector.name} updated successfully`; + // Generate toast message based on connector type + const toastTitle = `${editingConnector.name} updated successfully`; - toast.success(toastTitle, { - description: periodicEnabled - ? `Periodic sync ${frequency ? `enabled every ${getFrequencyLabel(frequencyMinutes)}` : "enabled"}. ${indexingDescription}` - : indexingDescription, - }); + toast.success(toastTitle, { + description: periodicEnabled + ? `Periodic sync ${frequency ? `enabled every ${getFrequencyLabel(frequencyMinutes)}` : "enabled"}. ${indexingDescription}` + : indexingDescription, + }); - // Update URL - the effect will handle closing the modal and clearing state - const url = new URL(window.location.href); - url.searchParams.delete("modal"); - url.searchParams.delete("tab"); - url.searchParams.delete("view"); - url.searchParams.delete("connectorId"); - router.replace(url.pathname + url.search, { scroll: false }); + // Update URL - the effect will handle closing the modal and clearing state + const url = new URL(window.location.href); + url.searchParams.delete("modal"); + url.searchParams.delete("tab"); + url.searchParams.delete("view"); + url.searchParams.delete("connectorId"); + router.replace(url.pathname + url.search, { scroll: false }); - refreshConnectors(); - queryClient.invalidateQueries({ - queryKey: cacheKeys.logs.summary(Number(searchSpaceId)), - }); - } catch (error) { - console.error("Error saving connector:", error); - toast.error("Failed to save connector changes"); - } finally { - setIsSaving(false); - } + refreshConnectors(); + queryClient.invalidateQueries({ + queryKey: cacheKeys.logs.summary(Number(searchSpaceId)), + }); + } catch (error) { + console.error("Error saving connector:", error); + toast.error("Failed to save connector changes"); + } finally { + setIsSaving(false); + } }, [ editingConnector, diff --git a/surfsense_web/components/assistant-ui/connector-popup/tabs/active-connectors-tab.tsx b/surfsense_web/components/assistant-ui/connector-popup/tabs/active-connectors-tab.tsx index 1e25d24a0..a518d63a6 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/tabs/active-connectors-tab.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/tabs/active-connectors-tab.tsx @@ -96,9 +96,7 @@ export const ActiveConnectorsTab: FC = ({ // Separate OAuth and non-OAuth connectors const oauthConnectors = connectors.filter((c) => oauthConnectorTypes.has(c.connector_type)); - const nonOauthConnectors = connectors.filter( - (c) => !oauthConnectorTypes.has(c.connector_type) - ); + const nonOauthConnectors = connectors.filter((c) => !oauthConnectorTypes.has(c.connector_type)); // Group OAuth connectors by type const oauthConnectorsByType = oauthConnectors.reduce( @@ -150,8 +148,7 @@ export const ActiveConnectorsTab: FC = ({ }); const hasActiveConnectors = - filteredOAuthConnectorTypes.length > 0 || - filteredNonOAuthConnectors.length > 0; + filteredOAuthConnectorTypes.length > 0 || filteredNonOAuthConnectors.length > 0; return ( 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 260d6c926..2487b7276 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 @@ -123,7 +123,6 @@ export const AllConnectorsTab: FC = ({ isConnecting={isConnecting} documentCount={documentCount} accountCount={accountCount} - isIndexing={isIndexing} onConnect={() => onConnectOAuth(connector)} onManage={ @@ -165,9 +164,13 @@ export const AllConnectorsTab: FC = ({ // 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 mcpConnectorCount = + isMCP && allConnectors + ? allConnectors.filter( + (c: SearchSourceConnector) => + c.connector_type === EnumConnectorName.MCP_CONNECTOR + ).length + : undefined; const handleConnect = onConnectNonOAuth ? () => onConnectNonOAuth(connector.connectorType) 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 36afc44f0..d1d5bee08 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 @@ -1,14 +1,14 @@ /** * MCP Configuration Validator Utility - * + * * Shared validation and parsing logic for MCP (Model Context Protocol) server configurations. - * + * * Features: * - Zod schema validation for runtime type safety * - Configuration caching to avoid repeated parsing (5-minute TTL) * - Standardized error messages * - Connection testing utilities - * + * * Usage: * ```typescript * // Parse and validate config @@ -18,14 +18,14 @@ * } else { * // Show result.error to user * } - * + * * // Test connection * const testResult = await testMCPConnection(config); * if (testResult.status === "success") { * console.log(`Found ${testResult.tools.length} tools`); * } * ``` - * + * * @module mcp-config-validator */ @@ -36,7 +36,7 @@ import { connectorsApiService } from "@/lib/apis/connectors-api.service"; /** * Zod schema for MCP server configuration * Provides compile-time and runtime type safety - * + * * Exported for advanced use cases (e.g., form builders) */ export const MCPServerConfigSchema = z.object({ @@ -95,11 +95,11 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult => // Check cache first const cached = configCache.get(configJson); if (cached && Date.now() - cached.timestamp < CACHE_TTL) { - console.log('[MCP Validator] ✅ Using cached config'); + console.log("[MCP Validator] ✅ Using cached config"); return { config: cached.config, error: null }; } - console.log('[MCP Validator] 🔍 Parsing new config...'); + console.log("[MCP Validator] 🔍 Parsing new config..."); // Clean up expired cache entries periodically if (configCache.size > 100) { @@ -111,7 +111,7 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult => // Validate that it's an object, not an array if (Array.isArray(parsed)) { - console.error('[MCP Validator] ❌ Error: Config is an array, expected object'); + console.error("[MCP Validator] ❌ Error: Config is an array, expected object"); return { config: null, error: "Please provide a single server configuration object, not an array", @@ -125,27 +125,23 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult => // Format Zod validation errors for user-friendly display const firstError = result.error.issues[0]; const fieldPath = firstError.path.join("."); - + // Clean up error message - remove technical Zod jargon let errorMsg = firstError.message; - + // Replace technical error messages with user-friendly ones if (errorMsg.includes("expected string, received undefined")) { - errorMsg = fieldPath - ? `The '${fieldPath}' field is required` - : "This field is required"; + errorMsg = fieldPath ? `The '${fieldPath}' field is required` : "This field is required"; } else if (errorMsg.includes("Invalid input")) { - errorMsg = fieldPath - ? `The '${fieldPath}' field has an invalid value` - : "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}`; } - - console.error('[MCP Validator] ❌ Validation error:', errorMsg); - console.error('[MCP Validator] Full Zod errors:', result.error.issues); - + + console.error("[MCP Validator] ❌ Validation error:", errorMsg); + console.error("[MCP Validator] Full Zod errors:", result.error.issues); + return { config: null, error: errorMsg, @@ -164,8 +160,8 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult => config, timestamp: Date.now(), }); - - console.log('[MCP Validator] ✅ Config parsed successfully:', config); + + console.log("[MCP Validator] ✅ Config parsed successfully:", config); return { config, @@ -173,7 +169,7 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult => }; } catch (error) { const errorMsg = error instanceof Error ? error.message : "Invalid JSON"; - console.error('[MCP Validator] ❌ JSON parse error:', errorMsg); + console.error("[MCP Validator] ❌ JSON parse error:", errorMsg); return { config: null, error: errorMsg, @@ -222,11 +218,11 @@ export const testMCPConnection = async ( export const extractServerName = (configJson: string): string => { try { const parsed = JSON.parse(configJson); - + // Use Zod to validate and extract name field safely const nameSchema = z.object({ name: z.string().optional() }); const result = nameSchema.safeParse(parsed); - + if (result.success && result.data.name) { return result.data.name; } 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 fc1f76f66..a48ca02e6 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 @@ -82,11 +82,13 @@ 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 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) { @@ -177,58 +179,58 @@ export const ConnectorAccountsListView: FC = ({ ) : (

      {typeConnectors.map((connector) => { - const isIndexing = indexingConnectorIds.has(connector.id); + const isIndexing = indexingConnectorIds.has(connector.id); - return ( -
      + return (
      - {getConnectorIcon(connector.connector_type, "size-6")} -
      -
      -

      - {getDisplayName(connector)} -

      - {isIndexing ? ( -

      - - Syncing +

      + {getConnectorIcon(connector.connector_type, "size-6")} +
      +
      +

      + {getDisplayName(connector)}

      - ) : ( -

      - {isIndexableConnector(connector.connector_type) - ? connector.last_indexed_at - ? `Last indexed: ${formatLastIndexedDate(connector.last_indexed_at)}` - : "Never indexed" - : "Active"} -

      - )} + {isIndexing ? ( +

      + + Syncing +

      + ) : ( +

      + {isIndexableConnector(connector.connector_type) + ? connector.last_indexed_at + ? `Last indexed: ${formatLastIndexedDate(connector.last_indexed_at)}` + : "Never indexed" + : "Active"} +

      + )} +
      +
      - -
      - ); - })} + ); + })}
      )}
      diff --git a/surfsense_web/lib/apis/connectors-api.service.ts b/surfsense_web/lib/apis/connectors-api.service.ts index 17e2d0b3f..0e4f7f4d5 100644 --- a/surfsense_web/lib/apis/connectors-api.service.ts +++ b/surfsense_web/lib/apis/connectors-api.service.ts @@ -267,9 +267,13 @@ class ConnectorsApiService { search_space_id: String(queryParams.search_space_id), }).toString(); - return baseApiService.post(`/api/v1/connectors/mcp?${queryString}`, undefined, { - body: data, - }); + return baseApiService.post( + `/api/v1/connectors/mcp?${queryString}`, + undefined, + { + body: data, + } + ); }; /** From 0c3307fabbbd4ebae68c9d8432088370bc7d0bb9 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Tue, 20 Jan 2026 11:49:24 +0530 Subject: [PATCH 06/11] fix: use keyboard smoothly while editing the MCP json --- .../connect-forms/components/mcp-connect-form.tsx | 15 +++++++++++++++ .../connector-configs/components/mcp-config.tsx | 15 +++++++++++++++ 2 files changed, 30 insertions(+) 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 b4207acfe..11d58bfc7 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 @@ -175,6 +175,21 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) id="config" value={configJson} onChange={(e) => handleConfigChange(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Tab") { + e.preventDefault(); + const target = e.target as HTMLTextAreaElement; + const start = target.selectionStart; + const end = target.selectionEnd; + const indent = " "; // 2 spaces for JSON + const newValue = configJson.substring(0, start) + indent + configJson.substring(end); + handleConfigChange(newValue); + // Set cursor position after the inserted tab + requestAnimationFrame(() => { + target.selectionStart = target.selectionEnd = start + indent.length; + }); + } + }} placeholder={DEFAULT_CONFIG} rows={16} className={`font-mono text-xs ${jsonError ? "border-red-500" : ""}`} 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 f7e103bd3..ab6f5f0fb 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 @@ -181,6 +181,21 @@ export const MCPConfig: FC = ({ connector, onConfigChange, onNam id="config" value={configJson} onChange={(e) => handleConfigChange(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Tab") { + e.preventDefault(); + const target = e.target as HTMLTextAreaElement; + const start = target.selectionStart; + const end = target.selectionEnd; + const indent = " "; // 2 spaces for JSON + const newValue = configJson.substring(0, start) + indent + configJson.substring(end); + handleConfigChange(newValue); + // Set cursor position after the inserted tab + requestAnimationFrame(() => { + target.selectionStart = target.selectionEnd = start + indent.length; + }); + } + }} rows={16} className={`font-mono text-xs ${jsonError ? "border-red-500" : ""}`} /> From e65d9fa1dbe7bb638c11c3b93fa17857fd651162 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Tue, 20 Jan 2026 11:50:17 +0530 Subject: [PATCH 07/11] chore: ran linting --- .../components/mcp-connect-form.tsx | 31 ++++++++++--------- .../components/mcp-config.tsx | 12 ++++--- .../utils/mcp-config-validator.ts | 25 ++++++++------- 3 files changed, 36 insertions(+), 32 deletions(-) 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 11d58bfc7..c1a1af5a1 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 @@ -44,7 +44,7 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) name: "My Remote MCP Server", url: "https://your-mcp-server.com/mcp", headers: { - "API_KEY": "your_api_key_here", + API_KEY: "your_api_key_here", }, transport: "streamable-http", }, @@ -137,11 +137,12 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) return (
      - - - Connect to an MCP (Model Context Protocol) server. Each MCP server is added as a separate connector. - - + + + Connect to an MCP (Model Context Protocol) server. Each MCP server is added as a separate + connector. + +
      @@ -182,7 +183,8 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) const start = target.selectionStart; const end = target.selectionEnd; const indent = " "; // 2 spaces for JSON - const newValue = configJson.substring(0, start) + indent + configJson.substring(end); + const newValue = + configJson.substring(0, start) + indent + configJson.substring(end); handleConfigChange(newValue); // Set cursor position after the inserted tab requestAnimationFrame(() => { @@ -194,11 +196,10 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) rows={16} className={`font-mono text-xs ${jsonError ? "border-red-500" : ""}`} /> - {jsonError && ( -

      JSON Error: {jsonError}

      - )} + {jsonError &&

      JSON Error: {jsonError}

      }

      - Paste a single MCP server configuration. Must include: name, command, args (optional), env (optional), transport (optional). + Paste a single MCP server configuration. Must include: name, command, args (optional), + env (optional), transport (optional).

      @@ -232,7 +233,9 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting })
      - {testResult.status === "success" ? "Connection Successful" : "Connection Failed"} + {testResult.status === "success" + ? "Connection Successful" + : "Connection Failed"} {testResult.tools.length > 0 && ( + +
      +
      +

      + Use presets to quickly apply Editor (create/read/update) or Viewer (read-only) permissions +

      {Object.entries(groupedPermissions).map(([category, perms]) => { @@ -1427,10 +1500,8 @@ function CreateRoleDialog({ return (
      - +
      {perms.map((perm) => ( - + ))}
      From e578bb9e774925718416034e3ee146abee96ef84 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Tue, 20 Jan 2026 03:00:02 -0800 Subject: [PATCH 11/11] chore: linting --- surfsense_backend/app/agents/new_chat/tools/mcp_client.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/surfsense_backend/app/agents/new_chat/tools/mcp_client.py b/surfsense_backend/app/agents/new_chat/tools/mcp_client.py index 9ec4b5fbf..44c48344c 100644 --- a/surfsense_backend/app/agents/new_chat/tools/mcp_client.py +++ b/surfsense_backend/app/agents/new_chat/tools/mcp_client.py @@ -292,9 +292,7 @@ async def test_mcp_http_connection( } ) - logger.info( - "HTTP MCP connection successful. Found %d tools.", len(tools) - ) + logger.info("HTTP MCP connection successful. Found %d tools.", len(tools)) return { "status": "success", "message": f"Connected successfully. Found {len(tools)} tools.",