Skip to main content
Glama
Kubernetes Tools MCP Server

Kubernetes Tools MCP Server

by BenedatLLC

Kubernetes Tools

Unit Tests

k8stools gives AI agents read-only access to a Kubernetes cluster. It provides 33 tools: from "what is wrong in this namespace?" down to a single pod's spec or logs. You can use them in two ways:

  • As an MCP server (k8s-mcp-server, included), for Claude Code, Cursor, GitHub Copilot or any other MCP client.

  • As Python functions, passed straight to an agent framework such as pydantic-ai, or called from ordinary scripts.

Some use cases:

  • Chat with your Kubernetes cluster from your coding agent.

  • Build agents that monitor your cluster or perform root cause analysis.

  • Build a custom chat UI, or use the tools in non-agentic automations.

  • Snapshot a live cluster to a file and replay it later as a test fixture, with no cluster needed. See Capturing and replaying a cluster.

Every tool only reads. None changes the cluster, and none reads Kubernetes Secret objects. Secret-shaped values in other objects (ConfigMaps, env vars, command lines, logs) are redacted by default.

Contents

What changed in each release is in the changelog.

Related MCP server: kubeview-mcp

Quick start

1. Install

k8stools needs Python 3.12 or newer.

pip install k8stools      # or: uv add k8stools

This installs three commands:

Command

What it does

k8s-mcp-server

The MCP server

k8s-capture-state

Snapshots a cluster to a file that the server can replay

k8s-mcp-client

Lists the server's tools and their schemas, for manual checking

2. Try it without a cluster

--mock serves a built-in snapshot of a small cluster that has a few things wrong with it (see Mock data), so you can see what the tools do before pointing them at anything real. To add it to Claude Code:

claude mcp add k8stools-mock -- k8s-mcp-server --mock

Then ask something like "What's wrong in the default namespace?"

For VS Code, Cursor or another client that reads an mcp.json, see Connecting an MCP client.

3. Point it at your cluster

Without --mock, the server uses the same cluster as kubectl: the file in KUBECONFIG (or ~/.kube/config) and its current context. To choose a different one, pass --kubeconfig and/or --context:

claude mcp add k8stools -- k8s-mcp-server --context my-cluster

The account it runs as needs only get and list permissions. See Permissions for a ClusterRole that covers every tool, and Selecting a cluster for the details.

Toolsets: choosing which tools an agent sees

An MCP client sends every tool's description to the model on every turn, and agents choose less reliably when they have many similar tools to pick from. So the server doesn't have to offer all 33 tools: it serves a named toolset, chosen with --toolset:

Toolset

What it contains

Use it when

triage

5 tools: get_cluster_info, get_namespace_health, get_workload_report, get_events, get_node_summaries

The agent's job is to find what's wrong and where. The two composite tools answer in one call what otherwise takes five or six.

investigate (default)

29 tools: every tool except the four that another tool already covers

The agent needs to dig into details: specs, history, logs, networking, storage.

all

All 33 tools

You want the full set, as k8stools 2.x served.

The four tools that investigate leaves out, and what to use instead:

Left out

Use instead

get_pod_events

get_events(involved_name=...)

get_replicaset_summaries

get_workload_history

get_logs_for_job, get_logs_for_cronjob

get_logs_for_pod_and_container; each pod's owner names its Job

You can adjust any toolset with --include TOOL and --exclude TOOL. Both can be repeated:

# The triage tools, plus pod specs and container logs
k8s-mcp-server --toolset triage --include get_pod_spec --include get_logs_for_pod_and_container

# Everything except ConfigMap contents
k8s-mcp-server --toolset all --exclude get_configmap

A misspelled toolset or tool name stops the server at startup, rather than quietly serving a different set. The table of tools below shows which toolsets each tool is in. In Python, the same sets are in k8s_tools.TOOLSETS (see Choosing tools in Python).

The tools

Each tool's description, which is what the model sees, is kept to a few lines. docs/TOOL_REFERENCE.md documents every parameter and field.

