Spec Driven Development
MCP Server that facilitates spec-driven development workflows, not just Vibe Coding.
Description from the maker (original English)
Verification layer for the agentic web: a signed answer about any site before an agent acts.
These three are never merged into one score: opinions, measurements and popularity are different things.
We have not written a plain-language summary for this one yet. Below are the facts we read from the registry.
Pick the AI app you use, then copy the settings.
You need
Which app do you use?
Pick your app above. We remember it, so next time the button says “Install in …”.
Works in Claude on the web, desktop and mobile:
https://crawlcheck.io/mcpRun this in your terminal
claude mcp add --transport http crawlcheck https://crawlcheck.io/mcpPaste into .cursor/mcp.json in your project, or ~/.cursor/mcp.json
{
"mcpServers": {
"crawlcheck": {
"url": "https://crawlcheck.io/mcp"
}
}
}Your browser asks to open the app, then the app shows the settings and asks you to confirm. Key values are left empty for you to fill in.
Paste into .vscode/mcp.json in your project
{
"servers": {
"crawlcheck": {
"type": "http",
"url": "https://crawlcheck.io/mcp"
}
}
}Your browser asks to open the app, then the app shows the settings and asks you to confirm. Key values are left empty for you to fill in.
ChatGPT connects only to servers on the internet:
https://crawlcheck.io/mcpMenu names can differ slightly by plan and over time.
Your choice stays on this device in a cookie; no account needed.
These settings are generated from the maker’s registry entry. If the README says otherwise, follow the README.
We have not read this server’s tool list yet; we check endpoints in rotation.
We check information anyone can see; we have not read the code. Checks that do not apply are left out of the count. · Checked Nov 6, 2026
The registry name was verified with the GitHub account emmanuelorta, which owns the repo
We have not read this repo from GitHub yet
We have not read this repo from GitHub yet
Not checked yet; we check endpoints in rotation
Not checked yet; we check endpoints in rotation
Not checked yet; we read the tool list when we check the endpoint
Compared with every active server in this category in the registry. Stars count only when the publisher owns the repo.
It passed 1 of 1 applicable basic checks. We have not read the code. Before adding a key or token, see what access the server asks for and use a key with only the permissions you need.
The registry does not list a required key, though some servers ask you to sign in on first use.
It runs in the maker’s cloud. Connect over the internet with nothing to install, but what you send passes through the maker’s servers.
The GitHub repo last changed –, and the latest version (1.1.0) reached the registry 21 days ago.
Apps that support MCP, such as Claude Desktop, Claude Code, Cursor and VS Code. It connects by URL, so apps that accept remote MCP servers work too, such as ChatGPT in developer mode.
In the same category: Spec Driven Development, Remote Desktop Commander, Toggl 2.0
Servers in the same category from other makers, sorted by stars.
Reading a card:★ GitHub starsiThree pillars: reviews · checks · usageiUpdated = latest version published to the registryi
MCP Server that facilitates spec-driven development workflows, not just Vibe Coding.
Remote Desktop Commander: use files, terminal and processes on your own computers from any AI app.
Toggl 2.0 MCP server for tasks, projects, time entries, and workspace operations.
Copied from the maker’s repo as written. PopNx MCP has not reviewed or endorsed it. HTML, images and scripts are removed. Links lead off this site.
The dependency-free modules of CrawlCheck, the verification layer for the agentic web, extracted from the production service. These are the readers behind its signed answer about what a site permits and serves to AI crawlers and agents. No build step, no dependencies, ESM, Node 20+.
The hosted service fetches a site as 12 crawler identities (plus a browser and two control requests) and measures 47 sections across three stages — reach (can the crawler get in), read (what it actually receives), quote (can an answer engine lift it). This repository is the open core: the readers and checks that need no network, no store and no key, published so the measurements can be reproduced.
What is here today: the robots.txt policy reader, the crawler identifier / log-line forgery verifier, the quotable-content reader, the image-signal reader, and the markdown-negotiation and agent-surface probes. What stays in the service: the scoring — the thresholds each reading is judged against, the weight every section carries in the grade, and the corpus calibration those numbers were set from — plus fetching as those crawler identities, the record over time, monitoring, the dataset, and billing. The rule is simple: a reading of your page is a fact you are entitled to check, and the calibration is the product.
@crawlcheck/sdk (npm): the typed client for the CrawlCheck API and the zero-dependency offline verifier for signed answers, evidence bundles and receipts. npm test runs its suite against production.crawlcheck (PyPI): the same client and verifier for Python 3.9+, no dependencies.Both are published from the private service repository by a workflow that refuses to publish unless the directory here is byte-identical to what it ships, so this copy is the source the package pages point at, not a mirror that can drift.
npm install github:emmanuelorta/crawlcheck-core
Not on the npm registry yet, so install it from the repository. Every subpath below resolves from that install.
import { crawlerPolicy } from "crawlcheck-core/robots";
const policy = crawlerPolicy(robotsTxt, /* unreadable */ false, "/wp-admin/"); policy.agents.find(a => a.ua === "GPTBot"); // { ua:"GPTBot", label:"OpenAI training corpus", role:"train", // allowed:true, allowed_root:true, named:true, rules_shadowed:true } policy.shadowed; // [{ uas:["gptbot"], missing:["/wp-admin/","/feed/"], missing_count:2 }]
For each of 114 named agents (answer engines, search indexes, training crawlers, SEO tools, social unfurlers, regional engines) it reports: which user-agent group applies (named), whether the given path and the root are allowed under that group, and whether the group shadows the * rules.
A crawler obeys only its most specific group. The moment you write
User-agent: *
Disallow: /wp-admin/
User-agent: GPTBot Allow: /
GPTBot no longer sees Disallow: /wp-admin/ — the * group stops applying to it entirely. Sites that add an allowlist for AI agents routinely void every rule they thought still held. shadowedGroups() lists each named group and the * disallows it lost. This was found on our own sites first: how to block AI crawlers and the glossary entry for user-agent group.
| Export | What it does |
|---|---|
crawlerPolicy(txt, unreadable, path) | Full per-agent policy for one file. measured:false with a reason when the file is empty or unreadable — an absence is never reported as a policy |
parseGroups(txt) | RFC 9309 groups: consecutive User-agent lines share one group; Allow/Disallow rules in order |
groupFor(groups, ua) | The group that applies to an agent, per RFC 9309: the most specific matching token wins (version suffix ignored), every group naming that token is combined, a group for a more specific token (Googlebot-News) never applies to the general crawler, Google's documented fallbacks (Google-InspectionTool obeys Googlebot) apply, and * is the last resort |
pathAllowed(group, path) / rootAllowed(group) | RFC 9309 matching (* anywhere, trailing $ anchors the end); longest rule wins; Allow beats Disallow at equal length |
ruleMatches(rule, path) / decide(rules, path) | The matcher itself, and the decision with the rule that made it |
shadowedGroups(groups) | Named groups for which a * disallow is lost (not covered by one of the group's own rules, e.g. its own Disallow: /) |
AGENTS | The 114-agent table: [token, label, role] |
Crawl-delay, Sitemap and unknown fields are ignored, and percent-encoding is not normalised (rules and paths are compared as written). Path matching follows RFC 9309: * matches any run of characters and a trailing $ anchors the end, so Disallow: / + Allow: /$ opens the homepage and nothing else. Versions before 0.6.0 stripped * and ignored $; production changed on 2026-09-30, and 0.6.0 brings this module back to the service's reading. The tests check parity on real robots.txt files, including paths decided by * rules.
import { identifyBot, loadRanges, verifyLines } from "crawlcheck-core/agents";
identifyBot("Mozilla/5.0 (compatible; GPTBot/1.2; +https://openai.com/gptbot)"); // "GPTBot"
const ranges = await loadRanges(fetch); // the operators' published IP feeds (cache this for a day) const r = verifyLines(accessLogText, ranges); r.tally; // { verified, spoofed, unverifiable, no_bot } r.forged_rate_pct; // spoofed / (verified + spoofed), or null when nothing is checkable r.rows[0]; // { line, bot, ip, verdict, why }
Three verdicts, never two. Verified: the address is inside a range the claimed operator publishes. Spoofed: it is outside every range that operator publishes. Unverifiable: the operator publishes no feed (Common Crawl, ByteDance, Meta) or the feed could not be read — and that is reported as unverifiable, not as forgery. The forged rate divides by verified + spoofed only; counting unverifiable hits as forged would inflate it and counting them as genuine would understate it.
| Export | What it does |
|---|---|
identifyBot(ua) | The named crawler token in a user-agent, longest match wins; null for browsers |
verifyLines(lines, ranges) | The verifier behind /api/tool/verify: string or array of log lines → tally, rate, per-line verdicts |
verifyBotIP(bot, ip, ranges) | true / false / null for one hit |
loadRanges(fetch) | Fetches the 10 operator feeds (OpenAI ×3, Anthropic, Perplexity ×2, Google ×2, Bing, Apple) into the shape the verifier reads. The only network call in the package; absent feeds make their operator unverifiable |
ipInCidr(ip, cidr), ip4ToInt, ip6ToBig | Range matching, IPv4 and IPv6 (including IPv4-mapped) |
BOT_TOKENS, BOT_FEED_OF, BOT_IP_FEEDS, CRAWLERS | The tables: tokens, token → feed family, feed URLs, and the 18-row operator/role/purpose table |
Why this exists: CrawlCheck's own telemetry once counted 78 of 85 "GPTBot" hits as forged — they were our own test requests. Reverse DNS is the older check the feeds replace; a user-agent is a self-assertion and proves nothing on its own.
import { quotable } from "crawlcheck-core/quotable";
const { signals } = quotable(html, "Acme Fencing LLC");
signals.lead_defines; // does the first sentence say what this IS signals.answer_units; // paragraphs of 15-70 words that carry a fact signals.quotable_share; // % of sentences an engine could attribute unedited signals.faq_questions; // declared in FAQPage markup signals.faq_visible; // ...and actually readable on the page
The question is not whether the copy is good. It is whether, when an answer engine wants to state a fact about this page, there is a sentence on it that can be lifted as it stands — or whether the engine has to assemble one, in which case the answer carries the engine's wording rather than yours.
This module publishes the reading, not the scoring. Every count and share the production service records for a page is produced here and is reproducible byte-for-byte against a live scan — that is what the parity test does, and it refuses to compare unless the page bytes still match the ones the fixture was captured from. What the service keeps is what those readings are judged against: the thresholds, the weight the section carries in the overall grade, and the multi-hundred-site corpus pass the numbers were calibrated from.
The distinction is not a marketing line, it is the one that keeps the product honest. Anyone can check that we read their page correctly; nobody has to take our word for a number. What they cannot lift is the calibration, which is the part that took a corpus to earn.
Two details that are easy to get wrong and are settled here. FAQ parity is checked against the whole visible page, not the stripped body — an FAQ accordion inside a is still text a reader can see. And counts come from body paragraphs and list items with navigation, header, footer and forms removed, because a nav menu is not prose and counting it inflates every ratio on the page.
| Export | What it does |
|---|---|
quotable(html, nameHint) | The production call site in one call: parses the JSON-LD graph and returns { signals } |
quoteSignals(html, nodes, nameHint) | The reader. nodes comes from ldGraphNodes(html); null when the input has no |
Scoring the reading — quoteRows, quoteBlock, the section weight and the score version — is in the service, not this package. Point the free scan at a URL to see the rows: https://crawlcheck.io/.
import { tgt, pctScore } from "crawlcheck-core/rows";
A row is { k, cur, opt, ok, why }: the check, what this page currently does, what it should do, the verdict, and why it matters. `ok: null` is the third state and it is load-bearing — it means *measured but not scored*. A signal too rare to judge on, one that does not apply to this kind of site, or one that is purely informational is shown to the reader and deliberately left out of the arithmetic. pctScore() divides by the scored rows only and returns null, never 0, for a section with none: an unmeasured section is not a failing one.
That rule is why a service-area business is not marked down for having no street address, and why a brand-new site with no field performance data is not reported as slow. Most audit tools have two states, and every gap in their own measurement becomes your zero.
import { agentSurfaces, grab } from "crawlcheck-core/surfaces";
const root = await grab("https://example.com/"); const out = await agentSurfaces("https://example.com", root);
out.found.markdown; // Accept: text/markdown got a text/markdown answer out.found.agents_md; // an authored /agents.md, not a 200 of HTML out.found.link_header; // read off the root response — no extra request out.evidence.markdown; // { content_type: "text/markdown; charset=utf-8" }
A scanner's Accept header is part of its identity, and getting it wrong makes the whole grade a measuremen
Only the beginning is shown; read the rest in the repo