MCP Usage Metering: Track Agent Tool Calls Without Billing Surprises A developer outlines a practical architecture for metering MCP (Model Context Protocol) tool usage in AI agents, addressing the challenge of fair billing when a single user request can trigger dozens of tool calls, retries, and failures. The proposed system uses a usage ledger with clear event types, idempotency, quotas, and pricing rules to avoid surprising users with opaque invoices. An AI agent can turn one user request into a small storm of model calls, MCP tool calls, retries, partial failures, and background work. If you only meter the final response, you are guessing. If you meter every low-level event without context, you create noise customers will not trust. That is the billing trap many AI product builders are walking into: the product feels simple, but the usage behind it is multi-step, non-deterministic, and easy to dispute. MCP makes this more urgent. The Model Context Protocol gives agents a standard way to call tools, but a standard tool call is not the same thing as a fair usage meter. A production meter needs to answer harder questions: This guide shows a practical MCP usage metering architecture for solo developers, micro product teams, and AI platform builders who need cost control without surprising users. Token tracking is mostly linear. You send a prompt, receive a response, and record input tokens, output tokens, model, latency, and cost. Agent tool usage is messier. A single request like "research these accounts and update the CRM" might trigger: Some calls are internal. Some are customer-visible. Some are expensive. Some are dangerous. Some are free but should be rate limited. Some fail after doing real work. Some are retried by the agent, the SDK, the queue, or the network layer. If you charge blindly per MCP call, users will feel punished for model behavior they did not control. If you absorb everything, your margins disappear. The middle path is a usage ledger with clear event types, idempotency, quotas, pricing rules, and receipts. Usage-based pricing can be fair when it maps to value. Developers already understand API calls, compute minutes, storage, seats, and messages. The problem is surprise. A user asks for one task. The agent makes 47 calls. The invoice says "47 tool invocations." The user asks, reasonably, "Why?" Your meter should make the answer easy: "This workflow used 12 billable enrichment calls, 3 CRM write attempts, and 1 approved export. Internal planning, cached reads, failed validation calls, and safety checks were not billed. Here is the run receipt." That sentence builds more trust than a low price alone. Think of MCP usage metering as five layers: Here is the basic flow: php Agent run - MCP gateway or wrapper - tool invocation event - idempotency check - policy and pricing classification - usage ledger - quota counter - invoice aggregator - customer receipt - observability trace You do not need a complex billing system on day one. You do need one invariant from the start: Every billable tool event must be traceable to a customer-visible action and safe to explain later. The cleanest place to meter MCP usage is the boundary where the agent calls tools. That could be: The goal is not just to count calls. It is to capture the minimum event that can later support billing, quotas, support, and debugging. A useful event shape looks like this: type ToolUsageEvent = { event id: string; idempotency key: string; tenant id: string; workspace id: string; user id?: string; agent run id: string; step id: string; protocol: "mcp" | "internal"; server name: string; tool name: string; action type: "read" | "write" | "compute" | "external api"; risk tier: "low" | "medium" | "high"; status: "started" | "succeeded" | "failed" | "rejected" | "cached"; started at: string; finished at?: string; latency ms?: number; billable: boolean; billable units: number; unit type: "call" | "record" | "minute" | "credit"; estimated cost cents?: number; trace id: string; request hash: string; result hash?: string; }; Notice what is not stored: raw prompts, raw credentials, full tool arguments, or private result bodies. Store hashes, references, and safe summaries unless you have a clear retention policy and user-facing reason to keep more. Not every tool call should become a charge. If the model calls a validation tool three times because it is uncertain, the customer should not automatically pay three times. Start with four buckets. | Event type | Example | Usually billable? | |---|---|---| | Internal reasoning support | policy lookup, schema fetch, cached context read | No | | Cheap read | list CRM fields, fetch allowed project names | Maybe quota only | | Expensive external action | enrichment API, web extraction, document parse | Yes | | Risky state change | send email, update CRM, export data | Bill only when accepted/executed | A good rule: bill for value delivered or cost incurred, not for agent confusion. That means you may track all calls internally while billing only a smaller subset. This gives you visibility without turning every agent behavior into an invoice line. Billing bugs often start as retry bugs. Imagine a tool call times out after the upstream system completes the work. The agent retries. Your meter records two successful calls. The customer sees a double charge. Support has no clean answer. Add idempotency at the usage layer before building pricing logic. A practical key can include: tenant id + agent run id + step id + tool name + normalized argument hash + billing intent Then enforce this rule: js async function recordUsage event: ToolUsageEvent { const existing = await db.usage events.findUnique { where: { idempotency key: event.idempotency key } } ; if existing { return existing; // retry or duplicate delivery, not new usage } return db.usage events.create { data: event } ; } Idempotency should be stable enough to catch duplicate execution, but not so broad that it hides legitimate repeated work. For high-risk write tools, pair it with a tool-side idempotency key too, not just a billing-side key. Do not ask the model to decide what is billable. The agent can describe intent, but billing policy belongs in code and configuration. A small pricing rule table is enough to start: { "server": "crm", "tool": "search contacts", "unit type": "call", "price cents": 0, "counts toward quota": true }, { "server": "enrichment", "tool": "lookup company", "unit type": "record", "price cents": 3, "counts toward quota": true }, { "server": "crm", "tool": "update contact", "unit type": "call", "price cents": 1, "requires success": true, "requires approval": true } Match from most specific to least specific: Keep the default conservative. Unknown tools should be unbillable until reviewed, or billable only against a clearly labeled internal cost budget. Surprise billing from a newly added tool is a fast way to lose trust. A meter that only reports after the fact is useful for accounting, but weak for product safety. Agents need pre-flight quota checks before expensive or risky calls. Before executing a billable MCP tool, check: A simple quota check can return an execution decision: type QuotaDecision = | { allow: true; reservation id: string } | { allow: false; reason: "budget exceeded" | "approval required" | "rate limited" }; async function beforeToolCall ctx, tool, units : Promise