Skip to main content
Glama
spcaeo
by spcaeo

Toadstone

A toadstone was a gem people wore to detect poison. It never detected anything. This one is named after that, on purpose.

Licence: Elastic 2.0 Python 3.11+ Tests Checks observed firing Self-hosted Live demo

β–Ά  Open the live demo

The real interface, frozen over a real crawl. Click the tabs. Open a finding. No sign-up.

🎬  Watch the tour

Two minutes. Why it exists, what it found, and every view in the app.

Why this exists Β· Quick start Β· What it finds Β· Screenshots Β· How it compares Β· For agents Β· Architecture Β· Security Β· FAQ


Why this exists

I run an SEO team of 22 people. Screaming Frog is licensed per machine.

That is not a complaint about the price β€” it is a good tool and worth its licence. It is that the shape is wrong for how we work. We are in Ahrefs every day. We need a full technical crawl maybe five or six times a month. Paying a seat for every executive so each of them can run a crawl a handful of times a month is paying for the wrong thing.

What I wanted was one instance I host myself, on my own Mac, that the whole team can reach β€” and that Claude Code can talk to directly over MCP, so a crawl comes back as a list of issues to fix rather than a CSV someone has to open, read and turn into tickets.

Nothing available had that shape. So I forked an open-source crawler and started building.

claude mcp add toadstone -- python -m src.mcp

That is the whole integration. Ten tools, read-only by default, unmetered β€” because they are your crawls, on your machine. "Crawl freshlycited.com and tell me what to fix" comes back as findings grouped by check, each with its evidence and a one-line fix, ready to become tickets. No credits, no units, no per-seat anything. See the tools β†’

Then I found something I was not looking for

Most SEO crawlers advertise how many checks they have. That number is almost meaningless, and here is why.

I counted how many of the ones I had inherited had ever been observed to fire on a real page. There were about 225. Eighty-four of them had ever fired. The rest passed their unit tests and sat inert:

  • eight AMP checks that could not fire on any site, because the markup they read was discarded before they ran;

  • every PDF check, dead from the moment JavaScript rendering was switched on;

  • a raw-versus-rendered comparison that had been comparing a document against itself;

  • a failed PageSpeed run that reported as a clean bill of performance β€” the API returned a quota error, the integration swallowed it, and the report came back with no performance issues at all;

  • three "checks" that were not checks, only sample data left in a test file.

None of that looked broken. Every report came back clean and confident, and clean was the wrong answer.

So the project built a site that is wrong in as many ways as possible, crawls it with the real crawler, and records which check slugs actually appear.

The last sweep, run while this README was being written, fired 180 distinct checks across the fixture's six modes β€” 162 in the default run alone, on 77 crawled pages. Here is that sweep, in full:

python tools/coverage_sweep.py
--- default                 77 pages crawled, 162 checks fired
--- sitemap-limits-1         3 pages crawled,  76 checks fired   50,001 URLs, 51 children
--- sitemap-limits-2         3 pages crawled,  70 checks fired   a sitemap listing nothing
--- sitemap-limits-3         3 pages crawled,  75 checks fired   52MB of sitemap
--- silent-image            85 pages crawled, 166 checks fired   a handler that answers nothing
--- https                    4 pages crawled,  30 checks fired   the checks that need TLS

180 distinct checks fired across 6 mode(s)

