Skip to main content
Glama
avantiketkar-lab

Billing Explainer

Billing Explainer

Ask "why did this account's bill go up?" and get a ranked, evidence-linked answer โ€” from an MCP server with a real authorization model, not a chat wrapper.

Built on Cloudflare Workers, the Agents SDK, D1, Durable Objects and Workers AI.

โ–ถ Try it: billing-explainer.avanti-b4d.workers.dev

๐Ÿ“„ Prompt history (the assignment's required artifact)

Demo build. All data is synthetic โ€” two fictional accounts, three months. There is no login: the role selector simulates an identity rather than authenticating one, which is the point of ยง "no login" below. Rate-limited to 40 model-backed investigations per hour.

The support-engineer view: a ranked explanation beside the tool-call trace


The 30-second version

A billing question โ€” why did this invoice change, what is this customer entitled to โ€” is answered today by a human clicking through a dashboard, because the answer lives across four systems: subscriptions, usage metering, invoicing and the ledger.

This exposes those systems as typed, permissioned tools an agent can call, so the question resolves in one step instead of becoming a support ticket.

The interesting problem isn't calling an LLM. It's the one a chat UI lets you skip:

What is an agent allowed to read versus change, and how is that audited?

Three answers this repo commits to:

  • The agent's write surface is its own workspace, never the ledger. Twelve read tools, four writes, and no agent-callable path that moves money. apply_credit does not exist โ€” the agent proposes with evidence, a human approves in a separately authenticated surface, the server applies.

  • account_id in a tool argument is a selector, not an authorization claim. Scope rides in a signed token bound to the session; model output can never widen it. A refusal is recorded with the account that was asked for.

  • Compute in code, narrate in the model. Invoice diffing is a full outer join, not a model task โ€” arithmetic over a revenue document is where a fluent wrong answer is most expensive and least detectable.

โ†’ Walkthrough with screenshots ยท Why it's built this way ยท Prompt history


Related MCP server: Claims Data MCP Server

Five things to try on the demo

Each one takes about fifteen seconds and shows a different property. The right-hand panel is the tool-call trace โ€” read it, it is the evidence that the answer came from the billing systems rather than from the model.

1. The question the whole thing exists for. Role Support engineer ยท account Northwind Analytics ยท July 2026 vs June 2026

Why did Northwind Analytics' invoice go up in July?

A 40% rise decomposing into three causes of different sizes โ€” a usage step change, a mid-period add-on prorated 16/31 days, and an expired promotional discount. The smallest is the one a human scanning a dashboard reliably misses, and it is found as a NULL on one side of a join rather than inferred from a total that moved. Every figure is computed in SQL; the model is forbidden to do arithmetic and its output is checked before you see it.

2. The control โ€” a boring account. Role Support engineer ยท account Harborlight Media ยท July 2026 vs June 2026

A change of about โˆ’1%. Nothing is dramatised. This is here so that answer 1 reads as a finding rather than as how the system always talks.

3. Scope, enforced. Role Customer โ€” Northwind Analytics only ยท account Harborlight Media

Refused, with an empty tool-call trace: the refusal happens before any billing data is read, checked against the signed session token rather than against the account id in the argument. Watch the tools panel as you switch roles โ€” it drops from eight entries to seven, and billing_propose_credit disappears. That panel renders what the server returned to tools/list.

4. Enumeration, prevented. Any role ยท account Acme Corp (or any name that does not exist)

The identical refusal. "Not found" and "not permitted" are indistinguishable to the caller, so the error cannot be used to discover which accounts exist. The audit log records which it actually was.

5. The periods are real selectors. Role Support engineer ยท account Northwind Analytics ยท July 2026 vs May 2026

Different pair, different arithmetic, same shape. The demo is not a canned answer โ€” and if you word the question differently, note that the wording changes and the numbers do not.

Account names must be typed exactly. billing_resolve_account matches an id or a full case-insensitive name, with no partial matching โ€” a deliberate choice, since fuzzy resolution on a revenue document is a way to answer confidently about the wrong customer.

If a narration is ever withheld, that is the output validator working, not a crash โ€” you get the computed ranking plus the specific reason the prose failed its check. See what that looks like.


Run it yourself

npm install
npm run db:init          # schema -> local D1
npm run db:seed          # 2 accounts, 3 months
npm run verify:fixture   # 24 assertions on the demo arithmetic
npm test                 # 24 tests
npx wrangler login       # Workers AI is remote-only
npm run dev

Full setup, including deploying your own copy: docs/DEPLOY.md.

npm run verify:fixture proves the demo before any agent runs: June $678.00 โ†’ July $949.16, +$271.16 = 39.994%, decomposing exactly into a usage step change ($136.00), a prorated mid-period add-on ($85.16), and an expired discount ($50.00). The fixture is engineered, not plausible โ€” a single-cause answer would prove nothing a dashboard filter couldn't do.

Without a model, the system still answers. Narration degrades to the computed ranking and says why. The arithmetic is the product; the prose is a layer over it.


Where things are

Path

src/db/scoped-db.ts

The only route to the database. Scope cannot be forgotten โ€” see DESIGN.

src/mcp/guard.ts

The enforcement path: scope check, audit write, fail-closed.

src/audit/audit-do.ts

Append-only, hash-chained audit log โ€” and its honest limit.

src/mcp/tools/compare-invoices.ts

The deterministic diff the model is not allowed to do.

src/agent/compose.ts

The composition prompt and the validator that checks the model obeyed it.

docs/tool-surface.md

Full tool list, authorization boundary, write gating, negative space.

docs/data-model.md

Schema, the fixture's arithmetic, six ranked tenancy-leak vectors.

docs/DEPLOY.md

Step-by-step deploy, and the three things that only break once it leaves the laptop.

docs/PROMPT-HISTORY.pdf

Every prompt used to build this, with what it produced.

About the demo

It is a demo, and two things about it are deliberately not production.

The data is synthetic โ€” two fictional accounts, three months, engineered so the demo question has a layered answer. No real billing data exists anywhere in this project.

Identity is simulated, not authenticated. There is no login. The role selector mints a signed token with the requested scope, so anyone can choose to be the support engineer. That is the honest boundary of what this demonstrates: how scope is enforced, not how it is established. The enforcement is real โ€” scope rides in a signed token, is checked server-side on every call, never comes from a tool argument, and every refusal is written to the audit log. The issuing of that token is a stub standing in for Cloudflare Access and the customer IdP; the claim shape is identical, so the swap is configuration.

Model-backed requests are capped at 40 investigations per hour, because the demo runs on a personal Cloudflare account and each one spends real inference quota. The counter lives in a Durable Object rather than in memory โ€” Workers are not one process, so an isolate-local counter is a rate limit in appearance only.

Scope, stated rather than hidden

Two days. Five specified read tools are designed in docs/tool-surface.md and not implemented. The approval surface is a working stub โ€” real signed URL, real state transition, no styling. R2 audit mirroring and external chain anchoring are specified, not built. Dev tokens stand in for Cloudflare Access; the claim shape is identical, so the swap is configuration rather than a rewrite.

Full list, including what wouldn't survive production: DESIGN โ†’ What I'd do next.

F
license - not found
Not graded
quality - not tested
C
maintenance

Maintenance

โ€“Maintainers
โ€“Response time
โ€“Release cycle
โ€“Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables secure interaction with billing systems through comprehensive PII/PCI redaction, field allowlisting, and multi-layer security controls. Protects sensitive financial data while providing LLMs safe access to account, subscription, and invoice information.
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables natural language querying of healthcare claims data by exposing a SQLite database with read-only SQL tools, allowing users to ask questions in plain English and get answers backed by real database queries.
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables natural-language queries over data warehouses with catalog-grounded semantics and per-query authorization, returning answers with attached reasoning.
    Apache 2.0

View all related MCP servers

Related MCP Connectors

  • A paid remote MCP for AI SDK data query MCP, built to return verdicts, receipts, usage logs, and aud

  • The grounded data layer for any LLM: governed SQL, metrics, lineage and catalog over your data.

  • Paid remote MCP for agent code search routing MCP, structured receipts, audit logs, and reviewer-rea

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/avantiketkar-lab/billing-explainer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server