import cls from 'classnames'; import React, { ChangeEvent, FC, FormEvent, useCallback, useEffect, useState } from 'react'; import { Select } from '@opensumi/ide-components'; import { Button } from '@opensumi/ide-components/lib/button'; import { Modal } from '@opensumi/ide-components/lib/modal'; import { useInjectable } from '@opensumi/ide-core-browser'; import { formatLocalize, localize } from '@opensumi/ide-core-common'; import { IMessageService } from '@opensumi/ide-overlay'; import { MCPServer, MCP_SERVER_TYPE } from '../../../../common/types'; import styles from './mcp-server-form.module.less'; export interface MCPServerFormData { name: string; command?: string; args?: string[]; env?: Record; type: MCP_SERVER_TYPE; url?: string; } interface Props { visible: boolean; initialData?: MCPServerFormData; servers: MCPServer[]; onSave: (data: MCPServerFormData) => void; onCancel: () => void; } export const MCPServerForm: FC = ({ visible, initialData, onSave, onCancel, servers: existingServers }) => { const [formData, setFormData] = useState(() => ({ name: '', command: '', args: [], env: {}, type: MCP_SERVER_TYPE.STDIO, ...initialData, })); const messageService = useInjectable(IMessageService); const [argsText, setArgsText] = useState(() => initialData?.args?.join(' ') || ''); const [envText, setEnvText] = useState(() => { if (!initialData?.env) { return ''; } return Object.entries(initialData.env) .map(([key, value]) => `${key}=${value}`) .join('\n'); }); useEffect(() => { setFormData({ name: '', command: '', args: [], env: {}, type: MCP_SERVER_TYPE.STDIO, ...initialData, }); setArgsText(initialData?.args?.join(' ') || ''); setEnvText( initialData?.env ? Object.entries(initialData.env) .map(([key, value]) => `${key}=${value}`) .join('\n') : '', ); }, [initialData]); const validateForm = useCallback( (formData: MCPServerFormData) => { if (formData.name.trim() === '') { messageService.error(localize('ai.native.mcp.name.isRequired')); return false; } if ( !initialData && existingServers.some((server) => server.name.toLocaleLowerCase() === formData.name.toLocaleLowerCase()) ) { messageService.error(formatLocalize('ai.native.mcp.serverNameExists', formData.name)); return false; } if (formData.type === MCP_SERVER_TYPE.SSE) { const isServerHostValid = formData.url?.trim() !== ''; if (!isServerHostValid) { messageService.error(localize('ai.native.mcp.url.isRequired')); } return isServerHostValid; } const isCommandValid = formData.command?.trim() !== ''; if (!isCommandValid) { messageService.error(localize('ai.native.mcp.command.isRequired')); } return isCommandValid; }, [existingServers, initialData], ); const handleSubmit = useCallback( (e: FormEvent) => { e.preventDefault(); const isValid = validateForm(formData); if (!isValid) { return; } const form = { ...formData, }; if (form.command) { form.command = form.command.trim(); } if (form.url) { form.url = form.url.trim(); } if (formData.type === MCP_SERVER_TYPE.SSE) { form.url = form.url?.trim(); } else { const args = argsText.split(' ').filter(Boolean); const env = envText .split('\n') .filter(Boolean) .reduce((acc, line) => { const [key, value] = line.split('='); if (key && value) { acc[key.trim()] = value.trim(); } return acc; }, {} as Record); form.args = args; form.env = env; } setFormData({ ...formData, name: '', command: '', url: '', args: [], env: {}, }); onSave(form); }, [formData, argsText, envText, onSave, validateForm], ); const handleCommandChange = useCallback( (e: ChangeEvent) => { setFormData({ ...formData, command: e.target.value }); }, [formData, argsText], ); const handleArgsChange = useCallback( (e: ChangeEvent) => { setArgsText(e.target.value); }, [argsText], ); const handleEnvChange = useCallback( (e: ChangeEvent) => { setEnvText(e.target.value); }, [envText], ); const handleServerHostChange = useCallback( (e: ChangeEvent) => { setFormData({ ...formData, url: e.target.value }); }, [formData], ); const handleTypeChange = useCallback( (value: MCP_SERVER_TYPE) => { setFormData({ ...formData, type: value, command: '', args: [], env: {}, url: '', }); }, [formData], ); const renderFormItems = useCallback(() => { if (formData?.type === MCP_SERVER_TYPE.STDIO) { return ( <>