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.
This commit is contained in:
Anish Sarkar 2026-01-19 19:50:07 +05:30
parent 48603b993d
commit 72ad558240
10 changed files with 148 additions and 290 deletions

View file

@ -22,7 +22,6 @@ import { useIndexingConnectors } from "./connector-popup/hooks/use-indexing-conn
import { ActiveConnectorsTab } from "./connector-popup/tabs/active-connectors-tab"; import { ActiveConnectorsTab } from "./connector-popup/tabs/active-connectors-tab";
import { AllConnectorsTab } from "./connector-popup/tabs/all-connectors-tab"; import { AllConnectorsTab } from "./connector-popup/tabs/all-connectors-tab";
import { ConnectorAccountsListView } from "./connector-popup/views/connector-accounts-list-view"; 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"; import { YouTubeCrawlerView } from "./connector-popup/views/youtube-crawler-view";
export const ConnectorIndicator: FC = () => { export const ConnectorIndicator: FC = () => {
@ -177,18 +176,16 @@ export const ConnectorIndicator: FC = () => {
{isYouTubeView && searchSpaceId ? ( {isYouTubeView && searchSpaceId ? (
<YouTubeCrawlerView searchSpaceId={searchSpaceId} onBack={handleBackFromYouTube} /> <YouTubeCrawlerView searchSpaceId={searchSpaceId} onBack={handleBackFromYouTube} />
) : viewingMCPList ? ( ) : viewingMCPList ? (
<div className="p-6 sm:p-12 h-full overflow-hidden"> <ConnectorAccountsListView
<MCPConnectorListView connectorType="MCP_CONNECTOR"
mcpConnectors={ connectorTitle="MCP Connectors"
(allConnectors || []).filter( connectors={(allConnectors || []) as SearchSourceConnector[]}
(c: SearchSourceConnector) => c.connector_type === "MCP_CONNECTOR" indexingConnectorIds={indexingConnectorIds}
) as SearchSourceConnector[] onBack={handleBackFromMCPList}
} onManage={handleStartEdit}
onAddNew={handleAddNewMCPFromList} onAddAccount={handleAddNewMCPFromList}
onManageConnector={handleStartEdit} addButtonText="Add New MCP Server"
onBack={handleBackFromMCPList} />
/>
</div>
) : viewingAccountsType ? ( ) : viewingAccountsType ? (
<ConnectorAccountsListView <ConnectorAccountsListView
connectorType={viewingAccountsType.connectorType} connectorType={viewingAccountsType.connectorType}

View file

@ -4,6 +4,7 @@ import { IconBrandYoutube } from "@tabler/icons-react";
import { FileText, Loader2 } from "lucide-react"; import { FileText, Loader2 } from "lucide-react";
import type { FC } from "react"; import type { FC } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useConnectorStatus } from "../hooks/use-connector-status"; import { useConnectorStatus } from "../hooks/use-connector-status";
@ -18,6 +19,7 @@ interface ConnectorCardProps {
isConnecting?: boolean; isConnecting?: boolean;
documentCount?: number; documentCount?: number;
accountCount?: number; accountCount?: number;
connectorCount?: number;
isIndexing?: boolean; isIndexing?: boolean;
onConnect?: () => void; onConnect?: () => void;
onManage?: () => void; onManage?: () => void;
@ -46,10 +48,12 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
isConnecting = false, isConnecting = false,
documentCount, documentCount,
accountCount, accountCount,
connectorCount,
isIndexing = false, isIndexing = false,
onConnect, onConnect,
onManage, onManage,
}) => { }) => {
const isMCP = connectorType === EnumConnectorName.MCP_CONNECTOR;
// Get connector status // Get connector status
const { getConnectorStatus, isConnectorEnabled, getConnectorStatusMessage, shouldShowWarnings } = const { getConnectorStatus, isConnectorEnabled, getConnectorStatusMessage, shouldShowWarnings } =
useConnectorStatus(); useConnectorStatus();
@ -112,13 +116,21 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
</p> </p>
) : isConnected ? ( ) : isConnected ? (
<p className="text-[10px] text-muted-foreground mt-1 flex items-center gap-1.5"> <p className="text-[10px] text-muted-foreground mt-1 flex items-center gap-1.5">
<span>{formatDocumentCount(documentCount)}</span> {isMCP && connectorCount !== undefined ? (
{accountCount !== undefined && accountCount > 0 && ( <span>
{connectorCount} {connectorCount === 1 ? "server" : "servers"}
</span>
) : (
<> <>
<span className="text-muted-foreground/50"></span> <span>{formatDocumentCount(documentCount)}</span>
<span> {accountCount !== undefined && accountCount > 0 && (
{accountCount} {accountCount === 1 ? "Account" : "Accounts"} <>
</span> <span className="text-muted-foreground/50"></span>
<span>
{accountCount} {accountCount === 1 ? "Account" : "Accounts"}
</span>
</>
)}
</> </>
)} )}
</p> </p>

View file

@ -1,20 +1,18 @@
"use client"; "use client";
import { CheckCircle2, ChevronDown, ChevronUp, Server, XCircle } from "lucide-react"; import { Server } from "lucide-react";
import { type FC, useRef, useState } from "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 { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import type { MCPToolDefinition } from "@/contracts/types/mcp.types";
import type { ConnectFormProps } from ".."; import type { ConnectFormProps } from "..";
import { import {
extractServerName, extractServerName,
parseMCPConfig, parseMCPConfig,
testMCPConnection, testMCPConnection,
type MCPConnectionTestResult,
} from "../../utils/mcp-config-validator"; } from "../../utils/mcp-config-validator";
export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => { export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
@ -22,8 +20,6 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
const [configJson, setConfigJson] = useState(""); const [configJson, setConfigJson] = useState("");
const [jsonError, setJsonError] = useState<string | null>(null); const [jsonError, setJsonError] = useState<string | null>(null);
const [isTesting, setIsTesting] = useState(false); const [isTesting, setIsTesting] = useState(false);
const [showDetails, setShowDetails] = useState(false);
const [testResult, setTestResult] = useState<MCPConnectionTestResult | null>(null);
const DEFAULT_CONFIG = JSON.stringify( const DEFAULT_CONFIG = JSON.stringify(
{ {
@ -69,19 +65,26 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
const handleTestConnection = async () => { const handleTestConnection = async () => {
const serverConfig = parseConfig(); const serverConfig = parseConfig();
if (!serverConfig) { if (!serverConfig) {
setTestResult({ toast.error("Invalid configuration", {
status: "error", description: jsonError || "Please check your MCP server configuration JSON.",
message: jsonError || "Invalid configuration",
tools: [],
}); });
return; return;
} }
setIsTesting(true); setIsTesting(true);
setTestResult(null);
const result = await testMCPConnection(serverConfig); 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); setIsTesting(false);
}; };
@ -143,7 +146,7 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
className={`font-mono text-xs ${jsonError ? "border-red-500" : ""}`} className={`font-mono text-xs ${jsonError ? "border-red-500" : ""}`}
/> />
{jsonError && ( {jsonError && (
<p className="text-xs text-red-500">JSON Error: {jsonError}</p> <p className="text-xs text-red-500">{jsonError}</p>
)} )}
<p className="text-[10px] sm:text-xs text-muted-foreground"> <p className="text-[10px] sm:text-xs text-muted-foreground">
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).
@ -158,72 +161,9 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
variant="outline" variant="outline"
className="w-full" className="w-full"
> >
{isTesting ? "Testing Connection..." : "Test Connection"} {isTesting ? "Testing Connection" : "Test Connection"}
</Button> </Button>
</div> </div>
{testResult && (
<Alert
className={
testResult.status === "success"
? "border-green-500/50 bg-green-500/10"
: "border-red-500/50 bg-red-500/10"
}
>
{testResult.status === "success" ? (
<CheckCircle2 className="h-4 w-4 text-green-600" />
) : (
<XCircle className="h-4 w-4 text-red-600" />
)}
<div className="flex-1">
<div className="flex items-center justify-between">
<AlertTitle className="text-sm">
{testResult.status === "success" ? "Connection Successful" : "Connection Failed"}
</AlertTitle>
{testResult.tools.length > 0 && (
<Button
type="button"
variant="ghost"
size="sm"
className="h-6 px-2"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setShowDetails(!showDetails);
}}
>
{showDetails ? (
<>
<ChevronUp className="h-3 w-3 mr-1" />
Hide Details
</>
) : (
<>
<ChevronDown className="h-3 w-3 mr-1" />
Show Details
</>
)}
</Button>
)}
</div>
<AlertDescription className="text-xs mt-1">
{testResult.message}
{showDetails && testResult.tools.length > 0 && (
<div className="mt-3 pt-3 border-t border-green-500/20">
<p className="font-semibold mb-2">
Available tools:
</p>
<ul className="list-disc list-inside text-xs space-y-0.5">
{testResult.tools.map((tool, i) => (
<li key={i}>{tool.name}</li>
))}
</ul>
</div>
)}
</AlertDescription>
</div>
</Alert>
)}
</div> </div>
</form> </form>
</div> </div>

View file

@ -22,23 +22,6 @@ interface MCPConfigProps extends ConnectorConfigProps {
} }
export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNameChange }) => { export const MCPConfig: FC<MCPConfigProps> = ({ 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 (
<Alert className="border-red-500/50 bg-red-500/10">
<XCircle className="h-4 w-4 text-red-600" />
<AlertTitle>Invalid Connector Type</AlertTitle>
<AlertDescription>
This component can only be used with MCP connectors.
</AlertDescription>
</Alert>
);
}
const [name, setName] = useState<string>(""); const [name, setName] = useState<string>("");
const [configJson, setConfigJson] = useState(""); const [configJson, setConfigJson] = useState("");
const [jsonError, setJsonError] = useState<string | null>(null); const [jsonError, setJsonError] = useState<string | null>(null);
@ -63,8 +46,24 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
}; };
setConfigJson(JSON.stringify(configObj, null, 2)); 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 (
<Alert className="border-red-500/50 bg-red-500/10">
<XCircle className="h-4 w-4 text-red-600" />
<AlertTitle>Invalid Connector Type</AlertTitle>
<AlertDescription>
This component can only be used with MCP connectors.
</AlertDescription>
</Alert>
);
}
const handleNameChange = (value: string) => { const handleNameChange = (value: string) => {
setName(value); setName(value);
@ -126,15 +125,21 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{/* Server Name */} {/* Server Name */}
<div className="space-y-2"> <div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<Label htmlFor="name">Server Name *</Label> <div className="space-y-2">
<Input <Label htmlFor="name" className="text-xs sm:text-sm">Server Name</Label>
id="name" <Input
value={name} id="name"
onChange={(e) => handleNameChange(e.target.value)} value={name}
placeholder="e.g., Filesystem Server" onChange={(e) => handleNameChange(e.target.value)}
required placeholder="e.g., Filesystem Server"
/> className="border-slate-400/20 focus-visible:border-slate-400/40"
required
/>
<p className="text-[10px] sm:text-xs text-muted-foreground">
A friendly name to identify this connector.
</p>
</div>
</div> </div>
{/* Server Configuration */} {/* Server Configuration */}
@ -168,8 +173,8 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
type="button" type="button"
onClick={handleTestConnection} onClick={handleTestConnection}
disabled={isTesting} disabled={isTesting}
variant="outline" variant="secondary"
className="w-full" 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"}
</Button> </Button>
@ -228,8 +233,8 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
Available tools: Available tools:
</p> </p>
<ul className="list-disc list-inside text-xs space-y-0.5"> <ul className="list-disc list-inside text-xs space-y-0.5">
{testResult.tools.map((tool, i) => ( {testResult.tools.map((tool) => (
<li key={i}>{tool.name}</li> <li key={tool.name}>{tool.name}</li>
))} ))}
</ul> </ul>
</div> </div>

View file

@ -151,7 +151,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h2 className="text-xl sm:text-2xl font-semibold tracking-tight text-wrap whitespace-normal wrap-break-word"> <h2 className="text-xl sm:text-2xl font-semibold tracking-tight text-wrap whitespace-normal wrap-break-word">
{connector.connector_type === "MCP_CONNECTOR" ? "MCP Server" : connector.name} {connector.name}
</h2> </h2>
<p className="text-xs sm:text-base text-muted-foreground mt-1"> <p className="text-xs sm:text-base text-muted-foreground mt-1">
Manage your connector settings and sync configuration Manage your connector settings and sync configuration

View file

@ -645,7 +645,7 @@ export const useConnectorDialog = () => {
}); });
const successMessage = currentConnectorType === "MCP_CONNECTOR" const successMessage = currentConnectorType === "MCP_CONNECTOR"
? `${connector.name} MCP server added successfully` ? `${connector.name} added successfully`
: `${connectorTitle} connected and indexing started!`; : `${connectorTitle} connected and indexing started!`;
toast.success(successMessage, { toast.success(successMessage, {
description: periodicEnabledForIndexing description: periodicEnabledForIndexing
@ -709,7 +709,7 @@ export const useConnectorDialog = () => {
} else { } else {
// Other non-indexable connectors - just show success message and close // Other non-indexable connectors - just show success message and close
const successMessage = currentConnectorType === "MCP_CONNECTOR" const successMessage = currentConnectorType === "MCP_CONNECTOR"
? `${connector.name} MCP server added successfully` ? `${connector.name} added successfully`
: `${connectorTitle} connected successfully!`; : `${connectorTitle} connected successfully!`;
toast.success(successMessage); toast.success(successMessage);

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import type { FC } from "react"; import type { FC } from "react";
import { EnumConnectorName } from "@/contracts/enums/connector";
import type { SearchSourceConnector } from "@/contracts/types/connector.types"; import type { SearchSourceConnector } from "@/contracts/types/connector.types";
import { ConnectorCard } from "../components/connector-card"; import { ConnectorCard } from "../components/connector-card";
import { CRAWLERS, OAUTH_CONNECTORS, OTHER_CONNECTORS } from "../constants/connector-constants"; import { CRAWLERS, OAUTH_CONNECTORS, OTHER_CONNECTORS } from "../constants/connector-constants";
@ -162,6 +163,12 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
); );
const isIndexing = actualConnector && indexingConnectorIds?.has(actualConnector.id); 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 const handleConnect = onConnectNonOAuth
? () => onConnectNonOAuth(connector.connectorType) ? () => onConnectNonOAuth(connector.connectorType)
: () => {}; // Fallback - connector popup should handle all connector types : () => {}; // Fallback - connector popup should handle all connector types
@ -176,7 +183,7 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
isConnected={isConnected} isConnected={isConnected}
isConnecting={isConnecting} isConnecting={isConnecting}
documentCount={documentCount} documentCount={documentCount}
connectorCount={mcpConnectorCount}
isIndexing={isIndexing} isIndexing={isIndexing}
onConnect={handleConnect} onConnect={handleConnect}
onManage={ onManage={

View file

@ -131,19 +131,24 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult =>
// Replace technical error messages with user-friendly ones // Replace technical error messages with user-friendly ones
if (errorMsg.includes("expected string, received undefined")) { 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")) { } 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:', errorMsg);
console.error('[MCP Validator] ❌ Validation error:', formattedError);
console.error('[MCP Validator] Full Zod errors:', result.error.issues); console.error('[MCP Validator] Full Zod errors:', result.error.issues);
return { return {
config: null, config: null,
error: formattedError, error: errorMsg,
}; };
} }

View file

@ -1,9 +1,10 @@
"use client"; "use client";
import { differenceInDays, differenceInMinutes, format, isToday, isYesterday } from "date-fns"; 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 type { FC } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import type { SearchSourceConnector } from "@/contracts/types/connector.types"; import type { SearchSourceConnector } from "@/contracts/types/connector.types";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@ -19,6 +20,7 @@ interface ConnectorAccountsListViewProps {
onManage: (connector: SearchSourceConnector) => void; onManage: (connector: SearchSourceConnector) => void;
onAddAccount: () => void; onAddAccount: () => void;
isConnecting?: boolean; isConnecting?: boolean;
addButtonText?: string;
} }
/** /**
@ -70,6 +72,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
onManage, onManage,
onAddAccount, onAddAccount,
isConnecting = false, isConnecting = false,
addButtonText,
}) => { }) => {
// Get connector status // Get connector status
const { isConnectorEnabled, getConnectorStatusMessage } = useConnectorStatus(); const { isConnectorEnabled, getConnectorStatusMessage } = useConnectorStatus();
@ -80,6 +83,20 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
// Filter connectors to only show those of this type // Filter connectors to only show those of this type
const typeConnectors = connectors.filter((c) => c.connector_type === connectorType); 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 ( return (
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
{/* Header */} {/* Header */}
@ -130,7 +147,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
)} )}
</div> </div>
<span className="text-[11px] sm:text-[12px] font-medium"> <span className="text-[11px] sm:text-[12px] font-medium">
{isConnecting ? "Connecting" : "Add Account"} {isConnecting ? "Connecting" : buttonText}
</span> </span>
</button> </button>
</div> </div>
@ -139,8 +156,27 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
{/* Content */} {/* Content */}
<div className="flex-1 overflow-y-auto px-6 sm:px-12 pt-0 sm:pt-6 pb-6 sm:pb-8"> <div className="flex-1 overflow-y-auto px-6 sm:px-12 pt-0 sm:pt-6 pb-6 sm:pb-8">
{/* Connected Accounts Grid */} {/* Connected Accounts Grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3"> {typeConnectors.length === 0 ? (
{typeConnectors.map((connector) => { <div className="flex flex-col items-center justify-center py-12 text-center">
<div className="h-16 w-16 rounded-full bg-slate-400/5 dark:bg-white/5 flex items-center justify-center mb-4">
{isMCP ? (
<Server className="h-8 w-8 text-muted-foreground" />
) : (
getConnectorIcon(connectorType, "size-8")
)}
</div>
<h3 className="text-sm font-medium mb-1">
{isMCP ? "No MCP Servers" : `No ${connectorTitle} Accounts`}
</h3>
<p className="text-xs text-muted-foreground max-w-[280px]">
{isMCP
? "Get started by adding your first Model Context Protocol server"
: `Get started by connecting your first ${connectorTitle} account`}
</p>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{typeConnectors.map((connector) => {
const isIndexing = indexingConnectorIds.has(connector.id); const isIndexing = indexingConnectorIds.has(connector.id);
return ( return (
@ -165,7 +201,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-[14px] font-semibold leading-tight truncate"> <p className="text-[14px] font-semibold leading-tight truncate">
{getConnectorDisplayName(connector.name)} {getDisplayName(connector)}
</p> </p>
{isIndexing ? ( {isIndexing ? (
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5"> <p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
@ -193,7 +229,8 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
</div> </div>
); );
})} })}
</div> </div>
)}
</div> </div>
</div> </div>
); );

View file

@ -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<MCPConnectorListViewProps> = ({
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 (
<Alert className="border-red-500/50 bg-red-500/10">
<XCircle className="h-4 w-4 text-red-600" />
<AlertTitle>Invalid Connector Type</AlertTitle>
<AlertDescription>
This view can only display MCP connectors. Found {invalidConnectors.length} invalid
connector(s).
</AlertDescription>
</Alert>
);
}
return (
<div className="flex flex-col h-full">
{/* Header */}
<div className="flex items-center justify-between mb-6 shrink-0">
<div className="flex items-center gap-3">
<Button
variant="ghost"
size="icon"
onClick={onBack}
className="h-8 w-8"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m15 18-6-6 6-6" />
</svg>
</Button>
<div>
<h2 className="text-lg sm:text-xl font-semibold">MCP Connectors</h2>
<p className="text-xs sm:text-sm text-muted-foreground">
Manage your Model Context Protocol servers
</p>
</div>
</div>
</div>
{/* Add New Button */}
<div className="mb-4 shrink-0">
<Button
onClick={onAddNew}
className="w-full"
variant="outline"
>
<Plus className="h-4 w-4 mr-2" />
Add New MCP Server
</Button>
</div>
{/* MCP Connectors List */}
<div className="space-y-3 flex-1 overflow-y-auto">
{mcpConnectors.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-center">
<div className="h-16 w-16 rounded-full bg-slate-400/5 dark:bg-white/5 flex items-center justify-center mb-4">
<Server className="h-8 w-8 text-muted-foreground" />
</div>
<h3 className="text-sm font-medium mb-1">No MCP Servers</h3>
<p className="text-xs text-muted-foreground max-w-[280px]">
Get started by adding your first Model Context Protocol server
</p>
</div>
) : (
mcpConnectors.map((connector) => {
// Extract server name from config
const serverName = connector.config?.server_config?.name || connector.name;
return (
<div
key={connector.id}
className={cn(
"flex items-center gap-4 p-4 rounded-xl border border-border transition-all",
"bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10"
)}
>
<div
className={cn(
"flex h-12 w-12 items-center justify-center rounded-lg border shrink-0",
"bg-slate-400/5 dark:bg-white/5 border-slate-400/5 dark:border-white/5"
)}
>
{getConnectorIcon("MCP_CONNECTOR", "size-6")}
</div>
<div className="flex-1 min-w-0">
<p className="text-[14px] font-semibold leading-tight truncate">
{serverName}
</p>
</div>
<Button
variant="secondary"
size="sm"
className="h-8 text-[11px] 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 shrink-0"
onClick={() => onManageConnector(connector)}
>
Manage
</Button>
</div>
);
})
)}
</div>
</div>
);
};