Commet

Add subscription-aware billing, feature gating, and usage tracking to your xmcp server with Commet

For the complete documentation index, see llms.txt. Markdown variants of every page are available by appending .md to the URL.

Overview

The Commet plugin enables subscription-aware billing for your xmcp server using Commet. Your tools get full context: which plan the customer is on, what features they can access, how much usage remains, and automatic consumption tracking.

  • Feature-level gating: Tool A is free, Tool B is Pro, Tool C is Enterprise
  • Usage tracking: report units or AI tokens, your plan's consumption model handles the rest
  • Rich context: your tools know the customer's plan, remaining quota, and limits
  • Full billing: invoices, proration, checkout, customer portal
  • Taxes and compliance: Commet handles everything as Merchant of Record

Installation

Install the Commet plugin:

pnpm i @xmcp-dev/commet

Commet Setup

Follow these steps to configure your billing product before integrating the plugin:

  1. Create an account at commet.co/templates/xmcp
  2. Copy your API Key (ck_xxx) from Settings > API Keys
  3. Create a Product from the dashboard. This represents your xmcp server
  4. Define your Plans (e.g., Free, Pro, Enterprise). Each plan includes a set of features
  5. Add Features to each plan. Choose the type per feature:
    • Boolean: on/off access (e.g., export, custom-branding)
    • Metered: usage-based with included quotas and optional overage pricing (e.g., ai_generate with 1000 included units)
  6. Set pricing for each plan: monthly/yearly intervals, per-seat, or flat rate

Configuration

Register the Commet provider in your middleware:

src/middleware.ts
import { commetProvider } from "@xmcp-dev/commet";

export default commetProvider({
  apiKey: process.env.COMMET_API_KEY!,
});

Configuration Options

  • apiKey: Your Commet API key (starts with ck_)
  • customerHeader: HTTP header name for the customer identifier (defaults to "customer-key")
  • debug: Enable verbose SDK logging (defaults to false)

Access the client

The getClient() function gives you access to the full @commet/node SDK, allowing you to leverage all Commet features in your MCP tools. The getCustomerId() function returns the customer ID extracted from the request header.

Example: Feature gating

Use the SDK to gate tools behind boolean features:

src/tools/export-tool.ts
import { z } from "zod";
import type { InferSchema, ToolMetadata } from "xmcp";
import { getClient, getCustomerId } from "@xmcp-dev/commet";

export const schema = {
  format: z.enum(["csv", "json", "pdf"]).describe("Export format"),
};

export const metadata: ToolMetadata = {
  name: "export",
  description: "Export data in multiple formats, Pro plan only",
};

export default async function exportData({
  format,
}: InferSchema<typeof schema>) {
  const client = getClient();
  const customerId = getCustomerId();
  const { data } = await client.features.get({ customerId, code: "export" });

  if (!data?.allowed) {
    return "Your plan does not include this feature.";
  }

  return `Exported data as ${format}`;
}

Example: Usage tracking

Track metered consumption with the SDK:

src/tools/ai-generate.ts
import { z } from "zod";
import type { InferSchema, ToolMetadata } from "xmcp";
import { getClient, getCustomerId } from "@xmcp-dev/commet";

export const schema = {
  prompt: z.string().describe("The prompt to generate content from"),
};

export const metadata: ToolMetadata = {
  name: "ai_generate",
  description: "Generate content with AI, tracks 1 unit per call",
};

export default async function aiGenerate({
  prompt,
}: InferSchema<typeof schema>) {
  const client = getClient();
  const customerId = getCustomerId();

  const { data } = await client.features.canUse({
    customerId,
    code: "ai_generate",
  });

  if (!data?.allowed) {
    return "Your plan does not include this feature.";
  }

  await client.usage.track({ feature: "ai_generate", customerId, value: 1 });

  return `Generated content for: "${prompt}"`;
}

Example: AI token tracking

Track per-model token consumption:

src/tools/ai-chat.ts
import { z } from "zod";
import type { InferSchema, ToolMetadata } from "xmcp";
import { getClient, getCustomerId } from "@xmcp-dev/commet";

export const schema = {
  prompt: z.string().describe("The prompt to send to the AI model"),
};

export const metadata: ToolMetadata = {
  name: "ai_chat",
  description: "Chat with AI, tracks token consumption per model",
};

export default async function aiChat({
  prompt,
}: InferSchema<typeof schema>) {
  const client = getClient();
  const customerId = getCustomerId();

  const { data } = await client.features.canUse({
    customerId,
    code: "ai_chat",
  });

  if (!data?.allowed) {
    return "Your plan does not include this feature.";
  }

  await client.usage.track({
    feature: "ai_chat",
    customerId,
    model: "anthropic/claude-haiku-4.5",
    inputTokens: 1200,
    outputTokens: 340,
  });

  return `Response to: "${prompt}"`;
}

Example: Billing portal

Get the customer's billing portal URL for upgrade and management flows:

src/tools/manage-billing.ts
import type { ToolMetadata } from "xmcp";
import { getClient, getCustomerId } from "@xmcp-dev/commet";

export const metadata: ToolMetadata = {
  name: "manage-billing",
  description: "Get the customer's billing portal link",
};

export default async function manageBilling(): Promise<string> {
  const client = getClient();
  const customerId = getCustomerId();
  const { success, data } = await client.portal.getUrl({ customerId });

  if (!success || !data) {
    return "Unable to retrieve billing portal.";
  }

  return `Manage your subscription: ${data.portalUrl}`;
}

Example

See the full working example with free, gated, metered, and AI token tools in the commet-http example.