refactor(mcp/ui): share normalizeEnvVars helper between create and edit forms

This commit is contained in:
mateo-berri 2026-06-04 05:01:23 +00:00
parent ca1e837532
commit c9cc963101
No known key found for this signature in database
3 changed files with 29 additions and 49 deletions

View file

@ -15,7 +15,7 @@ import OpenAPIFormSection, { OpenAPIKeyTool } from "./OpenAPIFormSection";
import MCPLogoSelector from "./MCPLogoSelector";
import EnvVarsSection from "./EnvVarsSection";
import { isAdminRole } from "@/utils/roles";
import { validateMCPServerUrl, validateMCPServerName } from "./utils";
import { validateMCPServerUrl, validateMCPServerName, normalizeEnvVars } from "./utils";
import NotificationsManager from "../molecules/notifications_manager";
import { useMcpOAuthFlow } from "@/hooks/useMcpOAuthFlow";
import { useTestMCPConnection } from "@/hooks/useTestMCPConnection";
@ -49,31 +49,6 @@ const reduceStaticHeaders = (list: unknown): Record<string, string> => {
}, {});
};
type EnvVarEntry = { name: string; value: string; scope: "global" | "user"; description?: string };
/** Normalize the env_vars form list into the payload shape the backend expects.
* Drops empty rows and any with invalid identifiers. */
const normalizeEnvVars = (list: unknown): EnvVarEntry[] => {
if (!Array.isArray(list)) return [];
const seen = new Set<string>();
const out: EnvVarEntry[] = [];
for (const entry of list) {
if (!entry || typeof entry !== "object") continue;
const name = String((entry as Record<string, unknown>).name ?? "").trim();
if (!name || seen.has(name)) continue;
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) continue;
const scope = (entry as Record<string, unknown>).scope === "user" ? "user" : "global";
out.push({
name,
value: scope === "user" ? "" : String((entry as Record<string, unknown>).value ?? ""),
scope,
description: ((entry as Record<string, unknown>).description as string | undefined) || undefined,
});
seen.add(name);
}
return out;
};
const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
userID,
userRole,

View file

@ -10,7 +10,7 @@ import MCPToolConfiguration from "./mcp_tool_configuration";
import StdioConfiguration from "./StdioConfiguration";
import MCPLogoSelector from "./MCPLogoSelector";
import EnvVarsSection from "./EnvVarsSection";
import { validateMCPServerUrl, validateMCPServerName } from "./utils";
import { validateMCPServerUrl, validateMCPServerName, normalizeEnvVars } from "./utils";
import NotificationsManager from "../molecules/notifications_manager";
import { useMcpOAuthFlow } from "@/hooks/useMcpOAuthFlow";
import { getSecureItem, setSecureItem } from "@/utils/secureStorage";
@ -431,28 +431,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
}, {})
: ({} as Record<string, string>);
const envVars = Array.isArray(envVarsList)
? envVarsList.reduce(
(acc: Array<{ name: string; value: string; scope: "global" | "user"; description?: string }>, entry: Record<string, unknown>) => {
const name = String(entry?.name ?? "").trim();
if (!name || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
return acc;
}
if (acc.some((existing) => existing.name === name)) {
return acc;
}
const scope = entry?.scope === "user" ? "user" : "global";
acc.push({
name,
value: scope === "user" ? "" : String(entry?.value ?? ""),
scope,
description: (entry?.description as string | undefined) || undefined,
});
return acc;
},
[],
)
: [];
const envVars = normalizeEnvVars(envVarsList);
const credentialsPayload =
credentialValues && typeof credentialValues === "object"

View file

@ -1,3 +1,5 @@
import { MCPEnvVar, MCPEnvVarScope } from "./types";
export const extractMCPToken = (url: string): { token: string | null; baseUrl: string } => {
try {
const mcpIndex = url.indexOf("/mcp/");
@ -51,3 +53,27 @@ export const validateMCPServerName = (value: string) => {
? Promise.reject("Cannot contain '-' (hyphen) or spaces. Please use '_' (underscore) instead.")
: Promise.resolve();
};
// Normalize the env_vars form list into the payload shape the backend expects.
// Drops empty rows, invalid identifiers, and duplicate names; user-scoped entries never carry a value.
export const normalizeEnvVars = (list: unknown): MCPEnvVar[] => {
if (!Array.isArray(list)) return [];
const seen = new Set<string>();
const out: MCPEnvVar[] = [];
for (const entry of list) {
if (!entry || typeof entry !== "object") continue;
const record = entry as Record<string, unknown>;
const name = String(record.name ?? "").trim();
if (!name || seen.has(name)) continue;
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) continue;
const scope: MCPEnvVarScope = record.scope === "user" ? "user" : "global";
out.push({
name,
value: scope === "user" ? "" : String(record.value ?? ""),
scope,
description: (record.description as string | undefined) || undefined,
});
seen.add(name);
}
return out;
};