Warnings that apply to a particular result come back in that result, rather than in the description: nodes, pods, container statuses, events and DaemonSets have a notes list (for example, that a node's Ready time is not its uptime), and a log can begin with [k8stools] note: lines (for example, that the "previous" log is the same as the current one). See Notes in results.

In the tables below, T means the tool is in the triage toolset and I that it is in investigate. Every tool is in all.

Start here: overview tools

Tool

What it answers

T

I

get_namespace_health

What is wrong in a namespace, and where. A full entry for each unhealthy workload (last termination with the exit code's meaning, how long instances live, the restart back-off, memory limit vs request, when the template last changed), pods that can't be scheduled and a Deployment's failing rollout conditions, the same for each recovering one (ready, but restarted within the last 24 hours), and one line for each healthy one. Workloads failing the same way are grouped.

✓

✓

get_workload_report

Everything about one workload in one call: images, resources and probes; each instance's state and last termination; deduplicated events; current and previous log tails of its most troubled pod, short ones of the others (grep searches every pod's whole log); the last template change; the ConfigMaps and Secrets it uses; its Services, Ingress rules, HPA and resource usage.

✓

✓

get_cluster_info

Which cluster the tools are answering from: kubeconfig context, API server URL and version, or the capture being replayed.

✓

✓

These composite tools are built from the other tools, and report facts rather than diagnoses: they say "exit code 137, without OOMKilled", not "this was an out-of-memory kill".

Cluster and nodes

Tool

What it answers

T

I

get_namespaces

The namespaces, like kubectl get namespace

✓

get_node_summaries

The nodes, like kubectl get nodes -o wide, with capacity, allocatable, conditions, taints and labels

✓

✓

get_node_metrics

Each node's current CPU and memory use against its allocatable, like kubectl top node (needs metrics-server)

✓

get_events

Cluster- or namespace-wide events, filtered on the server (by object, reason, type)

✓

✓

Pods, containers and logs

Tool

What it answers

T

I

get_pod_summaries

The pods, like kubectl get pods -o wide, with each pod's controlling owner (e.g. DaemonSet/otel-collector-agent)

✓

get_pod_container_statuses

Each container's state in a pod: running, waiting or terminated, restarts, last termination. Init containers are listed separately ([] when there are none); native sidecars are listed with the app containers.

✓

get_pod_spec

A pod's full spec

✓

get_pod_events

The events for one pod

get_logs_for_pod_and_container

A container's logs, with tail, since_seconds and previous (the instance before the last restart). When the tail leaves earlier lines out, a note says where the log starts. See Previous-instance log semantics.

✓

get_log_pattern_summary

When a pattern occurs across a container's whole retained log, not just a tail: the count, first and last match, matches per time bucket (so an onset or a gap shows), a few matching lines, and how far back the log goes. A small result whatever the log's size.

✓

get_container_metrics

Each running container's current CPU and memory use beside its requests and limits, like kubectl top pod --containers (needs metrics-server)

✓

Workloads

Tool

What it answers

T

I

get_deployment_summaries

Deployments, like kubectl get deployments

✓

get_statefulset_summaries

StatefulSets, like kubectl get statefulsets

✓

get_daemonset_summaries

DaemonSets, like kubectl get daemonsets -o wide, with node selector and images

✓

get_workload_history

What changed in a Deployment, StatefulSet or DaemonSet, and when: each retained revision compared with the one before (images, resources, probes, command and args, env var names, volumes, scheduling), plus the ConfigMaps and Secrets it references. It also says what the history cannot show.

✓

get_replicaset_summaries

A Deployment's ReplicaSets with their revision numbers and images

get_hpa_summaries

HorizontalPodAutoscalers, like kubectl get hpa plus describe: target, min/max/current/desired replicas, each metric against its target, and the scaling conditions

✓

get_cronjob_summaries

CronJobs, like kubectl get cronjobs

✓

get_job_summaries

Jobs, like kubectl get jobs

✓

get_logs_for_job

Logs from a Job's most recent pod

get_logs_for_cronjob

Logs from a CronJob's most recent run

Networking

Tool

What it answers

T

I

get_service_summaries

Services, like kubectl get services, with selector, labels, annotations, and ready / not-ready endpoint counts

✓

get_endpoint_summaries

Each Service's backends, from its EndpointSlices: every address's ready / serving / terminating state, pod and node

✓

get_ingress_summaries

Ingresses, like kubectl get ingress plus describe: each host/path rule with its backend Service and port resolved (flagged when the Service or port doesn't exist), TLS hosts and Secret names, load-balancer addresses

✓

get_networkpolicy_summaries

NetworkPolicies: the pods each selects, its rules, and what it allows per direction in plain words ("allows all ingress", "denies all egress", "allows ingress only from ..."), by the API's rules rather than the policy's name

✓

Configuration and storage

Tool

What it answers

T

I

get_configmap_summaries

ConfigMaps, like kubectl get configmaps

✓

get_configmap

The full contents of one ConfigMap (redacted by the server)

✓

get_pvc_summaries

PersistentVolumeClaims, like kubectl get pvc, with the pods that mount each one, and an unbound claim's events

✓

Custom resources

Tool

What it answers

T

I

get_custom_resource_definitions

The custom kinds the cluster serves (CRDs): group, kind, plural, scope, versions

✓

get_custom_resource_status

Custom resources' health: each instance's conditions (Ready, Synced, ...) and whether its controller has caught up with its spec. Not the objects themselves, which can be huge and can hold credentials.

✓

Running the MCP server

Connecting an MCP client

The server uses the stdio transport by default, which is what local agents such as Claude Code, Cursor and VS Code expect: the client starts the server itself.

Claude Code:

claude mcp add k8stools -- k8s-mcp-server
# with options:
claude mcp add k8stools -- k8s-mcp-server --context prod-eu --toolset triage

VS Code, Cursor and other clients with an mcp.json:

{
   "servers": {
      "k8stools": {
         "command": "${workspaceFolder}/.venv/bin/k8s-mcp-server",
         "args": ["--toolset", "investigate"],
         "envFile": "${workspaceFolder}/.envrc"
      }
   }
}

This assumes that:

  1. k8stools is installed in a virtual environment in .venv, at the root of your workspace.

  2. The environment file .envrc sets any variables you need: for example KUBECONFIG to point to your kubeconfig file and K8STOOLS_CONTEXT to pin a context. You can pass --kubeconfig and --context in args instead.

Command-line options

The options, grouped by what they're for:

Purpose

Options

Which tools to serve

--toolset {triage,investigate,all} (default investigate), --include TOOL, --exclude TOOL. See Toolsets.

Which cluster to read

--kubeconfig PATH, --context NAME. See Selecting a cluster.

Serve a snapshot instead of a cluster

--mock (the built-in snapshot), --state-file FILE (one of your own), --state-time {advancing,frozen}. See Mock data and Capturing and replaying a cluster.

Secret redaction

--no-redact turns it off (it is on by default). See Secret redaction.

Transport

--transport {stdio,streamable-http} (default stdio), --host (default 127.0.0.1), --port (default 8000)

Logging

--log-level, --debug

Some common combinations:

k8s-mcp-server                                         # stdio, current kubeconfig context, investigate toolset
k8s-mcp-server --toolset triage                        # just the five triage tools
k8s-mcp-server --context prod-eu                       # a specific cluster
k8s-mcp-server --mock                                  # the built-in snapshot, no cluster needed
k8s-mcp-server --state-file incident-1234.json         # replay your own capture
k8s-mcp-server --transport=streamable-http --port=8000 # HTTP, for remote access
usage: k8s-mcp-server [-h] [--transport {streamable-http,stdio}] [--host HOST] [--port PORT]
                      [--log-level {DEBUG,INFO,WARNING,ERROR,CRITICAL}] [--debug]
                      [--kubeconfig PATH] [--context NAME] [--mock] [--state-file FILE]
                      [--state-time {advancing,frozen}] [--no-redact]
                      [--toolset {triage,investigate,all}] [--include TOOL] [--exclude TOOL]

Run the MCP server.

options:
  -h, --help            show this help message and exit
  --transport {streamable-http,stdio}
                        Transport to use for MCP server [default: stdio]
  --host HOST           Hostname for HTTP service [default: 127.0.0.1]
  --port PORT           Port for HTTP service [default: 8000]
  --log-level {DEBUG,INFO,WARNING,ERROR,CRITICAL}
                        Log level [default: INFO]
  --debug               Enable debug mode [default: False]
  --kubeconfig PATH     Kubeconfig file to use [default: KUBECONFIG, then ~/.kube/config]
  --context NAME        Kubeconfig context to use [default: K8STOOLS_CONTEXT, then the
                        kubeconfig's current-context]. A kubeconfig or context that is given but
                        cannot be loaded stops the server rather than falling back to another
                        cluster.
  --mock                If specified, just run mock versions of the tools that don't need a
                        cluster
  --state-file FILE     Serve a captured cluster snapshot from FILE (implies --mock). Captures are
                        written by k8s-capture-state.
  --state-time {advancing,frozen}
                        Replay clock for a captured snapshot [default: advancing]. 'frozen' pins
                        every age at its captured value, so repeated runs are identical - what an
                        automated suite wants.
  --no-redact           Disable secret redaction of tool output (redaction is on by default; can
                        also be disabled with K8STOOLS_REDACT=0)
  --toolset {triage,investigate,all}
                        Which tools to serve [default: investigate]. 'triage' is the few needed to find
                        where to look, 'investigate' is every tool but those another tool covers,
                        'all' is everything.
  --include TOOL        Also serve TOOL (repeatable)
  --exclude TOOL        Don't serve TOOL (repeatable)

Environment variables:

Variable

Effect

KUBECONFIG

Kubeconfig file, when --kubeconfig isn't given

K8STOOLS_CONTEXT

Kubeconfig context, when --context isn't given

K8STOOLS_REDACT=0

Turns redaction off, like --no-redact

When a tool call fails

A failed call comes back to the client as an error result with the reason, for example 404 Not Found: pods "x" not found, a permission error naming what the account can't list, can't reach the Kubernetes API server at ..., or why a log can't be read, so an agent can correct itself instead of retrying blindly. The reason is redacted like any result. An unexpected failure (a bug) shows only Error executing tool <name>, and its details go to the server log.

Selecting a cluster

Each server answers from exactly one cluster, chosen at startup:

Kubeconfig file

Context

1st

--kubeconfig PATH

--context NAME

2nd

KUBECONFIG

K8STOOLS_CONTEXT

3rd

~/.kube/config

the file's current-context

With none of these given and no usable kubeconfig, the server falls back to the in-cluster service account, as when it runs in a pod.

The server logs the cluster it chose when it starts:

WARNING:root:Bound to cluster: context 'prod-eu' at https://prod-eu.example:6443 (kubeconfig: /home/me/.kube/prod.yaml)

and the get_cluster_info tool reports it, so an agent can check which cluster it is talking to.

A few rules keep a server from quietly answering from the wrong cluster:

  • An explicit selection never falls back. If a --kubeconfig, --context or K8STOOLS_CONTEXT is given but cannot be loaded (a missing file, a misspelled context), the server exits with an error. It does not fall back to in-cluster config, which in a pod would silently mean that pod's cluster.

  • The choice is made once. Every tool shares it, and it does not follow later changes to the kubeconfig: running kubectl config use-context does not repoint a server that is already running.

  • --kubeconfig and --context are rejected with --mock or --state-file, which serve a snapshot rather than a cluster.

To serve two clusters, run two servers:

k8s-mcp-server --transport=streamable-http --port=8001 --context prod-eu
k8s-mcp-server --transport=streamable-http --port=8002 \
    --kubeconfig ~/.kube/eks.yaml --context staging

Permissions

The tools only read, with the get and list verbs. A read-only ClusterRole that covers every tool:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: k8stools-reader
rules:
- apiGroups: [""]
  resources: [namespaces, nodes, pods, pods/log, events, services, configmaps,
              persistentvolumeclaims]
  verbs: [get, list]
- apiGroups: [apps]
  resources: [deployments, replicasets, statefulsets, daemonsets, controllerrevisions]
  verbs: [get, list]
- apiGroups: [batch]
  resources: [jobs, cronjobs]
  verbs: [get, list]
- apiGroups: [autoscaling]
  resources: [horizontalpodautoscalers]
  verbs: [get, list]
- apiGroups: [discovery.k8s.io]
  resources: [endpointslices]
  verbs: [get, list]
- apiGroups: [metrics.k8s.io]
  resources: [pods, nodes]
  verbs: [get, list]
- apiGroups: [networking.k8s.io]
  resources: [ingresses, networkpolicies]
  verbs: [get, list]
- apiGroups: [apiextensions.k8s.io]
  resources: [customresourcedefinitions]
  verbs: [get, list]

get_custom_resource_status also needs read access to each custom resource you want it to see, granted per group. Add a rule like this for each operator you use, rather than a wildcard:

- apiGroups: [cert-manager.io]
  resources: [certificates, issuers, clusterissuers]
  verbs: [get, list]

get_cluster_info also reads the server version (/version), which every authenticated user can read by default. No tool reads Secrets: get_workload_history names the Secrets a workload uses, from its pod template, without reading them.

The HTTP transport

--transport=streamable-http starts an HTTP server that listens on --host and --port (by default 127.0.0.1 and 8000). Use it when the agent is not on the same machine as the server.

Here's how to start the server and check it with curl:

# start the server
$ k8s-mcp-server --transport=streamable-http
INFO:     Started server process [6649]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

# in another terminal, list the tools
$ curl -H "Content-Type: application/json" \
       -H "Accept: application/json, text/event-stream" \
       -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}' \
       http://127.0.0.1:8000/mcp
event: message
data: {"jsonrpc":"2.0","id":1,"result":{"tools":[.... long text elided ...]}}

Checking the tools with k8s-mcp-client

k8s-mcp-client starts the server over stdio and prints each tool's description and input schema as Markdown. --short prints only the descriptions, and --tools NAME ... limits it to the tools named. It passes KUBECONFIG and K8STOOLS_CONTEXT through to the server.

Using the tools from Python

In an agent

The tools are in k8stools.k8s_tools. Here's an example with pydantic-ai:

from pydantic_ai.agent import Agent
from k8stools.k8s_tools import TOOLS
from k8stools.redaction import wrap_with_redaction

agent = Agent(
        model="openai:gpt-4.1",
        system_prompt=SYSTEM_PROMPT,
        tools=[wrap_with_redaction(fn) for fn in TOOLS],
)

result = agent.run_sync("What is the status of the pods in my cluster?")
print(result.output)

⚠️ Redaction is not automatic outside the k8stools MCP server. The functions in TOOLS (and mock_tools.TOOLS) return raw values: ConfigMap contents, env values in pod specs, container logs and command-line args, exactly as the API returns them. Only k8s-mcp-server and k8s-capture-state apply redaction for you. If you hand TOOLS to an agent, or build your own MCP server from them, wrap each one with wrap_with_redaction as above, or anything secret-shaped in your cluster goes straight into the model's context. See Where redaction applies.

Choosing a cluster

The tools use KUBECONFIG (or ~/.kube/config) and its current context. To pin a cluster instead, call configure before the first tool call:

from k8stools import k8s_tools

k8s_tools.configure(kubeconfig="~/.kube/prod.yaml", context="prod-eu")

The selection is resolved as described in Selecting a cluster.

To answer from something that isn't a cluster (a converter serving a recorded snapshot, say), bind an ApiClient whose rest_client answers the requests, and pin the clock to the snapshot's time. Both apply to every tool and to capture_state, and are restored at the end of the block:

with k8s_tools.use_api_client(api, context="snapshot-33"), \
        k8s_tools.pinned_clock(snapshot_time):
    state = capture_state()

Choosing tools in Python

TOOLS is every tool. The toolsets are in TOOLSETS, and select_tools builds an adjusted one, just like --toolset, --include and --exclude:

from k8stools.k8s_tools import TOOLS, TOOLSETS, select_tools

triage = TOOLSETS["triage"]
mine = select_tools(TOOLS, "triage", include=("get_pod_spec",), exclude=("get_events",))

tools = [wrap_with_redaction(fn) for fn in mine]   # wrap them, as above

mock_tools.TOOLSETS holds the same sets of mock tools.

Printing results

Most get_* tools have a print_* companion that prints the result as a table, which is handy when debugging (for example print_pod_summaries, print_workload_history, print_hpa_summaries). The log readers have none, since they already return text.

from k8stools.k8s_tools import print_pod_summaries
print_pod_summaries("default")

Mock data

When building agents, it can be helpful to test them against mock versions of the tools that don't need a real cluster but return realistic values. The module k8stools.mock_tools does just that, and so does the server's --mock option.

The data is a small, hand-maintained cluster modeled on a Minikube instance running the OpenTelemetry Demo application, with problems for an agent to find:

  • a crash-looping ad service, mid-upgrade, whose Service has no ready backends;

  • an accounting service that is ready but was OOM-killed 40 minutes ago, after 578 restarts, and whose previous log file is gone;

  • a checkout rollout that can't finish: its new pod can't be scheduled (a node selector no node matches), while the old pod still serves;

  • a Deployment whose HPA is at its maximum;

  • a new quote Deployment whose pod can't be scheduled, because the PersistentVolumeClaim it mounts can't bind: its storage class doesn't exist;

  • an Ingress with one rule to a missing Service;

  • a cert-manager Certificate that isn't Ready;

  • a DaemonSet, a CronJob and its Job, a StatefulSet with PVCs and a completed init container, and events.

The mock serves a capture (a JSON snapshot of one cluster) rather than a set of canned answers per tool, so the tools agree with each other: a pod that is not in the capture does not exist for any tool, and one that is has consistent statuses, events, spec and logs. You can snapshot your own cluster the same way.

Capturing and replaying a cluster

k8s-capture-state snapshots a live cluster into a single JSON file, and the MCP server replays that file as if it were the cluster. A whole scenario, such as a crash loop, a full volume or a bad rollout, becomes a fixture you can re-run, commit, or attach to an issue, with no cluster needed to reproduce it.

# Snapshot the current cluster (respects KUBECONFIG)
k8s-capture-state -o incident-1234.json

# Replay it
k8s-mcp-server --state-file incident-1234.json

It finishes with a summary of what it got:

$ k8s-capture-state --namespace default -o incident-1234.json
Captured 27 pod(s), 31 container(s).
  logs captured:          31
  previous logs captured: 31
  values redacted:        4
Wrote incident-1234.json (1,435,335 bytes, redacted).

k8s-capture-state options

usage: k8s-capture-state [-h] [--namespace NS [NS ...]] [-o FILE]
                         [--kubeconfig PATH] [--context NAME] [--no-logs]
                         [--max-log-lines MAX_LOG_LINES] [--no-previous-logs]
                         [--no-redact] [--redact-file FILE]
                         [--from-itbench DIR] [--at PULL] [--list-pulls]
                         [--log-level {DEBUG,INFO,WARNING,ERROR,CRITICAL}]

Snapshot a Kubernetes cluster to a JSON file replayable by the k8stools MCP
server (--state-file) or MockState.

options:
  -h, --help            show this help message and exit
  --namespace NS [NS ...]
                        Namespace(s) to capture namespaced resources from
                        [default: all]. Namespaces and nodes are always
                        captured in full.
  -o FILE, --output FILE
                        Output file [default: k8s-state-<timestamp>.json]. A
                        name ending in .gz is written gzipped; captures are
                        read back compressed or not either way.
  --kubeconfig PATH     Kubeconfig file to capture from [default: KUBECONFIG,
                        then ~/.kube/config]
  --context NAME        Kubeconfig context to capture from [default:
                        K8STOOLS_CONTEXT, then the kubeconfig's current-
                        context]
  --no-logs             Skip container logs entirely, including previous-
                        instance logs, for a structure-only snapshot
  --max-log-lines MAX_LOG_LINES
                        Log lines to capture per container, up to 16 MiB of
                        each [default: 1000]
  --no-previous-logs    Skip the previous-instance logs of restarted
                        containers. These usually carry the diagnosis for a
                        crash loop, so dropping them is rarely what you want.
  --no-redact           Disable secret redaction of captured values (redaction
                        is on by default; can also be disabled with
                        K8STOOLS_REDACT=0)
  --redact-file FILE    Instead of capturing, re-run redaction over an
                        existing capture FILE and rewrite it in place (keeping
                        its compression). Use on captures taken with --no-
                        redact, or before a newer k8stools added redaction
                        rules.
  --from-itbench DIR    Instead of capturing, import an ITBench-Lite snapshot
                        (a scenario directory with k8s_objects_raw.tsv) as of
                        one of its pulls. What it doesn't record replays as
                        "not recorded".
  --at PULL             With --from-itbench: the pull to import, numbered from
                        1 [default: the last]. --list-pulls shows them.
  --list-pulls          With --from-itbench: list the snapshot's pulls and
                        exit
  --log-level {DEBUG,INFO,WARNING,ERROR,CRITICAL}
                        Log level [default: INFO]

What gets captured

Everything the tools can read, so that every tool answers on replay:

Cluster

context name, API server URL and version, so get_cluster_info answers on replay (not the kubeconfig path, which names a file on the capturing machine)

Cluster-wide

namespaces, nodes, custom resource definitions and their instances' conditions (up to 500 per type), and resource usage (one metrics-server sample of containers and nodes, or why metrics weren't available)

Per namespace

deployments, replica sets, services and their endpoints, ingresses, statefulsets, daemonsets, HPAs, cronjobs, jobs, PVCs, events, and each workload's change history (get_workload_history's result, so no env values are written)

