diff --git a/src/components/mcp/hooks/useMcpServerForm.ts b/src/components/mcp/hooks/useMcpServerForm.ts index ea08fd28..e9224de5 100644 --- a/src/components/mcp/hooks/useMcpServerForm.ts +++ b/src/components/mcp/hooks/useMcpServerForm.ts @@ -3,7 +3,14 @@ import { useTranslation } from 'react-i18next'; import { DEFAULT_MCP_FORM, MCP_SUPPORTED_SCOPES, MCP_SUPPORTED_TRANSPORTS } from '../constants'; import type { McpFormState, McpProject, McpProvider, McpScope, McpTransport, ProviderMcpServer } from '../types'; -import { getErrorMessage, getProjectPath, isMcpTransport } from '../utils/mcpFormatting'; +import { + formatKeyValueLines, + getErrorMessage, + getProjectPath, + isMcpTransport, + parseKeyValueLines, + parseListLines, +} from '../utils/mcpFormatting'; type UseMcpServerFormArgs = { provider: McpProvider; @@ -13,6 +20,14 @@ type UseMcpServerFormArgs = { onSubmit: (formData: McpFormState, editingServer: ProviderMcpServer | null) => Promise; }; +type MultilineFieldText = { + args: string; + env: string; + headers: string; + envVars: string; + envHttpHeaders: string; +}; + const cloneDefaultForm = (provider: McpProvider): McpFormState => ({ ...DEFAULT_MCP_FORM, scope: MCP_SUPPORTED_SCOPES[provider][0], @@ -44,6 +59,14 @@ const createFormStateFromServer = ( envHttpHeaders: server.envHttpHeaders || {}, }); +const createMultilineTextFromForm = (formData: McpFormState): MultilineFieldText => ({ + args: formData.args.join('\n'), + env: formatKeyValueLines(formData.env), + headers: formatKeyValueLines(formData.headers), + envVars: formData.envVars.join('\n'), + envHttpHeaders: formatKeyValueLines(formData.envHttpHeaders), +}); + const normalizeScope = (provider: McpProvider, value: McpScope): McpScope => ( MCP_SUPPORTED_SCOPES[provider].includes(value) ? value : MCP_SUPPORTED_SCOPES[provider][0] ); @@ -61,6 +84,9 @@ export function useMcpServerForm({ }: UseMcpServerFormArgs) { const { t } = useTranslation('settings'); const [formData, setFormData] = useState(() => cloneDefaultForm(provider)); + const [multilineText, setMultilineText] = useState(() => ( + createMultilineTextFromForm(cloneDefaultForm(provider)) + )); const [jsonValidationError, setJsonValidationError] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); @@ -73,11 +99,15 @@ export function useMcpServerForm({ setJsonValidationError(''); if (editingServer) { - setFormData(createFormStateFromServer(provider, editingServer)); + const nextFormData = createFormStateFromServer(provider, editingServer); + setFormData(nextFormData); + setMultilineText(createMultilineTextFromForm(nextFormData)); return; } - setFormData(cloneDefaultForm(provider)); + const nextFormData = cloneDefaultForm(provider); + setFormData(nextFormData); + setMultilineText(createMultilineTextFromForm(nextFormData)); }, [editingServer, isOpen, provider]); const projectOptions = useMemo(() => ( @@ -135,6 +165,19 @@ export function useMcpServerForm({ validateJsonInput(value); }; + const updateMultilineText = (key: K, value: MultilineFieldText[K]) => { + setMultilineText((prev) => ({ ...prev, [key]: value })); + }; + + const createSubmitFormData = (): McpFormState => ({ + ...formData, + args: parseListLines(multilineText.args), + env: parseKeyValueLines(multilineText.env), + headers: parseKeyValueLines(multilineText.headers), + envVars: parseListLines(multilineText.envVars), + envHttpHeaders: parseKeyValueLines(multilineText.envHttpHeaders), + }); + const canSubmit = useMemo(() => { if (!formData.name.trim()) { return false; @@ -160,7 +203,9 @@ export function useMcpServerForm({ setIsSubmitting(true); try { - await onSubmit(formData, editingServer); + // Textareas keep raw strings while editing so users can create blank + // lines or partial KEY=value entries without the form rewriting them. + await onSubmit(createSubmitFormData(), editingServer); } catch (error) { alert(`Error: ${getErrorMessage(error)}`); } finally { @@ -171,6 +216,7 @@ export function useMcpServerForm({ return { formData, setFormData, + multilineText, projectOptions, isEditing, isSubmitting, @@ -180,6 +226,7 @@ export function useMcpServerForm({ updateScope, updateTransport, updateJsonInput, + updateMultilineText, handleSubmit, }; } diff --git a/src/components/mcp/view/modals/McpServerFormModal.tsx b/src/components/mcp/view/modals/McpServerFormModal.tsx index 2b307c09..09f0c217 100644 --- a/src/components/mcp/view/modals/McpServerFormModal.tsx +++ b/src/components/mcp/view/modals/McpServerFormModal.tsx @@ -9,7 +9,6 @@ import { MCP_SUPPORTS_WORKING_DIRECTORY, } from '../../constants'; import { useMcpServerForm } from '../../hooks/useMcpServerForm'; -import { formatKeyValueLines, parseKeyValueLines, parseListLines } from '../../utils/mcpFormatting'; import type { McpFormState, McpProject, McpProvider, McpScope, ProviderMcpServer } from '../../types'; type McpServerFormModalProps = { @@ -56,6 +55,7 @@ export default function McpServerFormModal({ const { t } = useTranslation('settings'); const { formData, + multilineText, projectOptions, isEditing, isSubmitting, @@ -65,6 +65,7 @@ export default function McpServerFormModal({ updateScope, updateTransport, updateJsonInput, + updateMultilineText, handleSubmit, } = useMcpServerForm({ provider, @@ -275,8 +276,8 @@ export default function McpServerFormModal({ {t('mcpForm.fields.arguments')}