import fs from 'fs'; import path from 'path'; import { z } from 'zod'; import { DEFAULT_CODE_MODE_DISABLED_TOOLS, POD_EXEC_TOOL_NAME, normalizeToolName, } from './ToolPolicy.js'; export const CodeModeConfigSchema = z.object({ workspaceDir: z.string().default('./workspace'), enablePII: z.boolean().default(false), disabledTools: z.array(z.string().min(1)).default([...DEFAULT_CODE_MODE_DISABLED_TOOLS]), sandbox: z .object({ memoryLimitMb: z.number().default(256), timeoutMs: z.number().default(5000), }) .prefault({}), servers: z .array( z.object({ name: z.string(), command: z.string(), args: z.array(z.string()).optional(), env: z.record(z.string(), z.string()).optional(), timeoutMs: z.number().optional(), }), ) .default([]), }); export type CodeModeConfig = z.infer; export function loadCodeModeConfig(cwd: string = process.cwd()): CodeModeConfig { const configFiles = ['kube-mcp.code-mode.json', 'kube-mcp.code-mode.example.json']; for (const file of configFiles) { const configPath = path.resolve(cwd, file); if (fs.existsSync(configPath)) { const content = fs.readFileSync(configPath, 'utf-8'); const json = JSON.parse(content); return applyDisabledToolsEnvironment(CodeModeConfigSchema.parse(json)); } } // Return defaults if no config found return applyDisabledToolsEnvironment(CodeModeConfigSchema.parse({})); } function applyDisabledToolsEnvironment(config: CodeModeConfig): CodeModeConfig { const envValue = process.env.MCP_CODE_MODE_DISABLED_TOOLS; const configured = envValue === undefined ? config.disabledTools : envValue .split(',') .map((name) => name.trim()) .filter(Boolean); const disabledTools = Array.from(new Set(configured.map(normalizeToolName))); if (disabledTools.includes(POD_EXEC_TOOL_NAME)) { throw new Error( `${POD_EXEC_TOOL_NAME} is permanently guarded and must not be listed in disabledTools`, ); } return { ...config, disabledTools }; }