{
  "source_file": "0930",
  "source_platform": "Dev.to",
  "author": "Nishikanta Ray",
  "title_listed": "InsightsTrack + Pulse: I taught Claude Desktop to read my web analytics (via MCP)",
  "keywords": "",
  "domain": "大模型算法",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://dev.to/nishikantaray/insightstrack-pulse-i-taught-claude-desktop-to-read-my-web-analytics-via-mcp-13bd",
  "publish_date": "2026-07-12",
  "corpus_stage": "",
  "notes": "",
  "id": "84227dacc32b",
  "collected_at": "2026-10-02T14:04:48",
  "title": "InsightsTrack + Pulse: I taught Claude Desktop to read my web analytics (via MCP) - DEV Community",
  "body_markdown": "This is a submission for\nWeekend Challenge: Passion Edition\nWhat I Built\nInsightsTrack\nis 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.\nBut the part I'm passionate about — and the reason I'm submitting it here — is\nPulse\n: a built-in\nAI analyst\nthat turns your analytics into a conversation.\nInstead of hunting through dashboards, you just\nask\n:\n\"What were my top pages last week?\"\n\"Where is my traffic coming from?\"\n\"Where are users dropping off in the funnel?\"\nPulse answers in plain English, with\nreal charts, tables, and CSV exports\n— every number backed by a live tool call, never hallucinated.\nAnd here's the passion project twist: Pulse isn't locked inside the dashboard. The\nsame analytics tools are exposed over MCP (Model Context Protocol)\n, so I can open\nClaude Desktop, Cursor, or any MCP client\nand ask\nit\nabout my website traffic — and it queries InsightsTrack directly.\nMy goal:\nmake your own analytics as easy to query as sending a text message — and make that capability portable to every AI tool you already use.\nDemo\n🔗 Live app:\nhttps://insightstrack.dev\n▶️ Video walkthrough:\nhttps://youtu.be/LGZw8temtPY\nWhat to show in the demo:\nOpen the dashboard, click\nAsk Pulse\n, type\n\"top pages last 7 days\"\n→ watch it stream an answer + render a table with a\nCSV\nbutton and an\nOpen pages →\ndeep link.\nSwitch the same result between\ntable / bar / line / donut\nwith one click.\nOpen\nClaude Desktop\n→ ask\n\"which sites do I have in InsightsTrack, and what's my top traffic source this month?\"\n→ it calls the MCP tools and answers with live data.\nCode\nGitHub:\nhttps://github.com/NishikantaRay/InsightTrack\nNishikantaRay\n/\nInsightTrack\nSelf-hosted, privacy-first web analytics — the open-source alternative to Google Analytics\n📊 InsightsTrack\nSelf-hosted, privacy-first web analytics — the open-source alternative to Google Analytics\nTrack visitors, pageviews, conversions, heatmaps, and Core Web Vitals — all on your own server.\nNo cookies, no consent banners, no data selling. Deploy in under 15 minutes.\nLive Demo\n·\nQuick Start\n·\nFeatures\n·\nDeploy\n·\nDocs\nWhat is InsightsTrack?\nInsightsTrack 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.\nWhy teams choose it:\n🔒\nPrivacy by design\n— no cookies, no fingerprinting, anonymous visitor IDs, GDPR-compliant, DNT/GPC honored.\n⚡\nFast at any scale\n— a dual-database design (PostgreSQL for writes, DuckDB for reads) answers 90-day queries in under 100 ms even over millions of events.\n🧩\nEverything in one place\n—…\nView on GitHub\nThe parts most relevant to this submission:\nMCP tool registry\n—\nanalytics-db/src/mcp/tools/registry.js\n(17 read-only tools)\nMCP protocol handler\n(JSON-RPC 2.0, remote Streamable-HTTP transport) —\nanalytics-db/src/mcp/protocol.js\nThe stdio bridge\nfor local clients —\nmcp-server/src/index.js\nThe AI loop + BYO-key provider layer\n(Anthropic / OpenAI / Gemini) —\nanalytics-db/src/routes/assistant.js\nand\nanalytics-db/src/mcp/llm/provider.js\nHow I Built It\nThe dual-database core\nInsightsTrack writes every tracking event to\nPostgreSQL\nand streams it into\nDuckDB\n, an embedded columnar engine. All analytics\nreads\nhit 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\nand\ncheap to self-host.\nPulse: one tool registry, two front doors\nThe interesting decision was\nnot\nbuilding the AI assistant and the MCP integration as two separate things. There's\none registry of 17 read-only tools\n(\nget_kpi\n,\nget_top_pages\n,\nget_funnel\n,\nget_realtime\n,\nlist_sites\n, …). That single registry powers:\nThe in-dashboard Pulse panel\n— a streaming chat loop that lets an LLM call the tools and renders each result as a chart/table/KPI card.\nMCP\n— the exact same tools, exposed to external clients.\nBecause both surfaces share the registry, an answer is consistent everywhere and I only maintain the tools once.\nMCP: two transports\nThis is the connector heart of the project. I implemented\nboth\nMCP transports:\nRemote (Streamable-HTTP)\n—\nPOST /api/mcp/http\nspeaks JSON-RPC 2.0 (\ninitialize\n/\ntools/list\n/\ntools/call\n). Clients that support remote MCP connect with just a URL + a bearer connect token. Nothing to install.\nLocal (stdio bridge)\n— a tiny\nmcp-server\nprocess (the only place the real\n@modelcontextprotocol/sdk\nis used) runs next to the client, speaks stdio, and\nproxies every call over HTTP to the API\n. It never touches the database — which keeps per-user site scoping and DuckDB's single-writer lock intact.\nConnect tokens are scoped JWTs (\nscope: 'mcp'\n, with\njti\nso they can be revoked one-by-one), so a leaked token from one laptop can be killed without affecting others. All tools are\nread-only\n— the AI can query but can never change or delete data.\nBring-your-own-key, encrypted at rest\nPulse runs on\nyour\nmodel key —\nAnthropic (Claude), OpenAI (GPT), or Google (Gemini)\n. Keys are stored\nAES-256-GCM encrypted\n(dependency-free\nnode:crypto\n, key derived from the server secret) and\nnever leave your server\n. 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.\"\nResult cards that do real work\nEvery tool returns a structured envelope —\n{ summary, data, render, download, deepLink }\n. The dashboard turns that into a card with a\nCSV export\n, a\nview switcher\n(table ↔ bar ↔ line ↔ donut, client-side, no re-query), and a\ndeep link\nto the matching dashboard page. Over MCP, the same envelope ships as\nstructuredContent\nso agent clients get the data too.\nThe honest hard part\nGetting Gemini to work over its OpenAI-compatible endpoint surfaced a subtle streaming bug: OpenAI streams tool-call arguments as\nfragments to concatenate\n, but Gemini sends the\ncomplete\narguments object per delta (and sometimes repeats it) — naive concatenation produced invalid JSON like\n{}{}\nthat 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\nINVALID_ARGUMENT\n.\nPrize Categories\nBest Use of Google AI\n— Pulse supports\nGoogle Gemini\nas 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\nand\nover MCP from Claude Desktop / Cursor.\nBuilt with ❤️ as an open-source project. Try it, self-host it, or connect it to your favorite AI client. Feedback and stars welcome →\nhttps://github.com/NishikantaRay/InsightTrack",
  "metadata": {
    "title": "InsightsTrack + Pulse: I taught Claude Desktop to read my web analytics (via MCP) - DEV Community",
    "description": "This is a submission for Weekend Challenge: Passion Edition           What I Built   InsightsTrack is... Tagged with mcp, ai, webdev, opensource.",
    "keywords": "mcp, ai, webdev, opensource, software, coding, development, engineering, inclusive, community",
    "author": null,
    "og:type": "article",
    "og:url": "https://dev.to/nishikantaray/insightstrack-pulse-i-taught-claude-desktop-to-read-my-web-analytics-via-mcp-13bd",
    "og:title": "InsightsTrack + Pulse: I taught Claude Desktop to read my web analytics (via MCP)",
    "og:description": "This is a submission for Weekend Challenge: Passion Edition           What I Built   InsightsTrack is...",
    "og:site_name": "DEV Community",
    "og:image": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F17ma01bulzk1j0w9w31s.png",
    "twitter:site": "@thepracticaldev",
    "twitter:creator": "@",
    "twitter:title": "InsightsTrack + Pulse: I taught Claude Desktop to read my web analytics (via MCP)",
    "twitter:description": "This is a submission for Weekend Challenge: Passion Edition           What I Built   InsightsTrack is...",
    "twitter:card": "summary_large_image",
    "twitter:widgets:new-embed-design": "on",
    "twitter:image:src": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F17ma01bulzk1j0w9w31s.png"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [],
  "images": [
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwgqolbojsrfgik6mx6jj.png",
      "bytes": 85034,
      "reused": true,
      "local_path": "attachments/84227dacc32b/images/002.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwgqolbojsrfgik6mx6jj.png",
      "alt": "insightstracks",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg",
      "bytes": 797,
      "reused": false,
      "local_path": "attachments/84227dacc32b/images/body-008554afb52ac122443e.svg",
      "src": "https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg",
      "alt": "GitHub logo",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://camo.githubusercontent.com/f43a51a164cf5f983be3c12a2c0ae6d92e5681ab465291ee1451bee73924d627/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4f70656e253230536f757263652d4d49542d363336366631",
      "bytes": 1298,
      "reused": false,
      "local_path": "attachments/84227dacc32b/images/body-e98e4f2a5ccac41970d5.img",
      "src": "https://camo.githubusercontent.com/f43a51a164cf5f983be3c12a2c0ae6d92e5681ab465291ee1451bee73924d627/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4f70656e253230536f757263652d4d49542d363336366631",
      "alt": "Open Source",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://camo.githubusercontent.com/7ad148220f3c4c917ac675fc5994ff877426e174f08cf70e4c29ddad1cfa861a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53656c662d2d486f737465642d2545322539432539332d313062393831",
      "bytes": 1292,
      "reused": false,
      "local_path": "attachments/84227dacc32b/images/body-fdd53c95ddc1bb34cf3c.img",
      "src": "https://camo.githubusercontent.com/7ad148220f3c4c917ac675fc5994ff877426e174f08cf70e4c29ddad1cfa861a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53656c662d2d486f737465642d2545322539432539332d313062393831",
      "alt": "Self-Hosted",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://camo.githubusercontent.com/be3fd7535ed16cf4d4fc507342a2b1f744fc69d66daccd304a31c7027b988aa0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f436f6f6b6965732d302d656634343434",
      "bytes": 1262,
      "reused": false,
      "local_path": "attachments/84227dacc32b/images/body-c3fbbb83dd705cb9e8fa.img",
      "src": "https://camo.githubusercontent.com/be3fd7535ed16cf4d4fc507342a2b1f744fc69d66daccd304a31c7027b988aa0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f436f6f6b6965732d302d656634343434",
      "alt": "No Cookies",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://camo.githubusercontent.com/38e45558ae5f1311ea0e03fc6cb86079939a31f9fd8fd1bdf5aca997fc50a64c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f526561637425323031382532302543322542372532304578707265737325323034253230254332254237253230506f737467726553514c2532302543322542372532304475636b44422d316131643237",
      "bytes": 1193,
      "reused": true,
      "local_path": "attachments/84227dacc32b/images/003.jpg",
      "src": "https://camo.githubusercontent.com/38e45558ae5f1311ea0e03fc6cb86079939a31f9fd8fd1bdf5aca997fc50a64c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f526561637425323031382532302543322542372532304578707265737325323034253230254332254237253230506f737467726553514c2532302543322542372532304475636b44422d316131643237",
      "alt": "Stack",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://camo.githubusercontent.com/05d8c2ed8cfde259492aa9dd3bff228814b32829af1a581cc12ec43ea6a62d08/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f25453225393925413525323053706f6e736f722d4e697368696b616e74615261792d6561346161613f6c6f676f3d67697468756273706f6e736f7273",
      "bytes": 2556,
      "reused": true,
      "local_path": "attachments/84227dacc32b/images/005.jpg",
      "src": "https://camo.githubusercontent.com/05d8c2ed8cfde259492aa9dd3bff228814b32829af1a581cc12ec43ea6a62d08/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f25453225393925413525323053706f6e736f722d4e697368696b616e74615261792d6561346161613f6c6f676f3d67697468756273706f6e736f7273",
      "alt": "Sponsor",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FNishikantaRay%2FInsightTrack%2FHEAD%2Fscreenshots%2F11-dashboard-full.png",
      "bytes": 219794,
      "reused": true,
      "local_path": "attachments/84227dacc32b/images/007.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FNishikantaRay%2FInsightTrack%2FHEAD%2Fscreenshots%2F11-dashboard-full.png",
      "alt": "InsightsTrack Dashboard",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsml3uqpbp6xivv3j6n23.png",
      "bytes": 72732,
      "reused": true,
      "local_path": "attachments/84227dacc32b/images/008.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsml3uqpbp6xivv3j6n23.png",
      "alt": "insightstracks",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiue392cu12z7v4ckqmmn.png",
      "bytes": 51514,
      "reused": true,
      "local_path": "attachments/84227dacc32b/images/009.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiue392cu12z7v4ckqmmn.png",
      "alt": "insightstracks",
      "downloaded": true
    }
  ],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://dev.to/challenges/weekend-2026-07-09",
      "label": "Weekend Challenge: Passion Edition",
      "type": "a"
    },
    {
      "href": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwgqolbojsrfgik6mx6jj.png",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://github.com/NishikantaRay/InsightTrack",
      "label": "https://github.com/NishikantaRay/InsightTrack",
      "type": "a"
    },
    {
      "href": "https://github.com/NishikantaRay",
      "label": "NishikantaRay",
      "type": "a"
    },
    {
      "href": "https://github.com/NishikantaRay/InsightTrack/LICENSE",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://github.com/sponsors/NishikantaRay",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://github.com/NishikantaRay/InsightTrack/docs/",
      "label": "Docs",
      "type": "a"
    },
    {
      "href": "https://github.com/NishikantaRay/InsightTrack/screenshots/11-dashboard-full.png",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsml3uqpbp6xivv3j6n23.png",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiue392cu12z7v4ckqmmn.png",
      "label": "",
      "type": "a"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:49:51Z",
  "source_url": "https://dev.to/nishikantaray/insightstrack-pulse-i-taught-claude-desktop-to-read-my-web-analytics-via-mcp-13bd",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 7082,
  "expected_body_images": 10,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/84227dacc32b/pages/source.html",
  "local_html": "attachments/84227dacc32b/pages/article.html",
  "body_base_path": "attachments/84227dacc32b/pages/",
  "body_markdown_path": "attachments/84227dacc32b/pages/article.md"
}