Fix MCP config form state preservation and validation

This commit is contained in:
Gabrielsv01 2026-05-04 23:14:07 -03:00
parent 3a96d8b1ea
commit f1737f7100
3 changed files with 34 additions and 12 deletions

View file

@ -152,11 +152,13 @@ export function ConfigPage() {
id: `mcp-${Date.now()}-${nextIndex}`, id: `mcp-${Date.now()}-${nextIndex}`,
name: "", name: "",
enabled: true, enabled: true,
deferredOverride: null,
type: "stdio", type: "stdio",
url: "", url: "",
command: "", command: "",
argsText: "", argsText: "",
envText: "{}", envText: "{}",
envFile: "",
headersText: "{}", headersText: "{}",
} }
updateField("mcpServers", [...form.mcpServers, server]) updateField("mcpServers", [...form.mcpServers, server])
@ -264,16 +266,20 @@ export function ConfigPage() {
"Cron exec timeout", "Cron exec timeout",
{ min: 0 }, { min: 0 },
) )
const mcpDiscoveryTTL = parseIntField( const mcpDiscoveryValidationEnabled =
form.mcpDiscoveryTTL, form.mcpEnabled && form.mcpDiscoveryEnabled
"MCP discovery ttl", const mcpDiscoveryTTL = mcpDiscoveryValidationEnabled
{ min: 0 }, ? parseIntField(form.mcpDiscoveryTTL, "MCP discovery ttl", {
) min: 0,
const mcpDiscoveryMaxSearchResults = parseIntField( })
: Number.parseInt(EMPTY_FORM.mcpDiscoveryTTL, 10)
const mcpDiscoveryMaxSearchResults = mcpDiscoveryValidationEnabled
? parseIntField(
form.mcpDiscoveryMaxSearchResults, form.mcpDiscoveryMaxSearchResults,
"MCP discovery max search results", "MCP discovery max search results",
{ min: 0 }, { min: 0 },
) )
: Number.parseInt(EMPTY_FORM.mcpDiscoveryMaxSearchResults, 10)
const execConfigPatch: Record<string, unknown> = { const execConfigPatch: Record<string, unknown> = {
enabled: form.execEnabled, enabled: form.execEnabled,
} }
@ -290,6 +296,7 @@ export function ConfigPage() {
name: server.name.trim(), name: server.name.trim(),
url: server.url.trim(), url: server.url.trim(),
command: server.command.trim(), command: server.command.trim(),
envFile: server.envFile.trim(),
})) }))
.filter((server) => server.name !== "") .filter((server) => server.name !== "")
@ -321,6 +328,11 @@ export function ConfigPage() {
.map((name) => [name, null] as const) .map((name) => [name, null] as const)
const upsertServerEntries = normalizedServers.map((server) => { const upsertServerEntries = normalizedServers.map((server) => {
const deferredPatch =
server.deferredOverride === null
? {}
: { deferred: server.deferredOverride }
if (server.type !== "stdio") { if (server.type !== "stdio") {
if (server.url === "") { if (server.url === "") {
throw new Error(`MCP server ${server.name} requires a URL.`) throw new Error(`MCP server ${server.name} requires a URL.`)
@ -329,6 +341,7 @@ export function ConfigPage() {
return [ return [
server.name, server.name,
{ {
...deferredPatch,
enabled: server.enabled, enabled: server.enabled,
type: server.type, type: server.type,
url: server.url, url: server.url,
@ -351,6 +364,7 @@ export function ConfigPage() {
return [ return [
server.name, server.name,
{ {
...deferredPatch,
enabled: server.enabled, enabled: server.enabled,
type: "stdio", type: "stdio",
command: server.command, command: server.command,
@ -359,9 +373,9 @@ export function ConfigPage() {
server.envText, server.envText,
`MCP server ${server.name} env`, `MCP server ${server.name} env`,
), ),
env_file: server.envFile === "" ? null : server.envFile,
url: null, url: null,
headers: null, headers: null,
env_file: null,
}, },
] as const ] as const
}) })

View file

@ -401,7 +401,7 @@ export function MCPSection({
/> />
</div> </div>
{server.type === "http" ? ( {server.type !== "stdio" ? (
<div className="mt-3 grid gap-3"> <div className="mt-3 grid gap-3">
<Input <Input
value={server.url} value={server.url}

View file

@ -40,11 +40,13 @@ export interface MCPServerForm {
id: string id: string
name: string name: string
enabled: boolean enabled: boolean
deferredOverride: boolean | null
type: MCPServerType type: MCPServerType
url: string url: string
command: string command: string
argsText: string argsText: string
envText: string envText: string
envFile: string
headersText: string headersText: string
} }
@ -144,6 +146,10 @@ function asBool(value: unknown): boolean {
return value === true return value === true
} }
function asOptionalBool(value: unknown): boolean | null {
return typeof value === "boolean" ? value : null
}
function asNumberString(value: unknown, fallback: string): string { function asNumberString(value: unknown, fallback: string): string {
if (typeof value === "number" && Number.isFinite(value)) { if (typeof value === "number" && Number.isFinite(value)) {
return String(value) return String(value)
@ -193,11 +199,13 @@ function mapMCPServers(value: unknown): MCPServerForm[] {
id: makeMCPServerID(name), id: makeMCPServerID(name),
name, name,
enabled: cfg.enabled !== false, enabled: cfg.enabled !== false,
deferredOverride: asOptionalBool(cfg.deferred),
type, type,
url, url,
command: asString(cfg.command), command: asString(cfg.command),
argsText: argsList.join("\n"), argsText: argsList.join("\n"),
envText: JSON.stringify(env, null, 2), envText: JSON.stringify(env, null, 2),
envFile: asString(cfg.env_file),
headersText: JSON.stringify(headers, null, 2), headersText: JSON.stringify(headers, null, 2),
} }
}) })