import React, { useState } from 'react'; import { useForm } from 'react-hook-form'; import { FiServer, FiDatabase, FiGithub, FiHardDrive, FiArrowRight, FiCheck, FiInfo, } from 'react-icons/fi'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '../ui/dialog'; import { Button } from '../ui'; import { Input } from '../ui'; import { Label } from '../ui/label'; import Typography from '../ui/Typography'; import { cn } from '../../lib/utils'; import { MCPServerType, MCPServerFormData, MCPServerConfigType } from '../../types/mcp'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '../ui/tooltip'; interface MCPSetupWizardProps { isOpen: boolean; onClose: () => void; onSubmit: (data: MCPServerFormData) => Promise; editingServer?: { name: string; type: MCPServerType; config: Record; }; } interface FormData { name: string; type: MCPServerType; rootPath?: string; allowedPaths?: string; excludePaths?: string; readOnly?: boolean; token?: string; owner?: string; repo?: string; branch?: string; host?: string; port?: number; database?: string; username?: string; password?: string; ssl?: boolean; path?: string; command?: string; args?: string; env?: string; cwd?: string; } const serverTypes = [ { type: 'filesystem' as MCPServerType, name: 'Filesystem', description: 'Access local files and directories', icon: , requiredFields: ['rootPath'], optionalFields: ['allowedPaths', 'excludePaths', 'readOnly'], }, { type: 'github' as MCPServerType, name: 'GitHub', description: 'Interact with GitHub repositories', icon: , requiredFields: ['token', 'owner', 'repo'], optionalFields: ['branch'], }, { type: 'postgres' as MCPServerType, name: 'PostgreSQL', description: 'Connect to PostgreSQL databases', icon: , requiredFields: ['host', 'port', 'database', 'username', 'password'], optionalFields: ['ssl'], }, { type: 'sqlite' as MCPServerType, name: 'SQLite', description: 'Access SQLite database files', icon: , requiredFields: ['path'], optionalFields: [], }, { type: 'custom' as MCPServerType, name: 'Custom', description: 'Run custom MCP server commands', icon: , requiredFields: ['command'], optionalFields: ['args', 'env', 'cwd'], }, ]; enum WizardStep { SelectType = 0, BasicInfo = 1, Configuration = 2, Review = 3, } /** * Enhanced MCP Server Setup Wizard with guided configuration */ export const MCPSetupWizard: React.FC = ({ isOpen, onClose, onSubmit, editingServer, }) => { const [currentStep, setCurrentStep] = useState( WizardStep.SelectType ); const [selectedType, setSelectedType] = useState( editingServer?.type || 'filesystem' ); const [isSubmitting, setIsSubmitting] = useState(false); const [validationErrors, setValidationErrors] = useState< Record >({}); const { register, handleSubmit, watch, setValue, reset, getValues, trigger, formState: { errors }, } = useForm({ defaultValues: editingServer ? { name: editingServer.name, type: editingServer.type, ...editingServer.config, } : { type: 'filesystem', }, }); const selectedServerType = serverTypes.find((st) => st.type === selectedType); const handleNext = async () => { const isValid = await validateCurrentStep(); if (!isValid) return; if (currentStep < WizardStep.Review) { setCurrentStep(currentStep + 1); } }; const handleBack = () => { if (currentStep > WizardStep.SelectType) { setCurrentStep(currentStep - 1); } }; const validateCurrentStep = async (): Promise => { setValidationErrors({}); switch (currentStep) { case WizardStep.SelectType: return true; case WizardStep.BasicInfo: { const nameValid = await trigger('name'); if (!nameValid) { setValidationErrors({ name: 'Server name is required' }); } return nameValid; } case WizardStep.Configuration: { if (!selectedServerType) return false; const errors: Record = {}; let isValid = true; for (const field of selectedServerType.requiredFields) { const value = getValues(field as keyof FormData); if (!value || (typeof value === 'string' && value.trim() === '')) { errors[field] = `${field} is required`; isValid = false; } } if (selectedType === 'postgres') { const port = getValues('port'); if ( port && (isNaN(Number(port)) || Number(port) < 1 || Number(port) > 65535) ) { errors.port = 'Port must be between 1 and 65535'; isValid = false; } } setValidationErrors(errors); return isValid; } default: return true; } }; const handleFormSubmit = async (data: FormData) => { setIsSubmitting(true); try { const config: Record = {}; switch (data.type) { case 'filesystem': config.rootPath = data.rootPath; if (data.allowedPaths) config.allowedPaths = data.allowedPaths .split(',') .map((p) => p.trim()); if (data.excludePaths) config.excludePaths = data.excludePaths .split(',') .map((p) => p.trim()); if (data.readOnly !== undefined) config.readOnly = data.readOnly; break; case 'github': config.token = data.token; config.owner = data.owner; config.repo = data.repo; if (data.branch) config.branch = data.branch; break; case 'postgres': config.host = data.host; config.port = Number(data.port); config.database = data.database; config.username = data.username; config.password = data.password; if (data.ssl !== undefined) config.ssl = data.ssl; break; case 'sqlite': config.path = data.path; break; case 'custom': config.command = data.command; if (data.args) config.args = data.args.split(' '); if (data.env) { config.env = {}; data.env.split(',').forEach((envVar) => { const [key, value] = envVar.split('='); if (key && value) (config.env as Record)[key.trim()] = value.trim(); }); } if (data.cwd) config.cwd = data.cwd; break; } await onSubmit({ name: data.name, type: data.type, config: config as unknown as MCPServerConfigType, }); reset(); setCurrentStep(WizardStep.SelectType); onClose(); } catch (_error) { } finally { setIsSubmitting(false); } }; /** * Step indicator component */ const StepIndicator: React.FC<{ currentStep: WizardStep }> = ({ currentStep }) => (
{[0, 1, 2, 3].map((step) => (
= step ? 'bg-brand-blue text-white' : 'bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400' )} > {currentStep > step ? : step + 1}
{step < 3 && (
step ? 'bg-brand-blue' : 'bg-gray-200 dark:bg-gray-700' )} /> )} ))}
); /** * Select server type step component */ const SelectTypeStep: React.FC<{ serverTypes: typeof serverTypes; selectedType: MCPServerType; setSelectedType: (type: MCPServerType) => void; setValue: (field: keyof FormData, value: unknown) => void; }> = ({ serverTypes, selectedType, setSelectedType, setValue }) => (
Select Server Type
{serverTypes.map((serverType) => ( ))}
); /** * Basic info step component */ const BasicInfoStep: React.FC<{ register: ReturnType>['register']; errors: ReturnType>['formState']['errors']; validationErrors: Record; }> = ({ register, errors, validationErrors }) => (
Basic Information
{(errors.name || validationErrors.name) && ( {errors.name?.message || validationErrors.name} )} Choose a descriptive name to identify this server
); /** * Configuration step component */ const ConfigurationStep: React.FC<{ selectedServerType: typeof selectedServerType; configFields: React.ReactElement; }> = ({ selectedServerType, configFields }) => (
{selectedServerType?.name} Configuration {configFields}
); /** * Review step component */ const ReviewStep: React.FC<{ watch: ReturnType>['watch']; selectedServerType: typeof selectedServerType; configSummary: React.ReactElement[]; }> = ({ watch, selectedServerType, configSummary }) => (
Review Configuration
Server Name {watch('name')}
Server Type {selectedServerType?.name}
Configuration
{configSummary}
); /** * Configuration fields renderer component */ const ConfigFieldsRenderer: React.FC<{ selectedType: MCPServerType; register: ReturnType>['register']; validationErrors: Record; }> = ({ selectedType, register, validationErrors }) => { switch (selectedType) { case 'filesystem': return ( <>
{validationErrors.rootPath && ( {validationErrors.rootPath} )}
); case 'github': return ( <>
{validationErrors.token && ( {validationErrors.token} )}
); case 'postgres': return ( <>
{validationErrors.port && ( {validationErrors.port} )}
); case 'sqlite': return (
{validationErrors.path && ( {validationErrors.path} )}
); case 'custom': return ( <>
{validationErrors.command && ( {validationErrors.command} )}
); default: return null; } }; /** * Config summary component */ const ConfigSummary: React.FC<{ selectedType: MCPServerType; values: FormData; }> = ({ selectedType, values }) => { const items: React.ReactElement[] = []; switch (selectedType) { case 'filesystem': if (values.rootPath) { items.push(
Root: {values.rootPath}
); } if (values.readOnly) { items.push(
Read-only mode enabled
); } break; case 'github': if (values.owner && values.repo) { items.push(
Repository: {values.owner}/{values.repo}
); } if (values.branch) { items.push(
Branch: {values.branch}
); } break; case 'postgres': if (values.host && values.database) { items.push(
{values.username}@{values.host}:{values.port || 5432}/ {values.database}
); } if (values.ssl) { items.push(
SSL enabled
); } break; case 'sqlite': if (values.path) { items.push(
Path: {values.path}
); } break; case 'custom': if (values.command) { items.push(
Command: {values.command}
); } break; } return <>{items}; }; return ( {editingServer ? 'Edit MCP Server' : 'Add MCP Server'}
{currentStep === WizardStep.SelectType && ( )} {currentStep === WizardStep.BasicInfo && ( )} {currentStep === WizardStep.Configuration && ( } /> )} {currentStep === WizardStep.Review && ( ]} /> )}
{currentStep < WizardStep.Review ? ( ) : ( )}
); };