finops-copilot
README.md
# An AI that right-sizes your cluster — and a platform that won't let it break prod
> **The problem:** "let an AI touch our infrastructure" is a great way to get a
> confident model to scale prod to one replica at 3am. The interesting
> engineering in agentic ops isn't the model — it's the **guardrails**. **The
> fix:** give the AI a *typed, bounded* interface to your cluster over **MCP**,
> and put the safety logic **server-side**, where the model can't route around
> it. The AI decides *what* to change; the platform decides what it's *allowed*
> to do — and refuses the rest, even when asked directly.
A **Model Context Protocol (MCP) server** exposes five FinOps tools over a live
Kubernetes cluster. **Claude** (or any MCP client — Claude Desktop, etc.) uses
them to find over-provisioned workloads and propose right-sizing. Every
*mutating* call is checked against a policy **before** anything happens and
defaults to a **dry-run**; unsafe changes — a protected `prod` namespace, a
resource floor, an over-aggressive cut — are **refused by the server** and
written to an **audit log**.
Runs entirely on a laptop: a **kind** cluster seeded with deliberately wasteful
workloads, a ~120-line MCP server, and a copilot driver that runs **offline by
default** (a deterministic mock, so it works with no API key and in CI) or with
**real Claude** when `ANTHROPIC_API_KEY` is set.
---
## What it does
```
Claude / MCP client
│ calls typed tools
▼
┌─────────────── MCP server (finops-copilot) ───────────────┐
│ list_namespaces list_workloads estimate_cost │
│ recommend_rightsizing get_audit_log │
│ │
│ apply_rightsizing(…, dry_run=True) │
│ │ │
│ ▼ ┌─────────── policy.py (guardrails) ────────┐ │
│ every │ protected namespaces? resource floors? │ │
│ apply ─┤ max single-step cut? → REFUSE + audit │ │
│ └───────────────────────────────────────────┘ │
└────────────────────────┬──────────────────────────────────┘
│ kubectl (read specs / patch)
▼
kind cluster: staging (waste) · prod (protected)
```
| Piece | Why it matters | How it works here |
|---|---|---|
| **MCP, not raw kubectl** | The AI gets *typed, auditable* actions, not an open shell | Five tools with schemas; the server is the only thing that touches the cluster |
| **Guardrails server-side** | Safety the model can't prompt its way around | `apply_rightsizing` runs every request through `policy.py` first; a refusal is a refusal |
| **Dry-run by default** | The safe thing is the default thing | `apply_rightsizing(dry_run=True)` shows the diff + saving; real applies are opt-in |
| **Everything audited** | "What did the AI try to do?" has an answer | Every apply — allowed, refused, or dry-run — is appended to an audit log the client can read |
---
## Run it
```bash
make up # kind cluster seeded with waste + Python env (MCP deps)
make demo # the copilot finds & cuts waste — OFFLINE, no API key needed
make demo-claude # real Claude drives the same MCP tools (needs ANTHROPIC_API_KEY)
make test # unit-test the guardrail policy (no cluster needed)
make down # delete the cluster
```
`make demo` runs a **deterministic mock** copilot over the **real MCP server** —
same tools, same guardrails as the Claude path — so it works with no key and in
CI. `make demo-claude` swaps the mock for Claude driving the tools agentically
(official Anthropic SDK + its MCP tool-runner).
### What the demo proves (real output)
Against a `staging` namespace seeded with four workloads (three grossly
over-provisioned, one healthy) and a protected `prod`:
1. **Finds the waste.** `staging` costs **~$138/mo** at current requests; the
tool flags the three workloads sitting at 1–4% CPU utilisation and proposes
CPU + replica cuts — **~$102/mo (73%)** in projected savings.
2. **Dry-runs first.** Every change is a dry-run showing the exact diff and its
monthly saving. Nothing is applied without an explicit non-dry-run call.
3. **The platform refuses prod.** Asked to trim `prod` too, the server
**refuses** — `namespace 'prod' is protected`. The AI asked; the guardrail
said no. No change, not even a dry-run.
4. **The platform refuses an over-aggressive cut.** Asked to slash a CPU request
to `10m`, the server refuses — below the floor, and a >90% single-step cut.
5. **Every action is logged.** The audit trail shows all five attempts —
three dry-runs, two refusals — with reasons.
> The AI found the waste and proposed the cuts. The platform decided what it was
> allowed to do. **That division of labour is the whole point.**
---
## Use it from Claude Desktop (real MCP)
This is a real MCP server — point any MCP client at it. For Claude Desktop, add
to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"finops": {
"command": "/path/to/mcp-finops-copilot/.venv/bin/python",
"args": ["-m", "finops.server"],
"env": { "FINOPS_CTX": "kind-mcp-finops" }
}
}
}
```
Then ask Claude: *"What's my staging namespace costing, and what can I safely
right-size?"*
---
## How it maps to production
| Demo | Production |
|---|---|
| kind, `pause` workloads | EKS / AKS / GKE with your real Deployments |
| utilisation from a seeded annotation | Prometheus / metrics-server (the `kube.py` read is the only thing that changes) |
| `policy.py` floors + protected namespaces | your org's guardrails — RBAC scope, PodDisruptionBudgets, change windows |
| in-memory audit log | your audit sink (stdout → Loki, an events table, etc.) |
| illustrative cost rates | your provider's real per-vCPU / per-GB pricing |
## Layout
```
finops/policy.py the guardrail layer — pure, unit-tested (make test)
finops/cost.py cost model + right-sizing heuristic
finops/kube.py kubectl read/patch helpers
finops/server.py the MCP server: 5 tools, dry-run-by-default, audited
copilot/mock_agent.py deterministic offline copilot (CI-safe)
copilot/llm_agent.py real Claude via the Anthropic SDK's MCP tool-runner
copilot/driver.py picks mock vs Claude by ANTHROPIC_API_KEY
k8s/ staging (seeded waste) + prod (protected)
```
---
MIT-licensed. A small, laptop-runnable, NDA-safe demo that proves one sharp
idea: **agentic ops is a guardrails problem. Give the AI real actions, but make
the platform the thing that says no.**
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues