import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { apiPost } from '@/utils/fetchInterceptor'; interface JSONImportFormProps { onSuccess: () => void; onCancel: () => void; } interface McpServerConfig { command?: string; args?: string[]; env?: Record; type?: string; url?: string; headers?: Record; openapi?: { version: string; url: string; }; } interface ImportJsonFormat { mcpServers: Record; } const JSONImportForm: React.FC = ({ onSuccess, onCancel }) => { const { t } = useTranslation(); const [jsonInput, setJsonInput] = useState(''); const [error, setError] = useState(null); const [isImporting, setIsImporting] = useState(false); const [previewServers, setPreviewServers] = useState | null>( null, ); const examplePlaceholder = `{ "mcpServers": { "stdio-server-example": { "command": "npx", "args": ["-y", "mcp-server-example"] }, "sse-server-example": { "type": "sse", "url": "http://localhost:3000" }, "http-server-example": { "type": "streamable-http", "url": "http://localhost:3001", "headers": { "Content-Type": "application/json", "Authorization": "Bearer your-token" } }, "openapi-server-example": { "type": "openapi", "openapi": { "url": "https://petstore.swagger.io/v2/swagger.json" } } } } Supports: STDIO, SSE, HTTP (streamable-http), OpenAPI All servers will be imported in a single efficient batch operation.`; const parseAndValidateJson = (input: string): ImportJsonFormat | null => { try { const parsed = JSON.parse(input.trim()); // Validate structure if (!parsed.mcpServers || typeof parsed.mcpServers !== 'object') { setError(t('jsonImport.invalidFormat')); return null; } return parsed as ImportJsonFormat; } catch (e) { setError(t('jsonImport.parseError')); return null; } }; const handlePreview = () => { setError(null); const parsed = parseAndValidateJson(jsonInput); if (!parsed) return; const servers = Object.entries(parsed.mcpServers).map(([name, config]) => { // Normalize config to MCPHub format const normalizedConfig: any = {}; if (config.type === 'sse' || config.type === 'streamable-http') { normalizedConfig.type = config.type; normalizedConfig.url = config.url; if (config.headers) { normalizedConfig.headers = config.headers; } } else if (config.type === 'openapi') { normalizedConfig.type = 'openapi'; normalizedConfig.openapi = config.openapi; } else { // Default to stdio normalizedConfig.type = 'stdio'; normalizedConfig.command = config.command; normalizedConfig.args = config.args || []; if (config.env) { normalizedConfig.env = config.env; } } return { name, config: normalizedConfig }; }); setPreviewServers(servers); }; const handleImport = async () => { if (!previewServers) return; setIsImporting(true); setError(null); try { // Use batch import API for better performance const result = await apiPost('/servers/batch', { servers: previewServers, }); if (result.success && result.data) { const { successCount, failureCount, results } = result.data; if (failureCount > 0) { const errors = results .filter((r: any) => !r.success) .map((r: any) => `${r.name}: ${r.message || t('jsonImport.addFailed')}`); setError( t('jsonImport.partialSuccess', { count: successCount, total: previewServers.length }) + '\n' + errors.join('\n'), ); } if (successCount > 0) { onSuccess(); } } else { setError(result.message || t('jsonImport.importFailed')); } } catch (err) { console.error('Import error:', err); setError(t('jsonImport.importFailed')); } finally { setIsImporting(false); } }; return (

{t('jsonImport.title')}

{error && (

{error}

)} {!previewServers ? (