import { readFileSync } from "node:fs"; import { isAbsolute } from "node:path"; export type TargetProfile = { id: string; displayName: string; kind: "local" | "staging" | "production"; baseUrl: string; usernameEnv: string; passwordEnv: string; }; export type TargetRegistry = { profiles: TargetProfile[]; defaultProfileId: string; }; const envNamePattern = /^[A-Z_][A-Z0-9_]*$/; const profileIdPattern = /^[a-z][a-z0-9_-]*$/; function isRecord(value: unknown): value is Record { return value !== null && typeof value === "object" && !Array.isArray(value); } function validateBaseUrl(value: unknown): string { if (typeof value !== "string") throw new Error("Target profile baseUrl must be a URL string."); let url: URL; try { url = new URL(value); } catch { throw new Error("Target profile baseUrl is invalid."); } const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname); if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) { throw new Error("Target profile baseUrl must use HTTPS, except HTTP loopback."); } if (url.username || url.password || value.includes("?") || value.includes("#") || !url.hostname) { throw new Error("Target profile baseUrl cannot include credentials, query, or fragment."); } return url.toString().replace(/\/+$/, ""); } /** Private registry stores references to secret environment variables, never values. */ export function loadTargetRegistry(env: NodeJS.ProcessEnv = process.env): TargetRegistry | null { const file = env.MCP_TARGET_PROFILES_FILE; if (!file) { if (env.MCP_DEFAULT_TARGET_PROFILE) throw new Error("MCP_DEFAULT_TARGET_PROFILE requires MCP_TARGET_PROFILES_FILE."); return null; } if (!isAbsolute(file)) throw new Error("MCP_TARGET_PROFILES_FILE must be an absolute path."); let raw: unknown; try { raw = JSON.parse(readFileSync(file, "utf8")); } catch { throw new Error("MCP target profile registry could not be read as JSON."); } if (!isRecord(raw) || Object.keys(raw).some((key) => key !== "profiles") || !Array.isArray(raw.profiles) || raw.profiles.length === 0) { throw new Error("MCP target profile registry must contain a non-empty profiles array only."); } const ids = new Set(); const credentialNames = new Set(); const urls = new Set(); const profiles = raw.profiles.map((entry: unknown): TargetProfile => { if (!isRecord(entry) || Object.keys(entry).some((key) => !["id", "displayName", "kind", "baseUrl", "usernameEnv", "passwordEnv"].includes(key))) { throw new Error("Target profile contains unsupported fields; literal credentials are not allowed."); } const { id, displayName, kind, baseUrl, usernameEnv, passwordEnv } = entry; if (typeof id !== "string" || !profileIdPattern.test(id) || ids.has(id)) throw new Error("Target profile ID is invalid or duplicated."); if (typeof displayName !== "string" || !displayName.trim()) throw new Error("Target profile displayName is required."); if (kind !== "local" && kind !== "staging" && kind !== "production") throw new Error("Target profile kind must be local, staging, or production."); if (typeof usernameEnv !== "string" || !envNamePattern.test(usernameEnv) || typeof passwordEnv !== "string" || !envNamePattern.test(passwordEnv) || usernameEnv === passwordEnv) { throw new Error("Target profile credential environment variable names are invalid."); } if (credentialNames.has(usernameEnv) || credentialNames.has(passwordEnv)) throw new Error("Target profiles must use distinct credential environment variables."); if (!env[usernameEnv] || !env[passwordEnv]) throw new Error(`Target profile ${id} is missing credential environment variables.`); const normalizedUrl = validateBaseUrl(baseUrl); if (urls.has(normalizedUrl)) throw new Error("Target profile baseUrl is duplicated."); ids.add(id); credentialNames.add(usernameEnv); credentialNames.add(passwordEnv); urls.add(normalizedUrl); return { id, displayName, kind, baseUrl: normalizedUrl, usernameEnv, passwordEnv }; }); const defaultProfileId = env.MCP_DEFAULT_TARGET_PROFILE; if (!defaultProfileId || !ids.has(defaultProfileId)) throw new Error("MCP_DEFAULT_TARGET_PROFILE must identify a configured target profile."); return { profiles, defaultProfileId }; }