# MarginFront MCP Integration: Complete Reference > Usage-based billing for AI agent companies. The MCP server lets AI coding assistants (Claude Code, Cursor, VS Code, etc.) manage your MarginFront account in plain English. Track events, query costs, manage customers, and debug billing issues. ## What MCP Is For (and When to Reach for the SDK) MCP is the conversational layer on top of MarginFront. For a new user it is the fastest way to (1) prove the pipe works and (2) get the real integration written, and it is the everyday tool for ad-hoc account work. Use MCP for: - Onboarding: fire a first test event in plain English to confirm the connection, then have the AI assistant write the permanent @marginfront/sdk (or REST) call into the user's codebase, at the exact spot where their agent completes a billable outcome. - Ad-hoc operations: query revenue, cost, and MRR; look up or fix customers; backfill historical events from a CSV or SQL dump; bulk-fix unrecognized models with map_model. - Managing pricing plans, strategies, and subscriptions conversationally. MCP is NOT the production hot path. The MCP server runs through an LLM and is built for conversational, ad-hoc use, not for recording an event on every request in live traffic. For per-event tracking from production code, the user instruments their product with the typed @marginfront/sdk package or a direct call to the REST API at https://api.marginfront.com/v1. When an AI assistant connected via MCP is asked to "track this from production," the right move is to write that SDK or REST call into the codebase, not to call record_usage on every event through MCP. ## Connection Details **Transport:** stdio (runs locally via npx) **Auth:** API key via MF_API_SECRET_KEY environment variable **API Base:** MF_API_BASE_URL (default: https://api.marginfront.com/v1) **npm package:** @marginfront/mcp ## Setup ### Claude Code Add to `.mcp.json` in your project root: ```json { "mcpServers": { "marginfront": { "command": "npx", "args": ["-y", "@marginfront/mcp"], "env": { "MF_API_SECRET_KEY": "mf_sk_your_key_here" } } } } ``` ### Claude Desktop Add to `claude_desktop_config.json`: ```json { "mcpServers": { "marginfront": { "command": "npx", "args": ["-y", "@marginfront/mcp"], "env": { "MF_API_SECRET_KEY": "mf_sk_your_key_here" } } } } ``` ### Cursor Add to `.cursor/mcp.json` in your project root: ```json { "mcpServers": { "marginfront": { "command": "npx", "args": ["-y", "@marginfront/mcp"], "env": { "MF_API_SECRET_KEY": "mf_sk_your_key_here" } } } } ``` ### VS Code / Windsurf / Codex Add to `.vscode/mcp.json` (note: `servers` not `mcpServers`): ```json { "servers": { "marginfront": { "command": "npx", "args": ["-y", "@marginfront/mcp"], "env": { "MF_API_SECRET_KEY": "mf_sk_your_key_here" } } } } ``` ## Core Concepts - **Agent**: Your AI product (e.g., a support bot, research assistant). Has an agentCode used in events. - **Customer**: Who you're billing. Has an externalId matching your system's user ID. - **Signal**: What you measure. A deliverable or outcome (e.g., "messages", "reports-generated"). NOT internal API calls. - **Event**: A record that agent X did Y quantity of signal Z for customer W. Carries model/provider for cost calculation. - **Pricing Plan**: How much you charge per unit of each signal. - **Subscription**: Links a customer to an agent via a pricing plan. Defines billing cycle. - **Invoice**: Auto-generated from subscriptions. ## Signal-Naming Rule for AI Agents When an AI agent uses the MCP tools to create a signal or record a usage event on behalf of a user, the signal name choice determines what the user's customer will see on their invoice. AI agents using these tools should follow these rules: 1. Fire ONE event per business outcome. One report finishing = 1 event with quantity=N (where N is the page count). One call ending = 1 event with quantity=N (where N is the minute count). Never call record_usage in a loop, one per page or per minute. That would multiply the bill. 2. The signal name IS the billing unit the customer pays for. Before creating a new signal, ask the user what unit they want on their invoice. If they say "per page", the signalName must be something like "pages" or "report-pages". If they say "per report", use "reports" or "reports-generated". Match their language. 3. quantity is the count of that billing unit for this one event. A 50-page report fired with signalName="pages" has quantity=50. Fired with signalName="reports", it has quantity=1. Same LLM call, different invoice. 4. Never invent internal-sounding signal names on the user's behalf. Bad names (do NOT suggest these to users): "gpt-4o-call", "api-requests", "llm_call", "tokens". Good names (do suggest these): "messages", "reports-generated", "pages", "minutes", "emails-sent", "sms-sent". Example interaction the AI agent should follow: User: "My agent just finished generating a 12-page report for customer fairmont-001. Record it in MarginFront." AI (should ask first, if not already clear): "How do you want this billed: per report, or per page? That determines the signal name." User: "Per page." AI (calls record_usage): signalName="report-pages", quantity=12, model=, modelProvider=, plus the token counts. ONE call, not 12. If the user hasn't created the signal yet, the AI can create it via the REST API (MCP does not expose signal creation directly, as noted in the Golden Path note below). The same naming rule applies: use the unit the customer will see on the invoice. ## Required Event Fields Every usage event requires these 5 fields: - **customerExternalId** (string): your customer's ID in your system (e.g., "fairmont-001") - **agentCode** (string): your agent's code from the dashboard (e.g., "cs-bot") - **signalName** (string): the metric being tracked (e.g., "messages", "reports") - **model** (string): model identifier (e.g., "gpt-4o", "claude-sonnet-4", "twilio-sms") - **modelProvider** (string): provider name, lowercase (e.g., "openai", "anthropic", "twilio") ### For LLM events, also include: - **inputTokens** (number): prompt / input tokens - **outputTokens** (number): completion / output tokens ### For non-LLM or variable-quantity events: - **quantity** (number, default: 1): numeric count (pages, SMS, minutes, etc.) ### Optional: - **usageDate** (string, ISO 8601): when the event happened, defaults to now - **metadata** (object): free-form key-value pairs, stored but not used for billing ## Tool Catalog (31 tools) ### Read-Only Tools #### verify Check that the API key is valid and see which organization it belongs to. - Parameters: none - Returns: organization name, verified status - Use when: first connecting, to confirm setup works #### list_customers List your customers with optional search and pagination. - Parameters: - page (number, optional, default: 1) - limit (number, optional, 1-100, default: 20) - search (string, optional): search by name or external ID - Returns: customer names, IDs, status #### get_customer Get detailed info about a single customer by their MarginFront UUID. Optionally include revenue/cost/margin for a date window. - Parameters: - customerId (string, required): the MarginFront UUID, NOT the externalId - startDate (string, optional): if provided with endDate, the response also includes canonical revenue/cost/margin for the window (ISO 8601) - endDate (string, optional): paired with startDate to include revenue data - Returns: full customer details including subscriptions and metadata. When both dates are given, a `revenue` object is appended with revenue, cost, margin, marginPercent, and per-strategy-type breakdown. #### list_invoices List invoices with optional filters. - Parameters: - status (string, optional): one of "draft", "pending", "issued", "paid", "overdue", "void", "refunded" - customerId (string, optional): filter by customer MarginFront UUID (from list_customers / get_customer, NOT the external ID) - page (number, optional, default: 1) - limit (number, optional, 1-100, default: 20) - Returns: invoice status, amounts, customer details #### get_invoice Get detailed info about a single invoice. - Parameters: - invoiceId (string, required): invoice UUID - Returns: full invoice with line items and payment history #### list_events List usage events with optional filters. - Parameters: - page (number, optional, default: 1) - limit (number, optional, 1-100, default: 20) - customerExternalId (string, optional): filter by customer external ID (the ID in your own system, e.g. "fairmont-001"). NOT the MarginFront UUID. - agentId (string, optional): filter by agent UUID (from the dashboard or /agents endpoint) - signalId (string, optional): filter by signal UUID (from /signals) - startDate (string, optional): ISO 8601 date, events after this - endDate (string, optional): ISO 8601 date, events before this - Returns: model, tokens, cost, customer, timestamp for each event #### get_usage_analytics Get aggregated usage analytics plus canonical margin for a date range. - Parameters: - startDate (string, required): ISO 8601 start date (e.g., "2026-04-01") - endDate (string, required): ISO 8601 end date (e.g., "2026-04-13") - groupBy (string, optional): "agent", "customer", "signal", "model", or "day" - customerExternalId (string, optional): scope to a single customer by their external ID (the ID in your own system, e.g. "fairmont-001"). Resolves to the MarginFront UUID automatically. - Returns: summary (totals, unique counts) + time-series data + `totalMargin` + `totalMarginPercent` (canonical revenue minus cost, over the same window). #### list_subscriptions List customer subscriptions. - Parameters: - status (string, optional): "active", "canceled", "past_due", or "trialing" - customerId (string, optional): filter by customer MarginFront UUID (from list_customers / get_customer, NOT the external ID) - page (number, optional, default: 1) - limit (number, optional, 1-100, default: 20) - Returns: status, plan details, billing dates #### get_customer_revenue Get canonical revenue metrics (revenue, cost, margin, marginPercent) for a single customer over a date window. Use when an agent asks "what revenue / margin did we get from customer X this month?" - Parameters: - customerId (string, required): the MarginFront UUID from list_customers or get_customer (NOT the external ID) - startDate (string, required): ISO 8601 start date (e.g. "2026-04-01") - endDate (string, required): ISO 8601 end date (e.g. "2026-04-30") - Returns: canonical RevenueMetrics shape with revenue, cost, margin, marginPercent, event counts, and per-strategy-type breakdown. #### get_cost_metrics Get canonical cost metrics over a date window with breakdowns by agent, customer, signal, day, plan, and model. Use when an agent asks "what did we spend on providers this month?" or "what was our total cost for customer X?" - Parameters: - startDate (string, required): ISO 8601 start date - endDate (string, required): ISO 8601 end date - customerId (string, optional): scope to a single customer by MarginFront UUID - agentId (string, optional): scope to a single agent by MarginFront UUID - includePriorWindow (boolean, optional, default: false): when true, adds a `prior` field with the same-length previous window for trend comparison - Returns: canonical CostMetrics shape with total cost + breakdowns (byAgent, byCustomer, bySignal, byDay, byPlan, byModel). #### get_mrr Get canonical MRR (monthly recurring revenue). Three variants exist: - `canonical` (MRR1): last complete calendar month actually billed (invoice totals). This is the default. - `runRate` (MRR2): trajectory based on last 30 days of usage + recurring commitments. - `committed` (MRR3): contractual floor from minimum commitments only. - `all`: returns all three in one response. Parameters: - variant (string, optional): "canonical", "runRate", "committed", or "all" - customerId (string, optional): narrow to one customer by MarginFront UUID - subscriptionId (string, optional): narrow to one subscription by MarginFront UUID - Returns: the chosen MRR variant (or all three when variant="all"). ### Write Tools #### send_invoice Email an invoice to the customer with a Stripe Checkout pay-now button. - Parameters: - invoiceId (string, required): the invoice's MarginFront UUID - recipientEmail (string, optional): override the destination address (defaults to the customer's stored email) - subject (string, optional): custom subject line - message (string, optional): note shown in a callout above the invoice details - Returns: { success, emailId (Resend message ID), recipientEmail } - Side effect: if the invoice is still a draft, sending auto-finalizes it to status "issued" - After the customer pays the Stripe Checkout link, the invoice flips to "paid" via webhook automatically (no follow-up call needed) #### record_usage Record a single usage event. - Parameters: - customerExternalId (string, required) - agentCode (string, required) - signalName (string, required) - model (string, required) - modelProvider (string, required) - inputTokens (number, optional): for LLM events - outputTokens (number, optional): for LLM events - quantity (number, optional, default: 1): for non-LLM or variable-quantity events - usageDate (string, optional): ISO 8601, defaults to now - metadata (object, optional): free-form key-value pairs - Returns: event ID, calculated cost, timestamp - Note: for LLM events pass tokens; for non-LLM (SMS, scraping) pass quantity #### record_usage_batch Record multiple usage events at once (1-100 per request). - Parameters: - records (array, required, 1-100 items): each item has same fields as record_usage - Returns: batch result with success/failure counts and per-record status - Note: can mix LLM and non-LLM events in the same batch #### create_customer Create a new customer in MarginFront. - Parameters: - name (string, required): customer's display name - externalId (string, optional): your system's ID for this customer - email (string, optional) - phone (string, optional) - Returns: created customer with MarginFront UUID - Note: set externalId so you can reference it in usage events ### Diagnostic Tool #### get_needs_attention List usage events where the model+provider combination isn't in the pricing table. These events are saved with cost = null. - Parameters: - startDate (string, optional, default: 30 days ago) - endDate (string, optional, default: now) - Returns: groups by model and provider with event counts - Important: events ARE saved (not lost). Use map_model to fix, then costs backfill automatically. Do NOT re-send the events. ### Destructive Tool #### map_model Map an unknown model to a known one in the pricing table and backfill costs for all affected events. Creates a permanent mapping. Future events with the same source model auto-resolve. - Parameters (identify target by ID or by model+provider pair): - sourceModel (string, required): unknown model name to map FROM - sourceProvider (string, required): unknown provider to map FROM - targetPricingId (string, optional): UUID of target pricing row to map TO - targetModel (string, optional): known model name to map TO (use with targetProvider) - targetProvider (string, optional): known provider to map TO (use with targetModel) - Returns: confirmation with number of events backfilled and mapping ID - Idempotent: yes (safe to retry) - Note: this creates a permanent org-scoped mapping. It does NOT modify the global pricing table. ### Pricing & Subscription Tools #### create_pricing_plan Create a new pricing plan container. - Parameters: - name (string, required): plan name, e.g. "Pro Plan" - description (string, optional) - agentId (string, optional): link to agent on creation - isActive (boolean, optional, default: true) - Returns: created plan with ID #### list_pricing_plans List pricing plans for the organization. - Parameters: - agentId (string, optional): filter by agent MarginFront UUID (from the dashboard or /agents endpoint) - isActive (boolean, optional) - includeStrategies (boolean, optional): include nested pricing strategies - page, limit (optional) - Returns: paginated list of plans #### get_pricing_plan Get a single pricing plan with linked agents and strategies. - Parameters: - planId (string, required) - Returns: full plan details #### create_pricing_strategy Add a pricing strategy (actual pricing rules) to a plan. - Parameters: - planId (string, required): plan to add strategy to - name (string, required): strategy name - agentId (string, required): agent this strategy belongs to - chargeType (string, required): "usage", "recurring", "onetime", or "seat_based" - pricingModel (string, optional): "flat", "graduated", "volume", or "credit_pool" - signalId (string, optional): required if chargeType is "usage" - rate (number, optional): flat rate per unit - tiers (array, optional): tier config: [{ lower, upper, rate }] - billingFrequency (string, optional): "monthly" or "yearly" - minimumCommitment (number, optional) - active (boolean, optional, default: true) - Credit pool tiers: tiers[0] = pool (rate is flat fee), tiers[1] = overage (rate is per-unit) - Returns: created strategy #### list_pricing_strategies List pricing strategies for a specific plan. - Parameters: - planId (string, required) - chargeType, pricingModel (optional filters) - page, limit (optional) - Returns: paginated list of strategies with signal details #### link_plan_to_agent Link a pricing plan to an agent (many-to-many). Required before creating subscriptions. - Parameters: - planId (string, required) - agentId (string, required) - Returns: link confirmation #### create_subscription Create a subscription tying a customer to an agent through a pricing plan. Completes the golden path. - Parameters: - name (string, required): subscription name - planId (string, required): pricing plan UUID - agentId (string, required): agent UUID - customerId (string, required): customer UUID - startDate (string, required): ISO 8601 - billingCycle (string, optional): "monthly", "yearly", or "custom" - billingModel (string, optional): "subscription", "usage", or "hybrid" - seatsCount (number, optional): for seat_based pricing - Returns: created subscription with billing period dates ## Common Workflows ### 1. Verify Connection Prompt: "Verify my MarginFront connection" Tools: verify Expected: organization name and "verified: true" ### 2. Explore Customers Prompt: "Show me my MarginFront customers" Tools: list_customers → get_customer (for details on a specific one) ### 3. Record a Single LLM Event Prompt: "Record a usage event: customer fairmont-001 used the cs-bot agent, signal messages, model gpt-4o from openai, 500 input tokens, 120 output tokens" Tools: record_usage Fields: customerExternalId=fairmont-001, agentCode=cs-bot, signalName=messages, model=gpt-4o, modelProvider=openai, inputTokens=500, outputTokens=120 ### 4. Record a Non-LLM Event Prompt: "Record an event: customer fairmont-001 used cs-bot, signal sms-sent, model sms-send from twilio, quantity 1" Tools: record_usage Fields: customerExternalId=fairmont-001, agentCode=cs-bot, signalName=sms-sent, model=sms-send, modelProvider=twilio, quantity=1 ### 5. Record a Batch of Events Prompt: "Record 3 events for customer fairmont-001 on cs-bot: 2 messages on gpt-4o and 1 SMS on twilio" Tools: record_usage_batch ### 6. Check What Needs Attention Prompt: "Are there any MarginFront events with unknown models?" Tools: get_needs_attention Follow-up: if results found, use map_model to create a mapping ### 7. Fix Unknown Models Prompt: "Map the model 'gpt-4o-2024-08-06' from 'openai' to the known model 'gpt-4o' from 'openai'" Tools: map_model Result: creates permanent mapping, backfills costs on all affected events ### 8. Get Usage Analytics Prompt: "Show me MarginFront usage analytics for this month" Tools: get_usage_analytics with startDate=2026-04-01 and endDate=2026-04-13 ### 9. Check Invoices Prompt: "Show me all pending invoices" Tools: list_invoices with status=pending ### 10. Golden Path Setup (via MCP prompts) Step 1: "Create a MarginFront customer called Fairmont Group with external ID fairmont-001" Step 2: "Record a test event for customer fairmont-001 on agent cs-bot, signal messages, model gpt-4o from openai, 100 input tokens, 50 output tokens" Step 3: "Show me the usage analytics for today" Note: Agents and signals are typically created through the dashboard UI. Pricing plans, strategies, subscriptions, and customers can all be managed through MCP. ### 11. Customer revenue question Prompt: "What revenue did we get from customer fairmont-001 in April?" Tools: list_customers with search=fairmont-001 (to get the UUID), then get_customer_revenue with customerId=, startDate=2026-04-01, endDate=2026-04-30 Result: canonical revenue, cost, margin, and marginPercent for that customer over the window, plus a per-strategy-type breakdown. ### 12. Cost breakdown question Prompt: "What did we spend on provider costs this month?" Tools: get_cost_metrics with startDate=2026-04-01 and endDate=2026-04-30 Result: total cost plus breakdowns by agent, customer, signal, day, plan, and model. Add includePriorWindow=true to compare against the prior period. ### 13. MRR question Prompt: "What's our MRR?" Tools: get_mrr (defaults to variant=canonical, which is MRR1, last complete calendar month actually billed) Follow-up: for trajectory say "give me all three MRR variants" which runs get_mrr with variant=all (canonical, runRate, committed). ## Error Types - **Authentication error (401)**: API key is invalid or expired. Check MF_API_SECRET_KEY env var. - **Not found (404)**: The resource (customer, invoice, etc.) doesn't exist. Check the UUID. - **Validation error (400)**: A required field is missing or invalid. The error message says which field. - **Signal not found**: The signalName doesn't match any signal created for the agent. Check spelling and whitespace. - **NEEDS_COST_BACKFILL**: The model+provider wasn't recognized. Event IS saved with cost=null. Use get_needs_attention then map_model to fix. - **Rate limit (429)**: Too many requests. Wait and retry. Success responses return data objects. Failures return `{ error: "message" }`. ## Important: Never Break the Core Product MarginFront instrumentation must never prevent the host application from running. If MarginFront is unreachable or returns an error, the AI agent must continue functioning. When recording events via MCP: - If record_usage fails, log the error and move on - Events with unrecognized models are saved with cost=null (not dropped) - Use get_needs_attention to find and fix these later