Metadata-Version: 2.4
Name: cloudlens-mcp
Version: 0.1.3
Summary: Find avoidable AWS spend and prove it ties to your invoice. Read-only MCP server for Claude and Cursor.
Project-URL: Homepage, https://github.com/Rishabhusesit/cloudlens
Project-URL: Source, https://github.com/Rishabhusesit/cloudlens
Project-URL: Issues, https://github.com/Rishabhusesit/cloudlens/issues
License: MIT
Keywords: aws,billing,cloud,cost,finops,mcp
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Requires-Dist: boto3>=1.35.0
Requires-Dist: duckdb>=1.1.0
Requires-Dist: mcp>=1.2.0
Requires-Dist: openpyxl>=3.1.0
Requires-Dist: rich>=13.7.0
Requires-Dist: typer>=0.12.0
Provides-Extra: dev
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Description-Content-Type: text/markdown

# cloudlens

Cloud cost findings priced at **your negotiated rates**, reconciled to **your
actual invoice**, and delivered as **changes you can merge**.

Runs on your machine. Reads your bill and your code. Sends nothing anywhere.

```bash
cloudlens check --profile your-aws-profile
```

```
About $210.44 a month.
$141.26 a month looks avoidable (67% of the bill).

$137.39/month — Client VPN endpoint billed continuously with nobody connecting
  Why: 4,656 endpoint-hours billed, with 0.0 connection-hours
  Do:  Delete the endpoint, or disassociate its subnets.
  Risk if wrong: A VPN kept for break-glass access is unused by design.

$3.23/month — Amazon DynamoDB is just over its free allowance
  Why: 24,056 unit-hours billed against a free allowance of 18,600
  Do:  Reduce to 18,600 or below and the charge disappears entirely.
```

That is the whole product. Everything below is optional depth.

