k8s-cronjob-mcp
Provides read-only triage of Kubernetes CronJobs through the cluster API: lists CronJobs with health verdicts (failing, stalled, never-succeeded, degraded, overrunning, suspended), checks whether a job is on schedule, late, missed or overrunning, returns retained Job run history with duration trends and pod exit codes, finds silent failures and CronJobs that stopped firing, lists recent failed Jobs and active runs, and synthesizes failure causes from pod state, Events and log tails. Access is limited to get/list/log reads on cronjobs, jobs, pods, pods/log and events, optionally confined to an allowlist of namespaces.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@k8s-cronjob-mcpWhich CronJobs in batch haven't succeeded this week, and why?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
k8s-cronjob-mcp
A read-only MCP server that lets an AI agent triage Kubernetes CronJobs: did it run, is it getting slower, has it quietly stopped succeeding, and why did that run fail?
General-purpose Kubernetes MCP servers expose CronJobs as just another resource type, so an agent has to fetch the CronJob, its Jobs, pods, Events and logs separately and then work out cron schedules, timezones and history limits itself. This server does that reasoning in tested code and returns a verdict with the evidence behind it, in one tool call, with zero write access to the cluster.
Where it fits
Use it for: on-demand triage. "Which CronJobs in
batchhaven't succeeded this week, and why?" "Is the nightly report job running late?" "Why did last night's run fail?"It is not alerting. It only looks when an agent asks. Keep your existing alerts (for example Prometheus with kube-state-metrics'
kube_cronjob_status_last_successful_time, or a dead-man's-switch service) to learn that something broke; use this server to find out what and why.
Tools
Tool | What it answers |
| Is the server set up correctly? Checks kubeconfig, context, auth helper, timezone, cluster reachability and read access to each resource, in the exact environment your MCP client runs it in. Every problem comes with the variable to set and where. |
| What CronJobs are there and how are they doing? One compact row each, worst first: schedule, effective timezone, health ( |
| Is it on schedule? Verdict: |
| Retained Jobs, newest first: start, duration, status ( |
| The flagship. CronJobs with no successful run in N days even if nothing is erroring right now, CronJobs that have stopped firing, and CronJobs whose runs outlast their schedule, ranked by urgency, with the pattern ( |
| What failed recently? Every failed Job in the window (including ones not created by a CronJob), newest first, each with its synthesized cause and whether a later run of the same CronJob has succeeded since. |
| Why a Job failed. A synthesized cause (e.g. |
| What is running right now? Unfinished Jobs (including ones not created by a CronJob), longest-running first, with owning CronJob, elapsed time, pod counts, retries, deadline, and a |
How the verdicts work
Timezone. Schedules are evaluated in
spec.timeZonewhen set, otherwise in UTC, which is what the kube-controller-manager almost always uses. The timezone of the machine running this server is never used. If your controller runs in another timezone, setCRON_DEFAULT_TIMEZONE.Schedule check. The expected last fire time is computed from
spec.schedule. The actual last run comes from thebatch.kubernetes.io/cronjob-scheduled-timestampannotation on Jobs where available (Kubernetes 1.32+), falling back tostatus.lastScheduleTime. A run due less than 60s ago is stillon-schedule; after that it islateuntilstartingDeadlineSeconds, thenmissed. IfstartingDeadlineSecondsis unset, Kubernetes itself has no deadline; the server uses a 300s cutoff as a heuristic and says so in the verdict.Stopped firing. If no run has started for 3 or more scheduled fire times (and none is active), the CronJob is reported as
stalled, even if it succeeded recently. This catches a frequent job that stopped an hour ago without waiting for thedayswindow.Overrun. Compares the median duration of recent runs (and any active run) with the shortest gap between scheduled fire times. A CronJob whose runs use more than 80% of that gap is reported as
at-risk(one slow run away from overrunning). A CronJob whose runs outlast that gap is reported asdegraded/overrunning, with the effect of itsconcurrencyPolicy: withForbid, scheduled runs are skipped or start late; withAllow, runs overlap; withReplace, runs are cancelled before they finish. Late starts (more than 60s after the scheduled time) are counted as supporting evidence.Silent failures. "Last success" is the later of
status.lastSuccessfulTime(which survives Job deletion) and retained Jobs. The window ismax(days, 2 × the schedule's cadence), so a weekly job is not flagged by a 7-day window. A brand-new CronJob that has not failed is not flagged. Suspended CronJobs are reported assuspended, not as failures.Failure cause. Checked in order: OOMKilled, DeadlineExceeded, image pull errors, container config errors, eviction, non-zero exit code (with hints such as 127 = command not found), unschedulable, then the most relevant warning Event. When a Job's pods are already gone, the cause falls back to what the Job object still records (failure reason and message, number of failed pods) and says why the evidence is missing, for example
ttlSecondsAfterFinished.Every answer says what it is based on. Each result has
asOf(when it was computed) andbasis, one sentence such as "Failed Jobs that finished in the last 3 days, from only the Jobs Kubernetes still keeps" or "median of last 2 successful runs". WhenALLOWED_NAMESPACESis set,scopeshows those namespaces instead of "all namespaces".
Related MCP server: kops
Read-only by construction
This is deliberate and enforced in two independent places:
Code. The only Kubernetes calls are
get/list/log reads. TheClusterReaderinterface in src/k8s/client.ts has no create/patch/delete/exec method for a tool to call.RBAC. deploy/rbac.yaml grants
get/list/watchon exactlycronjobs,jobs,pods,pods/logandevents. Nothing else: no secrets, nopods/exec, no write verbs.npm run check:rbacasserts this against a real cluster (52 checks: reads allowed, every write denied).
Also worth knowing:
Set
ALLOWED_NAMESPACES=a,bto confine the server to specific namespaces; anything else is refused before any API call. This is enforced by the server, not by Kubernetes: the bundled RBAC is a ClusterRole, so the ServiceAccount itself can still read pods and logs cluster-wide. If you need Kubernetes to enforce the boundary, bind the same rules with a namespaced Role and RoleBinding in each namespace instead.Log tails are passed through a best-effort secret masker (passwords, tokens, API keys, bearer tokens). It is pattern-based and will not catch everything; treat logs as potentially sensitive.
Job and pod data comes from live cluster state. There is no database. What you can see is bounded by
successfulJobsHistoryLimit/failedJobsHistoryLimit, and Events expire after about an hour by default.
Install and use
Requires Node 20+. No install step is needed; npx fetches it on demand.
Add it to Claude Code (stdio):
claude mcp add k8s-cronjobs -e K8S_CONTEXT=my-context -- npx -y k8s-cronjob-mcpOr to any MCP client config:
{
"mcpServers": {
"k8s-cronjobs": {
"command": "npx",
"args": ["-y", "k8s-cronjob-mcp"],
"env": {
"K8S_CONTEXT": "my-context",
"ALLOWED_NAMESPACES": "batch,reports"
}
}
}
}Variable | Meaning |
| Standard kubeconfig lookup, or in-cluster config when running in a pod. |
| Context to use instead of the kubeconfig's current one. Set this explicitly so the server cannot silently target the wrong cluster. |
| Optional comma-separated allowlist. |
| Timezone for CronJobs that do not set |
To run from source instead: npm install && npm run build, then use node /absolute/path/to/dist/index.js as the command.
Docker (stdio, so run with -i and mount a kubeconfig read-only):
docker build -t k8s-cronjob-mcp .
docker run -i --rm -v ~/.kube/config:/home/node/.kube/config:ro k8s-cronjob-mcpTo give the server its own least-privilege identity, apply deploy/rbac.yaml and build a kubeconfig from the k8s-cronjob-mcp ServiceAccount (see scripts/sa-kubeconfig.sh for an example).
Get more out of it: raise history limits
By default Kubernetes keeps only the last 3 successful and 1 failed Job per CronJob. That is too little evidence for useful answers: duration trends need at least 4 successful runs, and consecutive failures cannot be counted past 1. For the CronJobs you care about, raise the limits:
spec:
successfulJobsHistoryLimit: 10
failedJobsHistoryLimit: 5get_run_history warns when a CronJob's limits are too low.
Troubleshooting
Errors are short: what went wrong, the underlying error, what the server is using, and the fix. When a setting fixes it, the error names the variable, the value and the file to put it in. For example:
✗ No permission to read across all namespaces (403)
Error: cronjobs.batch is forbidden: User "you@example.com" cannot list resource "cronjobs" ... at the cluster scope
Using: context dev (pinned) · EKS dev-cluster (eu-west-2) · ~/.kube/config
Fix: Limit the server to namespaces you can read
Check one: kubectl auth can-i list cronjobs -n <namespace>
Set: ALLOWED_NAMESPACES=team-a
Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json → "env" of this server (then Cmd+Q and reopen)To check everything step by step:
From your agent: ask it to run
check_setup. This is the most accurate check, because it runs with the same environment as your MCP client (which, for desktop apps, is not your shell's environment).From a terminal:
npx -y k8s-cronjob-mcp --check(exit code 0 when everything passes).
If the configuration is broken at startup (for example a wrong K8S_CONTEXT), the server still starts and every tool returns the fix instead of failing silently. The startup summary and any problems are also written to the client's server log (Claude Desktop: ~/Library/Logs/Claude/mcp-server-k8s-cronjobs.log).
Symptom | Cause | Fix |
| MCP clients do not expand | Use the absolute path |
| Cluster name used instead of context name | Use the NAME column of |
| Desktop apps do not load your shell profile, so | Add a |
| You can read only some namespaces | Set |
| Expired login or token | Log in again with your cloud CLI, or create a new ServiceAccount token |
| Cluster down, VPN off, or wrong context | Check connectivity, or change |
Try it on a local cluster
The demo uses kind and a repo-local kubeconfig (.kube/). The scripts never read your $KUBECONFIG and refuse to continue unless the context is a kind-* cluster, so no other cluster is touched.
npm run demo:up # kind cluster + demo CronJobs + RBAC
npm run check:rbac # prove the role is read-only
npm run test:integration # runs every tool as the read-only ServiceAccount
npm run demo:downThe demo namespace contains: healthy, flaky (fails randomly), always-failing (the silent failure), paused (suspended), slowing (duration trend) and oom (OOMKilled). Give the CronJobs a few minutes (they fire every 2 minutes), then ask an agent: "Which CronJobs in demo are failing silently, and why?"
Development
npm test # unit tests (no cluster needed)
npm run typecheck
npm run dev # run from source over stdioThe logic lives in src/analysis/ as pure functions over Kubernetes objects, so it is tested with fixtures. src/tools/ is thin glue.
Publishing
See PUBLISHING.md for the npm release checklist.
Known limitations
Only retained Jobs are visible, so consecutive-failure counts are capped by
failedJobsHistoryLimit. A CronJob with a history limit of 0 leaves no evidence; the tools say so rather than reporting it as healthy.Trends need at least 4 successful runs (see raise history limits). The overrun check works from 1 finished run but is more reliable with more.
list_recent_failuresonly sees failed Jobs Kubernetes still retains; failures removed by history limits orttlSecondsAfterFinishedare gone.
Roadmap
A namespaced Role/RoleBinding option in
deploy/.Optional Prometheus source for run history beyond the retained Jobs.
compare_runs, an HTTP transport and a Helm chart.
This server cannot be deployed
Maintenance
Related MCP Connectors
Read-only, deterministic AI triage and readiness tools implementing Sophon's published rubrics.
Fail-closed policy guardrails for AI agents running kubectl, terraform, helm, and argocd.
Manage CronPulser from an AI agent: HTTP, Runner and TLS jobs, alerts, Runners and run history.
- SerenoOAuthcom.asksereno
A cron job monitor your AI agent sets up and can ask why a job failed. Free for up to 200 checks.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceProvides read-only access to Kubernetes clusters for AI assistants.24MIT
- AlicenseAqualityCmaintenanceA read-only kubectl MCP server for AI assistants. The kubectl verb is hardcoded in each tool (get/describe/logs/events) and user input only fills argument values — no mutation path even with malicious input, and Secret/ConfigMap values are never returned (metadata only). Returns structured JSON, plus one-shot k8s_triage (health scan) and k8s_inventory (cluster snapshot).61MIT
- FlicenseAqualityCmaintenanceRead-only MCP server that exposes Kubernetes platform state (tenants, pods, SLOs, ArgoCD applications, chaos schedules, and catalog services) to AI agents, enabling natural language queries about cluster health and configuration.6-
- FlicenseNot gradedqualityCmaintenanceProvides a read-only interface to Kubernetes clusters, enabling LLMs to list pods, get pod status and logs, fetch deployment manifests, and perform pod health analysis with resource trend tracking.-