UNPKG

mcp-server-kubernetes

Version:

MCP server for interacting with Kubernetes clusters via kubectl

188 lines (187 loc) 8.9 kB
import { kubectlGet } from "./kubectl-get.js"; import { execSync } from "child_process"; import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"; export const kubectlListSchema = { name: "kubectl_list", description: "List Kubernetes resources by resource type and optionally namespace", inputSchema: { type: "object", properties: { resourceType: { type: "string", description: "Type of resource to list (e.g., pods, deployments, services, configmaps, etc.)" }, namespace: { type: "string", description: "Namespace of the resources (optional - defaults to 'default' for namespaced resources)", default: "default" }, output: { type: "string", enum: ["json", "yaml", "wide", "name", "custom", "formatted"], description: "Output format - 'formatted' uses a resource-specific format with key information", default: "formatted" }, 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')" } }, required: ["resourceType", "namespace"], }, }; export async function kubectlList(k8sManager, input) { try { const resourceType = input.resourceType.toLowerCase(); const namespace = input.namespace || "default"; const output = input.output || "formatted"; const allNamespaces = input.allNamespaces || false; const labelSelector = input.labelSelector || ""; const fieldSelector = input.fieldSelector || ""; // If not using formatted output, delegate to kubectl_get if (output !== "formatted") { return await kubectlGet(k8sManager, { resourceType: input.resourceType, namespace: input.namespace, output: input.output, allNamespaces: input.allNamespaces, labelSelector: input.labelSelector, fieldSelector: input.fieldSelector }); } // For formatted output, we'll use resource-specific custom columns let customColumns = ""; switch (resourceType) { case "pods": case "pod": case "po": customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,STATUS:.status.phase,NODE:.spec.nodeName,IP:.status.podIP,AGE:.metadata.creationTimestamp"; break; case "deployments": case "deployment": case "deploy": customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,READY:.status.readyReplicas/.status.replicas,UP-TO-DATE:.status.updatedReplicas,AVAILABLE:.status.availableReplicas,AGE:.metadata.creationTimestamp"; break; case "services": case "service": case "svc": customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,TYPE:.spec.type,CLUSTER-IP:.spec.clusterIP,EXTERNAL-IP:.status.loadBalancer.ingress[0].ip,PORTS:.spec.ports[*].port,AGE:.metadata.creationTimestamp"; break; case "nodes": case "node": case "no": customColumns = "NAME:.metadata.name,STATUS:.status.conditions[?(@.type==\"Ready\")].status,ROLES:.metadata.labels.kubernetes\\.io/role,VERSION:.status.nodeInfo.kubeletVersion,INTERNAL-IP:.status.addresses[?(@.type==\"InternalIP\")].address,OS-IMAGE:.status.nodeInfo.osImage,KERNEL-VERSION:.status.nodeInfo.kernelVersion,CONTAINER-RUNTIME:.status.nodeInfo.containerRuntimeVersion"; break; case "namespaces": case "namespace": case "ns": customColumns = "NAME:.metadata.name,STATUS:.status.phase,AGE:.metadata.creationTimestamp"; break; case "persistentvolumes": case "pv": customColumns = "NAME:.metadata.name,CAPACITY:.spec.capacity.storage,ACCESS_MODES:.spec.accessModes,RECLAIM_POLICY:.spec.persistentVolumeReclaimPolicy,STATUS:.status.phase,CLAIM:.spec.claimRef.name,STORAGECLASS:.spec.storageClassName,AGE:.metadata.creationTimestamp"; break; case "persistentvolumeclaims": case "pvc": customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,STATUS:.status.phase,VOLUME:.spec.volumeName,CAPACITY:.status.capacity.storage,ACCESS_MODES:.spec.accessModes,STORAGECLASS:.spec.storageClassName,AGE:.metadata.creationTimestamp"; break; case "configmaps": case "configmap": case "cm": customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,DATA:.data,AGE:.metadata.creationTimestamp"; break; case "secrets": case "secret": customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,TYPE:.type,DATA:.data,AGE:.metadata.creationTimestamp"; break; case "jobs": case "job": customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,COMPLETIONS:.status.succeeded/.spec.completions,DURATION:.status.completionTime-(.status.startTime),AGE:.metadata.creationTimestamp"; break; case "cronjobs": case "cronjob": case "cj": customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,SCHEDULE:.spec.schedule,SUSPEND:.spec.suspend,ACTIVE:.status.active,LAST_SCHEDULE:.status.lastScheduleTime,AGE:.metadata.creationTimestamp"; break; default: // For unknown resource types, fall back to a generic format customColumns = "NAME:.metadata.name,NAMESPACE:.metadata.namespace,KIND:.kind,AGE:.metadata.creationTimestamp"; break; } // Build the kubectl command let command = "kubectl get "; // Add resource type command += resourceType; // Add namespace flag unless all namespaces is specified if (allNamespaces) { 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 custom columns format command += ` -o custom-columns="${customColumns}"`; // Execute the command try { const result = execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } }); 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 type ${resourceType} not found or no resources exist`, status: "not_found", }, null, 2), }, ], isError: true, }; } throw new McpError(ErrorCode.InternalError, `Failed to list resources: ${error.message}`); } } catch (error) { throw new McpError(ErrorCode.InternalError, `Failed to execute kubectl list command: ${error.message}`); } } // 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()); }