This is a submission for Weekend Challenge: Passion Edition
What I Built
InsightsTrack is a self-hosted, privacy-first web analytics platform — the open-source alternative to Google Analytics. No cookies, no consent banner, no data selling. You run it on your own server and get real-time visitors, funnels, heatmaps, Core Web Vitals, and 17+ analytics views.
But the part I'm passionate about — and the reason I'm submitting it here — is Pulse: a built-in AI analyst that turns your analytics into a conversation.
Instead of hunting through dashboards, you just ask:
"What were my top pages last week?"
"Where is my traffic coming from?"
"Where are users dropping off in the funnel?"
Pulse answers in plain English, with real charts, tables, and CSV exports — every number backed by a live tool call, never hallucinated.
And here's the passion project twist: Pulse isn't locked inside the dashboard. The same analytics tools are exposed over MCP (Model Context Protocol), so I can open Claude Desktop, Cursor, or any MCP client and ask it about my website traffic — and it queries InsightsTrack directly.
My goal: make your own analytics as easy to query as sending a text message — and make that capability portable to every AI tool you already use.
Demo
🔗 Live app: https://insightstrack.dev
▶️ Video walkthrough: https://youtu.be/LGZw8temtPY
What to show in the demo:
- Open the dashboard, click Ask Pulse, type "top pages last 7 days" → watch it stream an answer + render a table with a CSV button and an Open pages → deep link.
- Switch the same result between table / bar / line / donut with one click.
- Open Claude Desktop → ask "which sites do I have in InsightsTrack, and what's my top traffic source this month?" → it calls the MCP tools and answers with live data.
Code
GitHub: https://github.com/NishikantaRay/InsightTrack
NishikantaRay
/
InsightTrack
Self-hosted, privacy-first web analytics — the open-source alternative to Google Analytics
📊 InsightsTrack
Self-hosted, privacy-first web analytics — the open-source alternative to Google Analytics
Track visitors, pageviews, conversions, heatmaps, and Core Web Vitals — all on your own server. No cookies, no consent banners, no data selling. Deploy in under 15 minutes.
Live Demo · Quick Start · Features · Deploy · Docs
What is InsightsTrack?
InsightsTrack is a complete, production-grade web analytics platform you run yourself. It gives you the depth of Google Analytics 4 — real-time visitors, traffic sources, funnels, heatmaps, Web Vitals — without sending a single byte of your visitors' data to a third party.
Why teams choose it:
- 🔒 Privacy by design — no cookies, no fingerprinting, anonymous visitor IDs, GDPR-compliant, DNT/GPC honored.
- ⚡ Fast at any scale — a dual-database design (PostgreSQL for writes, DuckDB for reads) answers 90-day queries in under 100 ms even over millions of events.
- 🧩 Everything in one place —…
The parts most relevant to this submission:
-
MCP tool registry —
analytics-db/src/mcp/tools/registry.js(17 read-only tools) -
MCP protocol handler (JSON-RPC 2.0, remote Streamable-HTTP transport) —
analytics-db/src/mcp/protocol.js -
The stdio bridge for local clients —
mcp-server/src/index.js -
The AI loop + BYO-key provider layer (Anthropic / OpenAI / Gemini) —
analytics-db/src/routes/assistant.jsandanalytics-db/src/mcp/llm/provider.js
How I Built It
The dual-database core
InsightsTrack writes every tracking event to PostgreSQL and streams it into DuckDB, an embedded columnar engine. All analytics reads hit DuckDB — 90-day queries answer in tens of milliseconds, in-process, with no separate query cluster. Writes go to Postgres, reads come from DuckDB. That separation is what makes it fast and cheap to self-host.
Pulse: one tool registry, two front doors
The interesting decision was not building the AI assistant and the MCP integration as two separate things. There's one registry of 17 read-only tools (get_kpi, get_top_pages, get_funnel, get_realtime, list_sites, …). That single registry powers:
- The in-dashboard Pulse panel — a streaming chat loop that lets an LLM call the tools and renders each result as a chart/table/KPI card.
- MCP — the exact same tools, exposed to external clients.
Because both surfaces share the registry, an answer is consistent everywhere and I only maintain the tools once.
MCP: two transports
This is the connector heart of the project. I implemented both MCP transports:
-
Remote (Streamable-HTTP) —
POST /api/mcp/httpspeaks JSON-RPC 2.0 (initialize/tools/list/tools/call). Clients that support remote MCP connect with just a URL + a bearer connect token. Nothing to install. -
Local (stdio bridge) — a tiny
mcp-serverprocess (the only place the real@modelcontextprotocol/sdkis used) runs next to the client, speaks stdio, and proxies every call over HTTP to the API. It never touches the database — which keeps per-user site scoping and DuckDB's single-writer lock intact.
Connect tokens are scoped JWTs (scope: 'mcp', with jti so they can be revoked one-by-one), so a leaked token from one laptop can be killed without affecting others. All tools are read-only — the AI can query but can never change or delete data.
Bring-your-own-key, encrypted at rest
Pulse runs on your model key — Anthropic (Claude), OpenAI (GPT), or Google (Gemini). Keys are stored AES-256-GCM encrypted (dependency-free node:crypto, key derived from the server secret) and never leave your server. Precedence is BYO-key first, server key as fallback — so a self-hoster can either let every user bring their own, or set one server key so it "just works."
Result cards that do real work
Every tool returns a structured envelope — { summary, data, render, download, deepLink }. The dashboard turns that into a card with a CSV export, a view switcher (table ↔ bar ↔ line ↔ donut, client-side, no re-query), and a deep link to the matching dashboard page. Over MCP, the same envelope ships as structuredContent so agent clients get the data too.
The honest hard part
Getting Gemini to work over its OpenAI-compatible endpoint surfaced a subtle streaming bug: OpenAI streams tool-call arguments as fragments to concatenate, but Gemini sends the complete arguments object per delta (and sometimes repeats it) — naive concatenation produced invalid JSON like {}{} that Gemini then rejected on the next round. The fix: if an incoming fragment is already valid JSON, treat it as the whole value instead of appending. One provider quirk, but it's the difference between "Gemini works" and a mysterious INVALID_ARGUMENT.
Prize Categories
Best Use of Google AI — Pulse supports Google Gemini as a first-class model provider for the AI analyst. Bring a Gemini API key in Settings and every plain-English question about your analytics is answered by Gemini calling InsightsTrack's 17 read-only tools — in the dashboard and over MCP from Claude Desktop / Cursor.
Built with ❤️ as an open-source project. Try it, self-host it, or connect it to your favorite AI client. Feedback and stars welcome → https://github.com/NishikantaRay/InsightTrack




Top comments (0)