Skip to content

feat(stats.js): WebMCP dashboard opt-in + load-once guards - #447

Merged
ralyodio merged 1 commit into
masterfrom
feat/stats-webmcp-toggle
Nov 11, 2026
Merged

ralyodio merged 1 commit into
masterfrom
feat/stats-webmcp-toggle

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Follow-up to #446.

  • Dashboard opt-in: projects.webmcp_enabled / webmcp_config (migration 20261111100000, already applied to prod by hand + pgrst reload), Stats -> WebMCP plugin toggle, public GET /api/tracker/v1/webmcp?site= (only whitelisted embed options). stats.js asks only when the browser ships WebMCP and the tag has no data-webmcp; data-webmcp="off" always wins; tag attributes override dashboard config.
  • Load once: stats.js runs once per site per page (a duplicated tag used to double every pageview and click); the embed is injected at most once, never when the page has its own copy, including a module tag that has not run yet. Package side: @profullstack/webmcp 0.2.1 never registers a tool twice across copies.

Verified in Chrome 155 (WebMCPTesting), rendered stats.js, 6 scenarios all pass: attr, dashboard-only, double tag (1 pageview, 1 embed fetch), off beats dashboard, site's own embed (1 fetch), not enabled (no fetch). Contract assertions checked against the rendered script; changed TS/TSX transpiled.

🤖 Generated with Claude Code

- projects.webmcp_enabled / webmcp_config (migration applied by hand) +
  Stats -> WebMCP plugin toggle; GET /api/tracker/v1/webmcp?site= serves the
  public config. stats.js asks only when the browser ships WebMCP and the tag
  has no data-webmcp; data-webmcp="off" always wins.
- stats.js runs once per site per page (a duplicated tag no longer doubles
  pageviews/clicks); the WebMCP embed is injected at most once and never when
  the page has its own copy (incl. a not-yet-run module tag).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ralyodio
ralyodio merged commit 4c69028 into master Nov 11, 2026
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

50 finding(s)

HIGH/CRITICAL: 2 | MEDIUM: 30 | LOW: 18

Severity Rule Location
HIGH tls-verification-disabled lib/onion.ts:50
HIGH secret-generic-credential lib/sp/platforms/facebook.ts:32
MEDIUM js-unescaped-html-sink app/(app)/dashboard/admin/email-broadcast/EmailBroadcastForm.tsx:125
MEDIUM js-unescaped-html-sink app/(app)/dashboard/projects/[id]/autoblog/articles/[articleId]/page.tsx:214
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:74
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:102
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:177
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:181
MEDIUM js-unescaped-html-sink app/c/[project]/[slug]/page.tsx:76
MEDIUM js-unescaped-html-sink app/c/[project]/page.tsx:58
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:228
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:285
MEDIUM js-unescaped-html-sink app/layout.tsx:139
MEDIUM js-open-redirect app/login/form.tsx:40
MEDIUM js-unescaped-html-sink app/r/[token]/page.tsx:164
MEDIUM js-open-redirect app/signup/form.tsx:65
MEDIUM js-unescaped-html-sink app/surveys/[slug]/invite/page.tsx:68
MEDIUM js-unescaped-html-sink components/json-ld.tsx:8
MEDIUM js-unescaped-html-sink components/pit-footer.tsx:17
MEDIUM js-unescaped-html-sink components/report/markdown-view.tsx:16
MEDIUM js-unescaped-html-sink lib/careers/page-templates.ts:198
MEDIUM js-dynamic-code-execution lib/crawl-limits.ts:67
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:41
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:324
MEDIUM redos-nested-quantifier lib/lx/articleGen.ts:100
MEDIUM redos-nested-quantifier lib/tracker/agent-gate.ts:61
MEDIUM sh-predictable-temp-path ops/selfhost/server/setup-supabase.sh:218
MEDIUM sql-template-interpolation scripts/detect-slot-themes.ts:31
MEDIUM sql-template-interpolation scripts/purge-constructed-keywords.ts:163
MEDIUM sql-template-interpolation scripts/purge-offniche-keywords.ts:124
MEDIUM js-dynamic-code-execution scripts/test-crawl-limits.mjs:14
MEDIUM js-dynamic-code-execution scripts/test-crawl-limits.mjs:24
LOW secret-generic-credential app/(marketing)/docs/autoblog-webhook/page.tsx:156
LOW secret-generic-credential lib/sealed/migrate.ts:38
LOW secret-generic-credential lib/sp/platforms/linkedin.ts:25
LOW secret-generic-api-key tests/ads-generator-agent.test.ts:160
LOW secret-generic-api-key tests/ads-generator-agent.test.ts:187
LOW js-dynamic-code-execution tests/careers-page-templates.test.ts:21
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:19
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:69
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:51
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:52
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:20
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:24
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:25
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:26
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:31
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:35
LOW secret-generic-credential tests/contract/posthog-integration.test.ts:13
LOW secret-generic-credential tests/lead-campaign.test.ts:16

Snippets are redacted; ThreatCrush never prints matched credential material.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant