fix(mcp): form with multiline text handling for args, env, headers, and envVars

This commit is contained in:
Haileyesus
2026-04-16 22:29:34 +03:00
parent 358f47d020
commit 5143a92021
2 changed files with 69 additions and 21 deletions

View File

@@ -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<void>;
};
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<McpFormState>(() => cloneDefaultForm(provider));
const [multilineText, setMultilineText] = useState<MultilineFieldText>(() => (
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 = <K extends keyof MultilineFieldText>(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,
};
}

View File

@@ -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')}
</label>
<textarea
value={formData.args.join('\n')}
onChange={(event) => updateForm('args', parseListLines(event.target.value))}
value={multilineText.args}
onChange={(event) => updateMultilineText('args', event.target.value)}
className="w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2 text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"
rows={3}
placeholder="--port&#10;3000"
@@ -285,14 +286,14 @@ export default function McpServerFormModal({
{supportsWorkingDirectory && (
<div>
<label className="mb-2 block text-sm font-medium text-foreground">
Working Directory
</label>
<Input
value={formData.cwd}
onChange={(event) => updateForm('cwd', event.target.value)}
placeholder="."
/>
<label className="mb-2 block text-sm font-medium text-foreground">
Working Directory
</label>
<Input
value={formData.cwd}
onChange={(event) => updateForm('cwd', event.target.value)}
placeholder="."
/>
</div>
)}
</div>
@@ -319,8 +320,8 @@ export default function McpServerFormModal({
{t('mcpForm.fields.envVars')}
</label>
<textarea
value={formatKeyValueLines(formData.env)}
onChange={(event) => updateForm('env', parseKeyValueLines(event.target.value))}
value={multilineText.env}
onChange={(event) => updateMultilineText('env', event.target.value)}
className="w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2 text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"
rows={3}
placeholder="API_KEY=your-key&#10;DEBUG=true"
@@ -334,8 +335,8 @@ export default function McpServerFormModal({
{t('mcpForm.fields.headers')}
</label>
<textarea
value={formatKeyValueLines(formData.headers)}
onChange={(event) => updateForm('headers', parseKeyValueLines(event.target.value))}
value={multilineText.headers}
onChange={(event) => updateMultilineText('headers', event.target.value)}
className="w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2 text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"
rows={3}
placeholder="Authorization=Bearer token&#10;X-API-Key=your-key"
@@ -349,8 +350,8 @@ export default function McpServerFormModal({
Environment Variable Names
</label>
<textarea
value={formData.envVars.join('\n')}
onChange={(event) => updateForm('envVars', parseListLines(event.target.value))}
value={multilineText.envVars}
onChange={(event) => updateMultilineText('envVars', event.target.value)}
className="w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2 text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"
rows={3}
placeholder="GITHUB_TOKEN&#10;API_KEY"