ConfigMaps

summary and full contents, so get_configmap works too

Per pod

summary, labels, container and init container statuses, spec, and per-container logs (init containers included)

A cluster is selected exactly as for the MCP server (see Selecting a cluster), and a selection that cannot be loaded fails the capture. The capture needs the permissions in Permissions, but a kind its role may not read (a 403) doesn't fail it: the kind is recorded as not readable, per namespace, and the capture carries on, so a role that predates a kind added in a newer k8stools still captures everything else. The summary at the end lists what wasn't readable and what to grant. Pods are required, and so is the namespace list unless --namespace is given. A custom-resource type it can't read, metrics it may not read, and a cluster without metrics-server are recorded the same way.

--namespace restricts only the namespaced resources; namespaces and nodes are always captured in full so the cluster still makes sense.

Logs dominate the file size: expect a few MB for a few dozen pods (the 27-pod capture above was 1.4 MB with only 50 lines per container; a 38-pod one at 200 lines was 3.3 MB). Use --max-log-lines to trade size against history, or --no-logs for a structure-only snapshot. A log is kept to its newest 16 MiB, which can leave fewer lines than --max-log-lines (the end-of-capture report lists those logs); the tools' 1 MiB limit on a result doesn't apply. The capture records where each log began and which limit cut it, so on replay get_log_pattern_summary and the log tool say when the capture kept only the newest lines, and why. Captures compress about 10x, and can be written and read gzipped; see Compression, and what to check in.

Captures made by older versions of k8stools still load. What a capture didn't record replays as not recorded, never as none, whether the capture predates it or didn't collect it:

  • A kind the capture has no list of (an older capture has no endpoints, say) makes its tool raise K8sNotRecorded, an error saying the kind is unknown rather than absent. get_namespace_health and get_workload_report name such kinds in one note instead of failing.

  • A container log that wasn't recorded (unreadable when captured, or --no-logs) raises K8sNotRecorded too, and so does a previous-instance log of a container that restarted, when its previous log wasn't captured. A container that never restarted has no previous instance, and says so, as live. A log recorded as empty is replayed as empty.

  • A kind the capture's role couldn't read raises K8sNotRecorded with the reason, in the namespaces it couldn't read and across all of them; the other namespaces answer as recorded. Such a capture is format version "2", which k8stools before 4.5.0 refuses rather than read those kinds as empty; a complete capture stays version "1".

  • A field the capture predates is unknown (None, never 0).

Importing an ITBench-Lite snapshot

