Skip to content

MCP HTML Reports ​

Interactive, self-contained HTML reports generated from Cavai aggregation data. Built as part of the cavai-mcp server (src/tools/reports/html-report.ts).

Architecture ​

convertToHtml() is a pure function:

convertToHtml(bucketsData, resourceNames, flowData?, dailyFlowData?, metadata?)
  → { html: string, rowCount: number }

It takes raw aggregation data and produces a single HTML file with no external dependencies (except Google Fonts and Chart.js CDN). Everything — styles, scripts, data, assets — is embedded inline.

Data flow ​

MCP tool call (cavai_make_report with format=html)
  → fetches buckets, flow, daily-flow from Aggregation API
  → passes to convertToHtml()
  → returns self-contained HTML string
  → served as download link (24h expiry)

Report structure ​

Four tabs:

TabContent
OverviewKPI cards, engagement funnel, detailed metrics, mini impressions chart
TimelineImpressions + interaction rate over time, viewability chart (if available)
FlowConversation flow tree visualization with indent levels
GlossaryMetric definitions (auto-filtered to only show metrics present in report)

Key features ​

Active campaign day filtering ​

Days with < 500 impressions are automatically excluded. This removes pre-launch test traffic and post-campaign tail. The date range in the header reflects the actual active period, not the raw query dates.

Threshold is set as ACTIVE_DAY_THRESHOLD = 500 in html-report.ts.

Viewability smoothing ​

Days with < 100 impressions get null viewability (hidden from chart) to avoid misleading spikes. Charts use spanGaps: true to bridge gaps.

Dynamic glossary ​

Only metrics with non-zero values appear in the glossary. No video metrics shown for non-video creatives, no "Avg. Rounds" unless replay behavior is detected, etc.

Inline tooltips ​

All KPI labels and glossary-referenced terms have hover tooltips. Implemented as a single shared <div class="tip-popup"> appended to <body> and positioned via JS on mouseenter. This avoids all stacking context / z-index issues.

Replay insight box ​

When reached_end > continued (indicating users replay the experience), a contextual insight box appears below the flow tree explaining the pattern and showing the avg. rounds.

Tab animations ​

Sliding pill indicator (.tab-indicator) moves between tabs using getBoundingClientRect and cubic-bezier transition. Tab content slides in from the right with a CSS animation.

CSV download ​

"Download CSV" button embeds raw bucket data as a JS object (RAW_BUCKETS) and generates a CSV file client-side via downloadCSV().

beforeprint event handler shows all tab views and calls chart.resize() on all Chart.js instances so hidden charts render correctly. afterprint restores the original view.

Seasonal theming ​

See seasonal-assets.md.

Responsive / mobile ​

Layout uses clamp() for font sizes, flex-wrap for KPI cards, and avoids horizontal scroll on mobile.

Files ​

FilePurpose
src/tools/reports/html-report.tsMain report generator (~1500 lines)
src/tools/reports/make-report.tsMCP tool handler (CSV/Excel/HTML dispatch)
src/tools/reports/bubbles-asset.tsBase64 Bubbles winking mascot (legacy fallback)
src/tools/reports/bubbles-normal.tsBase64 Bubbles normal mascot (default)
src/tools/reports/bubbles-christmas.tsBase64 Bubbles with Santa hat
src/tools/reports/bubbles-halloween.tsBase64 Bubbles halloween variant

Test data ​

Test xlsx files are stored in /Users/nicolay/Cavai/MCPReports/. Extracted JSON test data: /tmp/bmw_report_data.json and /tmp/puma_report_data.json.

To regenerate a report locally:

bash
cd cavai-mcp
npm run build
node -e "
const { convertToHtml } = require('./build/tools/reports/html-report.js');
const fs = require('fs');
const data = JSON.parse(fs.readFileSync('/tmp/puma_report_data.json', 'utf8'));
const result = convertToHtml(data.bucketsData, data.resourceNames, data.flowData, data.dailyFlowData, data.metadata);
fs.writeFileSync('report.html', result.html);
"

The input JSON must match the BucketsResponse / FlowResponse types from src/api/types.ts. Key field: buckets use time (not time_from).

Console utilities ​

In the browser console:

  • setSeason('christmas') — swap mascot, particles, and glow to any theme
  • Available themes: winter, spring, summer, autumn, new-year, valentines, easter, may17, midsummer, halloween, christmas, normal

Internal documentation