argus

One question in, one ranked diagnosis out.
argus is a Kubernetes incident-diagnosis MCP server. You ask "why is checkout-api broken?" and
it answers with a ranked list of causes, each one citing the evidence it used.
Named for Argus Panoptes, the hundred-eyed watchman who never slept because only some of his eyes
closed at a time β it watches twelve resource kinds at once so you don't have to correlate them
by hand.
π See it in action β backendarchitect.github.io/argus
Status: pre-release. diagnose_workload works end to end β six detectors, ranked findings,
mandatory evidence β and is verified against fixtures captured from real clusters.
cluster_triage and get_workload_logs are not built yet. See Roadmap.
Why this exists
Every Kubernetes MCP server available today is kubectl with a JSON schema stapled on:
get_pods, describe_pod, get_logs. That is worse than nothing, because it pushes the
correlation work onto the model across ten round-trips, each one dumping 8KB of managedFields
into the context window. By pod four the context is full of YAML noise and the model starts
guessing.
argus takes the opposite position: the unit of work is a question, not a resource.
|
Resource-shaped servers |
argus |
| Tool surface |
40β100+ tools mirroring kubectl |
3 tools, one per SRE question |
| Correlation |
the model does it, over many calls |
done server-side, in one call |
| Output |
raw objects |
ranked findings with mandatory evidence |
| Context per pod |
2,000+ tokens |
under 400, enforced by test |
| Repeated events |
one line each |
deduplicated, with the blast radius kept |
| Writes |
usually available, sometimes gated |
none β no mutating call site exists |
What it does today
# Diagnose a workload from the terminal β the same pipeline the MCP tool uses.
argus diagnose checkout-api -n prod
# Run as an MCP server over stdio.
argus serve
# Collect a raw diagnosis snapshot as YAML (this is also the fixture generator).
argus capture deploy/checkout-api -n prod
A real diagnosis, captured verbatim:
DIAGNOSIS Deployment argus-broken/oom-victim
replicas 0/1 ready, 1 updated, 0 available
findings 1 (1 critical)
1. [critical Β· confidence 71%] oomkill.limit-too-low
Container "app" is being OOM-killed by its own memory limit
The kernel killed container "app" for exceeding its memory limit of 32Mi. It is now in
CrashLoopBackOff, so it will keep restarting and dying until the limit is raised or its
memory use comes down. Current usage is unavailable (the metrics API did not answer), so
there is no measured basis for a new limit.
evidence:
Β· pod/oom-victim-7cb68bdb99-r8tsf (pod.lastState): reason=OOMKilled, exitCode=137, 127s ago
Β· pod/oom-victim-7cb68bdb99-r8tsf (pod.spec): container "app" memory limit is 32Mi
Β· pod/oom-victim-7cb68bdb99-r8tsf (pod.status): restarted 6 times; state CrashLoopBackOff
next: get_workload_logs(previous=true) β the current container is in backoff and has
produced nothing
incomplete β these lookups failed, and any finding relying on them has had its confidence
reduced:
Β· metrics: the server could not find the requested resource
(13 apiserver calls against kind-argus-test)
Note the confidence drop and the stated gap. That cluster had no metrics API, and saying so beats
implying memory looks fine.
Under the hood, one pass resolves a fuzzy name, fans out concurrently across the workload, its ReplicaSets, pods,
events, metrics, Services, endpoints, nodes, HPA and PDB, then projects all of it down to an
allowlisted, deduplicated snapshot. On a real production Deployment that is 13 apiserver calls
and ~3,300 tokens β the same data unprojected runs to roughly 10,400.
The snapshot is plain YAML by design. That single constraint is what makes the test suite
possible: capture writes fixtures, tests replay them with no cluster at all, and production runs
the identical code path.
argus capture β gather β Snapshot β yaml (writes testdata/snapshots/*.yaml)
go test β yaml β Snapshot β detect β assert (no cluster, sub-second)
argus serve β gather β Snapshot β detect β rank (production)
Read-only, and not on the honour system
argus never writes to your cluster. That is not a convention or a flag β there is no mutating call
site in the binary, and a test walks the entire source tree's AST and fails the build on any call
to Create, Update, Patch, Delete, Apply, Evict or friends.
This matters more than it sounds. The usual advice is "enforce read-only at the RBAC layer, not in
your code" β correct for an in-cluster deployment, but false for how you will actually run this,
which is from your own kubeconfig, where RBAC grants you everything. RBAC will not hold that line,
so the binary holds it instead.
Two more limits are built into the client rather than bolted on:
- A call budget. A hard cap on apiserver requests per invocation, enforced in an
http.RoundTripper so it counts every request and every retry. A diagnostic tool that DoSes the
control plane during an incident is a career-limiting artifact.
- A deadline with graceful degradation. A slow cluster produces a partial snapshot that records
what was missed, not a hung MCP session β and detectors reading partial data must lower their own
confidence rather than quietly reasoning from absence.
Full threat model, including prompt injection via log and event content: SECURITY.md.
Install
Requires Go 1.26+ and a working kubeconfig.
| Method |
Command |
| Go |
go install github.com/backendArchitect/argus@latest |
| Docker |
see below β the image runs as nonroot, so the kubeconfig needs mounting deliberately |
| Source |
git clone https://github.com/backendArchitect/argus && cd argus && go install . |
Prebuilt binaries (Linux/macOS/Windows Β· amd64 & arm64) are on the
Releases page.
The container image is distroless and runs as a nonroot uid, so mount the
kubeconfig file and name it explicitly β mounting ~/.kube as a directory
lands on a path the container user cannot read:
docker run --rm -u "$(id -u):$(id -g)" \
-v ~/.kube/config:/kube/config:ro \
ghcr.io/backendarchitect/argus \
diagnose checkout-api -n prod --kubeconfig /kube/config
Note the apiserver must be reachable from inside the container: this works for a
real cluster, but not for a local kind cluster listening on the host's
127.0.0.1 (add --network host for that).
Connect it to an AI editor:
claude mcp add argus -- argus serve
Roadmap
Working now
diagnose_workload β the flagship: one call in, a ranked diagnosis out
- Six detectors β OOM limit too low Β· bad rollout Β· image pull (four distinct causes) Β·
readiness misconfigured Β· endpoint gap (selector typo vs readiness failure) Β· node-caused,
which widens scope and suppresses the per-workload symptoms it explains
- The broken-fixture suite β each fixture asserts its detector fires and no others do,
with a healthy control that must produce nothing
- MCP server over stdio, with schemas derived from Go types
- Fuzzy workload resolution (Deployment / StatefulSet / DaemonSet / Argo Rollout), returning
candidates on ambiguity rather than guessing
- Concurrent gather with a call budget, deadline, and degradation tracking
- Projection layer: allowlisted fields, per-pod token budget, event deduplication
argus capture β snapshot to YAML, doubling as the fixture generator
- Read-only enforcement, verified by an AST test
v0.1 β next
cluster_triage β what is broken right now, grouped by owner rather than per pod
get_workload_logs β auto-selects the failing container, defaults to previous on crashloop,
groups stack traces, budgets output by tokens
v0.2 β explain_pending, trace_service_path, informer cache, kind-based CI.
v0.3 β GKE integrations (Cloud Logging fallback for dead pods, Autopilot, Managed Prometheus),
compare_environments, check_reachability, in-cluster deployment with Workload Identity.
Later, maybe never β mutations. That is where the liability is; read-only diagnosis is where
nearly all the value is.
Learn more
In one line: argus is a read-only Kubernetes diagnosis MCP server that answers SRE questions
with ranked, evidence-backed findings instead of handing a model a pile of YAML.
License
MIT