# Analytics

> Privacy-first analytics SDK for Clamp Analytics. MCP-native — your AI agent can query the same events. Auto-pageviews, sessions, revenue, errors. Browser, server, React, and one-tag CDN install for Webflow / Shopify / WordPress and other hosted platforms.

- **Type:** MCP server
- **Install:** `agentstack add mcp-clamp-sh-analytics`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [clamp-sh](https://agentstack.voostack.com/s/clamp-sh)
- **Installs:** 0
- **Category:** [Web & Browser](https://agentstack.voostack.com/c/web-and-browser)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [clamp-sh](https://github.com/clamp-sh)
- **Source:** https://github.com/clamp-sh/analytics
- **Website:** https://clamp.sh/docs/sdk

## Install

```sh
agentstack add mcp-clamp-sh-analytics
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# @clamp-sh/analytics

Web analytics SDK for [Clamp Analytics](https://clamp.sh) — a privacy-first product analytics platform with a built-in [MCP](https://modelcontextprotocol.io) server, so the same events you track with this SDK are queryable by Claude, Cursor, VS Code, and any other Model Context Protocol client.

Auto-pageviews, sessions, custom events, `identify` for logged-in users, typed `revenue()` for subscriptions and one-time purchases, and error capture in under 2 KB gzipped. No cookies, no personal data, no consent banner. Works in the browser, on the server, with React and Next.js, and via a one-tag CDN install for hosted platforms.

Hosted at [clamp.sh](https://clamp.sh/signup) — free tier covers 100k events/month. MIT SDK; the platform itself is hosted by Clamp.

## Install

```bash
npm install @clamp-sh/analytics
```

Or paste one script tag for hosted platforms — see [Script tag (no build step)](#script-tag-no-build-step) below.

## Framework support

Install guides at [clamp.sh/docs/install](https://clamp.sh/docs/install) and [clamp.sh/docs/integrations](https://clamp.sh/docs/integrations):

| Path | Install |
|---|---|
| Next.js (App Router + Pages) | [`/docs/install/nextjs`](https://clamp.sh/docs/install/nextjs) |
| Vite + React | [`/docs/install/vite-react`](https://clamp.sh/docs/install/vite-react) |
| Nuxt 3 | [`/docs/install/nuxt`](https://clamp.sh/docs/install/nuxt) |
| SvelteKit | [`/docs/install/sveltekit`](https://clamp.sh/docs/install/sveltekit) |
| Astro | [`/docs/install/astro`](https://clamp.sh/docs/install/astro) |
| Webflow | [`/docs/integrations/webflow`](https://clamp.sh/docs/integrations/webflow) |
| Shopify | [`/docs/integrations/shopify`](https://clamp.sh/docs/integrations/shopify) |
| WordPress | [`/docs/integrations/wordpress`](https://clamp.sh/docs/integrations/wordpress) |
| Squarespace | [`/docs/integrations/squarespace`](https://clamp.sh/docs/integrations/squarespace) |
| Ghost | [`/docs/integrations/ghost`](https://clamp.sh/docs/integrations/ghost) |
| Framer | [`/docs/integrations/framer`](https://clamp.sh/docs/integrations/framer) |
| Wix | [`/docs/integrations/wix`](https://clamp.sh/docs/integrations/wix) |
| Carrd, Notion, Mintlify, Docusaurus, Hugo, Jekyll, Eleventy | [`/docs/integrations`](https://clamp.sh/docs/integrations) |

## Script tag (no build step)

For sites you can't `npm install` into — Webflow, Shopify, WordPress, Squarespace, Ghost, Framer, Wix, Notion-as-site services, and others — drop one script tag in the page ``. The SDK reads the project ID from the tag's data attribute and starts tracking pageviews and sessions automatically.

```html

```

Replace `proj_xxx` with your project ID from the [Clamp dashboard](https://clamp.sh/dashboard). Platform-specific install guides live at [clamp.sh/docs/integrations](https://clamp.sh/docs/integrations).

### Opt into auto-tracking extensions

Add `data-clamp-extensions` (comma-separated):

```html

```

Available: `outbound-links`, `downloads`, `data-attributes`, `section-views`, `web-vitals`, `not-found`. See the [extensions reference](https://clamp.sh/docs/sdk/extensions).

### Manual init (for ESM or custom configuration)

If you need programmatic configuration — endpoint override, excluded paths, error capture toggle — use the manual API instead of the data attribute:

```html

  clamp.init("proj_xxx", {
    excludePaths: ["/dashboard"],
    captureErrors: true,
  });

```

Or import the ESM build directly:

```ts
import { init } from "@clamp-sh/analytics";
init("proj_xxx", { excludePaths: ["/dashboard"] });
```

## Browser

```ts
import { init, track, identify, reset, revenue, getAnonymousId, getUserId } from "@clamp-sh/analytics"

init("proj_xxx")

// Custom events
track("signup", { plan: "pro" })

// Bind the anonymous visitor to a known user (persists across sessions)
identify(user.id, { email: user.email, plan: user.plan })

// Record a payment
revenue({ amount: 29, currency: "USD", plan: "pro", billing: "monthly" })

// Logout — clears userId, rotates anonymous + session IDs
reset()

// Read IDs
getAnonymousId()  // rotating anon ID
getUserId()       // null before identify(), or the bound userId
```

After `init()`, pageviews are tracked automatically, including SPA navigations.

## React

```tsx
import { Analytics } from "@clamp-sh/analytics/react"

export default function RootLayout({ children }) {
  return (
    
      
        {children}
        
      
    
  )
}
```

Add to your root layout. Pageviews are tracked automatically. Use `track()` from `@clamp-sh/analytics` anywhere in your app for custom events.

## Server

```ts
import { init, track } from "@clamp-sh/analytics/server"

init({ projectId: "proj_xxx", apiKey: "sk_proj_..." })

await track("account_created", {
  anonymousId: "anon_abc123",
  properties: { plan: "pro" },
})
```

Server events require an API key (found in your project settings).

## Custom events

Track any action with `track(name, properties)`. Properties are flat key-value pairs. Each value is a string, finite number, boolean, or `Money`.

```ts
import { track } from "@clamp-sh/analytics"

track("signup", { plan: "pro", source: "pricing_page" })
track("feature_used", { name: "csv_export" })
track("invite_sent", { role: "editor", team: "acme" })
```

On the server:

```ts
import { track } from "@clamp-sh/analytics/server"

await track("invite_accepted", {
  anonymousId: "anon_abc123",
  properties: {
    team_id: "team_42",
    role: "editor",
  },
})
```

Pageviews are tracked automatically. Everything else goes through `track()`, or `revenue()` for payments (see [Revenue](#revenue)). The canonical revenue event names (`subscription_started`, `purchase`, `refund_issued`, etc.) always go through `revenue()`, not `track()` — only events emitted through `revenue()` carry `mrr_delta` and land on the Revenue tab with the `$`-prefixed wire name the dashboard and MCP tools query against.

## Identify users

Call `clamp.identify(userId, traits?)` after login or signup. The userId persists across reloads and tabs, and attaches to every subsequent event automatically, so events from the same user across devices and sessions land under one identity.

```ts
import { identify, reset } from "@clamp-sh/analytics"

// After auth succeeds:
identify(user.id, {
  email: user.email,
  plan: user.plan,
  signup_date: user.createdAt,
})

// On logout — clears userId, rotates anonymous + session IDs:
reset()
```

Traits are optional user attributes sent once as an `$identify` event. Use them for slicing in the dashboard ("customers on the Pro plan", "users who signed up this month"). Never send PII (raw email beyond hashing, addresses, payment details).

Call `reset()` on signout, not on session expiry — it rotates the anonymous + session IDs so the next visitor on this device isn't conflated with the one who logged out.

## Typed events

Define your event map once and get autocomplete and type checking across your app. Zero runtime cost.

```ts
import type { Money } from "@clamp-sh/analytics"

type Events = {
  signup: { plan: string; source: string }
  checkout_completed: { plan: string; total: Money; tax: Money }
  feature_used: { name: string }
  invite_sent: { role: string }
}

init("proj_xxx")

track("signup", { plan: "pro", source: "homepage" })   // autocomplete
track("signup", { wrong: "field" })                     // type error
track("unknown_event")                                  // type error
```

The typed event map covers custom `track()` events only. Canonical revenue names (`subscription_started`, `purchase`, `refund_issued`, etc.) go through `revenue()`, which is already typed via the `RevenueArgs` discriminated union.

Past a handful of events, declare them in [`event-schema.yaml`](https://github.com/clamp-sh/event-schema) and let the CLI generate the type — same compile-time safety, one source of truth across your codebase and your team.

Works the same way with the server SDK:

```ts
import { init, track } from "@clamp-sh/analytics/server"

init({ projectId: "proj_xxx", apiKey: "sk_proj_..." })

await track("checkout_completed", {
  properties: {
    plan:  "pro",
    total: { amount: 49, currency: "USD" },
    tax:   { amount: 7.35, currency: "USD" },
  },
})
```

## Revenue

Call `clamp.revenue()` wherever a customer pays. The SDK emits a typed event with the canonical `total` Money property, reserved `plan` / `billing_period` / `subscription_id` / `mrr_delta` properties, and an inferred event name (`subscription_started` for monthly or annual, `purchase` for one_time).

```ts
import { revenue } from "@clamp-sh/analytics"

// Subscription start — mrr_delta inferred as 29.
revenue({
  amount: 29,
  currency: "USD",
  plan: "pro",
  billing: "monthly",
  subscriptionId: sub.id,
})

// Annual subscription — mrr_delta inferred as 290 / 12 = 24.17.
revenue({
  amount: 290,
  currency: "USD",
  plan: "pro",
  billing: "annual",
  subscriptionId: sub.id,
})

// One-time purchase — mrr_delta is 0.
revenue({
  amount: 49,
  currency: "USD",
  billing: "one_time",
  product: "Template Pack",
})
```

### Three shapes, type-enforced

`RevenueArgs` is a discriminated union — TypeScript rejects mixing the two breakdown dimensions at compile time (no `purchase` events with `plan`; no `subscription_*` events with `product`; no `{ amount: 29 }` without either).

**SubscriptionRevenueArgs** — for `subscription_started` / `_renewed` / `_upgraded` / `_downgraded` / `_canceled` / `_paused` / `_resumed`. `plan` required, `product` forbidden, `billing` is one of `"monthly" | "annual" | "weekly"`.

**PurchaseRevenueArgs** — for `purchase`. `product` required; `plan` and `subscriptionId` forbidden; `billing` is `"one_time"`.

**RefundRevenueArgs** — for `refund_issued`. Permissive — pass `plan` + `subscriptionId` for a subscription refund, or `product` for a one-time refund. `event` is required here (no sensible default).

| Field | Type | Notes |
|---|---|---|
| `amount` | `number` | Major units (29 for $29, not 2900). For cancel, typically 0 — pair with `previousAmount`. For refund, negative. |
| `currency` | `string` | ISO 4217 ("USD", "EUR", "JPY"). Defaults to `"USD"`. |
| `billing` | `BillingPeriod` | Narrowed by shape — subs are `"monthly" \| "annual" \| "weekly"`; purchase is `"one_time"`; refund accepts any. |
| `event` | canonical name | Optional except on `RefundRevenueArgs`. Defaults follow the shape: `subscription_started` for subs, `purchase` for one-time. |
| `plan` | `string` | Required for subscription events, forbidden on purchase. Drives the Plans card. |
| `product` | `string` | Required for purchase, forbidden on subscription events. Drives the Products card. |
| `subscriptionId` | `string` | Allowed on sub events and refunds; forbidden on purchase. Links events across renew, upgrade, cancel. |
| `previousAmount` | `number` | Required on sub events where the SDK derives `mrr_delta` from the diff (upgrade, downgrade, canceled). |
| `mrrDelta` | `number` | Override the inferred delta. Use for partial refunds or other cases the inference table doesn't fit. |

`mrr_delta` is derived from the event name plus billing:

| Event | mrr_delta inferred as |
|---|---|
| `subscription_started` | `+amount` (monthly) or `+amount/12` (annual) |
| `subscription_renewed` | `0` (balance unchanged, just cash collected) |
| `subscription_upgraded` | `new_mrr − previous_mrr` (requires `previousAmount`) |
| `subscription_downgraded` | same shape, negative diff |
| `subscription_canceled` | `−amount` (monthly) or `−amount/12` (annual) |
| `purchase` / `refund_issued` | `0` (not recurring) |

Anything billed `one_time` is always `0` regardless of event. Anything recurring with an unrecognised event name defaults to `+amount` so a custom event like `subscription_resumed` does the sensible thing without configuration. Override `mrrDelta` directly for the long-tail cases.

```ts
// Upgrade from $29 to $89 — mrr_delta inferred as +60.
revenue({
  amount: 89,
  previousAmount: 29,
  currency: "USD",
  event: "subscription_upgraded",
  plan: "growth",
  billing: "monthly",
})

// Cancellation — no cash collected. previousAmount drives mrr_delta = -29.
revenue({
  amount: 0,
  previousAmount: 29,               // what they WERE paying
  currency: "USD",
  event: "subscription_canceled",
  plan: "pro",
  billing: "monthly",
  subscriptionId: sub.id,
})

// Renewal — mrr_delta inferred as 0 (cash collected, balance unchanged).
revenue({
  amount: 29,
  currency: "USD",
  event: "subscription_renewed",
  plan: "pro",
  billing: "monthly",
  subscriptionId: sub.id,
})
```

### Canonical subscription events

`revenue()` is strict about event names. The Revenue tab, MCP tools, and analytics-skills pack all assume the names below, so the `CanonicalRevenueEvent` type only allows these — TypeScript catches anything else at compile time, and the runtime guard rejects non-canonical names from JS callers with a typo suggestion when one exists (`subscription_created` → `subscription_started`, `checkout_completed` → `purchase`, etc.).

| Public name (passed to `revenue()`) | Wire name (sent to ingest, used in MCP queries / dashboard filters) | When to send |
|---|---|---|
| `subscription_started`   | `$subscription_started`   | First paid invoice on a new subscription |
| `subscription_renewed`   | `$subscription_renewed`   | Recurring renewal invoice paid |
| `subscription_upgraded`  | `$subscription_upgraded`  | Plan change to a higher tier |
| `subscription_downgraded`| `$subscription_downgraded`| Plan change to a lower tier |
| `subscription_canceled`  | `$subscription_canceled`  | Subscription ends |
| `subscription_paused`    | `$subscription_paused`    | Subscription paused |
| `subscription_resumed`   | `$subscription_resumed`   | Subscription resumed |
| `purchase`               | `$purchase`               | One-shot purchase (default for `billing: "one_time"`) |
| `refund_issued`          | `$refund_issued`          | Refund webhook fires |

User code always passes the un-prefixed public name to `revenue()`. The SDK translates to the `$`-prefixed wire name before emission. The wire name is what shows up in the events table, in MCP queries (e.g. `revenue.retention({ cohort_event: "$subscription_started" })`), and in dashboard filter values.

If you need a custom revenue-shaped event, drop to `track()` with a Money property + `mrr_delta` numeric prop:

```ts
track("trial_started", {
  total: { amount: 0, currency: "USD" },
  plan: "pro",
  mrr_delta: 0,
  billing_period: "monthly",
})
```

### TypeScript

```ts
import type { Money, BillingPeriod, RevenueArgs } from "@clamp-sh/analytics"

const args: RevenueArgs = {
  amount: 29,
  currency: "USD",
  plan: "pro",
  billing: "monthly" satisfies BillingPeriod,
}
```

### Server-side (e.g. from a Stripe webhook)

```ts
import { revenue } from "@clamp-sh/analytics/server"

await revenue({
  userId: customer.id,                         // bind to the identified user
  amount: invoice.amount_paid / 100,           // Stripe ships cents
  currency: invoice.currency.toUpperCase(),
  event: "subscription_renewed",
  plan: sub.metadata.plan,
  billing: "monthly",
  subscriptionId: sub.id,
})
```

### Raw money on custom events

For anything outside the canonical revenue events, `track()` still accepts any property whose value is a `Money` object. Use this for tax, discount, refund-attribution amounts, or anything else `revenue()` doesn't model directly.

```ts
track("checkout_completed", {
  plan: "pro",
  total: { amount: 49, currency: "USD" },
  tax:   { amount: 7.35, currency: "USD" },
})
```

Auto-tracked clicks can also carry money via `data-clamp-money-`:

```html
Buy
```

Your agent can now ask questions like "which source drove the most revenue last month", "what's our MRR this quarter", or "are subscribers expanding or contracting?" — see [the MCP revenue tools](https://clamp.sh/docs/mcp/tools#revenue) for the full set.

## Errors

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [clamp-sh](https://github.com/clamp-sh)
- **Source:** [clamp-sh/analytics](https://github.com/clamp-sh/analytics)
- **License:** MIT
- **Homepage:** https://clamp.sh/docs/sdk

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-clamp-sh-analytics
- Seller: https://agentstack.voostack.com/s/clamp-sh
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
