mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-04-21 13:01:31 +00:00
fix(mcp): form with multiline text handling for args, env, headers, and envVars
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 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 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 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 API_KEY"
|
||||
|
||||
Reference in New Issue
Block a user