Open source
contentgate stops twelve common mistakes before a page goes live: five checks on the content itself and seven on whether search engines and AI assistants can read the page. Each check was written the day after the thing it checks for went live and cost something. It is open source under MIT.
A guard that has never been shown to fail is decoration.
Every check ships negative tests: inputs it is required to reject. The suite runs before any verdict is reported, and a failing suite exits without saying anything about your content. A checker that cannot prove it still catches a known-bad input does not get to tell you your site is fine.
This is not theoretical. Guards written for this project have included: one that read its keyword out of the site-wide footer and therefore passed every page ever; one that ran after the step that regenerated the file it was checking; and one whose exit code was discarded by the shell pipeline it lived in, so it printed "deploy blocked" while the deploy proceeded. All three looked fine. All three were found by asking them to fail on purpose.
The page covers the topic and never contains the term people actually search for. Matching is literal: whitespace is never normalised, because the spaced and unspaced forms are different strings with different search volumes.
A translation that is no longer structurally the same document: a dropped table row, a price that changed digits, an internal link that lost its locale prefix and sends every reader back to the source-language site.
A guide that ranks, gets read, and sends nobody anywhere, because its only commercial link lives in the call-to-action block that appears on every page. So this check reads the body only and ignores boilerplate.
Finance, gambling and health copy gets rejected for stating outcomes as facts. The hard part is not finding the phrase, it is not firing on the sentence that exists to disclaim it.
Stamping today's date on every URL on every build is a lie told daily, and crawlers learn to ignore the field. The fix is a content hash state machine: the date moves only when the content genuinely changed.
Run them on the HTML your server actually sends, not on the source, for pages that are meant to be found through search engines and read by AI assistants. The 1200-character minimum and the 15% duplicate limit are house rules, not numbers any search engine publishes, and both are configurable.
| Check | Fails when |
|---|---|
| visible_text | fewer than min_chars characters (default 1200) are readable with JavaScript off |
| structured_data | a JSON-LD block does not parse, a required @type is missing, or there is no dateModified |
| outbound_links | an external link carries none of nofollow, sponsored, ugc and its site is not in cite_ok |
| images | an img has no alt or no width |
| favicon | the home page declares no icon, or only formats Google Search does not list |
| stock_phrases | a phrase from your list appears in the body copy (the default list is Traditional Chinese) |
| duplicate_sentences | two pages share more than max_share (default 15%) of their sentences word for word |
Two implementations. The Python CLI sits in front of a deploy, where the exit code is the contract. The MCP server lets an assistant check a page while it is being written, rather than after it is live.
Both run the same test file and are required to produce identical verdicts on every case in it. CI compares them case by case. A single disagreement means one of them has drifted, and the build stops.
MIT licensed. The Python side has no dependencies, standard library only.
git clone https://github.com/adsandcode/contentgate
cd contentgate
python -m contentgate init # write a starting config
python -m contentgate check # run the checksExit codes are the contract: 0 everything passed, 1 a guard failed on your content, 2 the guards' own negative tests failed so no verdict was given. Put it in front of your deploy and honour the exit code.
The same checks are also an MCP server, so an assistant can run them while a page is being written rather than after it is live:
npx mcp-server-contentgateEight tools: keyword coverage, translation drift, commercial links, regulated claims, sitemap lastmod, content hashing, audit_page and compare_pages. Every one is pure: content in, verdict out. No filesystem access, no network access, nothing written. The server runs its own negative tests at startup and refuses to start if they fail.
Each of these checks came out of a problem we wrote up:
When Your Sitemap lastmod Lies Every Day Operating on Keyword Cannibalization New Site With Zero Pages Indexed Your Site May Be Burning Crawl Budget
Why count text with JavaScript off?
Google renders JavaScript before indexing, but many AI crawlers do not. A December 2024 measurement by Vercel found that GPTBot, ClaudeBot and PerplexityBot fetch the HTML and stop there, so an article written into the page by a script is, to them, a navigation bar and a footer.
Does every outbound link need nofollow?
No. Google asks for sponsored on paid and affiliate links, ugc on user-generated links, and nofollow where neither fits; an ordinary editorial link needs nothing. A program cannot tell a citation from a placement, so every unqualified external link is listed unless you put its site in cite_ok.
Is an SVG favicon enough?
Not on its own. The formats Google Search lists for favicons are BMP, GIF, ICO, PNG, JPEG, PPM and TIFF, so add an ICO or PNG alongside an SVG.
Is it free?
Yes. It is MIT licensed, the Python side uses the standard library only, and the MCP server runs with npx.
These checks are part of what we deliver. If your site needs the foundations done properly from the start, describe your situation and we will tell you straight.
Chat on Telegram