librecrawl-audit — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited librecrawl-audit (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
These are hard rules. If you skip any of them, the audit is broken from the user's perspective even if the MCP call succeeded.
After librecrawl_audit_zip returns, you MUST decode the content_base64 and write it to a local file on the operator's machine. Use the user's current working directory (or ~/Downloads/<filename>). Use exactly the filename field from the response.
Pseudo-code your runtime should execute:
import base64
local_path = filename # use the filename field from the response, e.g. "example.com-1780572742.zip"
with open(local_path, "wb") as f:
f.write(base64.b64decode(content_base64))You report the LOCAL path back to the user. NEVER report the zip_path field from the response as the deliverable — that path is on the remote server, not on the operator's local machine. The user cannot open it.
auto_cleanup=TrueDefault and only sane choice. The user contract is: server forgets the audit the moment they have the zip. Do NOT pass auto_cleanup=False — it leaves audit data sitting on the remote server. If a user explicitly says "keep the data on the server", confirm twice and only then opt out.
Pattern your final response on this:
✅ Audit done. Saved locally: ./example.com-1780572742.zip (320 KB · sha256 verified) Contents: SUMMARY.txt + PDF report + 7 CSVs (per-page · sitemap-recon · external-links · content-audit · extended-checks). Server forgot the session — it's not stored anywhere remote.Do not say:
❌ "Zip created: /home/posimyth-brain/librecrawl-reports/example.com-XXXX.zip"
That path is on the server. Useless to the user.
Drive the librecrawl-technical-seo-audit-mcp MCP server to produce a complete technical SEO audit of any website. The MCP exposes 37 tools at mcp__librecrawl-posi__* (or whatever the local connector name is in the user's config).
The user wants to audit a website's technical SEO. Triggers:
<url>" / "audit this site"NOT for: keyword research, backlink analysis, competitor SERP analysis (use other tools).
ALWAYS use librecrawl_start_chunked_audit for real audits. It's the only one that handles big sites without timing out.
librecrawl_start_chunked_audit(
url="https://example.com",
total_max_pages=10000, # default 10k; raise to 100k for huge sites
chunk_target_pages=50, # polling-window size
politeness="auto", # AIMD adaptive (recommended)
fill_sitemap_orphans=True, # default — also fetch sitemap URLs not internally linked
sitemap_fill_cap=500 # default cap for orphan fill
)
→ returns { session_id, status: "queued", url, total_max_pages }Do NOT use librecrawl_audit (no start_chunked_ prefix) for production — it blocks for up to 2 hours and disconnects the MCP client on big sites. It exists for backwards-compat only.
librecrawl_audit_status(session_id) → {
status: "queued" | "crawling" | "throttled" | "paused" | "done" | "cancelled" | "failed",
pages_done: int,
total_max_pages: int,
current_delay_ms: int, # the AIMD controller tunes this live
last_chunks: [...], # last 3 chunk metrics (p95 / err_rate)
recent_events: [...],
audit_complete: bool, # True only if sitemap fully covered + no caps hit
incomplete_reasons: [...],
artifacts_ready: bool
}Poll every 20-30 seconds. The runner does its own background work; you do NOT need to keep the MCP call alive between polls. Status is read from local SQLite, so polling is essentially free.
When status == "done", proceed to step 3.
librecrawl_audit_zip(session_id, auto_cleanup=True) → {
filename: "<domain>-<unix-ts>.zip",
size_bytes: int,
file_count: 8, # SUMMARY.txt + 7 artifacts
sha256: "...",
content_base64: "...", # decode + save locally
zip_path: "/path/to/zip", # filesystem alternative
zip_path_persistent: bool, # False when auto_cleanup=True (zip unlinked after response)
cleanup: {
session_rows: { events, artifacts, chunks, sessions },
files_deleted: int,
upstream: { crawl_issues, crawl_links, crawled_urls, crawls }
},
files: [{kind, name, bytes}, ...]
}auto_cleanup=True is mandatory (see Rule 2). The server deletes every trace of the audit after this call returns. The base64 zip in the response IS the only copy.
Save to disk IMMEDIATELY before reporting back to the user — this is Rule 1, non-negotiable:
import base64
local_path = filename # exactly the response.filename field
with open(local_path, "wb") as f:
f.write(base64.b64decode(content_base64))
print(f"Saved locally: {local_path}")⚠️ The response also includes a zip_path field — that path is on the remote server, NOT on the operator's machine. It's there for forensics only. DO NOT report it as the deliverable. The local file (saved by the snippet above) is what the user opens.
The zip contains:
| File | Format | Use |
|---|---|---|
SUMMARY.txt | Plain | One-page orientation (site, session, pages, audit_complete, artifact list) |
<domain>-<ts>.pdf | Branded human-readable report (open in any PDF viewer) | |
<domain>-<ts>.md | Markdown | Source of the PDF — grep-friendly |
<domain>-<ts>.per-page.csv | CSV | One row per crawled URL × 30 columns of check booleans + failed_checks_list |
<domain>-<ts>.sitemap-recon.csv | CSV | Sitemap-vs-crawl diff |
<domain>-<ts>.external-links.csv | CSV | Every outbound URL with HEAD-validated status |
<domain>-<ts>.content-audit.csv | CSV | Per-page readability + AI-tells + punctuation findings |
<domain>-<ts>.extended-checks.csv | CSV | One row per (URL × check_name × severity × detail) finding |
When the user asks "show me the broken pages" or "what schema errors do I have", read the relevant CSV from the saved zip and filter — do NOT re-run the audit.
The server is back to zero state. No follow-up cleanup needed. If the user wants a fresh audit later, start over from step 1.
1. librecrawl_start_chunked_audit(url=X, total_max_pages=10000)
2. Poll librecrawl_audit_status until done
3. librecrawl_audit_zip(session_id)
4. Save zip locally
5. Read per-page.csv, filter status_4xx == 1 OR status_5xx == 1, show table
6. Read external-links.csv, filter status_class IN ("not_found", "forbidden", "server_error_5xx", "timeout", "dns_error"), show table1. Run chunked audit first (skill above)
2. After download: librecrawl_schema_validate(crawl_id) — wait, this needs an alive session.
PREFERRED: do this BEFORE auto_cleanup. Pass auto_cleanup=False to librecrawl_audit_zip,
call schema_validate, then librecrawl_wipe_everything later.Same chunked workflow — the .external-links.csv sidecar covers it. Don't run librecrawl_external_links_audit standalone unless the user has a crawl_id from a recent fresh chunked audit and auto_cleanup hasn't fired yet.
librecrawl_start_chunked_audit(
url=X,
total_max_pages=100000,
chunk_target_pages=100, # larger windows = faster polling resolution
confirm_unbounded=False # safety: leave the 100k cap
)The AIMD controller will adapt the crawl-delay based on the target's responsiveness. Don't override politeness unless the user has explicit permission to hammer the site.
When you start an audit, set expectations:
The server is ephemeral — emphasise that the zip is the only copy.
librecrawl_audit_zip before status is done — returns an error.librecrawl_brain_purge_audit — librecrawl_audit_zip(auto_cleanup=True) does this for you.Quick map of all tools the librecrawl MCP exposes. Most flows use only the highlighted 4.
Chunked audit flow (use these in 95% of cases):
Specialist tools (run standalone when needed, but most data is already in the chunked-audit zip):
librecrawl_external_links_audit(crawl_id) — re-run external-link validation on a specific crawllibrecrawl_schema_validate(crawl_id) — deeper schema validationlibrecrawl_merge_gsc_data(crawl_id, gsc_data) — fold in GSC clicks/impressionslibrecrawl_pagespeed_audit_all_crawl_pages(crawl_id) — full PSI across every crawled URLlibrecrawl_schema_check(url), librecrawl_schema_audit(urls) — per-URL schema inspectionlibrecrawl_site_check(url) — instant site-level check (robots, sitemap, HTTPS)librecrawl_pagespeed(url), librecrawl_pagespeed_audit(urls) — PSI for individual URLsMaintenance:
librecrawl_audit_pause, librecrawl_audit_resume, librecrawl_audit_cancel, librecrawl_audit_force_advance — session controlLegacy (avoid):
librecrawl_audit, librecrawl_full_audit_strict — blocking variants kept for backwards compatlibrecrawl_generate_report, librecrawl_report_content, librecrawl_audit_pdf — older artifact APIsGitHub: https://github.com/adityaarsharma/librecrawl-technical-seo-audit-mcp · MIT · By Aditya Sharma
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.