From 756b9995109a8c3e392edffd7180d1319ebd77fc Mon Sep 17 00:00:00 2001 From: Dmitry Alexeenko Date: Mon, 18 May 2026 12:10:18 +0100 Subject: [PATCH 1/2] Add billing skill (Cloudflare + Stripe via mcp-billing-server) Adds skills/billing/ with a structured investigation playbook for helping confused customers debug Cloudflare and Stripe billing. Backed by the open-source, read-only mcp-billing-server MCP (https://github.com/dalexeenko/mcp-billing-server). Includes tools.json with the full JSON Schema for every tool the MCP exposes, so code-mode hosts can generate TypeScript types without first connecting to the server. Co-Authored-By: Claude Opus 4.7 (1M context) --- skills/billing/SKILL.md | 172 ++++++++++++++++++++++++++++ skills/billing/tools.json | 233 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 405 insertions(+) create mode 100644 skills/billing/SKILL.md create mode 100644 skills/billing/tools.json diff --git a/skills/billing/SKILL.md b/skills/billing/SKILL.md new file mode 100644 index 0000000..955b32c --- /dev/null +++ b/skills/billing/SKILL.md @@ -0,0 +1,172 @@ +--- +name: billing +description: Debug Cloudflare (and optionally Stripe) billing for a confused customer — outstanding balances, why they're on FREE/PRO/BUSINESS, possible double charges, pending authorizations, which zones/products drive cost. Pulls subscriptions + billing history via the `mcp-billing-server` MCP and presents a friendly, plain-language summary the customer can act on. Load when a user asks why they were charged, what plan they're on, whether they have outstanding invoices, or asks for help reconciling a Cloudflare or Stripe bill. +--- + +# Billing (Cloudflare + Stripe) + +Backed by the open-source [`mcp-billing-server`](https://github.com/dalexeenko/mcp-billing-server) MCP server — read-only by construction. The server exposes Cloudflare subscription/billing-history tools and (optionally) Stripe read tools. This skill walks the agent through a structured investigation and produces a single, plain-language summary the customer can act on. + +Tool schemas for this MCP server are bundled in [`tools.json`](./tools.json) — code-mode hosts can consume them directly to generate TypeScript types without first connecting to the server. + +## Prerequisites + +The `mcp-billing-server` MCP must be connected. Two tokens, both read-only: + +- **Cloudflare API token** with `Account → Billing → Read` (required). `Zone → Zone → Read` is optional and only needed for `cloudflare_get_zone_subscription`. +- **Stripe restricted key** (`rk_...`) with read on Customers, Invoices, Subscriptions, Charges, Balance (optional — only if the customer also asks about Stripe). + +If the server isn't connected or a token is missing, tell the user how to set it up (see the [project README](https://github.com/dalexeenko/mcp-billing-server)) and stop — don't guess. + +## Tools you'll call + +**Cloudflare** + +| Tool | When | +| --- | --- | +| `cloudflare_list_account_subscriptions` | Primary — the full plan picture per account | +| `cloudflare_get_user_subscriptions` | Fallback when the user doesn't know their account ID | +| `cloudflare_list_billing_history` | Every charge, refund, proration with status | +| `cloudflare_get_zone_subscription` | Only if drilling into one specific domain | + +**Stripe** (only if asked) + +| Tool | When | +| --- | --- | +| `stripe_list_customers` / `stripe_get_customer` | Find / fetch a customer | +| `stripe_list_invoices` / `stripe_get_invoice` | Invoice-level questions | +| `stripe_list_subscriptions` | Recurring revenue / active subs | +| `stripe_list_charges` | Itemized payments | +| `stripe_get_balance` | Current available + pending balance | + +## What to ask before running anything + +At most **two** questions: + +1. **Account ID?** (the 32-char hex from the dash URL, e.g. `dash.cloudflare.com//...`). If they don't know it, fall back to `cloudflare_get_user_subscriptions`. +2. **Is there a specific charge or date that's confusing you?** Narrows the search if their history is long. + +Don't keep asking. Often the data answers the question on its own — run the tools and *then* clarify if needed. + +## Investigation order + +### Step 1 — Subscriptions + +Call `cloudflare_list_account_subscriptions` (or `cloudflare_get_user_subscriptions` as fallback). Group results into: + +- **Account-scoped plans** — `rate_plan.scope === "account"`. Pay-as-you-go consumption plans (Workers Paid, R2 Paid, Log Explorer, Teams). Usually `frequency: "monthly"` and a small or $0 base fee plus usage. +- **Zone-scoped plans** — `rate_plan.scope === "zone"`. One per domain, fixed monthly: FREE ($0), PRO ($25), BUSINESS ($250), ENTERPRISE (custom). Identifiable by `zone.name`. + +Interpret the `intent` field — it tells the customer *why* they're on a plan: + +| intent | Plain-language meaning | +| --- | --- | +| `PAYGO` | You opted into a usage-based product (typical for Workers/R2) | +| `BULK_ZONE` | You upgraded this domain from the dashboard | +| `EMPLOYEE` | Cloudflare employee benefit plan | +| `CONTRACT` | Enterprise contract | + +Note the `created_date` so you can say "you upgraded `example.com` to Business on 2024-08-07." + +### Step 2 — Billing history + +Call `cloudflare_list_billing_history` with `per_page: 50`. Walk each item: + +| Signal | Plain-language meaning | +| --- | --- | +| `status: CLOSED` AND `amount_to_pay: 0` | Settled, nothing owed | +| `status: OPEN` OR `amount_to_pay > 0` | **Outstanding balance — surface prominently** | +| `status: PENDING` | Bank authorization in flight, not a real charge yet | +| `type: refund` or negative `amount` | Money returned | +| Two items with same `amount` within ~5 min on same `source` | **Flag as possible double charge** — ask user to verify against their bank/card statement | +| `source: stripe` | Charged via Stripe (typical) | + +`receipt_id` (e.g. `IN-65007205`) is the user-visible invoice number — always quote it when referring to a specific item, never the opaque `id` UUID. + +### Step 3 — Reconcile + +- Sum monthly **base fees** from zone subs (e.g. PRO $25 + BUSINESS $250 = $275 base/month). +- Sum **recent monthly totals** from history (group `occurred_at` by month, sum `amount`). +- If history > base by > ~10%, the difference is **usage on PAYGO plans** (Workers, R2). Point at the relevant `rate_plan.public_name`. +- If history < base in a recent month, mention possible **proration, credit, or partial-period billing** (often happens after plan changes or refunds). +- If the most recent invoice is `amount_to_pay > 0`, that's almost certainly what they're asking about. Lead with it. + +## How to present the answer + +Use this structure — markdown is fine. Translate IDs to human names everywhere (zone IDs → domains, sub IDs → plan public names, item IDs → receipt numbers). + +``` +## What you're paying for + +**Zone plans** (per domain, fixed monthly) +- example.com — Pro Plan, $25/mo — upgraded 2024-08-07 +- other.com — Free Plan, $0/mo + +**Account plans** (pay-as-you-go consumption) +- Workers Paid — $5/mo base + usage +- R2 Paid — usage only +- Log Explorer — $0 base + usage + +**Total base:** $30/mo before usage. + +## Recent activity (last 3 months) + +| Date | Description | Amount | Status | Receipt | +| --- | --- | ---: | --- | --- | +| 2026-05-12 | Monthly invoice | $0.00 | CLOSED | IN-65007205 | +| 2026-04-12 | Monthly invoice | $0.00 | CLOSED | IN-62205358 | +| 2026-03-12 | Monthly invoice | $1.55 | CLOSED | IN-59612378 | +| 2026-02-28 | Mid-cycle invoice | **$11.56** | **OPEN** | IN-58696934 | + +## ⚠️ Things to check + +- **Outstanding $11.56** on receipt IN-58696934 from 2026-02-28 — pay at dash.cloudflare.com/billing. +- No pending authorizations. +- No duplicate charges detected. + +## What to do + +1. Pay the $11.56 outstanding invoice (IN-58696934) at dash.cloudflare.com/billing. +2. If you don't recognize the $1.55 from March, it's usage on Workers/R2 — check the dashboard's usage page for that month. +3. If anything still looks wrong, contact Cloudflare support with **receipt IN-58696934** and the specific amount. +``` + +**If the "Things to check" section is empty, say so explicitly** — "No outstanding balances, no pending authorizations, no duplicates detected" reassures more than silence. + +## Tone + +Friendly, plain, zero jargon. The reader is confused — meet them there. + +- Say "your `example.com` domain is on the $25/mo Pro plan" — **not** "zone `a506…494` has rate_plan `pro`". +- Say "you upgraded this on August 7, 2024" — **not** "created_date 2024-08-07T02:55:22Z". +- Say "this charge is still pending at your bank" — **not** "status: PENDING". +- Currency: always include the symbol ($) and 2 decimal places. + +## Common edge cases + +- **No charges at all** → likely a free-tier-only account. Say so plainly: "You're on free plans only and have no billing history to debug." +- **Tool returns auth error** → token missing or wrong scope. Tell the user to create a token with **Account → Billing → Read** at `dash.cloudflare.com/profile/api-tokens`. +- **Multiple accounts** → `cloudflare_get_user_subscriptions` shows subs across all accounts the token can see. If the user has multiple, ask which one they're asking about before pulling history (history is user-scoped, not account-scoped). +- **Deprecation notice** — the underlying billing history endpoint is marked deprecated by Cloudflare but remains live as of 2026. If the call starts failing, note it and recommend dash.cloudflare.com/billing. +- **Per-product usage breakdown** (Workers CPU ms, R2 storage, KV ops, etc.) is **not** exposed by this MCP server — the dashboard's "Billable usage" tab uses an internal Cloudflare endpoint that isn't in the public SDK. If the customer asks "what's driving my usage costs this month," direct them to `https://dash.cloudflare.com//billing/billable-usage` and offer to interpret the page if they paste the totals back. + +## Installing the MCP server + +Add to `claude_desktop_config.json` (or any MCP-aware client): + +```json +{ + "mcpServers": { + "billing": { + "command": "npx", + "args": ["-y", "mcp-billing-server"], + "env": { + "CLOUDFLARE_API_TOKEN": "...", + "STRIPE_API_KEY": "rk_live_..." + } + } + } +} +``` + +Source, setup walkthrough, safety model: . diff --git a/skills/billing/tools.json b/skills/billing/tools.json new file mode 100644 index 0000000..4db2fa1 --- /dev/null +++ b/skills/billing/tools.json @@ -0,0 +1,233 @@ +{ + "tools": [ + { + "name": "cloudflare_list_account_subscriptions", + "description": "List all subscriptions on a Cloudflare account.", + "inputSchema": { + "type": "object", + "properties": { + "account_id": { + "type": "string", + "minLength": 1, + "description": "Cloudflare account ID" + } + }, + "required": [ + "account_id" + ], + "additionalProperties": false + } + }, + { + "name": "cloudflare_get_zone_subscription", + "description": "Get the subscription details for a specific zone.", + "inputSchema": { + "type": "object", + "properties": { + "zone_id": { + "type": "string", + "minLength": 1, + "description": "Cloudflare zone ID" + } + }, + "required": [ + "zone_id" + ], + "additionalProperties": false + } + }, + { + "name": "cloudflare_get_user_subscriptions", + "description": "Get user-level (legacy) subscriptions for the API token's user.", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false + } + }, + { + "name": "cloudflare_list_billing_history", + "description": "List the user's billing history (charges, refunds, prorations, plan changes). Each item has amount, currency, description, type, action, occurred_at, and zone. Use this to answer 'why was I charged?', detect potential double charges, see outstanding balances over time, and reconcile against subscriptions. Note: backed by a deprecated Cloudflare API that remains live as of 2026; no replacement is currently exposed in the public SDK.", + "inputSchema": { + "type": "object", + "properties": { + "per_page": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50, + "description": "Items per page (max 100)." + }, + "page": { + "type": "integer", + "minimum": 1, + "default": 1 + }, + "type": { + "type": "string", + "description": "Filter by billing item type (e.g. 'charge', 'refund')." + }, + "action": { + "type": "string", + "description": "Filter by billing item action." + }, + "order": { + "type": "string", + "enum": [ + "type", + "occurred_at", + "action" + ] + } + }, + "additionalProperties": false + } + }, + { + "name": "stripe_list_customers", + "description": "List Stripe customers. Optionally filter by email.", + "inputSchema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + } + }, + "additionalProperties": false + } + }, + { + "name": "stripe_get_customer", + "description": "Get a single Stripe customer by ID.", + "inputSchema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^cus\\_" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + } + }, + { + "name": "stripe_list_invoices", + "description": "List Stripe invoices. Filter by customer and/or status.", + "inputSchema": { + "type": "object", + "properties": { + "customer": { + "type": "string", + "pattern": "^cus\\_" + }, + "status": { + "type": "string", + "enum": [ + "draft", + "open", + "paid", + "uncollectible", + "void" + ] + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + } + }, + "additionalProperties": false + } + }, + { + "name": "stripe_get_invoice", + "description": "Get a single Stripe invoice by ID.", + "inputSchema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^in\\_" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + } + }, + { + "name": "stripe_list_subscriptions", + "description": "List Stripe subscriptions. Filter by customer and/or status.", + "inputSchema": { + "type": "object", + "properties": { + "customer": { + "type": "string", + "pattern": "^cus\\_" + }, + "status": { + "type": "string", + "enum": [ + "active", + "past_due", + "unpaid", + "canceled", + "incomplete", + "incomplete_expired", + "trialing", + "all" + ] + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + } + }, + "additionalProperties": false + } + }, + { + "name": "stripe_list_charges", + "description": "List Stripe charges. Filter by customer.", + "inputSchema": { + "type": "object", + "properties": { + "customer": { + "type": "string", + "pattern": "^cus\\_" + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + } + }, + "additionalProperties": false + } + }, + { + "name": "stripe_get_balance", + "description": "Get the current Stripe account balance (available + pending per currency).", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false + } + } + ] +} \ No newline at end of file From 3989d032e5d1ba943cb036ef0ef4924616eb6452 Mon Sep 17 00:00:00 2001 From: Dmitry Alexeenko Date: Tue, 19 May 2026 14:45:53 +0100 Subject: [PATCH 2/2] Drop mcp-billing-server dependency; use Cloudflare REST API directly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review feedback that a Cloudflare-hosted skill shouldn't require a non-Cloudflare MCP server. The skill now calls the public Cloudflare billing endpoints (account/user subscriptions, billing history, zone subscription) directly via curl with a scoped `Account → Billing → Read` token — same pattern as cloudflare-email-service. - Rename `skills/billing` → `skills/cloudflare-billing` to match the `cloudflare-` convention used elsewhere in the repo. - Drop the Stripe portion entirely — it was also a non-Cloudflare dependency and the skill's purpose is Cloudflare-bill debugging. - Remove `tools.json` (was the MCP server's tool schema; no longer applicable). - Rewrite SKILL.md to document the four REST endpoints with example curl invocations and drop all references to mcp-billing-server. Investigation playbook (intake → subscriptions → history → reconcile → plain-language summary) is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) --- skills/billing/tools.json | 233 ------------------ .../{billing => cloudflare-billing}/SKILL.md | 114 ++++----- 2 files changed, 58 insertions(+), 289 deletions(-) delete mode 100644 skills/billing/tools.json rename skills/{billing => cloudflare-billing}/SKILL.md (51%) diff --git a/skills/billing/tools.json b/skills/billing/tools.json deleted file mode 100644 index 4db2fa1..0000000 --- a/skills/billing/tools.json +++ /dev/null @@ -1,233 +0,0 @@ -{ - "tools": [ - { - "name": "cloudflare_list_account_subscriptions", - "description": "List all subscriptions on a Cloudflare account.", - "inputSchema": { - "type": "object", - "properties": { - "account_id": { - "type": "string", - "minLength": 1, - "description": "Cloudflare account ID" - } - }, - "required": [ - "account_id" - ], - "additionalProperties": false - } - }, - { - "name": "cloudflare_get_zone_subscription", - "description": "Get the subscription details for a specific zone.", - "inputSchema": { - "type": "object", - "properties": { - "zone_id": { - "type": "string", - "minLength": 1, - "description": "Cloudflare zone ID" - } - }, - "required": [ - "zone_id" - ], - "additionalProperties": false - } - }, - { - "name": "cloudflare_get_user_subscriptions", - "description": "Get user-level (legacy) subscriptions for the API token's user.", - "inputSchema": { - "type": "object", - "properties": {}, - "additionalProperties": false - } - }, - { - "name": "cloudflare_list_billing_history", - "description": "List the user's billing history (charges, refunds, prorations, plan changes). Each item has amount, currency, description, type, action, occurred_at, and zone. Use this to answer 'why was I charged?', detect potential double charges, see outstanding balances over time, and reconcile against subscriptions. Note: backed by a deprecated Cloudflare API that remains live as of 2026; no replacement is currently exposed in the public SDK.", - "inputSchema": { - "type": "object", - "properties": { - "per_page": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 50, - "description": "Items per page (max 100)." - }, - "page": { - "type": "integer", - "minimum": 1, - "default": 1 - }, - "type": { - "type": "string", - "description": "Filter by billing item type (e.g. 'charge', 'refund')." - }, - "action": { - "type": "string", - "description": "Filter by billing item action." - }, - "order": { - "type": "string", - "enum": [ - "type", - "occurred_at", - "action" - ] - } - }, - "additionalProperties": false - } - }, - { - "name": "stripe_list_customers", - "description": "List Stripe customers. Optionally filter by email.", - "inputSchema": { - "type": "object", - "properties": { - "email": { - "type": "string", - "format": "email" - }, - "limit": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 20 - } - }, - "additionalProperties": false - } - }, - { - "name": "stripe_get_customer", - "description": "Get a single Stripe customer by ID.", - "inputSchema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^cus\\_" - } - }, - "required": [ - "id" - ], - "additionalProperties": false - } - }, - { - "name": "stripe_list_invoices", - "description": "List Stripe invoices. Filter by customer and/or status.", - "inputSchema": { - "type": "object", - "properties": { - "customer": { - "type": "string", - "pattern": "^cus\\_" - }, - "status": { - "type": "string", - "enum": [ - "draft", - "open", - "paid", - "uncollectible", - "void" - ] - }, - "limit": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 20 - } - }, - "additionalProperties": false - } - }, - { - "name": "stripe_get_invoice", - "description": "Get a single Stripe invoice by ID.", - "inputSchema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^in\\_" - } - }, - "required": [ - "id" - ], - "additionalProperties": false - } - }, - { - "name": "stripe_list_subscriptions", - "description": "List Stripe subscriptions. Filter by customer and/or status.", - "inputSchema": { - "type": "object", - "properties": { - "customer": { - "type": "string", - "pattern": "^cus\\_" - }, - "status": { - "type": "string", - "enum": [ - "active", - "past_due", - "unpaid", - "canceled", - "incomplete", - "incomplete_expired", - "trialing", - "all" - ] - }, - "limit": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 20 - } - }, - "additionalProperties": false - } - }, - { - "name": "stripe_list_charges", - "description": "List Stripe charges. Filter by customer.", - "inputSchema": { - "type": "object", - "properties": { - "customer": { - "type": "string", - "pattern": "^cus\\_" - }, - "limit": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 20 - } - }, - "additionalProperties": false - } - }, - { - "name": "stripe_get_balance", - "description": "Get the current Stripe account balance (available + pending per currency).", - "inputSchema": { - "type": "object", - "properties": {}, - "additionalProperties": false - } - } - ] -} \ No newline at end of file diff --git a/skills/billing/SKILL.md b/skills/cloudflare-billing/SKILL.md similarity index 51% rename from skills/billing/SKILL.md rename to skills/cloudflare-billing/SKILL.md index 955b32c..1f0dc5c 100644 --- a/skills/billing/SKILL.md +++ b/skills/cloudflare-billing/SKILL.md @@ -1,58 +1,81 @@ --- -name: billing -description: Debug Cloudflare (and optionally Stripe) billing for a confused customer — outstanding balances, why they're on FREE/PRO/BUSINESS, possible double charges, pending authorizations, which zones/products drive cost. Pulls subscriptions + billing history via the `mcp-billing-server` MCP and presents a friendly, plain-language summary the customer can act on. Load when a user asks why they were charged, what plan they're on, whether they have outstanding invoices, or asks for help reconciling a Cloudflare or Stripe bill. +name: cloudflare-billing +description: Debug Cloudflare billing for a confused customer — outstanding balances, why they're on FREE/PRO/BUSINESS, possible double charges, pending authorizations, which zones/products drive cost. Pulls subscriptions and billing history straight from the Cloudflare REST API and presents a friendly, plain-language summary the customer can act on. Load when a user asks why they were charged, what plan they're on, whether they have outstanding invoices, or asks for help reconciling a Cloudflare bill. --- -# Billing (Cloudflare + Stripe) +# Cloudflare Billing -Backed by the open-source [`mcp-billing-server`](https://github.com/dalexeenko/mcp-billing-server) MCP server — read-only by construction. The server exposes Cloudflare subscription/billing-history tools and (optionally) Stripe read tools. This skill walks the agent through a structured investigation and produces a single, plain-language summary the customer can act on. +A structured investigation playbook for making sense of a Cloudflare bill. Calls the Cloudflare REST API directly — no MCP server, no extra dependencies. The agent runs read-only `curl` requests with a scoped token and produces a single, plain-language summary the customer can act on. -Tool schemas for this MCP server are bundled in [`tools.json`](./tools.json) — code-mode hosts can consume them directly to generate TypeScript types without first connecting to the server. +Your knowledge of Cloudflare billing endpoints, plan names, and prices may be outdated. **Prefer retrieval over pre-training** — fetch the latest from the sources below before quoting specific numbers. + +## Retrieval Sources + +| Source | How to retrieve | Use for | +| --- | --- | --- | +| Cloudflare docs | `cloudflare-docs` search tool or `https://developers.cloudflare.com/billing/` | Plan names, pricing, billing FAQ | +| Cloudflare API reference | `https://developers.cloudflare.com/api/` | Subscription / billing-history endpoint contracts | +| Customer's dashboard | `https://dash.cloudflare.com//billing` | What the customer sees | ## Prerequisites -The `mcp-billing-server` MCP must be connected. Two tokens, both read-only: +A Cloudflare API token with **`Account → Billing → Read`** (required). `Zone → Zone → Read` is optional and only needed for the per-zone subscription drill-down. -- **Cloudflare API token** with `Account → Billing → Read` (required). `Zone → Zone → Read` is optional and only needed for `cloudflare_get_zone_subscription`. -- **Stripe restricted key** (`rk_...`) with read on Customers, Invoices, Subscriptions, Charges, Balance (optional — only if the customer also asks about Stripe). +Create one at `https://dash.cloudflare.com/profile/api-tokens`. Have the user export it before you run anything: -If the server isn't connected or a token is missing, tell the user how to set it up (see the [project README](https://github.com/dalexeenko/mcp-billing-server)) and stop — don't guess. +```sh +export CLOUDFLARE_API_TOKEN=... +``` -## Tools you'll call +If the variable isn't set, tell the user how to create the token and stop — don't guess. -**Cloudflare** +## API endpoints you'll call -| Tool | When | -| --- | --- | -| `cloudflare_list_account_subscriptions` | Primary — the full plan picture per account | -| `cloudflare_get_user_subscriptions` | Fallback when the user doesn't know their account ID | -| `cloudflare_list_billing_history` | Every charge, refund, proration with status | -| `cloudflare_get_zone_subscription` | Only if drilling into one specific domain | +All read-only `GET` requests against `https://api.cloudflare.com/client/v4`, with `Authorization: Bearer $CLOUDFLARE_API_TOKEN`. -**Stripe** (only if asked) +| Purpose | Endpoint | When | +| --- | --- | --- | +| Account subscriptions | `GET /accounts/{account_id}/subscriptions` | Primary — the full plan picture per account | +| User subscriptions (fallback) | `GET /user/subscriptions` | When the user doesn't know their account ID | +| Billing history | `GET /user/billing/history?per_page=50` | Every charge, refund, proration with status | +| Zone subscription | `GET /zones/{zone_id}/subscription` | Only if drilling into one specific domain | -| Tool | When | -| --- | --- | -| `stripe_list_customers` / `stripe_get_customer` | Find / fetch a customer | -| `stripe_list_invoices` / `stripe_get_invoice` | Invoice-level questions | -| `stripe_list_subscriptions` | Recurring revenue / active subs | -| `stripe_list_charges` | Itemized payments | -| `stripe_get_balance` | Current available + pending balance | +> **Note:** the billing-history endpoint is marked deprecated by Cloudflare but remains live as of 2026. No public replacement exposes per-product usage breakdowns (see edge cases below). + +### Example calls + +```sh +# Account subscriptions +curl -s "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/subscriptions" \ + -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq '.result' + +# User-level fallback (no account_id needed) +curl -s "https://api.cloudflare.com/client/v4/user/subscriptions" \ + -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq '.result' + +# Billing history +curl -s "https://api.cloudflare.com/client/v4/user/billing/history?per_page=50" \ + -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq '.result' + +# A specific zone's subscription +curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/subscription" \ + -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq '.result' +``` ## What to ask before running anything At most **two** questions: -1. **Account ID?** (the 32-char hex from the dash URL, e.g. `dash.cloudflare.com//...`). If they don't know it, fall back to `cloudflare_get_user_subscriptions`. +1. **Account ID?** (the 32-char hex from the dash URL, e.g. `dash.cloudflare.com//...`). If they don't know it, fall back to `GET /user/subscriptions`. 2. **Is there a specific charge or date that's confusing you?** Narrows the search if their history is long. -Don't keep asking. Often the data answers the question on its own — run the tools and *then* clarify if needed. +Don't keep asking. Often the data answers the question on its own — run the calls and *then* clarify if needed. ## Investigation order ### Step 1 — Subscriptions -Call `cloudflare_list_account_subscriptions` (or `cloudflare_get_user_subscriptions` as fallback). Group results into: +Call `GET /accounts/{account_id}/subscriptions` (or `GET /user/subscriptions` as fallback). Group results into: - **Account-scoped plans** — `rate_plan.scope === "account"`. Pay-as-you-go consumption plans (Workers Paid, R2 Paid, Log Explorer, Teams). Usually `frequency: "monthly"` and a small or $0 base fee plus usage. - **Zone-scoped plans** — `rate_plan.scope === "zone"`. One per domain, fixed monthly: FREE ($0), PRO ($25), BUSINESS ($250), ENTERPRISE (custom). Identifiable by `zone.name`. @@ -70,7 +93,7 @@ Note the `created_date` so you can say "you upgraded `example.com` to Business o ### Step 2 — Billing history -Call `cloudflare_list_billing_history` with `per_page: 50`. Walk each item: +Call `GET /user/billing/history?per_page=50`. Walk each item: | Signal | Plain-language meaning | | --- | --- | @@ -79,7 +102,7 @@ Call `cloudflare_list_billing_history` with `per_page: 50`. Walk each item: | `status: PENDING` | Bank authorization in flight, not a real charge yet | | `type: refund` or negative `amount` | Money returned | | Two items with same `amount` within ~5 min on same `source` | **Flag as possible double charge** — ask user to verify against their bank/card statement | -| `source: stripe` | Charged via Stripe (typical) | +| `source: stripe` | Charged via Stripe (Cloudflare's payment processor) | `receipt_id` (e.g. `IN-65007205`) is the user-visible invoice number — always quote it when referring to a specific item, never the opaque `id` UUID. @@ -118,7 +141,7 @@ Use this structure — markdown is fine. Translate IDs to human names everywhere | 2026-03-12 | Monthly invoice | $1.55 | CLOSED | IN-59612378 | | 2026-02-28 | Mid-cycle invoice | **$11.56** | **OPEN** | IN-58696934 | -## ⚠️ Things to check +## Things to check - **Outstanding $11.56** on receipt IN-58696934 from 2026-02-28 — pay at dash.cloudflare.com/billing. - No pending authorizations. @@ -145,28 +168,7 @@ Friendly, plain, zero jargon. The reader is confused — meet them there. ## Common edge cases - **No charges at all** → likely a free-tier-only account. Say so plainly: "You're on free plans only and have no billing history to debug." -- **Tool returns auth error** → token missing or wrong scope. Tell the user to create a token with **Account → Billing → Read** at `dash.cloudflare.com/profile/api-tokens`. -- **Multiple accounts** → `cloudflare_get_user_subscriptions` shows subs across all accounts the token can see. If the user has multiple, ask which one they're asking about before pulling history (history is user-scoped, not account-scoped). -- **Deprecation notice** — the underlying billing history endpoint is marked deprecated by Cloudflare but remains live as of 2026. If the call starts failing, note it and recommend dash.cloudflare.com/billing. -- **Per-product usage breakdown** (Workers CPU ms, R2 storage, KV ops, etc.) is **not** exposed by this MCP server — the dashboard's "Billable usage" tab uses an internal Cloudflare endpoint that isn't in the public SDK. If the customer asks "what's driving my usage costs this month," direct them to `https://dash.cloudflare.com//billing/billable-usage` and offer to interpret the page if they paste the totals back. - -## Installing the MCP server - -Add to `claude_desktop_config.json` (or any MCP-aware client): - -```json -{ - "mcpServers": { - "billing": { - "command": "npx", - "args": ["-y", "mcp-billing-server"], - "env": { - "CLOUDFLARE_API_TOKEN": "...", - "STRIPE_API_KEY": "rk_live_..." - } - } - } -} -``` - -Source, setup walkthrough, safety model: . +- **API returns an auth error** (HTTP 401/403, or `success: false` with code `10000`) → token missing or wrong scope. Tell the user to create a token with **Account → Billing → Read** at `dash.cloudflare.com/profile/api-tokens`. +- **Multiple accounts** → `GET /user/subscriptions` shows subs across all accounts the token can see. If the user has multiple, ask which one they're asking about before pulling history (history is user-scoped, not account-scoped). +- **Deprecation notice** — `GET /user/billing/history` is marked deprecated by Cloudflare but remains live as of 2026. If the call starts returning errors, note it and recommend `dash.cloudflare.com/billing`. +- **Per-product usage breakdown** (Workers CPU ms, R2 storage, KV ops, etc.) is **not** exposed by the public API — the dashboard's "Billable usage" tab uses an internal endpoint. If the customer asks "what's driving my usage costs this month," direct them to `https://dash.cloudflare.com//billing/billable-usage` and offer to interpret the page if they paste the totals back.