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 62d017509..92de0c059 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 @@ -8,9 +8,14 @@ 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 { MCPServerConfig, MCPToolDefinition } from "@/contracts/types/mcp.types"; -import { connectorsApiService } from "@/lib/apis/connectors-api.service"; +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 }) => { const isSubmittingRef = useRef(false); @@ -18,11 +23,7 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) const [jsonError, setJsonError] = useState(null); const [isTesting, setIsTesting] = useState(false); const [showDetails, setShowDetails] = useState(false); - const [testResult, setTestResult] = useState<{ - status: "success" | "error"; - message: string; - tools: MCPToolDefinition[]; - } | null>(null); + const [testResult, setTestResult] = useState(null); const DEFAULT_CONFIG = JSON.stringify( { @@ -38,35 +39,14 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) 2 ); - const parseConfig = (): MCPServerConfig | null => { - try { - const parsed = JSON.parse(configJson); - - // Validate that it's an object, not an array - if (Array.isArray(parsed)) { - setJsonError("Please provide a single server configuration object, not an array"); - return null; - } - - // Validate required fields - if (!parsed.command || typeof parsed.command !== "string") { - setJsonError("'command' field is required and must be a string"); - return null; - } - - const config: MCPServerConfig = { - command: parsed.command, - args: parsed.args || [], - env: parsed.env || {}, - transport: parsed.transport || "stdio", - }; - + const parseConfig = () => { + const result = parseMCPConfig(configJson); + if (result.error) { + setJsonError(result.error); + } else { setJsonError(null); - return config; - } catch (error) { - setJsonError(error instanceof Error ? error.message : "Invalid JSON"); - return null; } + return result.config; }; const handleConfigChange = (value: string) => { @@ -90,31 +70,9 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) setIsTesting(true); setTestResult(null); - try { - const result = await connectorsApiService.testMCPConnection(serverConfig); - - if (result.status === "success") { - setTestResult({ - status: "success", - message: `Successfully connected. Found ${result.tools.length} tool${result.tools.length !== 1 ? 's' : ''}.`, - tools: result.tools, - }); - } else { - setTestResult({ - status: "error", - message: result.message || "Failed to connect", - tools: [], - }); - } - } catch (error) { - setTestResult({ - status: "error", - message: error instanceof Error ? error.message : "Failed to connect", - tools: [], - }); - } finally { - setIsTesting(false); - } + const result = await testMCPConnection(serverConfig); + setTestResult(result); + setIsTesting(false); }; const handleSubmit = async (e: React.FormEvent) => { @@ -131,15 +89,7 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting }) } // Extract server name from config if provided - let serverName = "MCP Server"; - try { - const parsed = JSON.parse(configJson); - if (parsed.name && typeof parsed.name === "string") { - serverName = parsed.name; - } - } catch { - // Use default name - } + const serverName = extractServerName(configJson); isSubmittingRef.current = true; try { 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 4d141d4ea..f6788105e 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 @@ -8,25 +8,43 @@ 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 { MCPServerConfig, MCPToolDefinition } from "@/contracts/types/mcp.types"; -import { connectorsApiService } from "@/lib/apis/connectors-api.service"; import type { ConnectorConfigProps } from "../index"; +import { + parseMCPConfig, + testMCPConnection, + type MCPConnectionTestResult, +} from "../../utils/mcp-config-validator"; interface MCPConfigProps extends ConnectorConfigProps { onNameChange?: (name: string) => void; } 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); const [isTesting, setIsTesting] = useState(false); const [showDetails, setShowDetails] = useState(false); - const [testResult, setTestResult] = useState<{ - status: "success" | "error"; - message: string; - tools: MCPToolDefinition[]; - } | null>(null); + const [testResult, setTestResult] = useState(null); // Initialize form from connector config (only on mount) useEffect(() => { @@ -55,35 +73,14 @@ export const MCPConfig: FC = ({ connector, onConfigChange, onNam } }; - const parseConfig = (): MCPServerConfig | null => { - try { - const parsed = JSON.parse(configJson); - - // Validate that it's an object, not an array - if (Array.isArray(parsed)) { - setJsonError("Please provide a single server configuration object, not an array"); - return null; - } - - // Validate required fields - if (!parsed.command || typeof parsed.command !== "string") { - setJsonError("'command' field is required and must be a string"); - return null; - } - - const config: MCPServerConfig = { - command: parsed.command, - args: parsed.args || [], - env: parsed.env || {}, - transport: parsed.transport || "stdio", - }; - + const parseConfig = () => { + const result = parseMCPConfig(configJson); + if (result.error) { + setJsonError(result.error); + } else { setJsonError(null); - return config; - } catch (error) { - setJsonError(error instanceof Error ? error.message : "Invalid JSON"); - return null; } + return result.config; }; const handleConfigChange = (value: string) => { @@ -130,31 +127,9 @@ export const MCPConfig: FC = ({ connector, onConfigChange, onNam setIsTesting(true); setTestResult(null); - try { - const result = await connectorsApiService.testMCPConnection(serverConfig); - - if (result.status === "success") { - setTestResult({ - status: "success", - message: `Connected successfully! Found ${result.tools.length} tool(s).`, - tools: result.tools, - }); - } else { - setTestResult({ - status: "error", - message: result.message || "Failed to connect", - tools: [], - }); - } - } catch (error) { - setTestResult({ - status: "error", - message: error instanceof Error ? error.message : "Failed to connect", - tools: [], - }); - } finally { - setIsTesting(false); - } + const result = await testMCPConnection(serverConfig); + setTestResult(result); + setIsTesting(false); }; return ( 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 index 2da6352a9..c826e9fc4 100644 --- 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 @@ -1,8 +1,10 @@ "use client"; -import { Plus, Server } from "lucide-react"; +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"; @@ -20,6 +22,27 @@ export const MCPConnectorListView: FC = ({ 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 */}