Internal Ops · 03 · Scheduled ops & reportingLesson 1 of 4
The KPI report that writes itself
- Assemble weekly numbers from Stripe, CRM & analytics
- Deliver the narrative, not just the data
Source systems to story
The Monday metrics report is the same ritual everywhere: someone pulls revenue from billing, pipeline from the CRM, volume from support, pastes it into a doc, and writes three sentences about what changed. An hour of assembly for three sentences of thinking. This lesson automates the assembly and keeps the thinking.
- Revenue: Stripe - MRR movement, new and churned subscriptions, failed payments.
- Pipeline: your CRM - stage movement and new deals since last week.
- Workload: support or ops volume, from the Slack digest or your ticketing tool.
- The baseline: last week's report in reports/ - which is why generated reports stay in the repo.
By the end of this lesson the weekly-report skill exists and runs by hand. Next lesson puts it on a schedule - skill first, Routine second, in that order on purpose, because you never schedule a job you have not watched succeed.
Stripe, read-only, the right way
Stripe's official MCP server is reporting-grade: retrieve_balance, list_customers, list_invoices, list_subscriptions, list_payment_intents, list_disputes, and search_stripe_resources. Everything a weekly snapshot needs is a read tool. The catch is how you connect - get it wrong and you hand the agent your whole Stripe account.
- In the Stripe dashboard, create a RESTRICTED API key (it starts with rk_): read permission on the resources the report needs (customers, invoices, subscriptions, payment intents), write on nothing. Put it in your gitignored .env, never in chat or a committed file.
- Connect through the LOCAL server, which is what actually uses the restricted key: claude mcp add stripe -- npx -y @stripe/mcp@latest --api-key=rk_... (paste the key from your .env).
- Do NOT run claude mcp add --transport http stripe mcp.stripe.com/ - the remote server authenticates with OAuth by default, which connects with your FULL account access and quietly ignores the restricted key you just made. You would be admin while believing you are read-only.
- Test the connection read-only: "list the last 5 invoices" should work.
- Test the boundary - inverted on purpose: ask Claude to create a test customer. If it FAILS, the restricted key is doing its job. If it SUCCEEDS, you are NOT on the restricted key - you are connected as admin. Stop, remove the server, and redo step 2.
- Add a belt to the suspenders in settings.json: deny mcp__stripe__create_* and any other write-shaped Stripe tool.
If you must use the remote server (some hosted setups require it), the safe variant is passing the restricted key as a Bearer token in the Authorization header instead of going through OAuth - but for a non-engineer the local server above is the simpler path that stays read-only by construction.
QuickBooks and Xero: the month-end pack
For real accounting data, Intuit ships a first-party QuickBooks MCP server (intuit/quickbooks-online-mcp-server) - roughly 145 tools (29 entity types plus 11 financial reports such as P&L, balance sheet, cash flow, and A/R and A/P aging). Xero's official org publishes XeroAPI/xero-mcp-server with the same posture. Both run locally with OAuth through the vendor's developer portal.
Fair warning: the Intuit developer-portal app setup is the steepest configuration in this whole track. Treat it as an advanced build, done with your accountant's blessing and read-only scopes. The payoff is the month-end pack: P&L versus budget (the budget lives as a CSV in the brain), plus A/R aging with chase-email drafts - drafts only, with the escalation ladder at 7, 30, and 60 days overdue.
One efficiency note: for a number you pull on every run from a spreadsheet - say a Sheets tab of targets - a small Python script in scripts/ hitting the Sheets API on a service account beats the MCP route for scheduled reads. Cheaper, deterministic, and Claude writes the script once. MCP for exploration; scripts for repetition.
The weekly-report skill: narrative, not dashboard
The skill pulls each source, compares against last week's report, and writes the story: what changed, why it likely changed, what deserves attention. Numbers in a table at the bottom; the narrative on top.
- Template in templates/ - sections, ordering, and tone, so every week reads the same.
- Anomaly thresholds written into the skill: "flag any metric that moved more than 15% week over week, and say why if the sources show it."
- Output to reports/<date>_weekly.md, then posted to Slack - the file is the history, the post is the delivery.
Deliver it as a live page: Artifacts
The Markdown file in reports/ is the history. The Slack post is the notification. Neither is what a founder opens on a phone on Monday. Since June 2026 Claude Code can publish an Artifact: an HTML or Markdown page pushed from the session to a private claude.ai URL that updates in place every time you republish. Make it the skill's last step: render the report as a page with the numbers on top and the narrative under them, publish it, and put the link in the Slack post.
/artifactslists, opens, and copies your pages; Ctrl+] reopens the latest. Available on Pro, Max, Team, and Enterprise. It needs a claude.ai login and the Anthropic API, so it is not available through Bedrock, Vertex, or Foundry, or on ZDR, HIPAA, or CMEK setups.- Sharing: Team and Enterprise share inside the org and can add editors. Pro and Max share by public link only, so a KPI page on a personal plan is a link anyone holding it can open. Decide that before you publish revenue.
- Live data: a page can call your MCP connectors when someone views it, running under the viewer's own account, so a dashboard can show current numbers rather than Monday's. Connector-backed pages cannot be shared publicly, which is the right default.
- Comments: on Team and Enterprise, viewers can comment on the page and Claude can read and reply, which turns a broadcast into a thread.
- The rule we hold for every client dashboard: a dashboard shows numbers, it does not explain itself. Strip every sentence about method, provenance, or why you built it that way. Those live in the repo file, not on the page.
Do this now
Sources and further reading
Want us to set it up with you, end to end?
Three one-on-one sessions. We train you on your real stack and build your first agents together, until you can run it yourself. You keep everything.