curl -s https://www.pathwren.workers.dev/mcp-netcheck.json   # this page, as JSON

No key, no account, no handshake — every page here has a JSON twin one hop away. Machine doors: 6 keyless GET tools · documents.json · changes · llms.txt · openapi.json · agent card · mcp · a2a

Crawler IP Verifier — MCP server

Connect it: one paste, one call

https://www.pathwren.workers.dev/mcp/netcheck
no auth, read-only, public

That is the whole endpoint and those are its terms: Streamable HTTP (MCP), no API key, no account, no OAuth, no session to keep alive, nothing to install. Every tool is read-only, and none of them will fetch a URL on your behalf.

The JSON a connector config wants — Claude Desktop, Cursor, VS Code, Windsurf, Cline, LibreChat, Continue, anything that takes an mcpServers block. Complete as it stands; there is no field to fill in:

{
  "mcpServers": {
    "crawler-ip-verifier": {
      "type": "streamable-http",
      "url": "https://www.pathwren.workers.dev/c/mcp-connector/mcp/netcheck"
    }
  }
}

Claude Code takes one line instead:

claude mcp add --transport http crawler-ip-verifier https://www.pathwren.workers.dev/c/mcp-connector/mcp/netcheck

The URL in those three boxes carries /c/mcp-connector/, a channel tag: it is the same endpoint by another path, serving byte-identical responses, and it lets this host see that a client arrived from a config pasted off this page rather than from a directory. Strip the prefix and https://www.pathwren.workers.dev/mcp/netcheck is the canonical URL — both work, and nothing about the answer changes.

Then call no_arguments_report_the_crawler_ip_ranges_this_host_mirrors first. It takes no arguments at all, so there is nothing to invent and nothing to look up before you can see this server work — the subject of the answer is the mirror itself — every operator prefix list this host holds, and how stale each one is:

curl -s https://www.pathwren.workers.dev/c/mcp-connector/mcp/netcheck \
  -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"no_arguments_report_the_crawler_ip_ranges_this_host_mirrors","arguments":{}}}' \
  | jq -r '.result.content[0].text' | head -3

15/15 operator prefix lists mirrored, 1987 IPv4 + 1062 IPv6 prefixes (652 /24-equivalents), newest 58m ago, oldest 58m ago.
google-user-triggered: 529 IPv4 + 529 IPv6, fetched 58m ago
google-user-triggered-google: 248 IPv4 + 248 IPv6, fetched 58m ago

Those are the answer's own first three lines, from one real run on 2026-09-06 — the mirror refreshes every six hours, so run the curl and read today's. The tool takes no arguments because its subject is not you: it is the state of the mirror every other tool here checks against, so every caller gets the same bytes. That state is what decides how much any verdict is worth — a prefix list fetched four days ago and a verdict of "not in any published range" mean two different things, and this is the tool that tells you which one you have. The rest of the answer is the per-source table with coverage and freshness in minutes, the prefixes more than one operator claims, and the verification method each operator documents for the crawlers that publish no list at all. Nothing is fetched to answer it — this server makes no outbound request of any kind — and no argument exists. verify_my_address is the second zero-argument call and answers about YOU; whoami is the third and works unchanged on every MCP server here; example is the fourth. All four are safe first calls.

A user-agent is a claim anyone can type. The operators who take that seriously publish the networks their crawlers come from; this host mirrors every one of those lists it can find, and this server is the batch and prefix arithmetic over the mirror — not one address and a yes/no, which /mcp already answers, but the shapes the question actually takes in operations.

# a whole log's worth of addresses, in one call
curl -s https://www.pathwren.workers.dev/mcp/netcheck \
  -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"verify_batch",
       "arguments":{"addresses":["20.171.206.1","203.0.113.9"]}}}' \
  | jq -r '.result.structuredContent.results[] | "\(.ip)\t\(.verdict)"'

Tools

