feat(mcp): pre-fill wizard with existing configuration
Allow MCP wizard to load and edit existing server configuration: - Parse current config (JSON/TOML) and pass to wizard as initial data - Auto-detect server type (stdio/http) and populate form fields - Convert objects (env/headers) to multi-line text format for editing - Improves UX by avoiding manual re-entry of existing settings
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { X, Save, AlertCircle, ChevronDown, ChevronUp } from "lucide-react";
|
import { X, Save, AlertCircle, ChevronDown, ChevronUp } from "lucide-react";
|
||||||
import { McpServer, McpServerSpec } from "../../types";
|
import { McpServer, McpServerSpec } from "../../types";
|
||||||
@@ -117,6 +117,31 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
|||||||
// 判断是否使用 TOML 格式
|
// 判断是否使用 TOML 格式
|
||||||
const useToml = appType === "codex";
|
const useToml = appType === "codex";
|
||||||
|
|
||||||
|
const wizardInitialSpec = useMemo(() => {
|
||||||
|
const fallback = initialData?.server;
|
||||||
|
if (!formConfig.trim()) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useToml) {
|
||||||
|
try {
|
||||||
|
return tomlToMcpServer(formConfig);
|
||||||
|
} catch {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(formConfig);
|
||||||
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
||||||
|
return parsed as McpServerSpec;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
} catch {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}, [formConfig, initialData, useToml]);
|
||||||
|
|
||||||
// 预设选择状态(仅新增模式显示;-1 表示自定义)
|
// 预设选择状态(仅新增模式显示;-1 表示自定义)
|
||||||
const [selectedPreset, setSelectedPreset] = useState<number | null>(
|
const [selectedPreset, setSelectedPreset] = useState<number | null>(
|
||||||
isEditing ? null : -1,
|
isEditing ? null : -1,
|
||||||
@@ -661,6 +686,8 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
|||||||
onClose={() => setIsWizardOpen(false)}
|
onClose={() => setIsWizardOpen(false)}
|
||||||
onApply={handleWizardApply}
|
onApply={handleWizardApply}
|
||||||
onNotify={onNotify}
|
onNotify={onNotify}
|
||||||
|
initialTitle={formId}
|
||||||
|
initialServer={wizardInitialSpec}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { X, Save } from "lucide-react";
|
import { X, Save } from "lucide-react";
|
||||||
import { McpServerSpec } from "../../types";
|
import { McpServerSpec } from "../../types";
|
||||||
@@ -13,6 +13,8 @@ interface McpWizardModalProps {
|
|||||||
type: "success" | "error",
|
type: "success" | "error",
|
||||||
duration?: number,
|
duration?: number,
|
||||||
) => void;
|
) => void;
|
||||||
|
initialTitle?: string;
|
||||||
|
initialServer?: McpServerSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,6 +74,8 @@ const McpWizardModal: React.FC<McpWizardModalProps> = ({
|
|||||||
onClose,
|
onClose,
|
||||||
onApply,
|
onApply,
|
||||||
onNotify,
|
onNotify,
|
||||||
|
initialTitle,
|
||||||
|
initialServer,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [wizardType, setWizardType] = useState<"stdio" | "http">("stdio");
|
const [wizardType, setWizardType] = useState<"stdio" | "http">("stdio");
|
||||||
@@ -162,6 +166,55 @@ const McpWizardModal: React.FC<McpWizardModalProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) return;
|
||||||
|
|
||||||
|
const title = initialTitle ?? "";
|
||||||
|
setWizardTitle(title);
|
||||||
|
|
||||||
|
const resolvedType =
|
||||||
|
initialServer?.type ??
|
||||||
|
(initialServer?.url ? "http" : "stdio");
|
||||||
|
|
||||||
|
setWizardType(resolvedType);
|
||||||
|
|
||||||
|
if (resolvedType === "http") {
|
||||||
|
setWizardUrl(initialServer?.url ?? "");
|
||||||
|
const headersCandidate = initialServer?.headers;
|
||||||
|
const headers =
|
||||||
|
headersCandidate && typeof headersCandidate === "object"
|
||||||
|
? headersCandidate
|
||||||
|
: undefined;
|
||||||
|
setWizardHeaders(
|
||||||
|
headers
|
||||||
|
? Object.entries(headers)
|
||||||
|
.map(([k, v]) => `${k}: ${v ?? ""}`)
|
||||||
|
.join("\n")
|
||||||
|
: "",
|
||||||
|
);
|
||||||
|
setWizardCommand("");
|
||||||
|
setWizardArgs("");
|
||||||
|
setWizardEnv("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setWizardCommand(initialServer?.command ?? "");
|
||||||
|
const argsValue = initialServer?.args;
|
||||||
|
setWizardArgs(Array.isArray(argsValue) ? argsValue.join("\n") : "");
|
||||||
|
const envCandidate = initialServer?.env;
|
||||||
|
const env =
|
||||||
|
envCandidate && typeof envCandidate === "object" ? envCandidate : undefined;
|
||||||
|
setWizardEnv(
|
||||||
|
env
|
||||||
|
? Object.entries(env)
|
||||||
|
.map(([k, v]) => `${k}=${v ?? ""}`)
|
||||||
|
.join("\n")
|
||||||
|
: "",
|
||||||
|
);
|
||||||
|
setWizardUrl("");
|
||||||
|
setWizardHeaders("");
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
const preview = generatePreview();
|
const preview = generatePreview();
|
||||||
|
|||||||
Reference in New Issue
Block a user