import os from kubernetes import client, config from kubernetes.client.exceptions import ApiException from mcp.server.fastmcp import FastMCP mcp = FastMCP("k8s-cluster", host="0.0.0.0", port=8012) def _load_kube() -> None: kubeconfig = os.getenv("KUBECONFIG") try: if kubeconfig and os.path.exists(kubeconfig): config.load_kube_config(config_file=kubeconfig) else: config.load_incluster_config() except Exception as exc: raise RuntimeError(f"Failed to load Kubernetes config: {exc}") @mcp.tool() def list_namespaces() -> list[str]: """List all Kubernetes namespaces visible to this server.""" _load_kube() api = client.CoreV1Api() return [item.metadata.name for item in api.list_namespace().items] @mcp.tool() def list_pods(namespace: str | None = None, limit: int = 100) -> list[dict]: """List pods in a namespace with status and node placement.""" _load_kube() api = client.CoreV1Api() ns = namespace or os.getenv("K8S_DEFAULT_NAMESPACE", "default") pods = api.list_namespaced_pod(ns, limit=limit).items return [ { "name": pod.metadata.name, "phase": pod.status.phase, "node": pod.spec.node_name, "created": pod.metadata.creation_timestamp.isoformat() if pod.metadata.creation_timestamp else None, } for pod in pods ] @mcp.tool() def get_pod_logs(namespace: str, pod_name: str, tail_lines: int = 200) -> str: """Get recent logs from a specific pod.""" _load_kube() api = client.CoreV1Api() return api.read_namespaced_pod_log(name=pod_name, namespace=namespace, tail_lines=tail_lines) @mcp.tool() def list_deployments(namespace: str | None = None) -> list[dict]: """List deployments in the namespace with availability details.""" _load_kube() api = client.AppsV1Api() ns = namespace or os.getenv("K8S_DEFAULT_NAMESPACE", "default") try: deps = api.list_namespaced_deployment(ns).items except ApiException as exc: raise RuntimeError(f"Kubernetes API error: {exc}") return [ { "name": dep.metadata.name, "ready_replicas": dep.status.ready_replicas or 0, "replicas": dep.status.replicas or 0, } for dep in deps ] if __name__ == "__main__": mcp.run(transport="sse")