toolkit

Site crawl (Screaming Frog) · Playbook

Runbook — remote crawl → grade → prioritized report

A complete worked example: take a domain from zero to a prioritized SEO report using the remote Screaming Frog crawler. Nothing runs locally. Substitute your own domain for example.com.

Prereq: the remote MCP is connected (server pulse-screamingfrog, tools mcp__<name>__* visible). Setup is in SKILL.md → “Setup”. If the tools aren’t there, the MCP isn’t wired — fix that first; there is no local fallback crawler.


1. List before you crawl (cache-first)

list_crawl_records(domain: "example.com")
# → { records: [{ crawled_at, age_days: 0.2, verdict: "success", config: "full-js", archive_bytes: 28938568 }, … ] }

If a recent success archive exists and the user didn’t ask for a fresh crawl, skip to step 4 — a full crawl is expensive.

2. Queue the crawl

sf_crawl(domain: "example.com", config_name: "full-js")
# → { started: true, pid: 222436, concurrent_slot: "1/4", note: "…runs in background; poll sf_crawl_progress" }

full-js renders JavaScript (right for modern sites); use text-only for static/server-rendered sites when you want speed. A cache hit returns { started: false, cached_crawl } — that’s fine, go to step 4.

3. Poll until settled (back off — don’t spin)

sf_crawl_progress()
# → { crawls: [{ domain: "example.com", running: true, progress: { completed: 180, waiting: 40, percent: "50" } }] }
# repeat every ~15s (small) / 30s (large) / 60s+ (big JS) until running: false

4. Confirm the verdict (never grade before this)

crawl_verdict(domain: "example.com")
# → { results: [{ complete: true, verdicts: ["✓ example.com success (1309710 bytes)"], summary: { successful: "1/1" } }] }

Capture the verdict word. Only success is a crawl you grade. blocked / paused / failed → still fetch it if you want to look, but pass --crawl-verdict <that> so the grader returns BLOCKED, not a misleading REVIEW/FAIL, and tell the user (offer a force: true re-crawl).

5. Fetch + grade (one step)

get_download_link(domain: "example.com")
# → { download_url: "https://…/downloads/<uuid>", expires_in_hours: 12 }
node scripts/nkpfrog.mjs --fetch "<download_url>" \
  --url https://example.com --crawl-verdict success

--fetch downloads the .tar.gz, unpacks it, descends into the dated subfolder, and grades — no manual curl/tar, no zip (the archive is gzipped tar). Manual equivalent if you prefer:

curl -fsSL "<download_url>" -o crawl.tar.gz && tar -xf crawl.tar.gz -C ./sf-export
node scripts/nkpfrog.mjs ./sf-export --url https://example.com --crawl-verdict success

Representative output:

nkpfrog — Screaming Frog crawl audit
site:    https://example.com
verdict: REVIEW
crawled: 294 URLs
issues:  0 critical · 4 high · 8 medium · 26 low  (SEO-adjusted priority)

── PRIORITIZED ISSUES (38) ──
[HIGH]  8 URL(s)  Structured Data: Rich Result Validation Errors
[HIGH]  5 URL(s)  Directives: Noindex
[MEDIUM] 30 URL(s)  Page Titles: Over 60 Characters
[LOW]  253 URL(s)  Security: Missing Content-Security-Policy Header
...

What the priority column did: issues_overview_report.csv is re-ranked by the SEO Priority Framework (SKILL.md), not SF’s raw labels. The 253-URL “Missing Content-Security-Policy” sits in LOW even though it’s the highest-volume finding — security headers don’t affect rankings. A 5-URL accidental Noindex is promoted to HIGH because it blocks indexation. Volume ≠ priority.

6. Deliverables (optional)

# an action plan instead of a flat list — findings grouped + routed to the other toolkit skills
node scripts/nkpfrog.mjs ./sf-export --url https://example.com --enhanced

# slice one report out — "I just want the sitemap, plain text" / "the media report"
node scripts/nkpfrog.mjs ./sf-export --report sitemap
node scripts/nkpfrog.mjs ./sf-export --report images --format tsv --out media.tsv
node scripts/nkpfrog.mjs ./sf-export --list-reports        # everything sliceable in this archive

The grade run writes report.html (self-contained, theme-aware), report.txt, and nkpfrog.json to --out (default ~/Desktop/toolkit-reports/<domain>/nkpfrog).


Interpreting & going deeper

Once you have the prioritized list, drill in — the export already contains the per-URL detail:

You want to… Do this
Understand what an issue means / how to fix it references/deep/issues-reference*.md
Read a particular CSV’s columns references/deep/csv-exports*.md
The URLs behind a finding --report <slug> (e.g. directives_noindex), or open the issues_reports/<slug>.csv
Which pages link to a broken/noindexed URL open the issue’s *_inlinks.csv, or run klink
Per-page depth on a finding route to orbeetle / klink / abra / oranguru / porygon / axew / rapidash (or just --enhanced)
Confirm analytics coverage across the crawl feed the URL list to ampharos; audit GTM with beru
  1. Crawl overview — total URLs, verdict.
  2. Critical / High — indexation blockers and ranking-impacting items, with URL counts.
  3. Medium — crawl-efficiency and on-page items.
  4. Quick wins — low-effort, visible impact (missing meta descriptions, oversized images).
  5. Excluded from SEO scope — security headers and pure-UX items; mention but don’t rank as SEO priorities.