crawl-census
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@crawl-censuspreflight these domains for gptbot"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
crawl-census-client
Ask before you fetch. A drop-in client that stops your crawler spending requests on doors that are shut, and stops it routing around content someone is trying to sell.
Reading robots.txt answers one question and hides two others. Measured across 23,482 domains by Crawl Census:
2,874 domains permit AI agents in robots.txt and then refuse those same agents at the network edge. A parser sees permission; the fetch returns 403. You pay for the round trip and get nothing.
208 domains answer an AI user agent with
HTTP 402 Payment Required. That is a price, not a refusal. Treating it as a block walks away from content the operator wants to sell you. Retrying around it takes something they are charging for.
No dependencies. No key required.
MCP server
The same measurement is exposed as a remote MCP server, so an agent can ask before it fetches
rather than after it fails. Listed in the
official MCP registry as
io.github.taylorsmithgg/crawl-census.
{ "mcpServers": { "crawl-census": { "url": "https://crawlcensus.com/mcp" } } }Tool | Answers |
| will these domains serve my agent, refuse it, or charge it? |
| what does this census publish about my crawler, and how do I correct it? |
| the headline findings as dated records with denominators and citation lines |
| the stored audit for one domain |
| measure a domain now |
| corpus-level totals |
No authentication for read tools. Streamable HTTP.
Related MCP server: Maango-mcp
Install
npm i github:taylorsmithgg/crawl-census-client
pip install git+https://github.com/taylorsmithgg/crawl-census-clientUse
import { politeFetch } from "crawl-census-client";
const r = await politeFetch("https://example.com/", { agent: "gptbot" });
if (r.skipped) console.log(r.verdict, r.reason); // disallow | refuse | pay
else process(await r.response.text());from crawl_census import polite_fetch
r = polite_fetch("https://example.com/", agent="gptbot")
if r.skipped:
print(r.verdict, r.reason)
else:
process(r.body)Skipping is returned, not raised. It is the normal outcome for a large share of the web, and a crawl loop should be able to count skips without a try/except around every URL.
Split a queue before crawling it
One call per 1,000 domains instead of one per host:
const { crawl, skip, pay, unknown } = await partition(urls, { agent: "gptbot" });p = partition(urls, agent="gptbot")
p.crawl, p.skip, p.pay, p.unknownOr just take the file
For a fetcher that only needs a deny list in memory, skip the per-domain calls entirely:
curl https://crawlcensus.com/agents/gptbot/blocklist.txt # one domain per line, commented headerconst sync = await syncBlocklist("gptbot"); // full list once
if (sync.blocked.has(host)) skip();
setInterval(() => sync.refresh(), 3600_000); // then deltas only, a few hundred bytessync = BlocklistSync("gptbot")
if host in sync: skip()
sync.refresh() # {'added': 3, 'removed': 1, 'size': 3310, 'cursor': ...}The delta feed is https://crawlcensus.com/agents/<agent>/changes.json?since=<unix> and each
response carries next_since, so a long-running crawler stays current on a few hundred bytes
an hour instead of re-downloading the list.
That file covers robots.txt only. Edge refusal and HTTP 402 are per-request behaviours and
still need preflight or politeFetch.
What a crawl costs the census
Measured, not asserted. Twenty hosts fetched concurrently used to cost twenty preflight calls carrying one domain each; the same host requested three times at once cost three, because the cache only helps after the first lookup resolves. The anonymous allowance is 240 calls an hour, so a crawler hit its ceiling at 240 hosts when one call covers twenty-five.
politeFetch now shares work automatically: lookups issued in the same tick leave as one
batched call, and concurrent lookups for the same host await a single request.
pattern | before | now |
20 hosts, concurrent | 20 calls | 1 call of 20 |
1 host, 3 URLs, concurrent | 3 calls | 1 call |
60 hosts, concurrent | 60 calls | 3 calls (25 / 25 / 10) |
| 2 calls | 1 call |
batchSize defaults to 25, the per-call cap without a key. Raise it with a Pro or Data key.
batchWaitMs widens the coalescing window for concurrency that arrives in waves rather than
all at once; the default of zero flushes on the next tick.
Paying, when an origin quotes a price
A pay verdict carries the amount when the origin named one:
const r = await politeFetch(url, { agent: "claudebot" });
if (r.verdict === "pay") console.log(r.price); // "USD 0.5", or null if none was quotedTwo things worth knowing. Most origins answering HTTP 402 name no amount at all, so price
is usually null and the arrangement has to be made out of band. And pricing is per crawler:
across the measured corpus, 78 of 213 charging origins charge some agents and serve others
free, so ask with your own token rather than assuming a domain on the list will charge you.
Two kinds of unknown
partition splits a work queue into crawl, pay, skip, unknown and undecidable.
The last two look alike and are not. unknown means the census has not measured that domain
yet: submit it and the next pass gets a real verdict. undecidable means the site's robots.txt
disallows CrawlCensusBot, so this census will never measure it — retrying is guaranteed waste,
and a loop that resubmits its unknowns each pass would resubmit those forever. The server marks
the difference with a measurable boolean; read that, never the reason prose.
const p = await partition(urls, { agent: "gptbot" });
await Promise.all(p.crawl.map(politeFetchOne));
if (p.unmeasured.length) await submitUnmeasured(p, { agent: "gptbot" });
// p.undecidable: read their robots.txt yourself. Asking us again cannot help.Submission is a separate call on purpose. A library that quietly POSTs during what reads as a lookup is a bad citizen, and you should choose when your queue positions are spent.
Skipping everything that will not serve you
A deny list is the smaller half. Measured against the live census, a crawler that skips only robots disallows still spends around 2,900 requests a pass on domains that permit it in robots.txt and refuse it at the edge, or that answer HTTP 402 — for PerplexityBot that set is larger than its deny list. Those fetches return nothing and cost a round trip each.
const skip = await syncSkipList("gptbot");
if (skip.has(host)) continue; // disallowed, refused at the edge, or priced
skip.why(host); // "disallow" | "pay" | "refuse" | null
setInterval(() => skip.refresh(), 3600_000);agent | deny list | also skippable | total |
GPTBot | 3,542 | 2,944 | 6,486 |
ClaudeBot | 3,169 | 3,194 | 6,363 |
PerplexityBot | 1,128 | 3,658 | 4,786 |
The three sets are kept apart internally, so a change moves the one it belongs to. why()
follows the same precedence as preflight: a disallow outranks a price, because a price is not
permission.
Keeping a deny list current
syncBlocklist / BlocklistSync download the list once, then apply only what changed.
The list is served with the exact position in the change feed it was built at, in an
x-cursor header and a # cursor: comment. The clients read it and resume from there, so
there is no gap between the snapshot and the first poll, and no reliance on your clock being
in step with the server's. Polling by second cannot express a position inside a second, and a
crawl batch writes dozens of events into one, so a second-granularity resume can drop the
remainder of it: measured live, resuming after the first of three same-second changes
recovered both siblings by cursor and neither by second.
const sync = await syncBlocklist("gptbot"); // cursor comes from the list itself
if (sync.blocked.has(host)) skip();
setInterval(() => sync.refresh(), 3600_000); // a few hundred bytes per pollrefresh() applies only robots transitions to the list, because that is what the list is made
of. Edge refusals, new prices and llms.txt changes come back in other for you to act on
separately — an earlier version deleted those domains from the deny list, so a crawler resumed
fetching exactly what had just started refusing it.
Verdicts
The authoritative definition of each verdict — what it means, what it obliges a crawler to do,
and whether asking again could change it — is published as data at
/api/v1/verdicts. The list below is a summary; if
the two ever disagree, the endpoint is right and this file is stale.
politeFetch skips disallow, refuse and pay by default, which is the endpoint's derived
do_not_fetch set. The copy here is deliberate — a crawl loop should not need a network call to
decide — and a test compares the two so it cannot drift unnoticed.
Verdict | Meaning | Default behaviour |
| robots.txt permits this agent, and a live request carrying its user agent was served | fetch |
| robots.txt forbids this agent at the site root | skip |
| robots.txt permits it; the edge refused it anyway. The allowance is not real | skip |
| the origin answered HTTP 402. It will serve this agent on commercial terms | skip |
| not measured recently enough to answer | fetch |
onPay: "fetch" (on_pay="fetch") overrides the paywall default. It is an explicit opt-in and
is recorded on the result as paidRouteOverridden so it shows up in your logs.
It degrades, it does not fail
If the census is unreachable every verdict becomes unknown and your crawl proceeds as it
normally would. A third-party outage must never stop your pipeline. There is a live test for
exactly this.
What we publish about your agent
const p = await agentProfile("claudebot");
// robots disallow rate, edge refusal rate, operator page, correction channelIf a figure is wrong, the correction channel is in that response and on your operator page. Registry facts are corrected without argument; disputed measurements are published alongside the dispute with the underlying scan records, rather than quietly amended.
Limits
25 domains per preflight call anonymously, 200 with a Pro key, 1,000 with a Data key. Pass
apiKey. Details at https://crawlcensus.com/for-crawlers.
Tests
node test.mjs runs against the live census on purpose. The value of this client is whether
its verdicts match reality, and a mocked test would assert only that the mock agrees with itself.
MIT. Data is CC BY 4.0, attribute as "Source: Crawl Census (crawlcensus.com)".
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityDmaintenanceURL intelligence for AI agents. One URL in, structured security and data quality signals out across 7 dimensions. 13 tools, risk score 0-100 with 23 configurable weights.161601MIT

Maango-mcpofficial
AlicenseAqualityCmaintenanceMaango is the pre-flight check for AI agents on the web. Before an agent scrapes, summarises, trains on, or searches a site, it calls Maango and gets back whether the action is allowed for that domain, along with the reason and the policy signals that decided it.71MIT- AlicenseAqualityAmaintenanceEnables AI agents to check URL safety before fetching content, using Google Web Risk, URLhaus, PhishTank, and AI analysis to return SAFE/SUSPICIOUS/DANGEROUS verdicts.13651MIT
- AlicenseAqualityAmaintenanceEnables AI agents to check whether a public website is crawlable, understandable, and ready for AI search workflows through local-only audits of robots.txt, sitemaps, metadata, and llms.txt.3511MIT
Related MCP Connectors
Domain & company intel for AI agents: RDAP, DNS, email deliverability, tech stack. No API keys.
Domain intel for AI agents: RDAP registration, DNS, email deliverability, tech stack.
URL intelligence for AI agents and developers. 16 tools, 25 signal weights, 20 free checks.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/taylorsmithgg/crawl-census-client'
If you have feedback or need assistance with the MCP directory API, please join our Discord server