sluicer
Run read-only web scraping, extraction, auditing, and crawling tasks through MCP tools.
Extract declared structured data (JSON-LD, microdata, OpenGraph, etc.) with provenance, summaries, conflicts, and optional induced rows.
Convert pages to markdown with optional front matter from declared data.
Fetch raw HTML in slices, reporting fetch method and truncation.
Learn extractors from example values or CSS/XPath selectors, then replay them with drift detection that fails loudly when a page changes.
Heal extractors after redesigns by proposing where fields moved, with evidence and lost-field warnings.
Audit markup against Google rich-result rules and read robots.txt, llms.txt, and AI crawler policies.
Read feeds (RSS, Atom, JSON Feed).
Map sites via sitemaps or links, and crawl sites politely (respecting robots.txt, Crawl-delay, TDM).
Batch extract declared data from up to 25 URLs at once.
Test CSS/XPath selectors and see matched values with their XPaths.
Allows reading RSS feeds and retrieving their items, along with Atom and JSON Feed support.
Most scrapers break without a sound. The site changes its markup, and the scraper keeps running and returns nulls, or the wrong column, for weeks before anyone notices.
Sluicer works the other way round. Show it a value on a few pages of a site
(a price, a title, a date) and it learns where that value lives. On every
page it reads after that, it checks the page against what it learnt. If the
layout has changed, the run fails with exit code 3 and names the check that
broke. sluicer heal then proposes where each field went, when the new page
still shows values the extractor was learnt from.
It also reads everything a page already declares about itself: JSON-LD, microdata, RDFa, OpenGraph and four more vocabularies, merged into one record per thing, with every value pointing to the exact place on the page it came from. No model reads any page, so the same page always gives the same answer.
Quick start
pip install sluicer # or: uv tool install sluicer, pipx install sluicer
# Learn where a book's title and price sit, from two pages of one template.
sluicer compile https://books.toscrape.com/catalogue/page-1.html \
https://books.toscrape.com/catalogue/page-2.html \
--want title="A Light in the Attic" --want price=51.77 -o books.json
# Replay it on another page of that template: 20 rows of title and price, exit 0.
sluicer run books.json https://books.toscrape.com/catalogue/page-3.html
# A page it was not learnt for: exit 3, naming the check that failed.
sluicer run books.json https://quotes.toscrape.com/books.toscrape.com and quotes.toscrape.com are public sandboxes made for
trying scrapers on. The last command prints FAILED https://quotes.toscrape.com/: expected the listing at html>body>…>ol.row, got not found, its path shortened
here, and exits 3.
After a redesign, sluicer heal looks on the new page for the values the
extractor was learnt from, and proposes a new place for each field it finds
them in. In a clone of this repository, examples/shop/ holds a made-up shop
before and after a redesign that renamed every class:
sluicer compile examples/shop/before-1.html examples/shop/before-2.html \
--want title="A Light in the Attic" --want price=51.77 -o shop.json
sluicer run shop.json examples/shop/after.html # exit 3: the listing is not found
sluicer heal shop.json examples/shop/after.html -o shop-healed.jsoncontainer: html>body>div.page>ol.row -> html>body>main.content>div.page>section.grid
member: li.product -> div.card
moved: title -> h2.name>a (5 of 5 learnt values found there; the next best place had 0)
moved: price -> div.cost (5 of 5 learnt values found there; the next best place had 0)
Wrote shop-healed.json.Each move says how many of the field's learnt values were found in its new
place, and how many the next best place held. heal proposes; it does not
repair. It can only move a field whose old values the new page still shows, so
run it on a page you learnt from, or one listing the same items. On the
drift benchmark's
21 real redesigns, 18 of the new pages shared no item with the old ones, and
heal was fully right on none and partly right on 2. When a field or the listing
is lost, it exits 3 and writes nothing unless given --force.
Exit codes follow grep: 0 found -- a record or a summary answer, a <title>
alone included -- 1 found nothing, 2 could not read the page, and 3 when a page
broke its extractor's checks, a heal lost a field or left a move undecided, or
an audit found a documented rule broken. diff exits 1 when something changed.
The checks are about structure, not truth: a run fails when a field is no longer where it was learnt, no longer reads the way it did, or no longer has its shape. A change that keeps all three, such as a different number in the price's place, passes. On SWDE, the checks flagged 18% of the extractors' wrong answers; the other 82% passed.
Reading what a page declares needs no example at all. The product page read
here is
examples/brake-pads.html,
in a clone of this repository; without one, fetch it first with
mkdir -p examples && curl -o examples/brake-pads.html https://raw.githubusercontent.com/Gi0tto/sluicer/main/examples/brake-pads.html.
The library is imported from the Python you ran pip install sluicer in:
>>> import sluicer
>>> page = open("examples/brake-pads.html", "rb").read()
>>> result = sluicer.extract(page, url="https://example.com/p/bp-2210")
>>> price = result.summary["price"]
>>> price.value, price.source, price.key
('41.90', 'jsonld', 'Product.offers.price')
>>> price.where
'/html/head/script[1]#/offers/price'
>>> result.normalised
{'price': '41.90', 'currency': 'EUR', 'gtin': '4006381333931'}
>>> [(answer.value, answer.source) for answer in result.conflicts[0].answers]
[('41.90', 'jsonld'), ('39.90', 'opengraph')]The page states one price in its JSON-LD and another in its OpenGraph tags;
Sluicer reports the conflict instead of picking one in silence. sluicer inspect page.html shows the same reading laid out for a person.
Related MCP server: mcp-untun
Install
pip install sluicerThat is the whole install for every command but one kind of page: it reads
HTML you have, fetches and crawls over plain HTTP, audits, learns extractors
and turns a page into markdown. For the sluicer command in an environment of
its own, uv tool install sluicer or pipx install sluicer; to run it once,
installing nothing, uvx sluicer --version; in a uv project, uv add sluicer.
A page a script draws, which plain HTTP brings back as an empty shell, needs a
browser: add the browser extra, then let Sluicer download Playwright's
Chromium once.
pip install "sluicer[browser]"
sluicer install browsersluicer doctor says what is installed, what each missing piece is for, and
the command that adds it for the way you installed Sluicer (pip, uv tool, pipx
or uvx); a command that needs a missing extra names the same command.
extra | adds |
| a browser, Playwright's Chromium, for a page plain HTTP brings back as an empty shell; download Chromium with |
| the MCP server; add |
| the HTTP API, with |
| microformats2, which is off by default |
| every extra above |
| the stealth rung, by scrapling: one page, only when asked with |
| nothing more since 0.10, when trafilatura joined the base install; kept so an older install line still works |
| deprecated since 0.8: |
The base install is lxml, click, cssselect (CSS selectors), protego
(robots.txt) and trafilatura (markdown), and tomli on Python 3.10 to read a
configuration file: the
HTTP client is Python's own.
What you can give it
you have | run | and get |
a page, as HTML or a URL |
| every record it declares, a summary that answers 25 questions, its conflicts, each value with where it came from |
many pages of one template |
| the fields you gave an example of, from every page, checked |
the selectors you already know |
| the fields you named, held to the same checks: a selector a redesign broke fails the run |
a page that declares nothing |
| the rows its markup repeats: a listing's cards, a table's lines |
a whole site |
| its addresses from its sitemaps, or every page it links to, read politely |
a list of URLs, a web archive |
| one JSON line per page |
a feed |
| its items, as one JSON document |
an article |
| its main text as Markdown |
Every request names Sluicer, and robots.txt and Crawl-delay are obeyed; a
crawl waits when a site asks it to. Only a command that reads single pages or
learns an extractor can be told otherwise, with --stealth or --no-robots;
map, crawl and batch cannot. sluicer --help lists every command, and
the command line reference
explains each one.
In your agent
claude mcp add sluicer -- uvx --with "sluicer[mcp]" sluicer mcp # Claude Code
codex mcp add sluicer -- uvx --with "sluicer[mcp]" sluicer mcp # CodexThe MCP server has twelve read-only tools, among them extract_declared,
select_values, compile_extractor, run_extractor and heal_extractor. Every answer carries
ok, true only when it can be used as it is. The server does not fetch
localhost, private networks or cloud metadata addresses unless it is started
with SLUICER_ALLOW_PRIVATE=1.
In your agent
covers Cursor, VS Code, Gemini CLI, Claude Desktop, Zed, LangChain, the OpenAI
Agents SDK and Pydantic AI. For any other language, sluicer serve offers the
same tools over HTTP (HTTP API),
and at /mcp the MCP server itself over streamable HTTP, for n8n, Dify and any
client that does not start servers over stdio
(Over HTTP).
Measured, losses included
Every number below comes from a public test set, and every scoreboard gives the command that produces it again. Where another tool does better on what a row measures, the row shows it.
scoreboard | what is measured | Sluicer | beside it |
SWDE, 80 sites | extractors learnt from three pages, run on the other 124,051 | F1 0.850, 11,156 wrong answers | Scrapling 0.671, 56,058 wrong |
Drift, 44 before/after pairs on 25 sites | a change noticed: 21 changed, 23 did not | 0 failed silently, 0 false alarms | -- |
Products, 140 pages | price, availability (F1) | 0.750, 0.907 | Zyte's paid API 0.918, 0.957 |
WCXB, 511 pages | title, author, date found; dates invented | 0.727, 0.532, 0.581; 8 invented | trafilatura 0.745, 0.750, 0.838; 216 invented |
As served, 360 pages | dates found; right when it answers | 0.780; 0.734 | trafilatura 0.855; 0.393 |
News, 21 languages | title, author, date found | 0.871, 0.829, 0.970 | trafilatura 0.852, 0.879, 0.970 |
trafilatura's set, 851 annotated pages | title, author, date found | 0.776, 0.472, 0.588 | trafilatura 0.738, 0.669, 0.865 |
Sluicer reads only what a page states in its markup, so on titles, authors and
dates it answers less often than tools that also read the visible text, and it
invents far fewer dates. Its rules were written while reading the pages of these
scoreboards, so the numbers show how it does on pages it was tuned on. The one
held-out test is the half of SWDE's sites whose pages and errors were not read
while making the rules; its numbers were read at each release and a few times
besides, each listed there, once to decide whether to keep a rule. There it scores 0.845, including five camera sites that
were read before the split and so are not a clean test.
bench/PREREG.md
records which pages each rule was made on.
When not to use Sluicer
You need authors or dates that pages do not declare. trafilatura reads them from the visible text and finds more of them. Sluicer guesses them too, by default since 0.10, in a
visiblefield of its own that never enters the summary (--no-visibleorvisible=Falseturns it off): on the scoreboards it finds more of them and invents some, and on two of them its dates are right less often when it answers.You need an article's full text.
sluicer markdownuses trafilatura for it; if you need trafilatura's options or other output formats, use it directly.The site blocks bots. Sluicer is not built to get past bot protection: every request names it, unless a single-page command is given
--stealth.
Why Sluicer compares it with extruct, trafilatura, Scrapling, Crawl4AI and Firecrawl, and says when each is the better choice.
Learn more
Getting started, a ten-minute tour, and Extractors, how learning, checking and healing work.
FAQ, Known limits and What is stable before 1.0.
Moving from extruct:
from sluicer.compat import extructanswers extruct's own calls.The full documentation is at https://gi0tto.github.io/sluicer/.
Community
Questions, ideas and what you built go to Discussions. A page Sluicer read wrong is an issue: attach the page, so the fix comes with a test. Report a vulnerability privately, as SECURITY.md explains, and see CONTRIBUTING.md to set up a checkout.
Sluicer is built and maintained by one person. If it saves you time or money, sponsoring it pays for keeping the scoreboards measured and the extractors working as the web changes.
Licence
MIT, except two data files under their own licences: schema.org's type names
(CC BY-SA 3.0) and CLDR's month and weekday names (Unicode License v3); the
package's licence expression is MIT AND CC-BY-SA-3.0 AND Unicode-3.0. The base
install needs lxml, click, cssselect and protego, all BSD-3-Clause,
trafilatura, Apache-2.0, and on Python 3.10 tomli, MIT. The tree under
trafilatura and the extras is not all permissive: tld is MPL-1.1,
GPL-2.0-only or LGPL-2.1-or-later, and certifi is MPL-2.0, both brought by
trafilatura, and orjson, from an extra, is MPL-2.0 alongside Apache-2.0 or
MIT. CI lists every licence in that tree and fails on one
nobody has read; NOTICE
says more.
Available Tools
12 toolsaudit_pageAudit a page's markupARead-onlyIdempotent
Check a page's structured data against what Google documents for it.
html_or_url: an http(s) URL to fetch, or the HTML itself. site: for a URL, also read the site's robots.txt, llms.txt and llms-full.txt.
Returns {"ok", "url", "records", "page", "not_checked", "errors", "warnings", "notes"}, and for a URL read with site "crawlers", "robots_txt", "other_agents", "llms_txt", "llms_full_txt" and "fetch". Every JSON-LD, microdata and RDFa record lists the rich-result features its type is documented for, each with requirements_met and the required and recommended properties it lacks, and findings that each name a severity, the record's source, the property path and the URL of the rule. crawlers says, per AI agent from its vendor's own page, whether robots.txt admits the page. ok is true whenever the audit ran: a page with errors is an answer; "not_checked" says what was not. errors, warnings and notes count everything found; past 75,000 bytes the last records, page findings and other_agents are left out, counted in records_left_out, page_left_out and other_agents_left_out.
Inputs: html_or_url (required), site. Returns: ok, tdm, url, page, +15 more
compile_extractorLearn an extractorARead-onlyIdempotent
Learn an extractor from pages of one template, to replay later for free.
pages: http(s) URLs, or the HTML itself, of pages built from one template -- two or three pages of one listing, or of one kind of product page. listing: learn the rows the pages repeat; by default only where they declare nothing about a thing. want: example values one row of the listing holds, by the name each column is to have, as {"price": "41.90", "title": "Brake pad set"}: they choose the listing and the columns, and only those columns are kept. A value that no row holds is an error that names it. select: instead of want, each field by a CSS or XPath selector you write, as {"title": "h1", "price": "span.price::text"}; pages may then be empty. Where the fields are is not learnt; what the pages show of them is, and a run fails when a selector finds nothing. Try a selector first with select_values. rows: with select, the selector of a listing's rows ("li.product"), each field then read inside each row.
Returns {"ok", "extractor"}: keep that object and hand it to run_extractor. It holds what the pages declared, the listing's place, its fields, and what every field looked like. An extractor heavier than one answer may be, 75,000 bytes, is too_large: sluicer compile writes it to a file.
Inputs: pages (required), rows, want, select, +1 more. Returns: ok, error, extractor
crawl_siteCrawl a siteARead-onlyIdempotent
Crawl a site from url, following its links, and summarise every page.
url: an http(s) address where the crawl starts; only links on its site are followed. max_pages: the most pages taken, 1 to 25. max_depth: the most links from url, 0 to 3; 0 reads url alone. respect_tdm: give a page whose site reserves its text and data mining rights (TDMRep) as a tdm_reserved error, never its summary. include: text an address must contain for its link to be followed (any one of them); plain text, not a pattern. exclude: text that stops a link being followed when its address contains it.
Returns {"ok", "url", "pages", "stopped"}. Pages come breadth first, each {"ok", "url", "depth", "found_on", "landed", "fetch", "canonical", "summary", "sources", "types", "links"} -- the summary and the types declared, not the records; call extract_declared on a page for those -- or, when it has nothing, {"ok": false, "error"} with the page's reason. stopped is "done", "max_pages" (links were left unfollowed) or "time_budget" (a minute passed). One request at a time, a second apart or the site's Crawl-delay, robots.txt obeyed; a page asked again after a request that may succeed later says so in "retries". ok is false only when no page could be read, and error then says why. Past 75,000 bytes the heaviest summary answers of any page go first, named in that page's summary_left_out, then the last pages, counted in pages_left_out.
Inputs: url (required), exclude, include, max_depth, +2 more. Returns: ok, url, error, pages, +2 more
extract_declaredExtract a page's declared dataARead-onlyIdempotent
Read the structured data a page declares, with where each value came from.
html_or_url: an http(s) URL to fetch, or the HTML itself. induce: also read repeated rows (a listing, a feed) from a page that declares nothing about them; those fields say source "induced". at: a date (2024, 2024-06, 2024-06-01): read the URL as the Wayback Machine captured it nearest to then; "fetch" says which capture. respect_tdm: answer tdm_reserved instead of the page when the site reserves its text and data mining rights (TDMRep: its tdmrep.json, headers or meta tags). records: also return every record, not only the summary and what was normalised; false keeps the answer small. Records that would make the answer larger than 75,000 bytes are left out and counted in records_left_out. visible: also guess the title, author, publication and update dates the page shows a reader, in "visible", each {"value", "where", "rule"}; guesses, never part of the summary, which holds only what the page declares.
Returns {"ok", "url", "summary", "records", "sources"}, and "fetch" for a URL. records are typed fields, each {"value", "source", "where"}: source is the vocabulary that declared it (jsonld, microdata, opengraph, html, ...), where the XPath of the element that did -- for JSON-LD the block's, with a JSON pointer after "#" -- or null for a meta tag, whose key is its place. A nested value such as a price inside "offers" arrives whole. summary answers title, author, date, price and the rest, one value each, naming its source, key and where. conflicts lists each question the page answers two ways that mean different things -- a price of 41.90 in JSON-LD and 39.90 in OpenGraph -- the summary's answer first: say so rather than trusting either. On failure ok is false and "error" says why; there is never a record. An answer weighs at most 75,000 bytes: past it the records go first, counted in records_left_out, then the conflicts, counted in conflicts_left_out, then the heaviest summary, visible, normalised and links entries, each named in summary_left_out, visible_left_out, normalised_left_out or links_left_out.
Inputs: html_or_url (required), at, induce, records, +2 more. Returns: ok, url, error, fetch, +14 more
extract_manyExtract several pagesARead-onlyIdempotent
Read several pages' declared data, politely, in the order given.
urls: 1 to 25 http(s) addresses, on one site or several; one given twice is read once. records: also return each page's records, not only its summary; the heaviest pages' records are left out first to keep the answer under 75,000 bytes, each counted in that page's records_left_out. induce: also read repeated rows from a page that declares nothing about them; those fields say source "induced". respect_tdm: give a page whose site reserves its text and data mining rights (TDMRep) as a tdm_reserved error, never its data.
Returns {"ok", "pages", "stopped"}. Pages come in the order given, each {"ok", "url", "landed", "fetch", "canonical", "summary", "sources", "types", "links"}, and "records" when asked -- or, when it has nothing, {"ok": false, "error"} with the page's reason. A page asked again after a request that may succeed later says so in "retries". Each site is asked one request at a time, a second apart or its Crawl-delay, robots.txt obeyed; several sites at once. stopped is "done", or "time_budget" when a minute passed first and the pages after are left out. ok is false only when no page could be read, and error then says why. Past 75,000 bytes the heaviest pages' records go first, then the heaviest summary answers, named in summary_left_out, then the last pages, counted in pages_left_out. For many more addresses, or a whole site, the command line's sluicer batch has no such bounds.
Inputs: urls (required), induce, records, respect_tdm. Returns: ok, error, pages, stopped, +1 more
fetch_pageFetch a pageARead-onlyIdempotent
Fetch a page's HTML, and say what it cost: plain HTTP or a browser.
url: an http(s) URL. Literal HTML is refused, since nothing would be fetched. offset: where in the HTML this answer starts, 0 for the beginning; the answer before gives the next as next_offset. max_chars: the most characters this answer carries, 1 to 60,000; fewer when more would weigh over 75,000 bytes, as 60,000 characters of Chinese do.
Returns {"ok", "html", "url", "fetch", "truncated", "length", "next_offset"}: html is one slice of the page, length the whole page's, and next_offset where the next slice starts, or null when this one reaches the end. Prefer extract_declared or page_markdown, which return what is in the page rather than all of it.
Inputs: url (required), offset, max_chars. Returns: ok, url, html, error, +4 more
heal_extractorHeal an extractorARead-onlyIdempotent
Learn pages again after a redesign, and say what moved where.
extractor: the object compile_extractor returned. pages: http(s) URLs, or the HTML itself, of the redesigned pages.
Returns {"ok", "extractor", "changes", "lost"}. A field that moved keeps its old name, so rows read with the healed extractor keep their columns, and its change carries the evidence: how many of the values it was learnt with were found in the new place. "lost" is true when a change is data the page no longer has -- vanished, summary-lost, type-lost, listing-lost, or broken: a selector written by hand that the pages no longer bear out, which heal never rewrites -- and then ok is false: the old extractor, which keeps failing, is the safer one to keep until a person looks. A healed extractor heavier than 75,000 bytes is too_large.
Inputs: pages (required), extractor (required). Returns: ok, lost, error, changes, +1 more
map_siteMap a siteARead-onlyIdempotent
List a site's addresses, from its sitemaps or its start page's links.
url: an http(s) address on the site; its links stand in when the site has no sitemap. limit: the most addresses returned, 1 to 1,000.
Returns {"ok", "url", "source", "urls", "sitemaps", "truncated"}. source is "sitemaps" or "links"; each of urls is {"url", "lastmod", "sitemap"}, only addresses on the site, in the order the sitemaps list them; sitemaps says what became of each one tried. At most ten sitemaps are read, politely, within a minute; truncated is true when a bound cut the map short, urls_left_out counting the addresses left out to keep the answer under 75,000 bytes. Hand the addresses worth reading to extract_declared, or crawl_site to follow links from one.
Inputs: url (required), limit. Returns: ok, url, urls, error, +4 more
page_markdownRead a page as markdownARead-onlyIdempotent
Return a page's main content as markdown, without navigation or footer.
html_or_url: an http(s) URL to fetch, or the HTML itself. front_matter: open the markdown with a YAML block of what the page declares about itself (title, author, dates, url...) and each answer's source. at: a date: read the URL as the Wayback Machine captured it then. respect_tdm: answer tdm_reserved when the site reserves its text and data mining rights (TDMRep). offset: where in the markdown this answer starts, 0 for the beginning; the answer before gives the next as next_offset. max_chars: the most characters this answer carries, 1 to 60,000; fewer when more would weigh over 75,000 bytes, as 60,000 characters of Chinese do.
Returns {"ok", "markdown", "url", "length", "next_offset"}, and "fetch" for a URL: markdown is one slice, length the whole markdown's, next_offset where the next slice starts or null at the end. The markdown is always the page's own content: a failure is ok false with "error", never text that could be mistaken for the page.
Inputs: html_or_url (required), at, offset, max_chars, +2 more. Returns: ok, url, error, fetch, +3 more
read_feedRead a feedARead-onlyIdempotent
Read a feed's items: RSS, Atom or JSON Feed.
url_or_text: an http(s) URL of a feed -- or of a page that declares one, which is followed to it -- or the feed itself. limit: the most items answered, 1 to 500; items_total says how many the feed holds.
Returns {"ok", "url", "format", "title", "link", "description", "items", "items_total"}, each item {"title", "link", "id", "published", "updated", "summary", "content", "authors", "categories", "enclosures", "normalised"}, dates in normalised as ISO 8601. What is not a feed, and declares none, is bad_input. Items that would make the answer weigh over 75,000 bytes are left out, counted in items_left_out; fetch_page reads the whole feed in slices.
Inputs: url_or_text (required), limit. Returns: ok, url, link, error, +9 more
run_extractorRun an extractorARead-onlyIdempotent
Replay an extractor on one page, and check the page still keeps to it.
extractor: the object compile_extractor returned. html_or_url: an http(s) URL to fetch, or the HTML itself.
Returns {"ok", "rows", "fields", "summary", "failed"}; "fields" holds the page's own values an extractor learnt from examples. ok is false when the page drifted -- the listing moved, rows or a field vanished, a price no longer looks like a price -- and "failed" says which expectation broke. Never read rows from an answer whose ok is false as if nothing happened. Past 75,000 bytes the last rows are left out, counted in rows_left_out, then the heaviest summary and fields entries, named in summary_left_out and fields_left_out.
Inputs: extractor (required), html_or_url (required). Returns: ok, rows, error, failed, +5 more
select_valuesSelect values on a pageARead-onlyIdempotent
Say what a CSS or XPath selector gives on a page, and where each value is.
html_or_url: an http(s) URL to fetch, or the HTML itself. selector: CSS, with ::text for an element's own text and ::attr(name) for an attribute ("span.price::text", "a::attr(href)"), or XPath ("//h1", "//a/@href"), told apart by how it begins: an XPath begins with /, ./, ( or @, or is written after "xpath:". respect_tdm: answer tdm_reserved when the site reserves its text and data mining rights (TDMRep).
Returns {"ok", "url", "values", "count"}, and "fetch" for a URL: values are {"value", "where"}, the text or attribute read, spaces collapsed, links resolved, and the XPath of its element; count is how many the selector gave. A selector that cannot be read is bad_input naming it; one that gives nothing is ok with no values. Past 75,000 bytes the last values are left out, counted in values_left_out. Use it to try the selectors compile_extractor's select takes.
Inputs: selector (required), html_or_url (required), respect_tdm. Returns: ok, url, count, error, +3 more
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 12 tools
Each tool has a clearly distinct purpose: fetching raw HTML, extracting declared data, converting to markdown, testing selectors, compiling/replaying/healing extractors, auditing structured data, reading feeds, mapping sites, crawling, and batch extraction. Even overlapping tools like extract_declared and extract_many are separated by single vs. multiple pages, and crawl_site vs. map_site by following links vs. listing sitemap URLs. No two tools appear to do the same thing.
Most tools follow a verb_noun pattern (fetch_page, compile_extractor, run_extractor, audit_page, read_feed, map_site, crawl_site, select_values), but a few deviate: 'extract_declared' and 'extract_many' use verb+modifier, and 'page_markdown' is noun-like, breaking the pattern. Still, the naming is generally predictable and readable.
12 tools is well within the typical 3-15 range for a web data extraction server. Each tool addresses a distinct capability—fetching, extracting, testing, learning, replaying, healing, auditing, reading feeds, mapping, crawling, and batch processing—without redundancy or bloat.
The toolset covers the full lifecycle of web data extraction: raw fetch, structured data extraction, markdown conversion, selector testing, extractor creation/replay/healing, structured data auditing, feed reading, sitemap mapping, site crawling, and batched extraction. It also handles TDM rights, Wayback Machine, and pagination, leaving no obvious gaps for its stated purpose.
Maintenance
Related MCP Connectors
Related MCP Servers
- AlicenseAqualityBmaintenanceFirecrawl, the context API for AI agents, as an MCP server. Search the web, scrape any URL, parse PDFs and documents, crawl and map sites, interact with pages in a real browser, run Agent research jobs, monitor pages for changes, and search scientific papers. Returns clean Markdown or structured JSON. Run locally with npx firecrawl-mcp or connect to https://mcp.firecrawl.dev/v2/mcp28201,139 npm7,816MIT
- -
- -
- MIT