Or ask an agent instead of running a command — see [Use it from Claude](#use-it-from-claude).

---

## Why another one

Three things this does that the others do not.

**It knows what you actually pay.** The AWS Price List API is documented as
*"for informational purposes only"*. Every tool that prices findings from it —
including the pre-deploy estimators — is wrong for any customer with an
enterprise discount, commonly by 20–40%, always in the direction of overstating.
cloudlens derives your real rate card from your own bill:

```
Rate card  4 rates derived from your own billing data (4 measured)
Observed discount off public pricing: 16.0% — pricing findings at list price
would overstate savings by roughly this much.
```

**It refuses to publish numbers that don't tie out.** Every run reconciles
computed spend against your real invoice. If it doesn't match, you get the
discrepancy instead of a savings figure.

```
┏━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━┓
┃ Period  ┃ Computed ┃ Invoice ┃  Delta ┃          ┃
┡━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━┩
│ 2026-07 │  $253.46 │ $253.46 │ +0.00% │ ties out │
└─────────┴──────────┴─────────┴────────┴──────────┘
```

**It proves savings against history rather than projecting them.**

> *"Had this been applied at the start of 2026-06, your actual invoices for
> 2026-06 and 2026-07 would have been $27.22 and $27.22 lower — computed
> against your own line items."*

---

## Install

Nothing to install:

```bash
uvx --from cloudlens-mcp cloudlens check --profile prod
```

(`--from` names the package, `cloudlens` names the command inside it. Bare
`uvx cloudlens-mcp` starts the MCP server instead, which is the other half.)

Or clone it, to run the tests or work on it:

```bash
git clone <repo> && cd cloudlens
uv sync --extra dev        # or: pip install -e ".[dev]"
uv run cloudlens demo
uv run pytest
```

Python 3.11+. Five dependencies: duckdb, boto3, rich, typer, openpyxl.
The lockfile is committed, so installs are reproducible.

---

## Use

### Without a repo — works on day one

Source access is a hard ask on a first engagement, so everything here comes from
the bill alone. No CloudWatch, no agent, no describe permission.

```bash
cloudlens scan     --cur s3://bucket/prefix/ --invoices invoices.json
cloudlens trends   --cur s3://bucket/prefix/
cloudlens profile  --cur s3://bucket/prefix/
```

An hourly CUR is a time series, not a ledger — so the weekly rhythm of every
resource is already in it:

```
i-0uatworker   billed 1,464h · active 405h · 72% idle · $55.34

        00              06              12              18
  Sun  ························
  Mon  ·········█████████······
  Fri  ·········█████████······
  Sat  ························
```

### With a repo — ownership, AI defects, mergeable fixes

```bash
cloudlens findings --cur s3://... --repo . --invoices invoices.json --detail
cloudlens report   --cur s3://... --repo . --out report.xlsx
cloudlens fix      --cur s3://... --repo . --id gp2-to-gp3-001 --write
```

Attribution resolves ownership through a ten-rung ladder — code first, because
git history outlives CloudTrail's 90-day window:

```
Attribution  92.4% of $1,091.56 traced to an owner
│    1 │ infrastructure as code          │ $433.84 │ 53.2% │
│    3 │ AWS-generated tag               │ $302.40 │ 37.1% │
│    — │ no owner found                  │  $38.64 │  4.7% │
```

### In CI

```bash
cloudlens ci --cur s3://... --plan tfplan.json --repo . \
             --max-monthly-increase 500
```

Posts a PR comment nothing else can produce — the cost of the change **at your
rates**, plus existing waste attached to the code being touched.

---

## Commands

| | |
|---|---|
| `check` | **Start here.** What is wasting money, in plain English |
| `scan` | Rate card and reconciliation |
| `trends` · `profile` | What changed, what spiked, weekly usage rhythm |
| `attribute` · `ask` · `answer` | Who owns what; ask a human; record the answer |
| `findings` | Waste, priced and citing AWS best practices |
| `aiscan` | LLM cost defects in source — unbounded loops, uncapped prompts |
| `fix` · `tagplan` | Findings as mergeable diffs |
| `ci` | Price a Terraform plan, comment on the PR |
| `report` | Six-sheet spreadsheet for finance |

---

## Use it from Claude

Two ways, both read-only.

**MCP server.** Copy `.mcp.json.example` to `.mcp.json`:

```json
{
  "mcpServers": {
    "cloudlens": {
      "command": "uvx",
      "args": ["cloudlens-mcp@latest"],
      "env": { "AWS_PROFILE": "your-readonly-profile" }
    }
  }
}
```

**Nothing to clone and nothing to install.** `uvx` fetches the package, builds
a throwaway environment and runs it. The CLI is the same package, asked for by
command name:

```bash
uvx --from cloudlens-mcp cloudlens check --profile prod
```

Replace `@latest` with `==0.1.0` when you want a pinned version.

Six tools: `what_is_wasting_money`, `what_was_not_examined`, `what_changed`,
`free_tier_status`, `show_fix`, `capabilities`.

**Skill.** One command, which also has nothing to clone:

```bash
uvx --from cloudlens-mcp cloudlens install
```

It writes the skill to `~/.claude/skills/cloudlens/` and prints the MCP entry
to paste. `--skills-dir` puts it elsewhere, `--mcp-config .mcp.json` writes the
entry rather than printing it.

The skill teaches an agent how to read the output — which confidence tiers can
be added together, why a short findings list needs the coverage number beside
it, and always to relay the failure case before recommending a deletion. The
MCP server carries the essentials in its own `instructions`, which arrive over
the wire, so the two agree by construction: the skill ships inside the package
next to the server that describes it.

### On guardrails

AWS's Agent Toolkit needs `aws:CalledViaAWSMCP` deny policies so an agent
cannot do more through MCP than the human should, because that toolkit can call
anything.

This needs none. There is no mutating AWS call anywhere in the codebase — the
tool names contain no verbs, `show_fix` returns a diff for you to review rather
than applying it, and the only file ever written goes into your own working
copy. There is no privilege to escalate, so there is nothing to fence off. Ask
the `capabilities` tool and it will say so itself.

The credential is the AWS profile already on the machine. Nothing new is
issued, nothing is stored, and nothing leaves the host.

---

## Access

Minimum is read on one S3 prefix. See [docs/SETUP.md](docs/SETUP.md); for client
engagements see [docs/MULTI_CLIENT.md](docs/MULTI_CLIENT.md) and the
self-expiring [onboarding stack](docs/onboarding-stack.yaml).

No write action, anywhere. `fix --write` edits your local working copy so you
can review it with `git diff`; it never calls a mutating AWS API.

---

## What it will not do

Deliberate, and the reason to trust the numbers it does give:

- **Won't price from a public price list** when it has never seen that usage
  type in your account. It says `unpriced` and explains why.
- **Won't claim a saving it can't replay** against your real line items. Those
  say "estimate rather than a replay of history."
- **Won't report a resource as idle** because it emits no activity data. Absence
  of evidence is not evidence of idleness.
- **Won't tell you to schedule** something with no stopped state.
- **Won't reformat your Terraform** to change one attribute.
- **Won't apply anything.** You merge.

Confidence is stated on every figure — `measured`, `derived` or `bounded` — and
never summed into one optimistic headline.

---

## Status

253 tests. Full pipeline works end to end.

**Validated against synthetic data only.** The fixture plants findings with
known values and computes ground truth independently of the code under test, so
the logic and arithmetic are sound. What that cannot prove is that a real AWS
bill matches the assumptions — the reconciliation tolerance (0.5%), the
Terraform-type-to-usage-type mapping, and the flow-log field casing are all
calibrated against data I wrote.

Running it against one real CUR is the highest-value next step, and the fastest
way to find out which assumptions are wrong.