An ITBench-Lite scenario directory holds what an OpenTelemetry collector exported during the incident: Kubernetes objects pulled every five minutes (k8s_objects_raw.tsv), the event stream (k8s_events_raw.tsv) and the services' OTel log records (otel_logs_raw.tsv). --from-itbench turns one pull of it into a capture:

k8s-capture-state --from-itbench Scenario-33 --list-pulls      # numbered, with their kinds
k8s-capture-state --from-itbench Scenario-33 --at 7 -o s33.json  # default: the last pull
k8s-mcp-server --state-file s33.json --state-time frozen

The importer serves the pull to the real tools, with the clock set to the pull's time, and runs the same capture code against it, so an imported capture replays under the same rules as any other. What the snapshot doesn't hold replays as not recorded:

  • Kinds the pull has no batch of. A pull writes nothing for a kind with no objects, so a missing kind may be none or a lost batch; --list-pulls and the import's report name them. HPAs, CronJobs and ControllerRevisions are never pulled, so StatefulSet and DaemonSet revisions are unknown (Deployment history comes from the ReplicaSets).

  • Logs are OpenTelemetry records, not container output. Every replayed log says so in a note: a stack trace a process printed to stderr isn't in them. Records carry no container name: a record goes to its pod's only container, or the container named like its service, or none (counted). Only the current instance's records are kept (those since it started). A container with no records at all is not recorded, and previous-instance logs never are.

  • Metrics and custom resource instances were never collected, and say so.

  • Events are those that existed at the pull (each recorded twice in the snapshot, kept once). Endpoints are the core Endpoints objects, served as EndpointSlices.

Compression, and what to check in

A name ending in .gz is written gzipped, and captures are read back compressed or not either way. Detection is by content, so a capture still loads after being renamed:

k8s-capture-state -o incident-1234.json.gz     # 3.3 MB -> 364 KB
k8s-mcp-server --state-file incident-1234.json.gz

For a fixture you check into git, use plain .json anyway. Git already stores blobs compressed, so you do not pay the uncompressed size, and it deltas successive versions of a text file against each other, which it cannot do for a gzip blob. Measured on two captures of the same 38-pod cluster:

first commit

updating the fixture later

.json (3.3 MB)

404 KB

+61 KB

.json.gz (364 KB)

388 KB

+358 KB

The two cost about the same once, but every re-capture of a .gz adds a whole fresh blob, so the repo grows roughly 6x faster. Captures are also indented rather than compact for the same reason: it gives git something to delta. This is also why you probably do not need git-lfs here: LFS stores every version whole, which gives up exactly the delta compression that makes these files cheap.

Reach for .gz when the file travels on its own: attached to an issue, dropped in object storage, or simply too large to keep expanded in a working tree.

Previous-instance logs

The capture includes the previous-instance logs of every restarted container, which is what get_logs_for_pod_and_container(..., previous=True) returns. For a crash-looping container the current instance's logs are usually empty or post-restart, and the stack trace or OOM message that explains the crash lives in the previous instance, so a capture without them would be missing the evidence that matters most.

They roughly double the log payload for a cluster where a lot is restarting, which is exactly the cluster worth capturing. --no-previous-logs skips them. The summary reports how many could not be read (the previous instance may have been garbage-collected), because a crash-loop capture that lost them looks identical to one that never had any.

Previous-instance log semantics

previous=True asks the kubelet for the container's last terminated instance. Three of its behaviors read as bugs and are not, and all three show up in captures as well as in live queries:

  • In CrashLoopBackOff, both calls return the same text. There is no running instance during the backoff, so the kubelet serves the most recently terminated one for previous=False too. Check get_pod_container_statuses before concluding the flag was ignored.

  • A restart between two calls shifts the window. The instance that was current becomes the previous one, so a container crash-looping every few seconds can return byte-identical output for both. Compare the log timestamps, not the flag.

  • A reclaimed log file returns 200, not an error, with the body unable to retrieve container logs for <container-id>. It is a successful call whose content is an error message. The log tools don't return that text, which agents kept reading as the program's output: they raise K8sLogReclaimed, and get_workload_report shows the log tail as available: false, with no lines.

Only the single most recent terminated instance is retained; there is no way to reach further back than one.

The first is detected for you: the log then starts with a [k8stools] note: line saying so. The second can't be seen from a single call.

Replay clock

Every age in a capture is stored relative to the moment of capture, so the intervals between resources survive replay exactly: a deployment aged 8 days whose newest replica set is aged 7h34m still says "upgraded 7h34m ago" whenever it is replayed.

By default those ages advance in real time from when the server starts, which is what makes an interactive session feel like a live cluster. For an automated test suite pass --state-time frozen:

k8s-mcp-server --state-file incident-1234.json --state-time frozen

which pins every age at its captured value, so repeated runs are identical and a long session cannot see ages drift between its first tool call and its last. (--state-time requires --state-file; it is rejected rather than ignored, since a suite that believed it had frozen the clock and had not would be flaky with no visible cause.)

Absolute times are replayed the same way: every timestamp a tool returns is moved so the moment of capture becomes the moment the server started. A container killed 5 minutes before the capture reads as killed 5 minutes before server start, and get_cluster_info's captured_at is that start time, not the date in the file, so the agent sees one consistent clock. Log line timestamps (the kubelet's prefix) are moved too, so since_seconds works on a capture of any age; timestamps an application writes inside its own log messages are left as recorded. The file's own capture date is in the server's startup log.

Replaying from Python

For a test suite, skip the server and load the capture directly. MockState has the same query methods as the tools:

from pathlib import Path
from k8stools.mock_state import MockState

state = MockState.from_file(Path("incident-1234.json"), frozen=True)

pods = state.get_pod_summaries("default")
logs = state.get_logs_for_pod_and_container("ad-647b4947cc-s5mpm", "default",
                                            previous=True)

Or point the mock_tools module at it, so anything already calling k8stools.mock_tools serves your capture instead of the built-in one:

from k8stools import mock_tools

mock_tools.load_mock_state(Path("incident-1234.json"), frozen=True)
agent = Agent(model="openai:gpt-4.1", tools=mock_tools.TOOLS)

Captures and secrets

k8s-capture-state applies the same redaction pass as the MCP server, on by default, opt out with --no-redact or K8STOOLS_REDACT=0. This matters because a capture file is far more portable than cluster access: it gets committed to git, attached to issues, and passed around long after anyone remembers which cluster it came from. Redaction is one-way: replaying a redacted capture with --no-redact restores nothing, so a scenario that genuinely needs real values has to be re-captured.

A capture taken with --no-redact, or by an older k8stools before a redaction rule existed, can be redacted afterwards. This rewrites the file in place, keeping its compression; running it again finds nothing more:

k8s-capture-state --redact-file incident-1234.json

Captures taken before 3.0.0 may hold credentials embedded in args, URLs and connection strings (see below). Re-redact any you've committed or shared.

Secret redaction

Some read-only resources can carry secret-shaped values even though they are not Kubernetes Secret objects: ConfigMap data and the env blocks in a pod spec are the common cases. When you run the k8stools MCP server directly against an agent (with no wrapping service to scrub output), those values would otherwise flow straight into the model's context.

To prevent that, the MCP server applies a redaction pass to every tool's output. It is on by default and can be disabled with --no-redact or by setting K8STOOLS_REDACT=0. Redaction matches three ways, replacing each match with a visible [REDACTED] marker so the agent can tell "hidden" from "absent". We never provide a reader for Kubernetes Secret objects.

  1. By value shape: AWS access keys, JWT / bearer tokens, PEM private-key blocks.

  2. By key / env-var name: the name contains key, secret, token, password, passwd, credential or cred as a whole word. Names are split on separators and camelCase, so AWS_SECRET_ACCESS_KEY, apiKey and x-auth-token all match while VALKEY_ADDR does not.

    A field named exactly key is exempt: in the Kubernetes API that is always a map entry, a taint, a label selector or a projected-volume item, never a credential. This matters more than it sounds: on a real 38-pod cluster it was 130 of 139 redactions, none of them secrets: toleration keys such as node.kubernetes.io/not-ready, the filename ca.crt, and secretKeyRef.key. That last is worth stating plainly: a reference to a secret is not a secret. The value lives in the Secret object and is resolved by the kubelet, never appearing in tool output, so redacting the pointer hides which key feeds an env var and protects nothing. The exemption does not apply to env-var names, where a variable someone named KEY plausibly does hold one. Schema fields that compound key are exempt too, in both spellings: an affinity term's topologyKey / topology_key and label-key lists, and get_configmap's binary_data_keys.

  3. By context: a credential inside a longer string, found by what surrounds it, with only the credential replaced:

    Where

    Example

    URL userinfo

    postgres://otelu:[REDACTED]@postgresql/otel

    Sensitive flags and system properties

    --password=[REDACTED], --token [REDACTED], -Dapi.key=[REDACTED]

    Argv lists

    ["--token", "[REDACTED]"]

    Connection strings

    Password=[REDACTED];, password=[REDACTED] dbname=otel

    Query strings and presigned URLs

    ?access_token=[REDACTED]&, X-Amz-Signature=[REDACTED], sig=[REDACTED]

    Properties files

    spring.datasource.password=[REDACTED]

    SQL password literals

    CREATE USER otelu WITH PASSWORD '[REDACTED]', IDENTIFIED BY '[REDACTED]'

    In running text (sh -c "...", logs, --help output), a --flag value pair counts only when the value looks like a credential (a digit or symbol, or 16+ characters), because prose and help text put words and type names after flags.

Nothing that only locates a secret is redacted. Like secretKeyRef.key, these are references, and hiding them hides where a secret comes from and protects nothing: $(DB_PASSWORD), ${token}, $API_KEY, templates, file paths (--tls-key /etc/certs/tls.key), and flags named for a location (--password-file, --secret-name, --token-url). Booleans and masks (skip-secret: "true", password=****) are left alone too.

On a real 38-pod cluster, these rules redact 7 values, all real credentials (the seventh is the SQL password literal), where the 2.x rules redacted 6 of which 3 were not secrets.

Where redaction applies

How the tools are used

Redacted?

k8s-mcp-server

Yes, on by default; --no-redact or K8STOOLS_REDACT=0 turns it off

k8s-capture-state

Yes: the capture applies the pass itself before writing the file

k8s_tools.TOOLS / mock_tools.TOOLS given to an agent framework, or composed into your own MCP server

No. Wrap each function: [wrap_with_redaction(fn) for fn in TOOLS]

Calling a tool function directly in Python

No. Apply the pass to the result: redact_object(result) returns (redacted, count)

wrap_with_redaction keeps each function's name, signature and docstring, so a framework that builds tool schemas from them (pydantic-ai, MCP) sees the same tool. Redaction cannot catch everything. A secret with nothing naming it, such as a bare positional argument (app hunter2), can't be told apart from any other value; nor can an unusual format none of the rules above recognise.

Design approach

Our goal is quality over quantity: well-documented, strongly typed tools. We believe this is critical for agents to use tools effectively, beyond simple demos.

The tools are built on the Kubernetes Python client, and return one of three kinds of result:

  1. kubectl-like: Pydantic models that mirror a kubectl command's output. For example, get_pod_summaries is equivalent to kubectl get pods -o wide.

  2. API-typed: Pydantic models that match the Kubernetes client's types (see https://github.com/kubernetes-client/python/tree/master/kubernetes/docs), with lesser-used fields omitted. For example, get_pod_container_statuses.

  3. Raw dict: the result of to_dict() on the API's object (defined in https://github.com/kubernetes-client/python/tree/master/kubernetes/client/models), with its fields documented in the tool's docstring. For example, get_pod_spec.

Read-only tools come first, because we are focusing on monitoring and root cause analysis. If we add tools that change the cluster, they will be kept separate from the read-only ones, so you can still build "safe" agents.

Development

git clone https://github.com/BenedatLLC/k8stools.git
cd k8stools
uv sync

# Unit tests (no cluster needed)
uv run pytest -v

# Integration tests against a real cluster (skipped automatically if unreachable)
KUBECONFIG=~/.kube/your-config.yaml uv run pytest tests/test_k8s_tools_realk8s.py -v

# Integration tests that write to the cluster (skipped unless asked)
K8STOOLS_LIVE_WRITE=1 KUBECONFIG=~/.kube/your-config.yaml uv run pytest tests/test_live_writes.py -v

The integration tests above only read what the cluster has. The ones in tests/test_live_writes.py create the failures they check, such as a claim on a storage class that doesn't exist or a rollout whose new pod can't be scheduled, and compare the tools' output with the cluster's own messages. Each test creates a k8stools-test-<random> namespace and deletes it afterwards, so it needs permission to create and delete namespaces, Deployments and PersistentVolumeClaims. The tests pull registry.k8s.io/pause. Use a test cluster such as Minikube or kind. They take about 30 seconds. If a run is interrupted, remove what it left with kubectl get ns -o name | grep k8stools-test- | xargs -r kubectl delete.

Planned and deferred work is in docs/ROADMAP.md.

Available Tools

21 tools
get_cluster_infoA

Report which Kubernetes cluster these tools are bound to. Call this first when more than one cluster could be involved, and before drawing conclusions that depend on which cluster the data came from.

The binding is made once (at server startup, or on the first tool call) and
shared by every tool, so all tools answer from this cluster.

Parameters
----------
None
    This function does not take any parameters.

Returns
-------
ClusterInfo
    An object with the following fields:

    source : str
        "kubeconfig" (bound through a kubeconfig context), "in-cluster" (the
        service account of the pod the server runs in), or "capture" (a
        recorded snapshot of a cluster being replayed, not a live cluster).
    context : Optional[str]
        The kubeconfig context name. None for in-cluster config, and for a
        capture that did not record one.
    server : Optional[str]
        URL of the cluster's API server.
    kubeconfig : Optional[str]
        Path of the kubeconfig file (or ``:``-separated list of files) the
        binding was read from. None unless source is "kubeconfig".
    server_version : Optional[str]
        Kubernetes version reported by the API server, e.g. "v1.31.2". None if
        the server could not be reached, or the capture did not record it.
    captured_at : Optional[datetime.datetime]
        For a capture, when it was taken; every age and timestamp the tools
        return is relative to that moment. None for a live cluster.

Raises
------
K8sConfigError
    If no cluster configuration can be loaded.

Inputs: no named parameters. Returns: Which cluster these tools are answering from.

get_configmapA

Retrieves the full contents of a single ConfigMap.

