read-only · evidence-backed · pre-release

One question in.
One ranked diagnosis out.

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.

Get started → View source

Go 1.26 · MIT · no writes, enforced by the build

The thesis

Correlation belongs in the server, not the context window

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.

1 ranked diagnosis with mandatory evidence

Real output

What a diagnosis actually looks like

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.

argus diagnose

The difference

Resource-shaped vs question-shaped

Typical k8s MCP serverargus
Tool surface40–100+, mirroring kubectl6, one per SRE question
Correlationthe model does it, over many callsserver-side, in one call
Outputraw objects to interpretranked findings, evidence mandatory
Context per pod2,000+ tokensunder 400, enforced by test
Repeated eventsone line eachdeduplicated, blast radius kept
Confidenceabsent, or implied certaintystated, and lowered on partial data
Writesusually available, sometimes gatednone — no mutating call site exists

Measured, not estimated

Context discipline

From one real production Deployment with 93 environment variables and 11 ReplicaSets.

0
tokens per pod
was 946 · budget 400
0
tokens per snapshot
was ~10,400
0
apiserver calls
hard cap 60
0
test suite, no cluster
89 tests, detectors are pure

The correlation engine

Eleven detectors, twenty-seven named causes

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.

crashloop.*

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 error

oomkill.limit-too-low

The kernel killed it for exceeding its own limit — with a suggested new limit when usage data supports one.

lastState.reason=OOMKilled + limit + peak usage

rollout.bad-template

The new revision is failing and the previous one was healthy. Reports the fields that changed between them.

RS health pair + semantic template diff

image.*

Distinguishes a typo'd tag from a missing pull secret, refused credentials, and a registry rate limit — four causes, one symptom. A fifth is not a pull at all: an unparseable reference is rejected before any registry is contacted, so it sends you to the manifest rather than the registry.

waiting.reason + the runtime's own error text

endpoints.*

Tells a selector typo (matches nothing) apart from a readiness failure (matches pods, none ready). Invisible to kubectl get.

selector match count vs endpoint readiness

probe.startup-too-short

The slow boot mistaken for a crash loop. Liveness and readiness probes do not run until a startup probe passes — so while started is false, nothing else can be doing the killing. Suppresses the crash-loop finding, whose advice to read the logs is a dead end when the logs show a healthy boot.

startup budget vs started=false + signal kill

probe.readiness-misconfigured

Running, alive, not crashing — and still never ready. Compares the probe's real deadline against observed uptime.

initialDelay + period×failureThreshold

config.missing-*

A ConfigMap or Secret the pod references does not exist — or exists without the key it asks for, which kubectl get shows as perfectly healthy. Carries no log hint, deliberately: the container was never created, so the stream is empty and always will be.

CreateContainerConfigError, or a FailedMount event

hpa.cannot-scale

The autoscaler that is not autoscaling. Every object reads healthy — the HPA exists, the Deployment is Ready, the replica count looks deliberate — but no replica count is being computed, so the next traffic spike is an outage with no cause in any log.

HPA ScalingActive=False + its own reason

pdb.blocks-disruption

The cause of the incident that does not look like one: a drain or upgrade that hangs for hours. Gated on the workload being healthy — every broken one also allows zero disruptions, and that is the budget working correctly.

disruptionsAllowed vs currentHealthy

node.unhealthy-host

Widens 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 set

Safety

Read-only, and not on the honour system

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.

No mutating call site

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.

Prompt injection can mislead, never act

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.

Secrets never leave

Secret objects are never read. Environment variable values are never projected — only key names, which is all a detector needs. Verified by test.

Self-update fails closed

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.

It cannot flood your control plane

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

Install, connect, run

$ go install github.com/backendArchitect/argus@latest
$ claude mcp add argus -- argus serve

Or drive it straight from a terminal, no MCP involved:

$ argus diagnose checkout-api -n prod

And to update it later — checksum-verified, atomic, and it will not clobber a build you made from a clone:

$ argus update # or: argus update -check

Roadmap

Honest about what exists