paperops
by rbadillap
README.md
# paperops
<p align="center">
<img src="assets/og.png" alt="paperops — see everything your agent does in Paper" />
</p>
<p align="center"><code>bunx paperops</code></p>
Observability control plane for [Paper](https://paper.design)'s MCP. A pass-through proxy that sits between your MCP clients and Paper's local server, metering, attributing, and live-streaming every tool call. Paper bills per MCP call — and the calls are made by an agent, at a speed you were never meant to watch. paperops is where you watch them.
## Features
- **Live tape** — every MCP call as it happens: client, tool, the artboard or file it acted on, status, latency, size. Sort it, filter it, slice it by file tabs.
- **Call inspector** — click any row: plain-language facts, a latency sparkline against recent calls of the same tool, and the raw request/response one click (and one copy button) away.
- **Budgets** — weekly quotas from Paper's real plans (`free`/`pro`), per-file and per-client limits, runaway-session alerts. Declarative JSON, hot-reloaded, warn-only.
- **Timelapse** — every screenshot your agent takes, as a gallery. Each one names the artboard it is of and the client that took it, in the order they happened.
- **Light and dark** — follows your system until you choose; `D` toggles.
- **Passive by design** — zero MCP calls of its own. The meter never spends what it measures.
- **Private by design** — payloads and snapshots live in memory only, the on-disk ledger is metadata, the dashboard binds to localhost and makes zero external requests — fonts included.
- **Zero dependencies** — one Bun process, one HTML file. `bunx paperops` and you're watching.
## Run
```sh
bunx paperops
# paperops · proxy :29980/mcp → http://127.0.0.1:29979/mcp · dashboard http://127.0.0.1:29980/
```
Requires the Paper desktop app running (it serves the real MCP on `127.0.0.1:29979`).
`paperops --help` lists the flags — `--port`, `--target`, `--version`. From a clone, `bun dev` starts the same server without the CLI wrapper.
## Wire your clients through it
This repo ships a project-scoped [`.mcp.json`](.mcp.json): open it with Claude Code and the `paperops` server is already wired through the proxy — approve it once and every call appears on the tape at `http://127.0.0.1:29980/` in real time.
To observe sessions in **other** projects, point them at the proxy too:
```sh
claude mcp add --transport http paperops http://127.0.0.1:29980/mcp
```
Or copy the `.mcp.json` entry into that project.
## What gets recorded
One JSONL line per call in `~/.paperops/ledger.jsonl`:
```json
{"id":267,"ts":"2026-07-28T04:47:14.159Z","session":"8b252f7c-…","client":"claude-code","rpc":"tools/call","tool":"write_html","fileId":"01KYJQ…","fileName":"paperops","nodeId":"T5-0","nodeName":"paperops — calls (light)","status":200,"ms":40.6,"argsBytes":2333,"resultBytes":3563,"error":null}
```
- **Attribution** comes free: the proxy reads `clientInfo.name` from `initialize` payloads and tags every subsequent call in that session, and learns artboard names from `get_basic_info` responses in transit — so a line says who made the call and what it acted on, without either being asked for. Clients already connected when paperops starts show as `unknown`, because the name only ever travels in `initialize` — reconnect them and it clears. Restarting paperops itself is safe: it replays the map from the ledger.
- **The 7-day meter** counts `tools/call` requests against your weekly quota — which comes from `budgets.json` (`week`, or the `plan` preset). It survives restarts by replaying the ledger.
- The ledger is an **audit trail**: the exact sequence of every call — names, timing, sizes, attribution. Call payloads live in memory only (privacy by design), so sessions can be audited from disk but not re-executed.
## Budgets
`~/.paperops/budgets.json` — declarative limits, hot-reloaded on save:
```json
{
"plan": "pro",
"budgets": [
{ "scope": "file", "match": "paperops", "limit": 2000, "note": "design project" },
{ "scope": "client", "match": "claude-code", "limit": 50000 }
]
}
```
- **`plan`** — `"free"` or `"pro"`; sets the weekly quota to Paper's official number (100 or 1M calls/week, per [paper.design/pricing](https://paper.design/pricing)) and a sensible session alert (50 / 200 — a heavy design session measures ≈100 calls). Defaults to `free`: the Free user is the one a silent meter hurts most.
- **`week`** / **`sessionAlert`** — explicit overrides for the plan-derived values.
- **`budgets`** — one rule per line: `scope` (`file` or `client`), `match` (exact string — a file **name** or id, or a client name; no regex/glob), `limit` (weekly calls), optional `note` for humans. Matching by file name reads better but detaches if the file is renamed in Paper; ids survive renames.
States: ok → **warning** at 80% (chrome yellow) → **exceeded** (red), shown on the header meter, tab counts, and the budgets rail section. v1 warns, never blocks.
## Config
| Env var | Default | Meaning |
|---|---|---|
| `PAPEROPS_PORT` | `29980` | Proxy + dashboard port |
| `PAPEROPS_HOST` | `127.0.0.1` | Listen address. Localhost-only by default — your tape, payloads and frames are your designs. Set explicitly (e.g. `0.0.0.0`) to opt into network exposure. |
| `PAPEROPS_TARGET` | `http://127.0.0.1:29979/mcp` | The real Paper MCP endpoint |
| `PAPEROPS_QUOTA` | `1000000` | Fallback weekly quota, used **only** when `budgets.json` sets neither `plan` nor `week`. Normally the plan preset governs. |
Nothing Paper-specific lives in the proxy path — point `PAPEROPS_TARGET` at any streamable-HTTP MCP server and it meters that instead.
**Known limitation**: JSON-RPC batch requests pass through untouched but meter as a single request (no per-call accounting). No supported client sends batches today, and recent MCP revisions removed batching from the protocol.
---
<img src="assets/dashboard.png" alt="The paperops tape: every MCP call with its client, tool, the artboard it acted on, status, latency and size — with the inspector open on one call, showing its latency against recent calls of the same tool and the raw response" />
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues