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 getInitialServerEnvVars = (data: Server | null): EnvVar[] => { if (!data || !data.config || !data.config.env) return []; return Object.entries(data.config.env).map(([key, value]) => ({ key, value, description: '', // You can set a default description if needed })); }; const getInitialOAuthConfig = (data: Server | null): ServerFormData['oauth'] => { const oauth = data?.config?.oauth; return { clientId: oauth?.clientId || '', clientSecret: oauth?.clientSecret || '', scopes: oauth?.scopes ? oauth.scopes.join(' ') : '', accessToken: oauth?.accessToken || '', refreshToken: oauth?.refreshToken || '', authorizationEndpoint: oauth?.authorizationEndpoint || '', tokenEndpoint: oauth?.tokenEndpoint || '', resource: oauth?.resource || '', }; }; 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: getInitialServerEnvVars(initialData), 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, }, oauth: getInitialOAuthConfig(initialData), // KeepAlive configuration initialization keepAlive: { enabled: initialData?.config?.enableKeepAlive || false, interval: initialData?.config?.keepAliveInterval || 60000, }, // 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 || '', // Passthrough headers initialization passthroughHeaders: initialData.config.openapi.passthroughHeaders ? initialData.config.openapi.passthroughHeaders.join(', ') : '', } : { inputMode: 'url', url: '', schema: '', version: '3.1.0', securityType: 'none', passthroughHeaders: '', }, }); 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 [isOAuthSectionExpanded, setIsOAuthSectionExpanded] = useState(false); const [isKeepAliveSectionExpanded, setIsKeepAliveSectionExpanded] = 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); }; const handleOAuthChange = >( field: K, value: string, ) => { setFormData((prev) => ({ ...prev, oauth: { ...(prev.oauth || {}), [field]: value, }, })); }; // 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 oauthConfig = (() => { if (!formData.oauth) return undefined; const { clientId, clientSecret, scopes, accessToken, refreshToken, authorizationEndpoint, tokenEndpoint, resource, } = formData.oauth; const oauth: Record = {}; if (clientId && clientId.trim()) oauth.clientId = clientId.trim(); if (clientSecret && clientSecret.trim()) oauth.clientSecret = clientSecret.trim(); if (scopes && scopes.trim()) { const parsedScopes = scopes .split(/[\s,]+/) .map((scope) => scope.trim()) .filter((scope) => scope.length > 0); if (parsedScopes.length > 0) { oauth.scopes = parsedScopes; } } if (accessToken && accessToken.trim()) oauth.accessToken = accessToken.trim(); if (refreshToken && refreshToken.trim()) oauth.refreshToken = refreshToken.trim(); if (authorizationEndpoint && authorizationEndpoint.trim()) { oauth.authorizationEndpoint = authorizationEndpoint.trim(); } if (tokenEndpoint && tokenEndpoint.trim()) oauth.tokenEndpoint = tokenEndpoint.trim(); if (resource && resource.trim()) oauth.resource = resource.trim(); return Object.keys(oauth).length > 0 ? oauth : undefined; })(); 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 || '', }, }), }; } // Add passthrough headers if provided if ( formData.openapi?.passthroughHeaders && formData.openapi.passthroughHeaders.trim() ) { openapi.passthroughHeaders = formData.openapi.passthroughHeaders .split(',') .map((header) => header.trim()) .filter((header) => header.length > 0); } return openapi; })(), ...(Object.keys(headers).length > 0 ? { headers } : {}), } : serverType === 'sse' || serverType === 'streamable-http' ? { url: formData.url, ...(Object.keys(headers).length > 0 ? { headers } : {}), ...(Object.keys(env).length > 0 ? { env } : {}), ...(oauthConfig ? { oauth: oauthConfig } : {}), } : { command: formData.command, args: formData.args, env: Object.keys(env).length > 0 ? env : undefined, }), ...(Object.keys(options).length > 0 ? { options } : {}), // KeepAlive configuration (only for SSE/streamable-http types) ...(serverType === 'sse' || serverType === 'streamable-http' ? { enableKeepAlive: formData.keepAlive?.enabled || false, ...(formData.keepAlive?.enabled ? { keepAliveInterval: formData.keepAlive.interval || 60000 } : {}), } : {}), }, }; 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' && (