Built for real 3AM incidents.
argus is a Kubernetes incident-diagnosis MCP server. Ask “why is checkout-api broken?” and it correlates twelve resource kinds in a single read-only pass, then answers with ranked causes — each one citing the evidence it used.
Go 1.26 · MIT · no writes, enforced by the build
The thesis
Every other Kubernetes MCP server is kubectl with a JSON schema stapled on:
get_pods, describe_pod, get_logs. That pushes the
correlation onto the model across ten round-trips, each dumping kilobytes of
managedFields into the window. By pod four the context is noise, and a model
reasoning from noise guesses.
Real output
Generated from real output rather than retyped — this panel is produced from the bytes argus diagnose actually printed, confidence drop included, because that cluster had no metrics API.
The difference
| Typical k8s MCP server | argus | |
|---|---|---|
| Tool surface | 40–100+, mirroring kubectl | 6, one per SRE question |
| Correlation | the model does it, over many calls | server-side, in one call |
| Output | raw objects to interpret | ranked findings, evidence mandatory |
| Context per pod | 2,000+ tokens | under 400, enforced by test |
| Repeated events | one line each | deduplicated, blast radius kept |
| Confidence | absent, or implied certainty | stated, and lowered on partial data |
| Writes | usually available, sometimes gated | none — no mutating call site exists |
Measured, not estimated
From one real production Deployment with 93 environment variables and 11 ReplicaSets.
The correlation engine
Each is a pure function over a snapshot, so each is replayable against a committed fixture. Every fixture asserts its detector fires and that no others do — the negative half is the half that matters, because a tool that reports three wrong causes during an incident costs more time than it saves.
Reads the exit code instead of restating CrashLoopBackOff. A broken entrypoint, a container that exits zero and should be a Job, SIGSEGV, an abort, a liveness-probe SIGTERM — each gets its own remedy.
lastState reason + exit code + the runtime's own errorThe kernel killed it for exceeding its own limit — with a suggested new limit when usage data supports one.
lastState.reason=OOMKilled + limit + peak usageThe new revision is failing and the previous one was healthy. Reports the fields that changed between them.
RS health pair + semantic template diffDistinguishes a typo'd tag from a missing pull secret, refused credentials, and a registry rate limit — four causes, one symptom.
waiting.reason + the runtime's own error textTells a selector typo (matches nothing) apart from a readiness failure (matches pods, none ready). Invisible to kubectl get.
Running, alive, not crashing — and still never ready. Compares the probe's real deadline against observed uptime.
initialDelay + period×failureThresholdWidens scope. If the node is the cause, it says so and suppresses the per-workload symptoms rather than reporting one cause three times.
node conditions + affected pod setSafety
The usual advice is “enforce read-only through RBAC, not in code.” That is correct for an in-cluster deployment and false for how you will actually run this — from your own kubeconfig, where RBAC grants you everything. So the binary holds the line instead.
A test walks the entire source AST and fails the build on any call to Create, Update, Patch, Delete, Apply or Evict — with a companion test proving the walk can actually fail.
Log and event text is user-controlled and reaches model context. argus frames it as untrusted data — but the real mitigation is structural: there is no mutation path to reach.
Secret objects are never read. Environment variable values are never projected — only key names, which is all a detector needs. Verified by test.
The one thing argus writes is its own binary. The published SHA-256 must match or the update is abandoned, HTTPS is required across redirects, and the swap is a rename — a failure leaves the working binary intact.
A hard apiserver call budget lives in an http.RoundTripper, counting every request and retry. A diagnostic tool is run during an incident. Cluster-wide triage costs 10 calls whatever the cluster size — looping the per-workload diagnosis would have been ~1,700.
Install
Or drive it straight from a terminal, no MCP involved:
And to update it later — checksum-verified, atomic, and it will not clobber a build you made from a clone:
Roadmap
previous instance on a crashloop, collapses repeated lines, redacts credentials, budgets by tokens. Took a real crashloop from 206 lines to 7capture as the fixture generator · read-only enforced as a build gate · checksum-verified self-updatetargetPort naming a port no container declares leaves the EndpointSlice with no port at all, so every connection is refused while kubectl get shows a healthy systemargus update, which replaces argus's own binary against a published checksum and grants no cluster write access.