Web analytics

Tandem Analytics is privacy-friendly, first-party web analytics (powered by Umami). A server-side request baseline is always on for every service; these tools add per-visitor / per-session / per-event data and let you query it — all over MCP.

Enable it

{ "name": "provision_analytics", "arguments": { "serviceId": "…" } }

provision_analytics creates a first-party analytics website, injects two runtime env vars (TANDEM_ANALYTICS_WEBSITE_ID, TANDEM_ANALYTICS_SCRIPT_URL), and returns the exact <script> tag to add to the app’s HTML <head>. It is idempotent — re-enabling repairs the env vars and returns the existing site.

Add the returned tag to your pages (or fetch it again later with get_analytics_install_snippet). Because the script is served first-party, it survives most ad-blockers. list_analytics shows a project’s analytics sites.

External sites (hosted anywhere)

Analytics does not require Tandem to host the site. provision_external_analytics creates a standalone site in a project, keyed by the site’s domain:

{ "name": "provision_external_analytics", "arguments": { "projectId": "…", "domain": "docs.example.com" } }

The returned <script> tag is the whole integration — paste it into the external site’s <head>; there is no service and no env vars. Query it with the read tools below via siteId (from list_analytics).

The domain is the site’s identity, not an ingest filter. The tag records traffic from every host it is embedded on — www.example.com and the apex need no second site. Segment by hostname with the host filter/breakdown, or restrict collection client-side with the data-domains attribute (below).

Moving an app off Tandem? convert_analytics_to_external rebinds a service’s site as external with the same website id and script host — the already-installed tag keeps working and the full history is preserved. delete_service does this automatically by default (analytics: "keep"); pass analytics: "delete" to tear the data down instead.

First-party serving on your own domain

On an external site, the assigned analytics-*.launchtandem.com script host is third-party from the visitor’s browser, so strict ad-blockers may drop it. Upgrade to first-party serving:

{ "name": "set_analytics_custom_host", "arguments": { "siteId": "…" } }

Defaults to analytics.<the site's domain> (pass host to override). If the domain’s DNS zone is hosted on Tandem (same org), the record publishes automatically; otherwise the response returns the exact A/CNAME record to add at your DNS provider. Then:

{ "name": "verify_analytics_custom_host", "arguments": { "siteId": "…" } }

Verification also happens automatically — a background sweep rechecks pending hosts every minute and activates them as soon as DNS resolves, so calling verify is just the instant-feedback path. On success the route publishes, the TLS certificate issues automatically, and the install snippet switches to the custom host — update the tag on your pages to complete the upgrade. The assigned host keeps serving forever, so tags installed with either host continue to work; remove_analytics_custom_host reverses the upgrade the same way. Propagation before verify passes: minutes for external DNS, up to ~3h for zones hosted here (secondary-nameserver lag).

Tag attributes & custom events

The tag is the Umami tracker; the attributes agents typically need:

  • data-domains="example.com,www.example.com" — only collect on these hostnames (recommended, keeps localhost/dev traffic out).
  • data-auto-track="false" — disable automatic pageview/SPA-route tracking; call umami.track() yourself. By default the tracker auto-tracks history/pushState navigation, so SPAs work with no extra wiring.
  • Custom events: add data-umami-event="signup" (plus optional data-umami-event-* properties) to any element, or call umami.track('signup', { plan: 'pro' }) from JS. Events appear in get_analytics_breakdown with type: "event" and in the event filter.

Query the data

All query tools address a site by serviceId (service-bound) or siteId (any site, including external) and take a window — either range (24h/7d/30d/90d, default 7d) or explicit startTime+endTime (ISO 8601) — plus optional filters (referrer, country, device, url, …). Analytics must be enabled first.

Tool Answers Notable params
get_analytics_summary Headline totals — pageviews, visitors, visits, bounces (+ bounceRate, avgVisitSeconds), each vs the previous equal window. range / startTime + endTime, filters
get_analytics_breakdown “Where is traffic from / what’s popular” — top values for one axis. type (referrer / country / region / city / url / device / browser / os / event), limit
get_analytics_timeseries Trends over time — pageviews + sessions bucketed by unit. unit (hour/day/month), timezone
get_analytics_realtime Visitors active right now (~last 5 min).
get_analytics_markers “What changed after this deploy?” — timeline of deploys + domain/DNS changes from the audit log. range (works with or without tags enabled)

Pair get_analytics_timeseries with get_analytics_markers to correlate traffic shifts with deploys.

{ "name": "get_analytics_breakdown",
  "arguments": { "serviceId": "…", "type": "referrer", "range": "30d", "limit": 10 } }

Disable it

{ "name": "disable_analytics", "arguments": { "serviceId": "…" } }

Removes the injected analytics env vars and deletes the analytics website and its data. Works with siteId too (external sites). The always-on server-side baseline is unaffected. To keep the data when a service goes away, use convert_analytics_to_external instead.