UNPKG

mcp-server-kubernetes

Version:

MCP server for interacting with Kubernetes clusters via kubectl

156 lines (155 loc) • 6.43 kB
import { execFileSyncSafe } from "../security/kubectl-flags.js"; import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"; import { getSpawnMaxBuffer } from "../config/max-buffer.js"; import { contextParameter, namespaceParameter, } from "../models/common-parameters.js"; import { assertAllowedGenericCommand, assertNoDangerousFlags, assertNoRemoteFileReads, assertNotFlagLike, } from "../security/kubectl-flags.js"; export const kubectlGenericSchema = { name: "kubectl_generic", description: "Execute any kubectl command with the provided arguments and flags", annotations: { destructiveHint: true, }, inputSchema: { type: "object", properties: { command: { type: "string", description: "The kubectl command to execute (e.g. patch, rollout, top)", }, subCommand: { type: "string", description: "Subcommand if applicable (e.g. 'history' for rollout)", }, resourceType: { type: "string", description: "Resource type (e.g. pod, deployment)", }, name: { type: "string", description: "Resource name", }, namespace: namespaceParameter, allNamespaces: { type: "boolean", description: "If true, run the command across all namespaces", default: false, }, outputFormat: { type: "string", description: "Output format (e.g. json, yaml, wide)", enum: ["json", "yaml", "wide", "name", "custom"], }, flags: { type: "object", description: "Command flags as key-value pairs", additionalProperties: true, }, args: { type: "array", items: { type: "string" }, description: "Additional command arguments", }, context: contextParameter, }, required: ["command"], }, }; export async function kubectlGeneric(k8sManager, input) { try { // Reject credential/target-redirecting flags before constructing the // command. See src/security/kubectl-flags.ts for the rationale. assertNoDangerousFlags(input.flags, input.args); // The verb and the resource operands are never flags — only the free-form // `args` array is. Refusing a leading "-" in the operand slots closes the // positional injection point without narrowing what `args` can express. assertNotFlagLike(input.command, "command"); assertNotFlagLike(input.subCommand, "subCommand"); assertNotFlagLike(input.resourceType, "resourceType"); assertNotFlagLike(input.name, "name"); // Start building the kubectl command const command = "kubectl"; const cmdArgs = [input.command]; // Add subcommand if provided if (input.subCommand) { cmdArgs.push(input.subCommand); } // Add resource type if provided if (input.resourceType) { cmdArgs.push(input.resourceType); } // Add resource name if provided if (input.name) { cmdArgs.push(input.name); } // Add namespace scoping: --all-namespaces takes precedence over a // specific namespace (kubectl rejects using both together) if (input.allNamespaces) { cmdArgs.push("--all-namespaces"); } else if (input.namespace) { cmdArgs.push(`--namespace=${input.namespace}`); } // Add output format if provided if (input.outputFormat) { cmdArgs.push(`-o=${input.outputFormat}`); } // Add any provided flags if (input.flags) { for (const [key, value] of Object.entries(input.flags)) { if (value === true) { // Handle boolean flags cmdArgs.push(`--${key}`); } else if (value !== false && value !== null && value !== undefined) { // Skip false/null/undefined values, add others as --key=value cmdArgs.push(`--${key}=${value}`); } } } // Add any additional arguments if (input.args && input.args.length > 0) { cmdArgs.push(...input.args); } // Add context if provided if (input.context) { cmdArgs.push("--context", input.context); } // Reject server-side filesystem reads on remote transports, matching the // per-parameter guards in the structured tools. This tool hands the caller // a free-form kubectl argv, so there is no parameter to attach them to: // "--from-file", "-f" and friends arrive as raw tokens and have to be // matched in the argv. See src/security/transport.ts for the trust model. // No-op under stdio, where the files are the operator's own. assertNoRemoteFileReads(cmdArgs); // Some verbs reach the operator's credentials or the server's filesystem // through plain operands ("config view --raw", "config set ...server", // "cp <path> ..."), which none of the flag scans above can see. assertAllowedGenericCommand(cmdArgs); // Execute the command try { console.error(`Executing: kubectl ${cmdArgs.join(" ")}`); const result = execFileSyncSafe(command, cmdArgs, { encoding: "utf8", maxBuffer: getSpawnMaxBuffer(), env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG }, }); return { content: [ { type: "text", text: result, }, ], }; } catch (error) { throw new McpError(ErrorCode.InternalError, `Failed to execute kubectl command: ${error.message}`); } } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError(ErrorCode.InternalError, `Failed to execute kubectl command: ${error.message}`); } }