Each mode needs a crawl of its own β€” a sitemap of 50,001 URLs cannot share a run with one that works β€” and the sweep keeps a separate slug log per mode, because the harness truncates its log at the start of every run. (Six runs pointed at one file gives you the sixth mode's slugs and a confidently wrong total. That mistake was made while writing this, and it reported 30.)

BUILD-LOG.md carries the rest of the accounting day by day β€” including the thirteen checks that rest on a stubbed transport with real parsing either side, the one that rests on a model's verdict being supplied rather than asked for, the two that cannot honestly be reached at all, and every wrong turn taken on the way. It is written in the first person and it does not flatter the work.

Those commands are the whole argument. Run them before you trust anything above.


Related MCP server: seo-agent

What it is

A self-hosted SEO site crawler with a browser UI, an HTTP API, and an MCP server so an AI agent can query crawls directly. It renders JavaScript, keeps crawls so it can tell you what changed since the last one, and reports findings with evidence a developer can act on without re-fetching the page.

It is a fork of LibreCrawl by Phiality, with roughly a hundred commits of checks, integrations, API, agent support and UI work on top. See FORK.md for exactly what the fork adds.

No per-URL pricing, no seats, no cloud. Point it at a site you have permission to crawl and it runs on your machine.

What a finding looks like

Every finding, wherever it surfaces β€” UI, API, export, MCP β€” carries the same shape. This one is real, from the crawl in the screenshot above:

{
  "id": "69230cd05f9f7ca9ec7a66ee0f91a85ccf9f0d90",
  "check": "links_to_broken_urls",
  "type": "error",
  "category": "Technical",
  "issue": "Page Links to Broken URLs",
  "url": "http://toadstone-demo.localtest.me:8977/",
  "details": "1 link(s) on this page are broken: http://toadstone-demo.localtest.me:8977/missing-entirely.png (404)",
  "evidence": {
    "found": "1 broken links",
    "related_urls": ["http://toadstone-demo.localtest.me:8977/missing-entirely.png"]
  },
  "how_to_fix": "Update or remove the link; a visitor following it hits an error.",
  "docs_url": "/checks#links_to_broken_urls",
  "status": "open",
  "first_seen_crawl": 2
}

Two things in there do more work than they look like they do:

  • id is sha1(url + check + offending value). The same defect keeps the same identity between crawls. A fixed issue never comes back as "new", an accepted one stays accepted, and first_seen_crawl tells you when it appeared. Most tools re-number their findings every run, which makes "what changed?" unanswerable.

  • check is a slug, never free text. You can filter on it, alert on it, and diff on it. docs_url points at the generated reference page for it.


Watch it work

Toadstone crawling freshlycited.com: a URL typed in, 141 pages streaming through, then the findings grouped by check

Open this crawl in the live demo β†’ β€” the real interface, not a report. Click anything; starting a new crawl is the one thing it cannot do. It is the actual interface, frozen over a real crawl β€” the same templates, stylesheet and JavaScript, with every API response captured and replayed. Tabs switch, filters filter, findings expand, the command palette opens. Starting a new crawl is the one thing it cannot do. Built by tools/build_demo_app.py.


What it finds

222 checks, 180 of them observed firing in the last sweep. Every one has a slug, a severity, a category, an evidence block and a one-line fix. The running install generates its own reference page at /checks, built from what it has actually reported rather than from a hand-kept list that drifts.

Area

What it looks at

Technical

Canonicals (relative, cross-domain, chained, pointing at a redirect or a noindex page), redirect chains and loops, noindex and nofollow, hreflang, headings, images, response times, page weight, mixed host and protocol links, tracking parameters on internal links

Sitemaps

robots.txt β†’ index β†’ children; URLs that 404, redirect, are noindex, canonicalise elsewhere or are blocked; crawled pages missing from the sitemap; lastmod missing, malformed, in the future or never bumped; the 50,000-URL and 50MB limits; duplicates across children; http/https and www drift

Duplication

Scored on the page body with header, nav, footer and sidebar stripped, by exact hash or 64-bit simhash, reporting the shared passage rather than a percentage. Pages that share a layout no longer read as duplicates because of their chrome, and URLs that canonicalise to each other never do

Dates and freshness

Missing, unparseable or timezone-less dates; JSON-LD, article:modified_time, HTTP Last-Modified, sitemap lastmod and the visible "Updated on…" disagreeing; stale pages; future dates; modified before published; dates that are two reads of one clock rather than two events

Accessibility

axe-core 4.10.2, around 200 rules, each becoming its own check with its own slug (a11y_color-contrast, a11y_image-alt, …) so you can filter and diff on them

Rendered vs raw

What the browser produced against what the server sent: content only JavaScript creates, links only JavaScript creates, text hidden after render, console errors, layout shift sources

Security headers

HSTS and its max-age, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, cookie flags, insecure form actions, active and passive mixed content

Server logs

Your own access log against the crawl: Googlebot user agents whose reverse DNS does not confirm, crawl budget spent on pages that only 404, pages nothing links to that are requested anyway, pages search engines have never requested, unreadable log lines

Structured data

JSON-LD parse errors, microdata and RDFa, FAQ markup, breadcrumbs, reviews

AMP

Thirteen checks, including the ones that only mean anything when you compare the AMP page with its canonical

Content

Thin content, spelling, grammar, punctuation, casing and style via LanguageTool, E-E-A-T signals, optional LLM analyst verdicts

Performance

PageSpeed Insights and Chrome UX Report field data: LCP, CLS, INP, FCP, TTI, Speed Index, performance score, desktop against mobile β€” and an explicit finding when PageSpeed could not be reached, because silence used to read as health

CMS

A read-only WordPress audit: the SEO plugin's own fields, placeholder values left in them, stray whitespace, expected author

Between crawls

Text removed since the last crawl, internal links lost, and a new / fixed / still-open report keyed on stable issue ids

Full reference, slug by slug, with the βœ… against every one that has been watched to fire: docs/checks.md. It is generated from the running app's own catalogue by tools/generate_checks_doc.py, so it cannot drift from the wording the code uses β€” and it lists the 59 that fired in the harness but have no description yet, rather than quietly leaving them out.


See it

Every screenshot below is the real app on a clean install, crawling a real site β€” freshlycited.com, which we own, so the URLs and the findings are genuine rather than staged. They are regenerated by a script, and each one is checked for personal data before it is written.

What is wrong

Every check that fired, with its slug and how many pages it hit. Collapse the groups and the whole shape of a site's problems fits on one screen.

This one is the bundled fixture β€” the site in tests/ built to be wrong in as many ways as possible β€” because a healthy site makes a dull screenshot and an honest one. 66 pages, 488 findings. The hero above is the same screen on a real site that is in good shape; this is the same screen on one that is not.

Grouped by check, because forty pages missing a canonical is one job, not forty. Filter by severity, response code, content type, section of the site, or what is new since any previous crawl.

What was found

How it fits together

Getting around

And one tab that is not about your site

About sits at the end of the strip and tells the story β€” what a toadstone was, why a crawler is named after a gem that detected nothing, and what the audit found on the day this fork got its name. The last panel is live: it asks your install how many distinct checks it has actually reported, so the claim on screen is about your crawls rather than about somebody's marketing.

It is a plugin (web/static/plugins/about.js, ~180 lines) β€” the shortest worked example of the plugin API, which is documented in docs/plugins.md if you want a tab of your own.

And the licence, in plain words

A source-available licence has terms people need to know before they build on it, and "it's in LICENSE in the repo" is not where somebody running the app will look. Licence sits next to About: what you may do, what you may not, why the upstream MIT grant is what makes the relicence possible, and every third-party licence that travels with the software β€” with the two copyleft ones marked, because one of them surprised us.

Dark mode, properly


Quick start

git clone https://github.com/spcaeo/toadstone
cd toadstone
cp .env.example .env            # set SECRET_KEY, and a port if 5000 is taken
docker compose up -d
open http://localhost:5000

Docker, without Compose

docker build -t toadstone .
docker run -d --name toadstone -p 5000:5000 \
  -v "$PWD/data:/app/data" \
  -e SECRET_KEY="$(python3 -c 'import secrets;print(secrets.token_hex(32))')" \
  -e LOCAL_MODE=true \
  --shm-size=2g toadstone

--shm-size=2g matters: Chromium crashes in a container with the default 64MB of shared memory, and the crawl fails in ways that look like network problems.

Python, no container

git clone https://github.com/spcaeo/toadstone
cd toadstone
pip install -r requirements.txt
playwright install chromium
python main.py --local --port 5057

On a Mac, pass --port. macOS gives port 5000 to AirPlay Receiver, which answers every request with 403 AirTunes. Everything looks like it started and nothing works. Either pass another port or turn off System Settings β†’ General β†’ AirDrop & Handoff β†’ AirPlay Receiver.

One command, no UI (for CI)

python main.py --crawl https://example.com --fail-on error --report issues.json

Exits non-zero when the crawl finds an open issue at that severity or worse, so a pipeline can block a deploy on it.

What you need

Python

3.11 or later (the Docker image is python:3.11-slim)

Chromium

installed by playwright install chromium; already in the image

Memory

~1GB for a small crawl; a crawl stores page bodies, roughly 5–30KB per page

A key, for some things

Nothing is required. Google, PageSpeed, CrUX and the link-metrics vendors each add checks if you configure them, and say so plainly if you do not


Integrations

Every one of these is optional, off by default, and says when it could not run rather than returning nothing and letting that read as health.

Integration

What it adds

What it needs

Google Search Console

Impressions, clicks, CTR and position per crawled page; the indexing verdict from the URL Inspection API; a finding when Google picked a different canonical than the page declares

A Google account, connected with one button in Settings β†’ Google. Read-only

Google Analytics 4

Sessions and engagement per page; pages with traffic the crawl never found; high-bounce pages

The same connection, same button

PageSpeed Insights

Lab Core Web Vitals for the homepage and two top-level pages, mobile and desktop

Nothing, but an API key avoids the shared anonymous quota

Chrome UX Report

Real-user field data for the origin and its pages

A free, read-only GOOGLE_API_KEY

LanguageTool

Spelling, grammar, punctuation, casing and style, as five separate checks

A LanguageTool endpoint; the public one is free and rate-limited, or run your own

Moz / Majestic / Ahrefs

Domain authority, spam score, external link counts, and pages with authority that nothing links to

A paid key from one of them. Without one, the checks report that the provider refused rather than reporting zero backlinks

WordPress

A read-only audit of the SEO plugin's own fields: over-long descriptions, placeholder values, stray whitespace, expected author

The REST API being reachable

Your access log

Bot spoofing by reverse DNS, crawl budget waste, pages never requested by search, requests for pages the crawl never found

An access log file. Nothing leaves the machine

An LLM

Analyst verdicts on page experience and E-E-A-T

A Claude CLI on the host. Off by default, because a crawler that silently spends money or sends a client's page text to a third party has broken someone's trust


For agents: MCP, API and webhooks

Toadstone was built to be read by software as well as people.

MCP server

// Claude Code, Cursor, or any MCP client
{
  "mcpServers": {
    "toadstone": {
      "command": "python",
      "args": ["-m", "src.mcp", "--url", "http://localhost:5000"],
      "env": { "TOADSTONE_MCP_URL": "http://localhost:5000" }
    }
  }
}

It is read-only by default, restricted to hosts this install has already crawled, and capped at 2,000 URLs per crawl β€” defaults chosen on the assumption that nobody will ever re-read the config file they pasted it into. Writes need an explicit opt-in. See docs/mcp.md.

HTTP API

curl 'localhost:5000/api/crawls/133/issues?check=links_to_broken_urls&status=open'
curl 'localhost:5000/api/crawls/133/diff/132'           # new, fixed, still open
curl 'localhost:5000/api/checks'                        # the whole catalogue
curl -X POST 'localhost:5000/api/issues/<id>/accept' -d '{"reason":"by design"}'

Filters, formats (JSON, CSV, XLSX), crawl-to-crawl diffs, and issue acceptance with a reason β€” an accepted issue stays accepted across crawls, because the id is stable.

Webhooks and scheduling

A crawl can POST its summary when it finishes, and the scheduler runs standing crawls without anyone opening the UI.


How Toadstone compares

Twenty-six tools, every cell traced to a page the vendor owns, all fetched 2026-10-20. nc means not checked β€” I could not confirm it from the vendor's own material, so it is left blank rather than guessed. A gap is more useful than a wrong tick, and corrections are welcome as a pull request.

No prices in the capability tables, on purpose. Published prices move, third-party aggregators contradict each other and the vendors, and a README full of 2026 figures is misinformation by 2027. Where a vendor publishes a headline price on its own site it is noted in the pricing row; everything else links out.

βœ“ documented Β· ◐ partial or conditional Β· βœ— the vendor's own material indicates absence Β· nc not confirmable

Desktop and self-hosted crawlers

Toadstone

Screaming Frog

Sitebulb

Sitemoth

Netpeak Spider

Visual SEO Studio

Greenflare

Runs on

Docker / Python

Win Β· mac Β· Linux

Win Β· mac

Win Β· mac Β· Linux

Win Β· mac

Win Β· mac

Win Β· mac Β· Linux

Licence

Elastic 2.0, source-available

proprietary

proprietary

proprietary

proprietary

proprietary

GPL-3.0

Pricing model

self-hosted, free

annual licence, free ≀500 URLs

subscription, 14-day trial

free during beta, no account

subscription or one-off

annual per PC, free ≀500 URLs

free

Offline / air-gapped

βœ“

◐ licence activation nc

◐

βœ“

◐

◐

βœ“

JavaScript rendering

βœ“ Playwright

βœ“

βœ“

βœ“

βœ“

nc

βœ—

Crawl-to-crawl diffing

βœ“

βœ“

βœ“

βœ“

nc

◐ store / load

βœ—

Persistent per-finding id

βœ“ sha1(url+check+value)

nc

nc

nc

nc

nc

nc

Accessibility

βœ“ axe-core

βœ“ axe

βœ“ axe-core, 98 hints

βœ“ axe-core

βœ—

nc

βœ—

Server log analysis

βœ“ built in

◐ separate product

βœ—

βœ“ incl. fake-bot

βœ—

βœ—

βœ—

Search Console Β· GA4

βœ“ Β· βœ“

βœ“ Β· βœ“

βœ“ Β· βœ“

βœ“ Β· βœ“

βœ“ Β· βœ“

◐ GSC only

βœ—

Spelling and grammar

βœ“ LanguageTool

βœ“ 25+ languages

βœ—

βœ“ LanguageTool

βœ—

βœ—

βœ—

HTTP API

βœ“

βœ— CLI only

nc

βœ— CLI

βœ—

βœ—

βœ—

Webhooks

βœ“

nc

nc

βœ—

nc

βœ—

βœ—

Scheduling

βœ“

βœ“

◐ Pro

◐ via CLI

nc

nc

βœ—

MCP server

βœ“ unmetered, your data

βœ“ 29 tools, paid licence only

βœ“ read-only

βœ“ 18 tools, can crawl

βœ—

βœ—

βœ—

Actively maintained

β€”

βœ“

βœ“

◐ v0.1.12 beta

βœ“

βœ“

βœ— no releases; site unpublished

Cloud and enterprise platforms

Toadstone

Botify

Lumar

Oncrawl

JetOctopus

Conductor

seoClarity

Where the crawl runs

your machine

vendor cloud

vendor cloud

vendor cloud

vendor cloud

vendor cloud

vendor cloud

Pricing model

self-hosted, free

quote only

quote only

quote only

published tiers

quote, usage-based

published from-price

Per-seat cost

none

nc

nc

nc

none, unlimited seats

nc

unlimited users

Server log analysis

βœ“

βœ“

◐ via Logz.io / Splunk

βœ“ no sampling

βœ“ native, 40+ bots

βœ—

βœ“

Continuous monitoring

βœ— scheduled only

◐

◐

◐

◐

βœ“ its whole point

nc

CI/CD deploy gating

βœ—

◐

βœ“ 8 CI systems, blocks deploys

nc

nc

nc

◐

Accessibility

βœ“ axe-core

βœ—

βœ“ WCAG 2.2, engine unnamed

βœ—

βœ—

βœ—

βœ—

HTTP API

βœ“

nc

βœ“ REST + GraphQL

βœ“ REST + OQL

βœ“

◐

βœ“

MCP server

βœ“

βœ“ + agents that act

nc

βœ“

βœ“ can start crawls

◐ AgentStack

βœ“

Scale it is built for

tens of thousands

millions

millions

millions

millions

millions

millions

Support you can phone

βœ—

βœ“

βœ“

βœ“

βœ“

βœ“

βœ“

The audit module inside an SEO suite

Toadstone

Ahrefs

Semrush

SE Ranking

Serpstat

Sitechecker

Moz

Where the crawl runs

your machine

vendor cloud

vendor cloud

vendor cloud

vendor cloud

vendor cloud

vendor cloud

Crawl budget

your disk

credits

pages per month

pages per project

pages per month

nc

nc

Per-seat cost

none

+$40–80 per user

from $45 / user

extra seats from ~$16

tiered

nc

nc

JavaScript rendering

βœ“

βœ“

◐ Enterprise add-on

βœ“ opt-in

◐

nc

nc

Server log analysis

βœ“

βœ—

nc

βœ—

βœ—

βœ—

nc

Accessibility

βœ“

βœ—

◐ Enterprise

βœ—

βœ—

βœ—

nc

Backlink and keyword data

βœ— rents it, has none

βœ“ best in class

βœ“

βœ“

βœ“

◐

βœ“

Crawl-to-crawl diffing

βœ“

βœ“ any two crawls

◐ trend only

βœ“

βœ—

nc

nc

HTTP API

βœ“

βœ“

βœ“ metered in units

βœ“

βœ“ Team+

βœ“

nc

MCP server

βœ“

βœ“

βœ“ read-only

◐

βœ—

◐

nc

Rank tracking

βœ—

βœ“

βœ“

βœ“

βœ“

βœ“

βœ“

Two rows deserve a caveat rather than a victory lap. No vendor anywhere documents persistent per-finding identity, and webhooks are nc across all twenty-six. The honest claim is "no competitor documents equivalent behaviour" β€” not that they lack it. And moz.com could not be fetched from the environment this was compiled in, while every Bing Webmaster Tools help URL returned 404, so both are nc throughout and want a human with a browser.

On check counts, including ours

Only Screaming Frog and Sitebulb back their number with a public library you can count against β€” Sitebulb even enumerates its 98 accessibility hints. Ahrefs' "170+", Semrush's "140+", SE Ranking's "115+" and Lumar's "250+/350+" are marketing-defined with nothing public to count. JetOctopus, Oncrawl, Botify, Conductor, seoClarity, Serpstat and Sitechecker publish no count at all, so any claim that they have fewer checks than this would be indefensible.

Counts are not a common unit either: 222 here and Sitebulb's "300+" do not measure the same thing. The unusual claim is not the count β€” it is the coverage harness. No vendor publishes anything comparable to "here is the command, run it, watch which checks fire."

An MCP server is no longer a differentiator

Ten of the twenty-six document one, Screaming Frog's being the deepest at 29 tools. What differs is what it may do and what it costs:

Toadstone

Read your own crawls, unmetered, from your own install. Read-only by default, writes behind an explicit flag

Screaming Frog

The most capable β€” crawl control, bulk exports, URL inspection β€” but paid licence only, and its Node.js script-runner tools carry their own warning

Sitemoth, JetOctopus

Can start crawls, not only read them

Sitebulb, Semrush

Read-only

Ahrefs, Semrush

Metered: calls consume plan units

Botify

Goes furthest β€” agents that change the site, not only describe it


Trademarks. Screaming Frog, Sitebulb, Sitemoth, Netpeak Spider, Visual SEO Studio, Greenflare, SEO Macroscope, Botify, Lumar, Oncrawl, JetOctopus, Conductor, seoClarity, Ryte, Ahrefs, Semrush, Moz, SE Ranking, Serpstat, Sitechecker, Google, Search Console, Google Analytics, Chrome, Bing, axe, axe DevTools, WAVE, Pa11y and LibreCrawl are trademarks or registered trademarks of their respective owners. Toadstone is not affiliated with, endorsed by, sponsored by or supported by any of them. Those names are used only to identify the products being compared. The comparisons are drawn from each vendor's own published documentation on the date stated, are offered as fair comment, and may be out of date β€” a correction is a welcome pull request.

The free baseline

Google Search Console is not a crawler, and that is the point. It tells you what Google chose to crawl, on Google's schedule, capped at 1,000 example rows, with validation that takes about two weeks. You cannot crawl a URL set of your choosing, and it can only ever see production, after the fact β€” no staging, no pre-release, no diff you control. Toadstone reads Search Console and crawls; the interesting findings are where the two disagree.

Among paid tools the free tiers cluster hard at 500 URLs β€” Screaming Frog, Netpeak Spider, Visual SEO Studio and SiteAnalyzer all stop there. Sitemoth (unlimited, free, no account) and the GPL projects are the only ones that do not. Toadstone is in that company, not alone in it.

The single-purpose tools

For log files alone, Screaming Frog's Log File Analyser is mature and cheap, free up to 1,000 events. For accessibility alone, axe DevTools, WAVE and Pa11y are better than any crawler's accessibility module, this one included β€” Pa11y in particular does the CI half properly. Note that Toadstone, Screaming Frog, Sitebulb and Sitemoth all wrap the same engine: axe-core. Claiming accessibility as a differentiator against crawlers is fair; claiming it against accessibility tools is not.

Where others lead

  • Screaming Frog β€” a decade of edge cases, the deepest MCP of anyone, spelling in 25+ languages, and unlimited URLs for one annual licence. Hard to beat on value, and its configuration depth is unmatched when a site is being strange.

  • Lumar β€” stops regressions shipping: 8 CI systems and the ability to block a deploy that fails a health threshold. This finds things after they ship.

  • Conductor β€” latency. Continuous monitoring, alerts the moment something changes, 60 months of page-level changelog. A scheduled crawl finds yesterday's regression.

  • Botify β€” acts rather than reports: its agents and PageWorkers deploy the fix to the live site. The LLM analysts here score pages; they do not change them.

  • JetOctopus β€” unlimited seats on every plan and millions of log lines ingested from a dozen CDN formats, at a price that needs no procurement process.

  • Oncrawl β€” OQL across crawl, log and Search Console data, with exports into BI tools. A different product class from a crawler's filters.

  • Sitebulb β€” explains why a finding matters better than anyone, with a public hint library that is a genuine teaching resource, and ticketing integrations.

  • Ahrefs and Semrush β€” a backlink index and keyword data no self-hosted tool can reproduce. Toadstone has to rent that from Moz, Majestic or Ahrefs.

  • Sitemoth β€” the uncomfortable one. Desktop, free today, 190 checks, axe-core, logs with fake-bot detection, LanguageTool, crawl comparison, and an MCP that can crawl. Whatever the case for Toadstone is, "self-hosted with no per-URL pricing" is not it against this.

  • All of them β€” a company, a roadmap and support. This is a fork maintained by a small team, with no SLA and no phone number. The two dormant GPL crawlers in this survey are the honest illustration of how that can end.

Who should use what

  • Crawl data must not leave your infrastructure β€” NDAs, regulated sectors, staging sites that are not public β†’ Toadstone.

  • You want an agent driving the crawler end to end, unmetered, over your own data β†’ Toadstone, or Sitemoth if a desktop app suits you better.

  • One or a few practitioners wanting the most capability per pound β†’ Screaming Frog, plus its Log File Analyser.

  • The deliverable is a prioritised report a client reads β†’ Sitebulb.

  • Millions of URLs, many seats, someone else running the pipeline β†’ JetOctopus, Oncrawl, Botify or seoClarity.

  • You need to know within minutes β†’ Conductor. You need to block the deploy β†’ Lumar.

  • Site audit beside rank tracking, keywords and backlinks β†’ Ahrefs, Semrush or SE Ranking.

  • Accessibility is the actual job β†’ Pa11y in CI plus axe DevTools.

  • Toadstone is the wrong choice if you need a backlink index, deploy-time gating, sub-minute alerting, a support contract, millions of URLs, or if nobody will operate Docker and a browser pool. And if the appeal was "free, self-hosted, lots of checks" β€” evaluate Sitemoth first. It is free today and covers much of the same ground.


Architecture

flowchart LR
    subgraph you["Your machine"]
        UI["Browser UI<br/>11 views + plugins, ⌘K palette"]
        API["HTTP API<br/>JSON Β· CSV Β· XLSX"]
        MCP["MCP server<br/>read-only by default"]
    end

    subgraph app["Toadstone"]
        APP["Flask + Waitress"]
        SCHED["Scheduler<br/>standing crawls"]
        CRAWL["Crawl loop<br/>threaded HTTP, or<br/>Playwright for JS"]
        CHECKS["222 checks<br/>every finding via one factory:<br/>stable id, slug, evidence, fix"]
        DB[("SQLite<br/>crawls, issues,<br/>encrypted tokens")]
    end

    subgraph ext["Outside services, all optional, all off by default"]
        G["Search Console Β· GA4<br/>PageSpeed Β· CrUX"]
        LT["LanguageTool"]
        LM["Moz Β· Majestic Β· Ahrefs"]
        AI["Claude CLI<br/>via a loopback bridge"]
    end

    SITE["The site being crawled"]
    LOG["Your access log"]
    HOOK["A webhook, anywhere you like"]

    UI --> APP
    API --> APP
    MCP --> APP
    APP --> CRAWL
    SCHED --> CRAWL
    CRAWL --> CHECKS
    CHECKS --> DB
    APP --> DB
    CRAWL -->|fetch| SITE
    LOG -.-> CHECKS
    CHECKS -.-> G
    CHECKS -.-> LT
    CHECKS -.-> LM
    CHECKS -.-> AI
    APP -->|on finish| HOOK

Three things in that diagram are worth saying out loud:

  1. Everything outside the box is optional and off by default. A fresh install contacts nothing but the site you point it at.

  2. Every finding goes through one factory (src/utils/issue_factory.py), which is why they all carry the same shape and a stable id. A check cannot invent its own format.

  3. The Claude bridge keeps the credential on the host. The CLI ships as a native binary that will not run in a Linux container, so rather than bake a long-lived credential into the image, the container asks the host over a loopback-only bridge with a shared secret.


Reproduce the screenshots

Every image in this README is generated, not curated:

python main.py --local --port 5058 &          # a clean install, empty data/
python tools/screenshots.py --app http://127.0.0.1:5058

With no --site it serves the bundled fixture β€” the deliberately broken site in tests/ β€” so anyone can reproduce the shape of these images without having a site to point at. The published ones were taken with --site https://freshlycited.com/, because a fixture on 127.0.0.1 looks like a toy and ours is a real site we are allowed to show.

Either way it crawls through the UI exactly as a person would, captures every view, and refuses to write any screenshot whose page contains a banned string β€” which is not a theoretical guard. It caught six images carrying a real connected Google account and other people's crawl history while this README was being written, and nothing was written for those.


Security and privacy

  • Nothing leaves your machine unless you configure it to. No telemetry, no phone-home, no account.

  • Crawled page bodies are stored so that crawl-to-crawl diffing works. Roughly 5–30KB per page; Settings β†’ Crawls to keep bounds it.

  • Google tokens are Fernet-encrypted under a key derived from SECRET_KEY, in their own table, and never written into the crawl config (which is snapshotted to the database and included in exports).

  • --local disables authentication entirely and grants admin to everyone. Right on a laptop, wrong on anything reachable.

  • No third-party security audit has been performed.

The full posture, including what has and has not been reviewed, is in SECURITY.md. Report vulnerabilities privately through a GitHub advisory.


FAQ

No, and it is worth being precise about that. Toadstone is source-available under the Elastic License 2.0: you can read it, run it, modify it, self-host it for yourself or your clients, and redistribute it. What you cannot do is offer it to third parties as a hosted or managed service, or strip the notices. ELv2 is not an OSI-approved open-source licence.

The portions inherited from LibreCrawl remain MIT, and nothing here changes that β€” anyone can still take LibreCrawl from its own authors under MIT.

Much of what is here is upstreamable and some of it should be upstreamed. But the fork started by auditing the existing checks and finding that most had never been observed to fire, and the work that followed β€” a coverage harness, a stable issue identity, an evidence block on every finding, an API and an MCP server β€” changes enough of the shape of the thing that it would have been a very long pull request with no reviewer. Upstream is credited in NOTICE and in FORK.md, and general crawler fixes should go there first.

See the comparison. The short version: Screaming Frog is mature, fast, extremely well understood, and better than this at most of what both do. It also has the deepest MCP server of any vendor here β€” 29 tools, including crawl control β€” so "an agent can drive it" is not a difference between us, whatever you may read elsewhere.

What is different: the crawl and the data stay on your own machine, the findings come out of an HTTP API rather than an export, nothing is metered, and there is a published record of which checks have actually been watched to fire. If you want a desktop app, a licence and support, buy Screaming Frog β€” it is roughly Β£199 a year and worth it.

The crawl is bounded by max_urls and by memory, and it stores page bodies, so a million-URL crawl is not what this is for. It handles tens of thousands of pages on ordinary hardware. The UI shows a live estimate of disk per URL and what a million would cost, because that is a decision you should make before starting rather than discover afterwards.

Yes, and it reports what robots.txt blocked rather than silently skipping it β€” including sitemap URLs that are blocked and canonicals that point at a blocked page. You can turn the obedience off for a site you own, which is a setting you have to go and find.

It says so, as a finding. This is the single most important design decision in the project: a failed PageSpeed call used to produce no issues at all, which is indistinguishable from a site with perfect Core Web Vitals. Every integration now reports its own unavailability.

Yes β€” on your own sites, and on your clients' sites as part of your own work. What ELv2 stops is offering Toadstone itself to third parties as a hosted or managed service. If that is what you want to do, talk to us about a commercial licence.


Roadmap

Honest about what is not here yet.

Next

A generated docs/checks.md that stays in step with the code Β· scheduled-crawl notifications beyond webhooks Β· a crawl-loop race recorded as open in BUILD-LOG.md

Known debt

web/static/js/app.js is 4,548 lines and should be four files Β· three more files sit just over the project's own 1,000-line rule

Wanted, not started

Multi-site dashboards Β· a hosted demo Β· CSV import of a URL list to crawl Β· more CMS connectors than WordPress

Deliberately not doing

Rank tracking Β· keyword research Β· backlink acquisition Β· anything that writes to the site being crawled


Documentation

docs/checks.md

Every check, by category, with its slug, severity and fix β€” and a βœ… against each one that has been watched to fire. Generated from the running app

docs/mcp.md

The MCP server: every tool, the guardrails, config blocks for Claude Code and Cursor, and what it refuses to do

docs/google.md

Connecting Search Console and Analytics: the five-minute OAuth client setup, the exact redirect URI, and what each scope is for

docs/agents.md

The optional LLM analyst layer, and the loopback bridge that keeps the credential on the host instead of in the image

docs/agent-handoff.md

Written for an AI agent rather than a person: how to drive a crawl end to end over the API and read the findings back

docs/plugins.md

Writing a plugin: the register API, the lifecycle hooks, the data you get, and the two that ship β€” about.js is ~180 lines and is the example worth copying

docs/design/ui-redesign-2026-10.md

The interface's design brief β€” tokens, density, the rail, and the reasoning behind each reference it borrows from. Historical: the work shipped, the brief is kept for the argument

docs/demo/video-script.md

The demo video, scene by scene, with the rule that no claim appears in it the repository cannot back

docs/demo/launch-post.md

The launch narrative β€” long, short and one-line β€” plus the claims we deliberately do not make

docs/about-the-name.md

Why a fake medieval gem is the right name for a crawler, and the audit that earned it

BUILD-LOG.md

The day-by-day engineering record. 167 entries, first person, including every wrong turn. tools/verify_build_log.py fails CI if an entry ever goes missing from it

CHANGELOG.md

What changed and when, with the known problems listed rather than omitted

FORK.md

Exactly what this fork adds on top of LibreCrawl

SECURITY.md Β· CONTRIBUTING.md Β· CODE_OF_CONDUCT.md

How to report a vulnerability, how to get a change merged, and how to behave

Tools that ship with it

tools/coverage_sweep.py

Runs every fixture mode and prints how many distinct checks fired. The number in the badge

tools/screenshots.py

Regenerates every image in this README, refusing to write one that shows personal data

tools/build_demo_app.py

Freezes the running app over a real crawl into a static site β€” the published live demo

tools/generate_checks_doc.py

Rebuilds docs/checks.md from the app's own catalogue

tools/verify_google.py

Makes real calls to Google and checks every field and enum against Google's published Discovery documents

tools/verify_link_metrics.py

Calls Moz, Majestic and Ahrefs and asserts each refuses properly without a key β€” because an integration that turns a refusal into {} reads downstream as "this page has no backlinks"

tools/verify_build_log.py

Fails if any entry the build log ever carried has gone missing

tools/claude_bridge.py

The host-side bridge for the optional agent layer


Built by LabsMedia

Toadstone is built and maintained by LabsMedia, the digital marketing division of Space-O Technologies, and led by Rakesh Patel, founder of both.

We did not build this as a side project. We build links and run SEO for other people's sites, which means we spend our days looking at crawl reports and betting client money on whether they are true. The fork started because a report came back clean on a site we already knew was broken β€” and the reason turned out to be that most of the crawler's checks had never once been observed to fire. Everything in this repository follows from refusing to accept that again.

If you want that same scrutiny applied to your site, or links that are relevant, real and live rather than counted:

AI link building

labsmedia.com β€” Scout finds the sites, negotiates the price, and verifies the backlink is live on the page before anyone is invoiced

Guest posts and link insertions

labsmedia.com/services

Software and platform work

spaceo.ca

Talk to a human

labsmedia.com/contact

We also build Skald β€” self-hosted web analytics read from your server's own access log, which sees the AI assistants and crawlers that JavaScript analytics cannot. Toadstone tells you what is wrong with a site; Skald tells you who actually came to it.


Credits

LibreCrawl by Phiality, released under the MIT License, is the crawler this is built on. The MIT grant is also what makes it legal to distribute this work under different terms, and it would be poor manners not to say so clearly: a solo developer's work is the reason this project exists at all. Upstream is untouched beneath this fork, and its updates still merge.

axe-core by Deque Systems (MPL-2.0), bundled unmodified, is every accessibility check here.

Toadstone is not affiliated with, endorsed by, or supported by Phiality or LibreCrawl. The MIT licence grants no trademark rights and we claim none. If something here is broken, it is ours, and the issue tracker is here β€” not theirs.

Every other dependency, with its licence, is listed in NOTICE.


Contributing

One rule above all the others: a check is not finished until it has been seen firing in a real crawl. CONTRIBUTING.md explains what that means in practice, along with how to run the suites (34 of them, no framework, plain scripts) and what a pull request needs to show.

Found a check reporting something that is not true? That is the most valuable issue this project can receive β€” there is a template for it.


Licence

Elastic License 2.0 β€” source-available, not OSI open source. Self-host it, modify it, use it on your own and your clients' sites. You may not offer it to others as a hosted or managed service without a commercial licence. Portions inherited from LibreCrawl remain MIT.

Copyright 2026 Space-O Technologies. Author: Rakesh Patel.

Maintenance

ActivityMaintained
ResponsivenessResponsive

Related MCP Connectors

  • Live SEO intelligence, rank tracking, site audits, and backlink analysis for AI agents.

    https://app.fastestrank.com/mcp
  • Run full technical SEO audits of any website from your AI agent and get structured findings back.

    https://seoreport.dev/mcp
  • Automated SEO: Site Health, fix plan, Search Console, keywords and draft scoring for AI agents.

    https://api.seonix.ai/mcp
  • Your agent needs to crawl a site and say what is wrong with it β€” broken tags, duplicate content, pages nothing can index, resources that never load. **What you can ask for** β€’ "Crawl this site and list every page with a duplicate title or missing description." β€’ "Which pages are non-indexable, and why?" β€’ "Run Lighthouse on these URLs and give me the failing audits." β€’ "Show the internal link graph and the orphan pages." β€’ "Give me this page's raw HTML and its microdata." **How to use it** Point any MCP client at https://mcp.aisa.one/seo-onpage/mcp and sign in with OAuth β€” there is no key to create or paste. 20 tools: submit a crawl and read its summary, pages, resources, links and waterfall; duplicate content and duplicate tags; keyword density; non-indexable and uncrawlable resources; parsed content, raw HTML, microdata, screenshots and Lighthouse. **Why this rather than the source** A crawler you drive from the agent, with the audit results as structured data rather than a PDF. **It is also a door to the rest** The same login reaches 26 sources and 580+ operations. Find the broken pages here, then ask the same agent what those pages used to rank for β€” without adding a second server. **What it costs** Finding and inspecting an operation is free. Running one is billed per call at API prices, with no seat and no monthly minimum, and every call takes max_price_usd so an agent cannot overspend by accident. **Where else it reaches** https://mcp.aisa.one/seo/mcp for all of it at once β€” rankings, keywords, backlinks, site health and AI-answer visibility across DataForSEO, Semrush and Ahrefs.

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to access self-hosted SEO data and workflows, including keyword research, rank tracking, competitor insights, backlinks, site audits, and AI visibility.
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents to crawl live websites, audit AEO readiness, generate Schema.org @graph JSON-LD, llms.txt, ai.txt, and robots.txt, inject structured data into HTML, validate optimizations, and retrieve framework-specific code snippets.
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Lets AI assistants crawl sites or load exported crawl data, then query SEO issues, inspect individual pages, and trace broken internal links from real crawl results. It supports live same-origin crawls up to 200 pages and exported datasets for deeper analysis.
    6
    MIT