import { useState } from 'react' import { useTranslation } from 'react-i18next' import { Server, EnvVar, ServerFormData } from '@/types' interface ServerFormProps { onSubmit: (payload: any) => void onCancel: () => void initialData?: Server | null modalTitle: string formError?: string | null } const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formError = null }: ServerFormProps) => { const { t } = useTranslation() // Determine the initial server type from the initialData const getInitialServerType = () => { if (!initialData || !initialData.config) return 'stdio'; if (initialData.config.type) { return initialData.config.type; // Use explicit type if available } else if (initialData.config.url) { return 'sse'; // Fallback to SSE if URL exists } else { return 'stdio'; // Default to stdio } }; const [serverType, setServerType] = useState<'stdio' | 'sse' | 'streamable-http' | 'openapi'>(getInitialServerType()); const [formData, setFormData] = useState({ name: (initialData && initialData.name) || '', url: (initialData && initialData.config && initialData.config.url) || '', command: (initialData && initialData.config && initialData.config.command) || '', arguments: initialData && initialData.config && initialData.config.args ? Array.isArray(initialData.config.args) ? initialData.config.args.join(' ') : String(initialData.config.args) : '', args: (initialData && initialData.config && initialData.config.args) || [], type: getInitialServerType(), // Initialize the type field env: [], headers: [], options: { timeout: (initialData && initialData.config && initialData.config.options && initialData.config.options.timeout) || 60000, resetTimeoutOnProgress: (initialData && initialData.config && initialData.config.options && initialData.config.options.resetTimeoutOnProgress) || false, maxTotalTimeout: (initialData && initialData.config && initialData.config.options && initialData.config.options.maxTotalTimeout) || undefined, }, // OpenAPI configuration initialization openapi: initialData && initialData.config && initialData.config.openapi ? { url: initialData.config.openapi.url || '', schema: initialData.config.openapi.schema ? JSON.stringify(initialData.config.openapi.schema, null, 2) : '', inputMode: initialData.config.openapi.url ? 'url' : (initialData.config.openapi.schema ? 'schema' : 'url'), version: initialData.config.openapi.version || '3.1.0', securityType: initialData.config.openapi.security?.type || 'none', // API Key initialization apiKeyName: initialData.config.openapi.security?.apiKey?.name || '', apiKeyIn: initialData.config.openapi.security?.apiKey?.in || 'header', apiKeyValue: initialData.config.openapi.security?.apiKey?.value || '', // HTTP auth initialization httpScheme: initialData.config.openapi.security?.http?.scheme || 'bearer', httpCredentials: initialData.config.openapi.security?.http?.credentials || '', // OAuth2 initialization oauth2Token: initialData.config.openapi.security?.oauth2?.token || '', // OpenID Connect initialization openIdConnectUrl: initialData.config.openapi.security?.openIdConnect?.url || '', openIdConnectToken: initialData.config.openapi.security?.openIdConnect?.token || '' } : { inputMode: 'url', url: '', schema: '', version: '3.1.0', securityType: 'none' } }) const [envVars, setEnvVars] = useState( initialData && initialData.config && initialData.config.env ? Object.entries(initialData.config.env).map(([key, value]) => ({ key, value })) : [], ) const [headerVars, setHeaderVars] = useState( initialData && initialData.config && initialData.config.headers ? Object.entries(initialData.config.headers).map(([key, value]) => ({ key, value })) : [], ) const [isRequestOptionsExpanded, setIsRequestOptionsExpanded] = useState(false) const [error, setError] = useState(null) const isEdit = !!initialData const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target setFormData({ ...formData, [name]: value }) } // Transform space-separated arguments string into array const handleArgsChange = (value: string) => { const args = value.split(' ').filter((arg) => arg.trim() !== '') setFormData({ ...formData, arguments: value, args }) } const updateServerType = (type: 'stdio' | 'sse' | 'streamable-http' | 'openapi') => { setServerType(type); setFormData(prev => ({ ...prev, type })); } const handleEnvVarChange = (index: number, field: 'key' | 'value', value: string) => { const newEnvVars = [...envVars] newEnvVars[index][field] = value setEnvVars(newEnvVars) } const addEnvVar = () => { setEnvVars([...envVars, { key: '', value: '' }]) } const removeEnvVar = (index: number) => { const newEnvVars = [...envVars] newEnvVars.splice(index, 1) setEnvVars(newEnvVars) } const handleHeaderVarChange = (index: number, field: 'key' | 'value', value: string) => { const newHeaderVars = [...headerVars] newHeaderVars[index][field] = value setHeaderVars(newHeaderVars) } const addHeaderVar = () => { setHeaderVars([...headerVars, { key: '', value: '' }]) } const removeHeaderVar = (index: number) => { const newHeaderVars = [...headerVars] newHeaderVars.splice(index, 1) setHeaderVars(newHeaderVars) } // Handle options changes const handleOptionsChange = (field: 'timeout' | 'resetTimeoutOnProgress' | 'maxTotalTimeout', value: number | boolean | undefined) => { setFormData(prev => ({ ...prev, options: { ...prev.options, [field]: value } })) } // Submit handler for server configuration const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setError(null) try { const env: Record = {} envVars.forEach(({ key, value }) => { if (key.trim()) { env[key.trim()] = value } }) const headers: Record = {} headerVars.forEach(({ key, value }) => { if (key.trim()) { headers[key.trim()] = value } }) // Prepare options object, only include defined values const options: any = {} if (formData.options?.timeout && formData.options.timeout !== 60000) { options.timeout = formData.options.timeout } if (formData.options?.resetTimeoutOnProgress) { options.resetTimeoutOnProgress = formData.options.resetTimeoutOnProgress } if (formData.options?.maxTotalTimeout) { options.maxTotalTimeout = formData.options.maxTotalTimeout } const payload = { name: formData.name, config: { type: serverType, // Always include the type ...(serverType === 'openapi' ? { openapi: (() => { const openapi: any = { version: formData.openapi?.version || '3.1.0' }; // Add URL or schema based on input mode if (formData.openapi?.inputMode === 'url') { openapi.url = formData.openapi?.url || ''; } else if (formData.openapi?.inputMode === 'schema' && formData.openapi?.schema) { try { openapi.schema = JSON.parse(formData.openapi.schema); } catch (e) { throw new Error('Invalid JSON schema format'); } } // Add security configuration if provided if (formData.openapi?.securityType && formData.openapi.securityType !== 'none') { openapi.security = { type: formData.openapi.securityType, ...(formData.openapi.securityType === 'apiKey' && { apiKey: { name: formData.openapi.apiKeyName || '', in: formData.openapi.apiKeyIn || 'header', value: formData.openapi.apiKeyValue || '' } }), ...(formData.openapi.securityType === 'http' && { http: { scheme: formData.openapi.httpScheme || 'bearer', credentials: formData.openapi.httpCredentials || '' } }), ...(formData.openapi.securityType === 'oauth2' && { oauth2: { token: formData.openapi.oauth2Token || '' } }), ...(formData.openapi.securityType === 'openIdConnect' && { openIdConnect: { url: formData.openapi.openIdConnectUrl || '', token: formData.openapi.openIdConnectToken || '' } }) }; } return openapi; })(), ...(Object.keys(headers).length > 0 ? { headers } : {}) } : serverType === 'sse' || serverType === 'streamable-http' ? { url: formData.url, ...(Object.keys(headers).length > 0 ? { headers } : {}) } : { command: formData.command, args: formData.args, env: Object.keys(env).length > 0 ? env : undefined, } ), ...(Object.keys(options).length > 0 ? { options } : {}) } } onSubmit(payload) } catch (err) { setError(`Error: ${err instanceof Error ? err.message : String(err)}`) } } return (

{modalTitle}

{(error || formError) && (
{formError || error}
)}
updateServerType('stdio')} className="mr-1" />
updateServerType('sse')} className="mr-1" />
updateServerType('streamable-http')} className="mr-1" />
updateServerType('openapi')} className="mr-1" />
{serverType === 'openapi' ? ( <> {/* Input Mode Selection */}
setFormData(prev => ({ ...prev, openapi: { ...prev.openapi!, inputMode: 'url' } }))} className="mr-1" />
setFormData(prev => ({ ...prev, openapi: { ...prev.openapi!, inputMode: 'schema' } }))} className="mr-1" />
{/* URL Input */} {formData.openapi?.inputMode === 'url' && (
setFormData(prev => ({ ...prev, openapi: { ...prev.openapi!, url: e.target.value } }))} className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline form-input" placeholder="e.g.: https://api.example.com/openapi.json" required={serverType === 'openapi' && formData.openapi?.inputMode === 'url'} />
)} {/* Schema Input */} {formData.openapi?.inputMode === 'schema' && (