webaudit-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@webaudit-mcp Audit https://web.dev for tracking tags and performance"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
webaudit-mcp
MCP server that audits any public web page for tracking tags, duplicate pixels, page weight, header hygiene, and lab performance, from inside Claude Code or Claude Desktop.
![]()
Screenshot | What it shows |
Real | |
The same smoke script pointed at |
Both screenshots are captured from real sessions on 2026-07-19 against the current build. The second one is a selling point, not an error state: point any tool at a private, broken, or non-HTML URL and you get a structured, actionable error, never a crash or a hang.
Error: Refusing to fetch "localhost": local targets are blocked (SSRF protection).Quickstart
Requires Node 20 or newer. From a clean clone:
git clone https://github.com/lpecom/webaudit-mcp.git webaudit-mcp
cd webaudit-mcp
npm install
npm run build # compiles TypeScript into dist/
npm test # 60 tests across 6 files, all offline, ~300 msStarting the server directly prints one line to stderr and then waits for an MCP client on stdio (stop it with Ctrl+C):
node dist/index.js
# webaudit-mcp running on stdio (7 tools registered)Claude Code:
claude mcp add webaudit -- node /absolute/path/to/webaudit-mcp/dist/index.jsClaude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"webaudit": {
"command": "node",
"args": ["/absolute/path/to/webaudit-mcp/dist/index.js"]
}
}
}Then ask: "Audit https://example.com" or "What tracking is installed on web.dev?"
Optional, only needed for measure_performance (lab Core Web Vitals):
npx playwright install chromiumTo see every tool fire against a live site without any MCP client:
npm run smoke -- https://web.devNo API keys, no environment variables, no configuration files.
Related MCP server: Browsershot MCP
Why I built this
I have spent years on both sides of this problem: running Google Ads, Meta, and Taboola campaigns, and building the tracking under them myself (GA4, GTM, server-side events). Broken tracking silently burns ad budgets: a duplicate GA4 config doubles every metric, a leftover GTM container re-fires conversions, and nobody notices until the numbers stop making sense. The tools to check for it are scattered across browser extensions, PageSpeed, and manual view-source archaeology.
This server puts the whole check behind one conversational interface, built the way I think an MCP server should be: strict input validation, SSRF-guarded fetching, structured errors instead of crashes, and honest labels on every number (lab vs field, measured vs estimated). The report is written for a developer and a media buyer: which analytics and pixel IDs are installed and where, whether anything double-fires, what the page weighs, and how the response headers look.
Tool reference
All 7 tools take a single url argument (absolute http/https URL) and return Markdown.
Tool | What it does |
| One-shot report: tracking tags, duplicate risks, header hygiene, resource counts. Fast, no browser. |
| Static HTML scan for GA4, GTM, legacy UA, Google Ads, Meta Pixel, TikTok, LinkedIn Insight, Hotjar, Microsoft Clarity, server-side tagging, and consent platforms. Reports IDs and where each was found. |
| Flags double-initialized IDs, multiple GTM containers, GTM plus hardcoded GA4 double-fire patterns, duplicate pixel IDs, dead UA tags. |
| Headless-Chromium lab metrics: TTFB, FCP, LCP, CLS, load times, request count, transfer sizes. Clearly labeled lab data. |
| Counts and byte sizes of scripts, styles, images, fonts, and iframes referenced in the HTML; largest third-party scripts. |
| Caching, compression, HSTS, CSP, clickjacking, referrer policy. Informational grades with explanations, no synthetic scores. |
| Plain-language briefing on the tracking setup, written for a media buyer: what is installed, what it means for campaigns, consent posture, issues to raise. |
All example outputs below are real, captured 2026-07-19 against https://web.dev and trimmed for length.
audit_page
The one call to start with: combines the tracking scan, duplicate check, and header check in a single report.
# Audit of https://web.dev/
- HTTP status: 200
- HTML size: 130.7 KB
- Resources referenced in HTML: 14 (0 scripts, 4 stylesheets, 9 images, 0 fonts, 1 iframes; 5 third-party)
[tracking, duplicate, and header sections follow, same content as the
individual tools below, then pointers to the byte-level and lab tools]detect_tracking_tags
Static scan with evidence: every finding names the vendor, the extracted ID, and the exact place in the HTML that triggered it.
# Tracking tags on https://web.dev/
| Vendor | ID | Found in | Evidence |
|---|---|---|---|
| Google Tag Manager | GTM-MZWCJPP | inline script | `[], "gtm": [{"id": "GTM-MZWCJPP", "purpose": 0}], ...` |
## Distinct IDs
- Google Tag Manager: GTM-MZWCJPP
## Consent signals
- None detected. If this site serves EU users with these tags firing pre-consent,
that is a GDPR/ePrivacy problem.
_Static scan: 6 inline scripts and 0 external script tags inspected. Tags added
at runtime by GTM or a CMP are not visible to this scan._find_duplicate_tags
Checks for the patterns that double-count: the same GA4 ID initialized twice, multiple GTM containers, a hardcoded GA4 tag next to a GTM container, multiple Meta pixel IDs, the gtag.js loader included more than once, and leftover Universal Analytics tags. web.dev is clean:
# Duplicate-tag check for https://web.dev/
No duplicate IDs or double-fire patterns found across 3 detected tag reference(s).When something is found, each issue comes with a severity: HIGH (double-counting almost certain), MEDIUM (likely misconfiguration), INFO (verify manually).
measure_performance
Loads the page once in headless Chromium and reports what that load observed. Numbers vary run to run; the report says so.
# Lab performance metrics for https://web.dev/
**LAB DATA**: one headless-Chromium load from this machine. Not field data (no CrUX/RUM), no INP.
| Metric | Value |
|---|---|
| TTFB (time to first byte) | 495 ms |
| FCP (first contentful paint) | 764 ms |
| LCP (largest contentful paint) | 1.02 s |
| CLS (cumulative layout shift) | 0.088 |
| Load event | 2.34 s |
| Requests | 60 |
| Transferred (compressed) | 152.9 KB |
[transfer-by-initiator table and reference thresholds follow]analyze_page_weight
Static resource inventory with real byte measurements (capped, guarded requests; up to 30 resources, HEAD first, GET fallback):
# Page weight for https://web.dev/
- HTML document: 130.7 KB
- Resources referenced: 14 (0 scripts, 4 stylesheets, 9 images, 0 fonts, 1 iframes)
- Measured 14 of 14 resources: 1.42 MB totalNote the honest gap between this and measure_performance above: the static scan sees 14 referenced resources, the browser load makes 60 requests. JavaScript loads the rest, and each report tells you which view you are looking at.
check_headers
# Header check for https://web.dev/ (HTTP 200)
| Check | Grade | Value | Note |
|---|---|---|---|
| Cache-Control | [OK] | `no-cache, must-revalidate` | Caching policy declared. |
| Compression (Content-Encoding) | [OK] | `gzip` | Response compressed with gzip. |
| HSTS (Strict-Transport-Security) | [OK] | `max-age=63072000; includeSubdomains; preload` | HSTS present with a solid max-age. |
| Referrer-Policy | [MISSING] | n/a | Default referrer behavior leaks full URLs to third parties on some browsers. |
_Grades are informational statements about header presence, not a security score._tracking_summary
The same detection engine, rendered as a briefing for a non-developer. See the hero screenshot above for the full real output; the sections are: What is installed, What this means for media buying, Consent, Issues to raise.
How it works
src/
index.ts entry point: McpServer + stdio transport, registers the tools
tools.ts tool registration, zod input schemas, Markdown renderers,
error mapping (every failure becomes an isError result)
lib/
url-guard.ts SSRF checks: scheme, hostname denylist, private IP ranges, DNS
safe-fetch.ts hardened fetch: timeout, byte cap, manual redirects, content type
tracking.ts static tag detection (regex + node-html-parser) and duplicate analysis
page-weight.ts resource extraction and capped byte measurement
headers.ts response-header checks
performance.ts Playwright Chromium lab metrics
format.ts byte/ms formatting and grade markersSSRF protection is layered. url-guard runs two checks: a synchronous one (http/https only; localhost, .local, .internal, .home.arpa and similar hostnames refused; private, loopback, link-local, and CGNAT IP literals refused, for v4 and v6 including IPv4-mapped forms) and an asynchronous one that resolves the hostname with DNS and refuses if any returned address is private, so a public-looking hostname pointing at 192.168.x.x is also blocked. safe-fetch then follows redirects manually (max 5) and re-runs both checks on every hop, enforces a 15 s wall-clock timeout and a 5 MiB body cap, and rejects non-HTML content types for the HTML tools. Every outbound request in the codebase, including page-weight resource measurements, goes through this one client.
Static analysis vs browser analysis. Six of the seven tools parse the served HTML only: fast, no browser dependency, and honest about the boundary (every report footnotes that runtime-injected tags and JS-loaded resources are invisible to it). measure_performance is the one browser tool: it launches headless Chromium via Playwright, registers buffered PerformanceObservers before navigation, loads the page once, waits 3 s for LCP/CLS to settle, and reads the metrics out.
Graceful degradation without Chromium. Playwright is a devDependency and is imported dynamically only inside measure_performance. If the package or the browser binary is missing, the tool returns a structured error containing the exact fix (npx playwright install chromium) instead of crashing, and the other six tools are unaffected.
Tests
npm test runs 60 vitest tests in 6 files, entirely offline (network and DNS are mocked; nothing leaves the process):
url-guard.test.ts: scheme rejection, hostname denylist, private v4/v6 ranges including IPv4-mapped and CGNATsafe-fetch.test.ts: timeouts, byte-cap truncation, manual redirect following and re-validationtracking.test.ts: tag and consent detection plus duplicate analysis against synthetic fixtures (clean.html,duplicates.html,full-stack.html)page-weight.test.ts: resource extraction, third-party heuristic (includingco.uk-style domains), HEAD/GET measurement fallbackheaders.test.ts: grades for present, missing, and weak headerstools.test.ts: a real MCP client calling the real server over the SDK's in-memory transport, covering both success and error paths
npm run smoke -- <url> is the live counterpart: it drives the built stdio server with a real MCP client against a real site, on purpose.
Design decisions
Never throw from a handler. Every tool wraps its body in try/catch and converts
UrlGuardError,FetchError, andPerformanceUnavailableErrorinto MCPisErrorresults whose message says what to do next.Reports are Markdown a model can relay verbatim. The renderers produce tables and sections a client model can paste into an answer without rework, and each report ends with an honest footnote about what the scan could not see.
Bounded everything. 15 s request timeout, 5 MiB response cap, max 5 redirects, 30-resource measurement cap with concurrency 6, 10 s per-resource timeout, content-type enforcement for HTML tools. The numbers live as constants in
safe-fetch.tsandpage-weight.ts.Honest labels on every number. Lab data is stamped LAB DATA, static scans say they are static, the third-party classifier is documented as a heuristic in the report itself.
Playwright stays a devDependency so the core install is three runtime dependencies (
@modelcontextprotocol/sdk,node-html-parser,zod) and the browser tool degrades gracefully.
Built with the MCP TypeScript SDK (McpServer + registerTool, zod input schemas, stdio transport). Developed with Claude Code.
Limitations
Static scan.
detect_tracking_tags,find_duplicate_tags, andtracking_summaryread the served HTML only. Tags injected at runtime by GTM, a CMP after consent, or client-side routing are invisible to them. The reports say so explicitly.Lab data, not field data.
measure_performanceis one headless load from your machine: no INP (needs real interaction), no CrUX percentiles, and numbers vary run to run. Treat it as a smoke check, not a Core Web Vitals verdict.Single page scope. Every tool audits exactly the URL you give it. No crawling.
Third-party classification is a heuristic. Registrable-domain comparison without a full Public Suffix List;
foo.co.ukstyle domains are handled, exotic suffixes may misclassify.Transfer sizes can read 0 B. Cross-origin resources without
Timing-Allow-Originhide their size from the browser; page-weight measurements reflect what this client was served, which may differ from what your browser gets.Bot protection. Sites behind aggressive bot walls (Cloudflare challenges and similar) may serve this tool different HTML than they serve a real browser.
License
MIT license, see LICENSE.
Available Tools
7 toolsanalyze_page_weightAnalyze page weightA
Static page-weight analysis: counts and byte sizes of scripts, stylesheets, images, fonts and iframes referenced in the HTML, plus the largest third-party scripts. Measures up to 30 resources with capped, guarded requests.
Inputs: url (required).
audit_pageAudit a web pageA
One-shot audit of a URL: tracking tags, duplicate-tag risks, response-header hygiene, and resource counts, as a single readable report. Fast (no browser). For byte-level page weight use analyze_page_weight; for lab Core Web Vitals use measure_performance.
Inputs: url (required).
check_headersCheck response headersA
Inspect the page's HTTP response headers: caching policy, compression, HSTS, CSP, clickjacking protection, referrer policy, CDN presence. Informational grades with explanations, no synthetic scores.
Inputs: url (required).
detect_tracking_tagsDetect tracking tagsA
Static scan of a page's HTML for analytics/advertising tags: GA4, Google Tag Manager, Universal Analytics, Google Ads, Meta Pixel, TikTok, LinkedIn Insight, Hotjar, Microsoft Clarity, server-side/first-party tagging, and consent platforms. Reports IDs and where each was found.
Inputs: url (required).
find_duplicate_tagsFind duplicate tagsA
Flag duplicate GA4/GTM/pixel installations and double-fire patterns: same ID initialized twice, multiple containers, hardcoded GA4 next to GTM, multiple pixel IDs, legacy UA leftovers.
Inputs: url (required).
measure_performanceMeasure lab performanceA
Load the page in headless Chromium (Playwright) and capture lab metrics: TTFB, FCP, LCP, CLS, load times, request count and transfer sizes. Clearly labeled lab data, not field data. Requires the Playwright Chromium browser; returns the install command if missing.
Inputs: url (required).
tracking_summaryTracking setup summaryA
Plain-language summary of a site's tracking setup written for a media buyer or marketer: what is installed, what it means for campaigns, consent posture, and issues to raise with the dev team.
Inputs: url (required).
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 7 tools
Tools are mostly distinct with clear roles: audit_page is a composite overview while the others go deeper on specific aspects. The descriptions explicitly cross-reference each other, reducing ambiguity, though audit_page does overlap with several specialized tools.
Most names follow verb_noun convention (audit_page, detect_tracking_tags, find_duplicate_tags, measure_performance, analyze_page_weight, check_headers). tracking_summary breaks the pattern by being a noun phrase, causing a minor inconsistency.
Seven tools is well-scoped for a web audit server, covering tracking, performance, headers, and weight without redundancy or bloat. Each tool has a distinct purpose and contributes to the overall workflow.
The set covers the core audit lifecycle: detection, duplicate analysis, performance, page weight, headers, and a summary. Minor gaps exist (e.g., no field performance data or cookie inspection), but the provided tools handle the primary use cases effectively.
Related MCP Connectors
Can ChatGPT, Claude and Perplexity crawlers read your page? Audit, fixes, compare, monitoring.
Run SEO + AI-visibility (GEO) audits from Claude, Cursor & other AI clients.
Technical SEO and AI-readiness audits for any page, plus a check that your fix worked.
Audit public webpages and supplied markup for HTML, CSS, SEO, JSON-LD, and link issues.
Related MCP Servers
- AlicenseAqualityDmaintenanceCore Web Vitals analysis powered by Lighthouse. Four tools: analyze a URL, compare two URLs, check against thresholds, or crawl an entire site. Works with Claude Code, Cursor, Windsurf, and any MCP-compatible AI tool.464 npmMIT
- AlicenseAqualityDmaintenanceEnables Claude Code to capture and analyze web page screenshots, responsive layouts, and page metadata using Puppeteer. It allows developers to perform visual UI inspections and compare designs across various viewports directly within the terminal.3MIT
- AlicenseAqualityCmaintenanceEnables auditing web pages for WCAG violations, applying deterministic fixes and PRs, all through MCP clients like Claude Desktop.7MIT
- AlicenseNot gradedqualityCmaintenanceEnables coding agents to audit public or localhost pages, gather structured SEO evidence, and retrieve compact reports, finding details, and next actions for triage.16 npmMIT