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 as the fetchgate package on PyPI by @MoAz06, installable in Claude, Cursor, and any MCP client.

Solo · open source, released as fetchgate on PyPI · unrelated to the fetchgate.dev product of the same name

snapshot
type
AI agent guardrail
period
2026
source
public github

$ pip install "fetchgate[mcp]"

view repository →

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: released on PyPI and GitHub, installable with one command

receipts

Run it yourself.

Real output from the repo. Clone it, run the same commands, and check the numbers match.

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

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

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

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

Read the source

The repo has the code, structure, and implementation detail.