AI agent guardrail · 2026
FetchGate
A deterministic gate at the fetch boundary for AI agents. It returns RETRIEVED, FAILED, or UNKNOWN, so an agent never answers as if it read a page it could not actually fetch. Published on PyPI and installable in Claude, Cursor, and any MCP client.
Solo · open source on PyPI
- type
- AI agent guardrail
- period
- 2026
- source
- public github
- write-up
- A 200 Is Not a Read
- pypi
- fetchgate 0.2.0
$ pip install "fetchgate[mcp]"
problem
When a fetch quietly comes back empty (a JavaScript app, a 403, a Cloudflare wall), an agent often answers from memory as if it read the page. FetchGate turns the question of whether the page was actually read into a deterministic verdict that fails closed before the model can answer.
outcomes
- Stops an agent from answering as if it read a page it never retrieved
- Deterministic and auditable: no model, no API keys, every verdict re-checkable
- Shipped end to end: on PyPI, GitHub, and the MCP registries
receipts
Run it yourself.
Real output from the repo. Clone it, run the same commands, and check the numbers match.
$ python -m unittest discover -s tests
.............................................
----------------------------------------------------------------------
Ran 45 tests in 2.141s
OK
captured 2026-07-12 · fetchgate 0.2.0
$ python -m fetchgate.eval.run_eval
across 9 scenarios the gate admitted the 3 true reads and refused the 6 non-reads. 0 non-reads reached answerable state (enforced by construction: the gate mints no handle unless the verdict is RETRIEVED).
verify: OK (recomputed offline from stored signals)
captured 2026-07-12 · fetchgate 0.2.0
$ python demo.py https://example.com https://x.com
https://example.com
verdict : RETRIEVED (clean)
bytes : raw 559 / extracted 112
https://x.com
verdict : FAILED (transport.render_error)
refused: the model gets no content and cannot answer as if it read this page.
captured 2026-07-12 · fetchgate 0.2.0 with the [browser] extra
what i built
- Three-layer detection: transport, extraction, content-validity fingerprint
- Deterministic decide() returning RETRIEVED, FAILED, or UNKNOWN, strictest layer wins
- Provenance envelope: final URL, status, raw vs extracted bytes, content hash
- Escalation to a real headless browser for JavaScript pages, then re-classify
- Trafilatura extraction and a real redirect chain when the extras are installed
- Offline, re-checkable evaluation plus a build-breaking cheat-test
- An MCP server (fetch_url) for Claude Desktop and Cursor
- A Claude Code plugin, a .mcpb desktop bundle, and an Add to Cursor deeplink
- Config via fetchgate.toml, no code changes needed
tech stack
Read the source
The repo has the code, structure, and implementation detail.