mcp-server-kubernetes
Version:
MCP server for interacting with Kubernetes clusters via kubectl
156 lines (155 loc) • 6.43 kB
JavaScript
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}`);
}
}