ToolWhat it does
no_arguments_report_the_crawler_ip_ranges_this_host_mirrorsTAKES NO ARGUMENTS. The state of the mirror every other tool here checks against: each crawler-operator prefix list this host holds, its IPv4 and IPv6 prefix counts and address total, when it was last fetched and how many minutes ago that was, which crawlers it covers, which prefixes more than one operator claims, and the verification method each operator documents for the crawlers that publish no list at all. It is the fact that decides how much any verify_batch verdict is worth. The input schema is literally empty — `arguments: {}` and no arguments key at all both work. No key, no account, no OAuth, read-only. Nothing is fetched to build it: no request leaves this edge and none is made to you. Example: arguments={} returns the per-source table with freshness in minutes.
verify_my_addressTakes no arguments. Safe to call. Deterministic. Touches no third party. THE CALL TO MAKE FIRST on this server. Takes the address you are calling from and checks it against every operator-published prefix list this host mirrors, with your user-agent taken as the claim: the verdict, the prefix that matched, which operator published it and when it was last mirrored — and, when your user-agent names a crawler this index tracks, how that operator can be verified at all. Same verify_batch a paid call would run, one address, in process. No key, no account, no OAuth, read-only, nothing to invent. Example: arguments={} returns a verdict for your own address and the sentence that a miss is not evidence of a fake.
verify_batchCheck up to 500 addresses against every mirrored operator prefix list in one call. Returns, per address, the matching prefix, the source that published it and when it was mirrored, plus a rollup by operator. Accepts {ip, claim} pairs to test a claimed identity against the ranges, and says plainly that a miss is not evidence of a fake. Example: addresses=[{ip:'66.249.66.1', claim:'Googlebot'}, {ip:'203.0.113.9', claim:'GPTBot'}] — the first matches a published Google prefix, the second matches nothing. Also callable without MCP, same implementation: GET https://www.pathwren.workers.dev/tools/verify-crawler?ip=66.249.66.1&ua=Googlebot&s=client-dossiers
lookup_prefixGive a CIDR and get every published operator prefix that contains it, is contained by it, or partially overlaps it — the check for 'does my network collide with a crawler range' and for auditing an allowlist somebody handed you. Give an operator or source slug instead and get everything that source publishes. Example: cidr='66.249.66.0/24'.
export_ip_aclTurn a set of operators into a config you can paste: nginx geo, nginx allow/deny, Apache, HAProxy, a Cloudflare firewall expression, an ipset script, a Caddy matcher, or a plain CIDR list. Every export carries a provenance header naming each source URL and the mirror time, and reports the rule cost. Example: operators='all', format='cidr-list', action='allow'.
verification_methodsPer crawler: whether the operator publishes IP ranges, documents reverse DNS, or publishes no verification method at all — with the exact source URL or the exact dig commands, and how fresh this mirror is. Call it before reading anything into an address that matched nothing. Reverse DNS is never run here; the command is handed back instead. Example: crawler='claudebot'. Also callable without MCP, same implementation: GET https://www.pathwren.workers.dev/tools/verification-methods?crawler=claudebot&s=client-dossiers
range_statsTakes no arguments. Safe to call. Deterministic. Touches no third party. Per source: prefix counts, total addresses and /24-equivalents, smallest and largest prefix, the publishing URL and when it was last fetched; plus prefixes published by more than one operator. This is the sizing and freshness information an allowlist decision needs. Example: {} — no arguments at all.
whoamiTakes no arguments. Safe to call. Deterministic. Touches no third party. Classifies the request you just sent: the user-agent you claim, the address you came from, the class this host's own instrument books you as, whether we have seen you here before and what you fetched, and what this host's robots policy says about you. Every fact comes from the headers on your own request or from a file this host already publishes — nothing is fetched, nothing about you is invented, no argument exists. Example: arguments={} returns your user-agent, your address, the class we book you as and whether we have seen you here before.
exampleTakes no arguments. Safe to call. Deterministic. Touches no third party. Runs this server's own worked example end to end — one of its real tools, on a canned input taken from this host's own published data — and returns exactly the structuredContent a real call returns, not a mock and not a description of one. Use it to see the shape of an answer before you decide what to send. No URL of yours is fetched and no third party is touched. Example: arguments={} runs it and returns the real answer.

What is behind it

15 operator-published range endpoints, re-fetched every six hours, unioned into /ip-ranges/all.json. Every answer carries the source URL the prefix came from and the minute that source was last fetched, because a range list without a date is a guess with better formatting. An upstream that is down keeps its last known prefixes and is marked failed on /status.json rather than silently shrinking.

Allowlists you can paste

export_ip_acl emits nginx geo, nginx allow/deny, Apache Require ip, an HAProxy list, a Cloudflare firewall expression, an ipset script, a Caddy remote_ip matcher, or a plain CIDR list — with a header naming every source and its fetch time, and the rule cost stated up front, because whether an allowlist is 40 lines or 2,900 decides which of those formats you can afford.

What it refuses to claim

Absence from a range list is not evidence of a fake. Most crawlers in this index publish no ranges at all, and several document reverse DNS instead — so a miss means "cannot be checked this way", not "impostor". verification_methods exists to tell you which of the three cases you are in before you act on a result, and every answer repeats the distinction rather than assuming you read it once.

Reverse DNS is never performed here. This server makes no outbound request of any kind — no tool takes a URL or a hostname to resolve. Where an operator's documented method is the reverse/forward lookup, you get the exact dig commands and an explicit "not run", the same refusal crawler-log-triage makes.

How is this different from /mcp?

is_verified_crawler_ip over on ai-crawler-index takes one address and returns a boolean. That is the right tool for one question asked once. This server takes 500 addresses and returns a table plus a rollup; takes a CIDR and returns containment and overlap; takes a set of operators and returns a config file. No tool name is shared with any of the other four servers here.

Protocol versions 2025-06-18, negotiated per call. server/discover answers for clients on 2026-07-28, initialize for everyone else. Read-only, stateless, no key. Listed in the official MCP Registry as dev.workers.pathwren.www/crawler-ip-verifier.

This page as markdown: /c/smithery-direct/mcp-netcheck.md — the same text, no markup to strip, no JavaScript, no key, CC0. Every page here has one: add .md to any address (also .mdx, <page>.html.md, <page>.html.mdx), or send Accept: text/markdown to this one. All of them in a single index: /c/smithery-direct/sitemap.md.