WARNING: ConfigMaps can contain secret-shaped values (e.g. credentials stored
as plain config). When this tool is served through the k8stools MCP server, the
output passes through a redaction step by default (see the `redaction` module
and the server's `--no-redact` flag). Callers using this function directly get
the raw values and are responsible for their own redaction.

Parameters
----------
name : str
    Name of the ConfigMap.
namespace : str, optional
    Namespace of the ConfigMap (default is "default").

Returns
-------
dict[str, Any]
    A dictionary describing the ConfigMap with the following keys:

    name : str
        Name of the ConfigMap.
    namespace : str
        Namespace of the ConfigMap.
    data : dict[str, str]
        The string key/value data map (empty dict if none).
    binary_data_keys : list[str]
        Names of any binary keys. The binary values themselves are not
        returned.

Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the ConfigMap is not found or the API call fails.

Inputs: name (required), namespace. Returns: object

get_configmap_summariesA

Retrieves a list of ConfigMapSummary objects for ConfigMaps in a given namespace or all namespaces, similar to kubectl get configmaps.

Note that this returns only summary metadata (not the ConfigMap contents); use
`get_configmap` to read the actual data map.

Parameters
----------
namespace : Optional[str], default=None
    The specific namespace to list ConfigMaps from. If None, lists ConfigMaps from
    all namespaces.

Returns
-------
list of ConfigMapSummary
    A list of ConfigMapSummary objects, each with the following fields:

    name : str
        Name of the ConfigMap.
    namespace : str
        Namespace in which the ConfigMap lives.
    key_count : int
        Number of keys across `data` and `binary_data`.
    data_size : int
        Approximate total size in bytes of all values.
    age : datetime.timedelta
        Age of the ConfigMap (current time minus creation timestamp).

Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the API call to list ConfigMaps fails.

Inputs: namespace. Returns: result

get_cronjob_summariesA

Retrieves a list of CronJobSummary objects for CronJobs in a given namespace or all namespaces, similar to kubectl get cronjobs.

Parameters
----------
namespace : Optional[str], default=None
    The specific namespace to list CronJobs from. If None, lists from all
    namespaces.

Returns
-------
list of CronJobSummary
    A list of CronJobSummary objects, each with the following fields:

    name : str
        Name of the CronJob.
    namespace : str
        Namespace of the CronJob.
    schedule : str
        Cron schedule expression.
    suspend : bool
        Whether the CronJob is suspended.
    active : int
        Number of currently active (running) jobs.
    last_schedule_time : Optional[datetime.timedelta]
        Time since the CronJob was last scheduled (None if never).
    last_successful_time : Optional[datetime.timedelta]
        Time since the CronJob last completed successfully (None if never).
    age : datetime.timedelta
        Age of the CronJob (current time minus creation timestamp).
    containers : list[ContainerTemplateSummary]
        The job pod template's containers, each with name, image, and literal
        environment values.

Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the API call to list CronJobs fails.

Inputs: namespace. Returns: result

get_daemonset_summariesA

Retrieves a list of DaemonSetSummary objects for DaemonSets in a given namespace or all namespaces, similar to kubectl get daemonsets -o wide.

A DaemonSet runs one pod on each node it targets; its pods are named
"<daemonset>-<5 characters>" and have `owner` "DaemonSet/<daemonset>" in
`get_pod_summaries`. A shortfall between the counts below ("desired 5,
ready 4") is often the first sign of a problem with a particular node.

Parameters
----------
namespace : Optional[str], default=None
    The specific namespace to list DaemonSets from. If None, lists from all
    namespaces.

Returns
-------
list of DaemonSetSummary
    A list of DaemonSetSummary objects, each with the following fields:

    name : str
        Name of the DaemonSet.
    namespace : str
        Namespace in which the DaemonSet runs.
    desired_number_scheduled : int
        Number of nodes that should be running the DaemonSet's pod - the
        actual number of nodes it targets.
    current_number_scheduled : int
        Number of nodes running at least one of its pods that should.
    number_ready : int
        Number of nodes whose pod is ready.
    updated_number_scheduled : int
        Number of nodes running a pod from the current pod template. Less
        than desired means a rollout is in progress or stuck.
    number_available : int
        Number of nodes whose pod has been ready for at least minReadySeconds.
    number_misscheduled : int
        Number of nodes running its pod that should not be.
    node_selector : dict[str, str]
        The pod template's nodeSelector (kubectl's NODE SELECTOR column).
        Many DaemonSets choose their nodes with node affinity and
        tolerations instead, which this does not show, so an empty selector
        does not mean "every node"; desired_number_scheduled is the count.
    update_strategy : str
        Update strategy type ("RollingUpdate" or "OnDelete").
    images : list[str]
        Container images of the current pod template, in container order. A
        pod running a different image is from an earlier revision.
    age : datetime.timedelta
        Age of the DaemonSet (current time minus creation timestamp).

Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the API call to list DaemonSets fails.

Inputs: namespace. Returns: result

get_deployment_summariesA
Retrieves a list of DeploymentSummary objects for deployments in a given namespace or all namespaces.
Similar to `kubectl get deployements`.

Parameters
----------
namespace : Optional[str], default=None
    The specific namespace to list deployments from. If None, lists deployments from all namespaces.

Returns
-------
list of DeploymentSummary
    A list of DeploymentSummary objects, each providing a summary of a deployment's status with the following fields:

    name : str
        Name of the deployment.
    namespace : str
        Namespace in which the deployment is running.
    total_replicas : int
        Total number of replicas desired for this deployment.
    ready_replicas : int
        Number of replicas that are currently ready.
    up_to_date_replicas : int
        Number of replicas that are up to date.
    available_replicas : int
        Number of replicas that are available.
    age : datetime.timedelta
        Age of the deployment (current time minus creation timestamp).

Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the API call to list deployments fails.

Inputs: namespace. Returns: result

get_eventsA

Lists cluster- or namespace-wide events with optional server-side filtering.

Unlike `get_pod_events` (which is scoped to a single named pod), this supports
the sweep queries common in capacity/storage runbooks — e.g. all "Evicted"
events, or all "FailedScheduling" events — where the affected pods often have no
stable name to look up. Note Kubernetes expires an event record about an hour
(by default) after its last occurrence, so events that stopped repeating
earlier than that are gone.

Parameters
----------
namespace : Optional[str], default=None
    Namespace to list events from. If None, lists across all namespaces.
reason : Optional[str], default=None
    If set, only return events with this reason (e.g. "Evicted",
    "FailedScheduling", "BackOff").
involved_kind : Optional[str], default=None
    If set, only return events whose involved object is of this kind (e.g.
    "Pod", "Node", "PersistentVolumeClaim").
involved_name : Optional[str], default=None
    If set, only return events whose involved object has this name.
event_type : Optional[str], default=None
    If set, only return events of this type ("Normal" or "Warning").

Returns
-------
list of EventSummary
    Matching events. Each EventSummary has the following fields:

    last_seen : Optional[datetime.timedelta]
        Time since the event was last seen (if available).
    first_seen : Optional[datetime.timedelta]
        Time since the first occurrence combined into this record (if
        available).
    count : Optional[int]
        How many occurrences Kubernetes combined into this record: repeats
        of the same event on the same object are counted in one record
        rather than listed separately. The count covers first_seen to
        last_seen, not the object's lifetime - a record that stops repeating
        expires (after 1h by default), and a later repeat starts a new one.
        For a crash-looping container, count the "Created" or "Started"
        events to get restarts. "BackOff" is emitted repeatedly while the
        kubelet waits to restart, so its count is several times the number
        of restarts. count divided by (first_seen - last_seen) is the
        average rate over that window, not the current back-off.

        A record can lag behind what it counts. By default the kubelet
        writes at most one event update per object and event type
        ("Normal" or "Warning") every 5 minutes, once a burst of 25 is
        used up; occurrences in between are counted but only written with
        the next update, which then jumps by several at once. So count and
        last_seen describe the most recent *written* occurrence and can
        trail reality by several occurrences and tens of minutes. They lag
        together, so the rate above still holds, but last_seen is not the
        time of the last restart: for that, use the container status
        (last_state.finished_at, state.started_at from
        get_pod_container_statuses) or PodSummary.last_restart, which come
        from the kubelet's status rather than from events. "Pulled",
        "Created" and "Started" share one write budget, so their counts for
        the same restarts can differ by a few; don't compare counts across
        reasons.
    type : str
        Type of the event ("Normal" or "Warning").
    reason : str
        Reason for the event.
    object : str
        The involved object as "Kind/name" (or just the name when the kind is
        unavailable).
    message : str
        Message describing the event.

Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the API call to list events fails.

Inputs: reason, namespace, event_type, involved_kind, +1 more. Returns: result

get_job_summariesA

Retrieves a list of JobSummary objects for Jobs in a given namespace or all namespaces, similar to kubectl get jobs.

Parameters
----------
namespace : Optional[str], default=None
    The specific namespace to list Jobs from. If None, lists from all namespaces.

Returns
-------
list of JobSummary
    A list of JobSummary objects, each with the following fields:

    name : str
        Name of the Job.
    namespace : str
        Namespace of the Job.
    owner : Optional[str]
        Name of the owning CronJob, if this Job was created by one.
    active : int
        Number of actively running pods.
    succeeded : int
        Number of pods that completed successfully.
    failed : int
        Number of pods that terminated in failure.
    start_time : Optional[datetime.timedelta]
        Time since the Job started (None if not started).
    completion_time : Optional[datetime.timedelta]
        Time since the Job completed (None if not complete).
    conditions : list[str]
        Status condition types currently True (e.g. ["Complete"], ["Failed"]).
    age : datetime.timedelta
        Age of the Job (current time minus creation timestamp).
    containers : list[ContainerTemplateSummary]
        The Job pod template's containers, each with name, image, and literal
        environment values.

Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the API call to list Jobs fails.

Inputs: namespace. Returns: result

get_logs_for_cronjobA

Retrieves logs from the most-recent run of a CronJob.

Finds the newest Job owned by the CronJob, then returns the logs of that Job's
most-recently-created pod. Removes the need to manually locate the right
short-lived pod for a CronJob's last tick.

Parameters
----------
cronjob_name : str
    Name of the CronJob.
namespace : str, optional
    Namespace of the CronJob (default is "default").
container_name : str, optional
    Container within the pod. If None, defaults to the first container.
tail : int, optional
    Number of lines from the end of the log (default: last 1000).
since_seconds : int, optional
    If set, only return logs newer than this many seconds.
previous : bool, default False
    If True, return logs from the previous terminated container instance.
    See `get_logs_for_pod_and_container` for the cases - CrashLoopBackOff, a
    restart racing the call, a reclaimed log file - where that legitimately
    returns the same text as `previous=False`, or a 200 whose body is an
    error message.

Returns
-------
str, optional
    Log content, or None if the CronJob has no jobs/pods yet.

Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the API call fails.

Inputs: cronjob_name (required), tail, previous, namespace, +2 more. Returns: result

get_logs_for_jobA

Retrieves logs from the most-recently-created pod of a Job.

Job pods are short-lived, so this convenience finds the newest pod belonging to
the Job (via its `job-name` label) and returns its logs, saving the caller from
listing pods and sorting by creation time.

Parameters
----------
job_name : str
    Name of the Job.
namespace : str, optional
    Namespace of the Job (default is "default").
container_name : str, optional
    Container within the pod. If None, defaults to the first container.
tail : int, optional
    Number of lines from the end of the log (default: last 1000).
since_seconds : int, optional
    If set, only return logs newer than this many seconds.
previous : bool, default False
    If True, return logs from the previous terminated container instance.
    See `get_logs_for_pod_and_container` for the cases - CrashLoopBackOff, a
    restart racing the call, a reclaimed log file - where that legitimately
    returns the same text as `previous=False`, or a 200 whose body is an
    error message.

Returns
-------
str, optional
    Log content, or None if the Job has no pods.

Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the API call fails.

Inputs: job_name (required), tail, previous, namespace, +2 more. Returns: result

get_logs_for_pod_and_containerA
Retrieves logs from a Kubernetes pod and container.

Args:
    pod_name (str): The name of the pod.
    namespace (str): The namespace of the pod.
    container_name (str, optional): The name of the container within the pod.
                                    If None, defaults to the first container.
    tail (int, optional): Number of lines to return from the end of the log.
                          If None, defaults to the last 1000 lines. Pass a
                          larger value to retrieve more history, or a small
                          value for a bounded recent slice.
    since_seconds (int, optional): If set, only return logs newer than this
                          many seconds. Combines with `tail` (both limits
                          apply).
    previous (bool, default False): If True, return logs from the *previous*
                          terminated instance of the container instead of the
                          current one. Indispensable for crashloop analysis,
                          where the current instance's logs are empty or
                          post-restart. Fails if there is no previous instance.

                          Three Kubernetes behaviors make `previous=True`
                          look as though it were ignored or broken; none is a
                          fault in this tool, and all are worth knowing before
                          drawing a conclusion from the output:

                          * While a container sits in CrashLoopBackOff there
                            is no running instance, so the kubelet serves the
                            most recently *terminated* one for `previous=False`
                            as well - both calls then return the same text.
                            Check the container's state with
                            `get_pod_container_statuses` before concluding the
                            flag had no effect.
                          * A restart between two calls shifts the window: the
                            instance that was current becomes the previous one,
                            so a container crash-looping every few seconds can
                            legitimately return identical bytes for both.
                            Compare the log timestamps, not just the flag.
                          * Once the previous instance's log file has been
                            reclaimed, the API answers 200 with the text
                            `unable to retrieve container logs for <id>` -
                            a successful call whose body is an error message,
                            not a raised error.

                          Only the single most recent terminated instance is
                          retained by the kubelet; there is no way to reach
                          further back than one.

Returns:
    str: The log content as text, with real newlines. An empty string if the
         container has produced no log output (never None).

Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the API call to fetch logs fails or an unexpected error occurs.

Inputs: pod_name (required), tail, previous, namespace, +2 more. Returns: result

get_namespacesA

Return a summary of the namespaces for this Kubernetes cluster, similar to that returned by kubectl get namespace.

Parameters
----------
None
    This function does not take any parameters.

Returns
-------
list of NamespaceSummary
    List of namespace summary objects. Each NamespaceSummary has the following fields:

    name : str
        Name of the namespace.
    status : str
        Status phase of the namespace.
    age : datetime.timedelta
        Age of the namespace (current time minus creation timestamp).
Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the API call to list namespaces fails.

Inputs: no named parameters. Returns: result

get_node_summariesA

Return a summary of the nodes for this Kubernetes cluster, similar to that returned by kubectl get nodes -o wide.

Parameters
----------
None
    This function does not take any parameters.

Returns
-------
list of NodeSummary
    List of node summary objects. Each NodeSummary has the following fields:

    name : str
        Name of the node.
    status : str
        Status of the node (Ready, NotReady, etc.).
    roles : list[str]
        List of roles for the node (e.g., ['control-plane', 'master']).
    age : datetime.timedelta
        Age of the node (current time minus creation timestamp).
    version : str
        Kubernetes version running on the node.
    internal_ip : Optional[str]
        Internal IP address of the node.
    external_ip : Optional[str]
        External IP address of the node (if available).
    os_image : Optional[str]
        Operating system image running on the node.
    kernel_version : Optional[str]
        Kernel version of the node.
    container_runtime : Optional[str]
        Container runtime version on the node.
    capacity : dict[str, str]
        Total capacity of the node keyed by resource name (e.g. "cpu",
        "memory", "ephemeral-storage", "pods"). Empty if unavailable.
    allocatable : dict[str, str]
        Resources allocatable to pods (capacity minus system-reserved),
        keyed by resource name. Empty if unavailable.
    conditions : dict[str, str]
        Node conditions keyed by type with their status, e.g.
        {"Ready": "True", "MemoryPressure": "False", "DiskPressure": "False"}.
    conditions_since : dict[str, datetime.timedelta]
        For each condition in `conditions`, the time since its status last
        changed (the API's lastTransitionTime), e.g. how long the node has
        been Ready, or how long ago a MemoryPressure episode ended. A
        condition with no transition time is left out.

        It changes only when the status does. A node or kubelet restart in
        which the node comes back Ready without having been marked NotReady
        or Unknown in between does not reset it: on a minikube cluster
        stopped and started together with its control plane, Ready stayed
        "True since the node was created" across a reboot. So a long Ready
        age does not mean the node or its pods have been running that long,
        and a short one can come from a brief NotReady flap rather than a
        reboot. To date a node's last start, use its "Starting" or
        "Rebooted" events (`get_events(involved_kind="Node")`, while they
        last - events usually expire after an hour), or the `started_at` of
        containers that start with the node, such as kube-proxy and the
        kube-system control-plane containers (`get_pod_container_statuses`).
    taints : list[str]
        Taints on the node formatted as "key=value:effect" (value omitted
        when empty).
    labels : dict[str, str]
        All labels on the node. Useful for identifying node pool / instance
        type and spotting version skew across pools.

Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the API call to list nodes fails.

Inputs: no named parameters. Returns: result

get_pod_container_statusesA
Get the status for all containers in a specified Kubernetes pod.

Parameters
----------
pod_name : str
    Name of the pod to retrieve container statuses for.
namespace : str, optional
    Namespace of the pod (default is "default").

Returns
-------
list of ContainerStatus
    List of container status objects for the specified pod. Each ContainerStatus has the following fields:

    pod_name : str
        Name of the pod.
    namespace : str
        Namespace of the pod.
    container_name : str
        Name of the container.
    image : str
        Image name.
    ready : bool
        Whether the container is currently passing its readiness check.
        The value will change as readiness probes keep executing.
    restart_count : int
        Number of times the container has restarted.
    started : Optional[bool]
        Started indicates whether the container has finished its postStart
        lifecycle hook and passed its startup probe.
    stop_signal : Optional[str]
        Stop signal for the container.
    state : Optional[ContainerState]
        Current state of the container.
    last_state : Optional[ContainerState]
        Last state of the container. When Terminated, ``ran_for`` is how long
        that instance ran (``finished_at - started_at``). It is not the
        restart interval, which adds the back-off named in a Waiting
        ``state``'s message, and it is not how much time the container's log
        covers: a process can stop logging long before it is killed.
    volume_mounts : list[VolumeMountStatus]
        Status of volume mounts for the container
    resource_requests : dict[str, str]
        Describes the minimum amount of compute resources required. If Requests
        is omitted for a container, it defaults to Limits if that is explicitly specified,
        otherwise to an implementation-defined value. Requests cannot exceed Limits. 
    resource_limits : dict[str, str]
        Describes the maximum amount of compute resources allowed.
    allocated_resources : dict[str, str]
        Compute resources allocated for this container by the node.

Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the API call to read the pod fails.

Inputs: pod_name (required), namespace. Returns: result

get_pod_eventsA
Get events for a specific Kubernetes pod. This is equivalent to the kubectl command:
`kubectl get events -n NAMESPACE --field-selector involvedObject.name=POD_NAME,involvedObject.kind=Pod`

Parameters
----------
pod_name : str
    Name of the pod to retrieve events for.
namespace : str, optional
    Namespace of the pod (default is "default").

Returns
-------
list of EventSummary
    List of events associated with the specified pod. Each EventSummary has the following fields:

    last_seen : Optional[datetime.timedelta]
        Time since the most recent occurrence of the event (if available).
    first_seen : Optional[datetime.timedelta]
        Time since the first occurrence combined into this record (if
        available).
    count : Optional[int]
        How many occurrences Kubernetes combined into this record: repeats
        of the same event on the same object are counted in one record
        rather than listed separately. The count covers first_seen to
        last_seen, not the object's lifetime - a record that stops repeating
        expires (after 1h by default), and a later repeat starts a new one.
        For a crash-looping container, count the "Created" or "Started"
        events to get restarts. "BackOff" is emitted repeatedly while the
        kubelet waits to restart, so its count is several times the number
        of restarts. count divided by (first_seen - last_seen) is the
        average rate over that window, not the current back-off.

        A record can lag behind what it counts. By default the kubelet
        writes at most one event update per object and event type
        ("Normal" or "Warning") every 5 minutes, once a burst of 25 is
        used up; occurrences in between are counted but only written with
        the next update, which then jumps by several at once. So count and
        last_seen describe the most recent *written* occurrence and can
        trail reality by several occurrences and tens of minutes. They lag
        together, so the rate above still holds, but last_seen is not the
        time of the last restart: for that, use the container status
        (last_state.finished_at, state.started_at from
        get_pod_container_statuses) or PodSummary.last_restart, which come
        from the kubelet's status rather than from events. "Pulled",
        "Created" and "Started" share one write budget, so their counts for
        the same restarts can differ by a few; don't compare counts across
        reasons.
    type : str
        Type of the event.
    reason : str
        Reason for the event.
    object : str
        The object this event applies to.
    message : str
        Message describing the event.
Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the API call to list events fails.

Inputs: pod_name (required), namespace. Returns: result

get_pod_specA
Retrieves the spec for a given pod in a specific namespace.

Args:
    pod_name (str): The name of the pod.
    namespace (str): The namespace the pod belongs to (defaults to "default").

Returns
-------
dict[str, Any]
    The pod's spec object, containing its desired state. It is converted
    from a V1PodSpec to a dictionary. Key fields include:

    containers : list of kubernetes.client.V1Container
        List of containers belonging to the pod. Each container defines its image,
        ports, environment variables, resource requests/limits, etc.
    init_containers : list of kubernetes.client.V1Container, optional
        List of initialization containers belonging to the pod.
    volumes : list of kubernetes.client.V1Volume, optional
        List of volumes mounted in the pod and the sources available for
        the containers.
    node_selector : dict, optional
        A selector which must be true for the pod to fit on a node.
        Keys and values are strings.
    restart_policy : str
        Restart policy for all containers within the pod.
        Common values are "Always", "OnFailure", "Never".
    service_account_name : str, optional
        Service account name in the namespace that the pod will use to
        access the Kubernetes API.
    dns_policy : str
        DNS policy for the pod. Common values are "ClusterFirst", "Default".
    priority_class_name : str, optional
        If specified, indicates the pod's priority_class via its name.
    node_name : str, optional
        NodeName is a request to schedule this pod onto a specific node.

Raises
------
K8SConfigError
    If unable to initialize the K8S API
K8sApiError
    If the pod is not found, configuration fails, or any other API error occurs.

Inputs: pod_name (required), namespace. Returns: object

get_pod_summariesA
Retrieves a list of PodSummary objects for pods in a given namespace or all namespaces.

Parameters
----------
namespace : Optional[str], default=None
    The specific namespace to list pods from. If None, lists pods from all namespaces.

Returns
-------
list of PodSummary
    A list of PodSummary objects, each providing a summary of a pod's status with the following fields:

    name : str
        Name of the pod.
    namespace : str
        Namespace in which the pod is running.
    total_containers : int
        Total number of containers in the pod.
    ready_containers : int
        Number of containers currently in ready state.
    restarts : int
        Total number of restarts for all containers in the pod.
    last_restart : Optional[datetime.timedelta]
        Time since a container of this pod last terminated - the most
        recent last_state.finished_at across its containers - or None if
        none has. For a container waiting in CrashLoopBackOff this is its
        last crash, not a restart: it has not been started again yet. Taken
        from the kubelet's status, so it is current, unlike event records.
    age : datetime.timedelta
        Age of the pod (current time minus creation timestamp).
    ip : Optional[str]
        Pod IP address (None if not assigned).
    node : Optional[str]
        Name of the node where the pod is running (None if not scheduled).
    owner : Optional[str]
        The pod's controlling owner, as "Kind/name" - the object that
        created and manages it, e.g. "DaemonSet/otel-collector-agent",
        "StatefulSet/valkey-cart", "Job/backup-29318400". None for a bare
        pod nobody manages. This is the direct owner only: a Deployment's
        pods are owned by one of its replica sets ("ReplicaSet/ad-7d9f8c6b5"),
        whose `owner_deployment` (`get_replicaset_summaries`) names the
        Deployment; likewise a Job's `owner` names its CronJob. Static
        (mirror) pods such as the control plane's report "Node/<node name>".
        Group pods into workloads by this field rather than by stripping
        name suffixes.
Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the API call to list pods fails.

Inputs: namespace. Returns: result

get_pvc_summariesA

Retrieves a list of PVCSummary objects for PersistentVolumeClaims in a given namespace or all namespaces, similar to kubectl get pvc.

For each PVC, this also resolves which pods currently mount it (by scanning pod
volumes in the same scope), which is useful for spotting orphaned PVCs — claims
with an empty `mounted_by` and no owning pod.

Parameters
----------
namespace : Optional[str], default=None
    The specific namespace to list PVCs from. If None, lists from all namespaces.

Returns
-------
list of PVCSummary
    A list of PVCSummary objects, each with the following fields:

    name : str
        Name of the PVC.
    namespace : str
        Namespace of the PVC.
    status : str
        Phase of the PVC ("Bound", "Pending", or "Lost").
    volume_name : Optional[str]
        Name of the bound PersistentVolume (None if unbound).
    capacity : Optional[str]
        Storage capacity (e.g. "10Gi"); falls back to the requested size when
        the claim is not yet bound.
    access_modes : list[str]
        Access modes (e.g. ["ReadWriteOnce"]).
    storage_class : Optional[str]
        StorageClass backing the claim.
    mounted_by : list[str]
        Names of pods (in the same scope) currently mounting this PVC. Empty
        for orphaned/unmounted claims.
    age : datetime.timedelta
        Age of the PVC (current time minus creation timestamp).

Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the API call to list PVCs fails.

Inputs: namespace. Returns: result

get_replicaset_summariesA
Retrieves a list of ReplicaSetSummary objects, similar to `kubectl get replicasets`
but including each replica set's deployment revision and container images.

A Deployment's replica sets are its revision history: every update to a Deployment
creates a new replica set carrying that revision's pod template, and older replica
sets are retained (scaled to zero). Listing them for one deployment therefore shows
when it last changed and what its image was at each revision - which is how you
answer "did something change recently?" without access to deployment tooling or
version control.

Results are grouped by namespace and owning deployment, and within each
deployment sorted by revision, oldest first. So when filtered to a single
deployment, the last entry is that deployment's current revision.

Replica sets with no revision (see `revision` below) sort *first*, ahead of
revision 1, rather than being dropped. They have no place in any deployment's
history, so they are listed before it rather than appended to it. In practice
they are only visible in an unfiltered listing: a replica set without a
revision has no owning deployment either, so passing `deployment` filters them
out, and the "last entry is the current revision" guarantee above is unaffected.

Parameters
----------
namespace : Optional[str], default=None
    The specific namespace to list replica sets from. If None, lists from all namespaces.
deployment : Optional[str], default=None
    If given, return only replica sets owned by this deployment. This is the common
    case: one deployment's revision history.

Returns
-------
list of ReplicaSetSummary
    A list of ReplicaSetSummary objects with the following fields:

    name : str
        Name of the replica set.
    namespace : str
        Namespace in which the replica set is defined.
    owner_deployment : Optional[str]
        Name of the Deployment that owns this replica set, or None if it is
        standalone (not managed by a Deployment).
    revision : Optional[int]
        The deployment revision this replica set represents, taken from the
        `deployment.kubernetes.io/revision` annotation. None when that
        annotation is absent, which means no Deployment created this replica
        set - only the Deployment controller writes it. A hand-written replica
        set, or one created by another controller, therefore has no revision
        (and no `owner_deployment`), and appears in neither `kubectl rollout
        history` nor a `deployment`-filtered call here. Also None if the
        annotation is present but not an integer.
    desired_replicas : int
        Replicas desired for this replica set. Old revisions are scaled to 0.
    current_replicas : int
        Replicas currently running.
    ready_replicas : int
        Replicas currently ready.
    images : list[str]
        Container images in this revision's pod template, in container order.
        Comparing this across revisions shows what an upgrade changed.
    age : datetime.timedelta
        Age of the replica set (current time minus creation timestamp). For the
        newest revision this is how long ago the deployment last changed.

Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the API call to list replica sets fails.

Inputs: namespace, deployment. Returns: result

get_service_summariesA

Retrieves a list of ServiceSummary objects for services in a given namespace or all namespaces. Similar to kubectl get services.

Parameters
----------
namespace : Optional[str], default=None
    The specific namespace to list services from. If None, lists services from all namespaces.

Returns
-------
list of ServiceSummary
    A list of ServiceSummary objects, each providing a summary of a service's status with the following fields:

    name : str
        Name of the service.
    namespace : str
        Namespace in which the service is running.
    type : str
        Type of the service (ClusterIP, NodePort, LoadBalancer, ExternalName).
    cluster_ip : Optional[str]
        Cluster IP address assigned to the service (None for ExternalName services).
    external_ip : Optional[str]
        External IP address if applicable (for LoadBalancer services).
    ports : list[PortInfo]
        List of ports (and their protocols) exposed by the service.
    age : datetime.timedelta
        Age of the service (current time minus creation timestamp).
    selector : dict[str, str]
        The label selector the service uses to choose backing pods. Empty
        for services without a selector (e.g. ExternalName, or manually
        managed Endpoints).
    labels : dict[str, str]
        Labels on the service object.
    annotations : dict[str, str]
        Annotations on the service object.

Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the API call to list services fails.

Inputs: namespace. Returns: result

get_statefulset_summariesA

Retrieves a list of StatefulSetSummary objects for StatefulSets in a given namespace or all namespaces, similar to kubectl get statefulsets.

Parameters
----------
namespace : Optional[str], default=None
    The specific namespace to list StatefulSets from. If None, lists from all
    namespaces.

Returns
-------
list of StatefulSetSummary
    A list of StatefulSetSummary objects, each with the following fields:

    name : str
        Name of the StatefulSet.
    namespace : str
        Namespace in which the StatefulSet runs.
    total_replicas : int
        Desired number of replicas.
    ready_replicas : int
        Number of replicas currently ready.
    current_replicas : int
        Number of replicas created by the current revision.
    update_strategy : str
        Update strategy type (e.g. "RollingUpdate", "OnDelete").
    service_name : Optional[str]
        Name of the governing (headless) service.
    age : datetime.timedelta
        Age of the StatefulSet (current time minus creation timestamp).

Raises
------
K8sConfigError
    If unable to initialize the K8S API.
K8sApiError
    If the API call to list StatefulSets fails.

Inputs: namespace. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.9/5.0

    Scored across 21 tools

    Disambiguation4/5

    Most tools target clearly distinct Kubernetes resources and levels (nodes, pods, deployments, services, logs, etc.), and the descriptions explicitly distinguish similar tools. However, get_pod_events overlaps with get_events, which can also filter by involved_kind and involved_name, creating a minor selection ambiguity.

    Naming Consistency5/5

    All tool names use snake_case with a consistent get_ prefix and resource-oriented nouns. The patterns get_<resource>_summaries, get_<resource>, and get_logs_for_<entity> are predictable and easy to scan.

    Tool Count3/5

    With 21 tools, the set is on the heavy side for a read-only inspection server. Many resource summaries are justified, but the surface could be consolidated (e.g. pod-specific events and generalized events, or separate job/cronjob log helpers).

    Completeness3/5

    Core workload inspection is well covered: pods, containers, logs, events, deployments, replica sets, stateful sets, daemon sets, jobs, cronjobs, services, configmaps, PVCs, nodes, and namespaces. Notable gaps remain for common Kubernetes resources such as ingresses, endpoints, secrets, storage classes, persistent volumes, HPA, and RBAC objects.

    Maintenance

    ActivityActive
    ResponsivenessUsually handled

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      A read-only MCP server for Kubernetes that allows querying cluster information and diagnosing issues through natural language interfaces like Claude.
      7
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      A read-only MCP server for inspecting Kubernetes clusters, allowing LLMs to list resources, describe pods, and read logs without mutation.
      5
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      Exposes a Kubernetes cluster to MCP-compatible AI clients, enabling read-only and optional write operations on cluster resources like pods, deployments, and namespaces through natural language.
      9
      1
      MIT