UNPKG

mcp-server-kubernetes

Version:

MCP server for interacting with Kubernetes clusters via kubectl

263 lines (262 loc) 10.3 kB
import { execSync } from "child_process"; import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"; export const kubectlGetSchema = { name: "kubectl_get", description: "Get or list Kubernetes resources by resource type, name, and optionally namespace", inputSchema: { type: "object", properties: { resourceType: { type: "string", description: "Type of resource to get (e.g., pods, deployments, services, configmaps, events, etc.)", }, name: { type: "string", description: "Name of the resource (optional - if not provided, lists all resources of the specified type)", }, namespace: { type: "string", description: "Namespace of the resource (optional - defaults to 'default' for namespaced resources)", default: "default", }, output: { type: "string", enum: ["json", "yaml", "wide", "name", "custom"], description: "Output format", default: "json", }, allNamespaces: { type: "boolean", description: "If true, list resources across all namespaces", default: false, }, labelSelector: { type: "string", description: "Filter resources by label selector (e.g. 'app=nginx')" }, fieldSelector: { type: "string", description: "Filter resources by field selector (e.g. 'metadata.name=my-pod')" }, sortBy: { type: "string", description: "Sort events by a field (default: lastTimestamp). Only applicable for events." }, }, required: ["resourceType", "name", "namespace"], }, }; export async function kubectlGet(k8sManager, input) { try { const resourceType = input.resourceType.toLowerCase(); const name = input.name || ""; const namespace = input.namespace || "default"; const output = input.output || "json"; const allNamespaces = input.allNamespaces || false; const labelSelector = input.labelSelector || ""; const fieldSelector = input.fieldSelector || ""; const sortBy = input.sortBy; // Build the kubectl command let command = "kubectl get "; // Add resource type command += resourceType; // Add name if provided if (name) { command += ` ${name}`; } // For events, default to all namespaces unless explicitly specified const shouldShowAllNamespaces = resourceType === "events" ? input.namespace ? false : true : allNamespaces; // Add namespace flag unless all namespaces is specified if (shouldShowAllNamespaces) { command += " --all-namespaces"; } else if (namespace && !isNonNamespacedResource(resourceType)) { command += ` -n ${namespace}`; } // Add label selector if provided if (labelSelector) { command += ` -l ${labelSelector}`; } // Add field selector if provided if (fieldSelector) { command += ` --field-selector=${fieldSelector}`; } // Add sort-by for events if (resourceType === "events" && sortBy) { command += ` --sort-by=.${sortBy}`; } else if (resourceType === "events") { command += ` --sort-by=.lastTimestamp`; } // Add output format if (output === "json") { command += " -o json"; } else if (output === "yaml") { command += " -o yaml"; } else if (output === "wide") { command += " -o wide"; } else if (output === "name") { command += " -o name"; } else if (output === "custom") { if (resourceType === "events") { command += ` -o 'custom-columns=LAST SEEN:.lastTimestamp,TYPE:.type,REASON:.reason,OBJECT:.involvedObject.kind/.involvedObject.name,MESSAGE:.message'`; } else { command += ` -o 'custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace,STATUS:.status.phase,AGE:.metadata.creationTimestamp'`; } } // Execute the command try { const result = execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG }, }); // Format the results for better readability const isListOperation = !name; if (isListOperation && output === "json") { try { // Parse JSON and extract key information const parsed = JSON.parse(result); if (parsed.kind && parsed.kind.endsWith("List") && parsed.items) { if (resourceType === "events") { const formattedEvents = parsed.items.map((event) => ({ type: event.type || "", reason: event.reason || "", message: event.message || "", involvedObject: { kind: event.involvedObject?.kind || "", name: event.involvedObject?.name || "", namespace: event.involvedObject?.namespace || "", }, firstTimestamp: event.firstTimestamp || "", lastTimestamp: event.lastTimestamp || "", count: event.count || 0, })); return { content: [ { type: "text", text: JSON.stringify({ events: formattedEvents }, null, 2), }, ], }; } else { const items = parsed.items.map((item) => ({ name: item.metadata?.name || "", namespace: item.metadata?.namespace || "", kind: item.kind || resourceType, status: getResourceStatus(item), createdAt: item.metadata?.creationTimestamp, })); return { content: [ { type: "text", text: JSON.stringify({ items }, null, 2), }, ], }; } } } catch (parseError) { // If JSON parsing fails, return the raw output console.error("Error parsing JSON:", parseError); } } return { content: [ { type: "text", text: result, }, ], }; } catch (error) { if (error.status === 404 || error.message.includes("not found")) { return { content: [ { type: "text", text: JSON.stringify({ error: `Resource ${resourceType}${name ? `/${name}` : ""} not found`, status: "not_found", }, null, 2), }, ], isError: true, }; } throw new McpError(ErrorCode.InternalError, `Failed to get resource: ${error.message}`); } } catch (error) { throw new McpError(ErrorCode.InternalError, `Failed to execute kubectl get command: ${error.message}`); } } // Extract status from various resource types function getResourceStatus(resource) { if (!resource) return "Unknown"; // Pod status if (resource.status?.phase) { return resource.status.phase; } // Deployment, ReplicaSet, StatefulSet status if (resource.status?.readyReplicas !== undefined) { const ready = resource.status.readyReplicas || 0; const total = resource.status.replicas || 0; return `${ready}/${total} ready`; } // Service status if (resource.spec?.type) { return resource.spec.type; } // Node status if (resource.status?.conditions) { const readyCondition = resource.status.conditions.find((c) => c.type === "Ready"); if (readyCondition) { return readyCondition.status === "True" ? "Ready" : "NotReady"; } } // Job/CronJob status if (resource.status?.succeeded !== undefined) { return resource.status.succeeded ? "Completed" : "Running"; } // PV/PVC status if (resource.status?.phase) { return resource.status.phase; } return "Active"; } // Helper function to determine if a resource is non-namespaced function isNonNamespacedResource(resourceType) { const nonNamespacedResources = [ "nodes", "node", "no", "namespaces", "namespace", "ns", "persistentvolumes", "pv", "storageclasses", "sc", "clusterroles", "clusterrolebindings", "customresourcedefinitions", "crd", "crds", ]; return nonNamespacedResources.includes(resourceType.toLowerCase()); }