Install
$ agentstack add skill-amirjahfar1-automate-seo-with-claude-seo-technical-audit ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
> Example output: [examples/seo-technical-audit-linear-app-20260514/TECH-AUDIT.md](../../examples/seo-technical-audit-linear-app-20260514/TECH-AUDIT.md)
Technical Audit
A one-shot technical SEO audit for a domain. Discovers the URL set (XML sitemap / GSC indexed pages / DataForSEO top pages), loops DataForSEO On-Page checks over it, layers GSC indexation + sitemap truth and field Core Web Vitals, categorizes findings by area (crawlability, indexability, security, mobile, structured data, etc.), severity-sorts within each, and produces a top-10 fix list ranked by impact × effort.
The audit has a single canonical path: a single-page fast path (on_page_instant_pages + on_page_lighthouse) when the target is one URL, and a full-site path that loops those same On-Page tools over a capped, traffic-weighted URL set discovered from the sitemap, GSC, and DataForSEO. There is no hosted multi-page crawler — the crawl is rebuilt from tools the user already has.
Prerequisites
- DataForSEO MCP server connected.
- GSC (
mcp__gscServer__*) recommended — it's the authoritative source for indexation status and sitemap ingestion truth on the user's own verified property. Firecrawl optional — for URL discovery (firecrawl_map) and raw HTML / JSON-LD where attributes matter. - Claude's
WebFetchtool available (used for sense-checking robots.txt and sitemap presence, and as a fallback URL-discovery / HTML source when Firecrawl is absent). - User provides: a target domain (e.g.
example.com). Optional: target country (defaultus), URL-ceiling override (default: top ~50–100 pages by traffic for large sites).
> Optional accelerator: if you have a hosted-crawl MCP you can substitute it for the URL-discovery + per-page fetch loop (steps 1–2) — not required.
Process
- Validate target & preflight. See
skills/seo-firecrawl/references/preflight.mdfor the canonical 3-stage preflight (cost note, Firecrawl availability, Google APIs). Skill-specific notes:
- Normalise domain (strip protocol, trailing slash) before continuing.
- DataForSEO bills per call; looping
on_page_instant_pagesover a whole large site is the most expensive operation here. Cap to a documented top-N ceiling (default ~50–100 pages chosen by GSC/DataForSEO traffic + a sitemap sample) and say so in the deliverable. Use the documentedlimit/ceilingparams to cap DataForSEO On-Page calls. - Firecrawl: optional. When available, step 8 (Modern signals checklist) runs on 5 sample URLs and
/robots.txt, ~6 Firecrawl credits (hard cap). Without it, step 8 is skipped — JS-render canonical/noindex divergence, X-Robots-Tag headers, and AI-crawler robots-rule analysis are unavailable but the full technical-audit deliverable still ships. Pass--no-firecrawlto skip step 8 even when Firecrawl is available (saves credits). - Google APIs: tier 0 unlocks step 8b (CrUX field data); tier 1 also unlocks step 8c (per-URL GSC Inspection on top 5 traffic pages). See
skills/seo-google/references/cross-skill-integration.md§ "seo-technical-audit" for the full recipe and per-tier branches.
- Discover the URL set (what to audit)
- Single-page fast path. When the target is one URL (not a full domain), skip discovery and run
mcp__dataforseo__on_page_instant_pages+mcp__dataforseo__on_page_lighthouseon that URL — single-page On-Page checks, then jump to step 7. - Full-site path. Build the URL set from any combination of:
- XML sitemap —
mcp__firecrawl-mcp__firecrawl_map(orWebFetch+ parse/sitemap.xml, recursing sitemap-of-sitemaps). The declared page inventory. - GSC indexed pages —
mcp__gscServer__get_search_analyticswithdimensions=["page"]— the pages Google actually indexes, each with clicks/impressions for traffic-weighting. - DataForSEO top pages —
mcp__dataforseo__dataforseo_labs_google_relevant_pages— top pages by estimated traffic. - Union the lists, dedupe, and cap to a top-N ceiling (default ~50–100 pages, biased toward GSC/DataForSEO traffic + a sitemap sample). State the ceiling in the deliverable.
- Per-page technical fetch
mcp__dataforseo__on_page_instant_pages(looped)
- Loop
on_page_instant_pagesover the capped URL set. Each call returns status code, redirects, meta/title, headings, canonical, hreflang, structured-data presence, internal/external links, and resource counts. - Use
mcp__dataforseo__on_page_content_parsingfor main-content extraction where thin-content / duplicate checks need the body text. - Run
mcp__dataforseo__on_page_lighthousefor lab CWV on the key pages only (homepage + top traffic pages — it's heavier per call). - This looped output is the audit's raw crawl: aggregate it by the skill into top-line metrics (pages fetched, health score derived from issue counts, total issues by severity) and per-category issue buckets (crawlability, indexability, mobile, security, structured data, content).
- Derive per-issue page lists (from step 3 fields + GSC)
- For each significant issue (severity ≥ medium, count ≥ 5), list the affected URLs straight from the
on_page_instant_pagesfields (e.g. pages with anoindexrobots directive, a non-self canonical, a missing ``, a redirect chain). - Enrich indexability buckets with
mcp__gscServer__check_indexing_issues(coverage-state errors Google reports that the On-Page fetch can't see). - This produces the actionable fix list.
- Cross-reference key URLs (aggregate per-URL from step 3)
- For the top 5 pages by traffic (from
mcp__gscServer__get_search_analyticsclicks, ormcp__dataforseo__dataforseo_labs_google_relevant_pages, or homepage + key landing pages if no traffic data), collect all issues for those specific URLs from the loopedon_page_instant_pagesresults. - This catches cases where one important page concentrates many issues.
5b. Indexation & sitemap truth (GSC) mcp__gscServer__*
- Indexation:
mcp__gscServer__inspect_url_enhanced/mcp__gscServer__batch_url_inspectionon the top traffic pages (indexed? coverage state? Google-chosen vs user canonical?) — see step 8c for the detailed branch. - Sitemap status:
mcp__gscServer__get_sitemaps/mcp__gscServer__list_sitemaps_enhanced/mcp__gscServer__get_sitemap_details(what Google ingested, error/warning counts). Surface ingestion errors in the Crawlability category.
- Sense-check
WebFetch
- Fetch
/robots.txtand/sitemap.xmldirectly. - Confirm the audit's findings match reality on these critical files (audits sometimes lag behind same-day deploys).
- Extended security headers. WebFetch the homepage and 3 sample URLs (top-traffic landing pages from step 5, fall back to homepage + key landing pages if no keyword data); read response headers and flag any of:
csp_missing—Content-Security-Policyabsent.xframe_missing—X-Frame-Optionsabsent (informational; CSPframe-ancestorssupersedes).xcontent_missing—X-Content-Type-Optionsnot set tonosniff.referrer_policy_missing—Referrer-Policyabsent.hsts_no_preload—Strict-Transport-Securitypresent butpreloaddirective missing AND domain not on the Chromium HSTS preload list.- Map findings via
references/severity-mapping.md§ Security and surface inevidence/02-issues-by-category/security.md(and inline into TECH-AUDIT.md's "By category → Security" section).
- Categorize and prioritize using
references/severity-mapping.md
- Map each issue code to severity, fix, and effort estimate.
- Score each finding: severity × affected-page-count / effort.
- Build the top-10 fix list.
- Modern signals checklist
mcp__firecrawl-mcp__firecrawl_scrape
- The
on_page_instant_pagesfetch doesn't execute JS and doesn't expose every response header per page. This step surfaces what's invisible to it. - If Firecrawl available (~6 Firecrawl credits, hard cap): pick 5 sample URLs from the audit — bias toward high-traffic landing pages and pages already flagged with noindex / canonical issues. For each:
- JS-rendered canonical vs initial-HTML canonical (
js_canonical_mismatch). Comparemetadata.canonical(after JS render) against the canonical the audit recorded. Flag any divergence — per Google's Dec-2025 JavaScript SEO guidance, when a canonical in raw HTML differs from one injected by JS, Google MAY use either one, making canonical decisions non-deterministic. JS-injected canonical changes silently break indexing on JS-heavy sites. - JS-rendered noindex. Check
metadata.robotsfornoindexafter render. Catches client-side-onlynoindexinjection that the audit can't see. - X-Robots-Tag header. Read response headers from
metadata. Flag anynoindex/nofollow/nonedirectives at the HTTP layer. - Dec-2025 JS-SEO risk detection (Google's December 2025 JavaScript SEO guidance — four risks the static crawler cannot detect):
- Risk 1 — Rendering-budget cuts (
js_render_budget). Compare initial-HTML body size to rendered-HTML body size. Flag pages where rendered HTML is,, and` against the same fields in the JS-rendered DOM. Flag any divergence — Google does not reliably index content that only appears post-render, so the empty/wrong initial values may be what gets indexed. - Risk 4 — Soft-404 from JS errors (
js_soft_404). Flag rendered pages where body text content is .txt` if a key was hinted in (1) or (2). If neither hint exists, additionally probe a small set of conventional locations only when the user's domain has signalled IndexNow elsewhere (e.g. Bing Webmaster integration disclosed in robots.txt). - Map findings via
references/severity-mapping.md§ IndexNow: - No key advertised anywhere →
indexnow_no_key(Low; informational — Bing-only benefit). - Key advertised but
/.txtcontent ≠ advertised key →indexnow_key_mismatch(Medium). - Key file present and matches but no recent submissions detected →
indexnow_not_submitted_recently(Low; informational). - Detect last-key-rotation date when possible: WebFetch the key file and read the
Last-Modifiedresponse header (or fall back to the file'sDateheader). - Surface in
evidence/02-issues-by-category/security.md(or a newevidence/02-issues-by-category/indexnow.mdif findings are non-trivial; either way, fold into TECH-AUDIT.md's "By category" section) and add a row to theTECH-AUDIT.mdModern signals section showing IndexNow status: configured (Y/N) and last-key-rotation date if detectable.
- Synthesise
TECH-AUDIT.md
Output format
Create a folder seo-technical-audit-{target-slug}-{YYYYMMDD}/ with:
seo-technical-audit-{target-slug}-{YYYYMMDD}/
├── TECH-AUDIT.md (synthesised top-10 fix list + category summary — primary deliverable; inlines 01-audit-summary header + the six 02-issues-by-category/* tables under "By category")
├── issues.csv (every issue: code, severity, count, fix, effort — load-bearing CSV engineering pastes into Jira)
├── 03-key-pages-issues.md (top 5 traffic pages, all their issues — load-bearing reference engineering / on-call consult per-URL)
└── evidence/
├── 02-issues-by-category/ (raw per-category tables — preserved in case a reader wants the unmerged view)
│ ├── crawlability.md
│ ├── indexability.md
│ ├── security.md
│ ├── mobile.md
│ ├── structured-data.md
│ └── content.md
├── 04-robots-sitemap-snapshot.md (raw fetched files — preserved for reproducibility)
└── 05-modern-signals.md (JS-render canonical/noindex divergence, X-Robots-Tag, AI-crawler rules — requires Firecrawl)
Top-level: TECH-AUDIT.md + issues.csv + 03-key-pages-issues.md. The audit summary header (01-audit-summary) is already in TECH-AUDIT.md's header; the six per-category tables (02-issues-by-category/*.md) are inlined under TECH-AUDIT.md's "By category" section. The raw category files, robots/sitemap snapshot, and modern-signals dump live under evidence/ for reproducibility.
TECH-AUDIT.md follows this shape:
# Technical Audit: {domain}
> Audit date {YYYY-MM-DD} · Pages crawled: {n} · Health score: {n}/100
## Summary
| Severity | Count |
|---|---|
| Critical | {n} |
| High | {n} |
| Medium | {n} |
| Low | {n} |
## Top 10 fixes (impact × effort)
| Rank | Issue | Severity | Pages | Fix | Effort |
|---|---|---|---|---|---|
| 1 | {issue name} | {severity} | {n} | {one-line fix} | {S/M/L} |
| ... |
## By category
### Crawlability ({n} issues)
- {issue name} ({n} pages) — {fix}
- ...
### Indexability ({n} issues)
- ...
### Security ({n} issues)
- ...
### Mobile ({n} issues)
- ...
### Structured data ({n} issues)
- ...
### Content ({n} issues)
- ...
### Modern signals ({n} findings — Firecrawl)
- {URL} — initial-HTML canonical `{X}` differs from JS-rendered canonical `{Y}` (`js_canonical_mismatch`)
- {URL} — JS-rendered `noindex` not visible to static crawler
- {URL} — `X-Robots-Tag: noindex` at HTTP layer
- {URL} — rendered HTML .txt` {found / missing / mismatch} · last-key-rotation: {YYYY-MM-DD or "unknown"}
- (Or: `Modern signals: skipped — Firecrawl not installed`)
### Security headers (extended — WebFetch)
| Header | Homepage | Sample 1 | Sample 2 | Sample 3 | Issue |
|---|---|---|---|---|---|
| Content-Security-Policy | {present/absent} | … | … | … | `csp_missing` if absent |
| X-Frame-Options | {present/absent} | … | … | … | `xframe_missing` if absent (informational; CSP frame-ancestors supersedes) |
| X-Content-Type-Options | {`nosniff`/absent/other} | … | … | … | `xcontent_missing` if not `nosniff` |
| Referrer-Policy | {present/absent} | … | … | … | `referrer_policy_missing` if absent |
| HSTS preload | {preload directive Y/N · on Chromium preload list Y/N} | … | … | … | `hsts_no_preload` if not on list |
## Core Web Vitals (field data — CrUX)
| Metric | p75 (current) | 25-week trend | Threshold | Status |
|---|---|---|---|---|
| LCP | {n} ms | {improving/stable/degrading} | ≤2500 ms good · ≤4000 ms needs improvement | {pass/warn/fail} |
| INP | {n} ms | … | ≤200 ms good · ≤500 ms needs improvement | … |
| CLS | {n} | … | ≤0.1 good · ≤0.25 needs improvement | … |
| FCP | {n} ms | … | ≤1800 ms good · ≤3000 ms needs improvement | … |
| TTFB | {n} ms | … | ≤800 ms good · ≤1800 ms needs improvement | … |
Source: CrUX 28-day origin. If insufficient field data: "CrUX: insufficient data for {domain} (low-traffic origin)."
(Or: `CWV (field data): not configured — run `bash extensions/google/install.sh` for setup.`)
## Indexation reality check (GSC URL Inspection)
| URL | Status | userCanonical → googleCanonical | Last crawled |
|---|---|---|---|
| {top-traffic URL 1} | {INDEXED|EXCLUDED|...} | {URL} {→ different URL if divergent} | {YYYY-MM-DD} |
| {top-traffic URL 2} | … | … | … |
| ... |
Source: GSC URL Inspection (Tier 1). If property not verified: "GSC: {domain} not verified — add it in Search Console."
(Or: `Indexation reality check: not configured (Tier 1 setup required).`)
## Key-page deep dives
### {URL with most issues}
{n} issues found. Top fixes:
1. ...
2. ...
## Recommended cadence
Re-run this skill monthly to catch regressions, or wire `seo-drift` to baseline + diff between audits.
issues.csv columns: category,issue_code,issue_name,severity,affected_pages,suggested_fix,effort,priority_score
Tips
- DataForSEO allows up to 2,000 calls/min, 30 concurrent. Skills pace sequentially.
- The looped
on_page_instant_pagesfetch is the most expensive operation — cap to the documented top-N ceiling (default ~50–100 pages) and lead with the highest-traffic pages so the budget is spent where impact is highest. - Looping On-Page over a large URL set takes time (one call per page, sequential). Lead with GSC/D
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: amirjahfar1
- Source: amirjahfar1/automate-seo-with-claude
- License: MIT
- Homepage: https://nextbrainsolutions.com/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.