skip to content
mohamed azahrioui
‹ work

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.

snapshot
type
AI agent guardrail
period
2026
source
public github
build
Solo · open source on PyPI

$ 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

Do not take my word for it. Run this.

Real output, captured from the repository as it ships. Clone it, run the same commands, and compare.

the whole suite: no network, no model

$ python -m unittest discover -s tests

.............................................

----------------------------------------------------------------------

Ran 45 tests in 2.141s

OK

captured 2026-07-12 · fetchgate 0.2.0 · run it yourself and check
the evaluation re-checks its own receipts offline

$ 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 · run it yourself and check
one allowed read, one refused non-read (excerpt)

$ 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 · run it yourself and check

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

PythonMCPhttpxtrafilaturaPlaywrightpytest

View the source code

The repository includes the code, structure, and implementation details for this project.