Skip to main content

A deterministic gate at the fetch boundary: a 200 is transport success, not a read.

Project description

FetchGate

A 200 is not a read.

When an AI agent fetches a page and it quietly comes back empty (a JavaScript app, a 403, a Cloudflare wall), the agent often does not stop. It answers from memory and never tells you it did not actually read the page. FetchGate is a small gate at the fetch boundary that refuses to let that happen: the agent gets the content only when the page was really read, and an honest refusal otherwise.

It is deterministic (no model, no API keys), stdlib-only at its core, and free.

Quickstart

git clone https://github.com/MoAz06/FetchGate
cd FetchGate
python demo.py https://example.com https://httpbin.org/status/403
https://example.com
  verdict     : RETRIEVED  (clean)
  answerable  : ALLOW
  bytes       : raw 559 / extracted 127
  tiers       : static
  gate-confirmed content (127 chars): Example Domain This domain is for use ...

https://httpbin.org/status/403
  verdict     : FAILED  (transport.http_403)
  answerable  : STOP
  refused: the model gets no content and cannot answer as if it read this page.

Run the tests (no network, no model):

pip install -e .
python -m unittest discover -s tests

Easiest: one double-click (Claude Desktop)

Download fetchgate.mcpb from the Releases page and double-click it. Claude Desktop shows an Install button, no JSON editing. The bundle source is in bundle/; rebuild it with npx @anthropic-ai/mcpb pack bundle fetchgate.mcpb.

Vibecoders: add it from inside your editor

Cursor, one click (needs uv, which most setups have):

Add FetchGate to Cursor

Or just ask your AI. Paste this into Cursor, Claude Code, or Windsurf:

Add the fetchgate MCP server to this setup. Run pip install "fetchgate[mcp]" and register it as an MCP server named fetchgate with the command fetchgate-mcp. It stops an agent from answering as if it read a page it could not actually fetch.

Your assistant runs the install and wires up the config for you.

Use it in Claude Desktop or Cursor (manual)

pip install ".[mcp,http,extract]"    # the fetch tool, real redirect chain, better extraction
pip install ".[browser]"             # optional: render JavaScript pages
python -m playwright install chromium

Add to your MCP config (details in docs/mcp.md):

{ "mcpServers": { "fetchgate": { "command": "fetchgate-mcp" } } }

Your agent now has a fetch_url tool that returns content only for a confirmed read, and a refusal otherwise.

This repo is also a Claude Code plugin. After pip install "fetchgate[mcp]":

/plugin marketplace add MoAz06/FetchGate
/plugin install fetchgate@fetchgate

See docs/claude-code-plugin.md.

Use it in your own agent

from fetchgate import default_gate

gate = default_gate()   # renders JS pages if the [browser] extra is installed

def read_url(url: str) -> str:
    out = gate.guarded_fetch(url)
    content = gate.content_for(out.handle)
    if content is None:
        return f"NOT READ: {url} ({out.result.reason}). Do not answer as if you read it."
    return content

How it decides

Every fetch gets one verdict:

  • RETRIEVED: a real read. The agent may answer and cite it.
  • FAILED: a definite non-read (a 4xx or 5xx, a timeout, an https to http downgrade, an empty body). Stop.
  • UNKNOWN: fetched, but not enough readable content to confirm a read (often a JavaScript page). Stop, or render and try again.

Three layers feed the verdict, and the result is the strictest of them:

  • transport: status and redirect chain.
  • extraction: readable text kept separate from raw bytes. A 17 byte price JSON is a read, a 400 KB article at 2 percent text is a read, a JS shell with no text is not.
  • content-validity: a best-effort fingerprint for Cloudflare, consent, paywall, and soft-404 walls. It can only downgrade to UNKNOWN, never fake a read.

When a page looks thin or empty and the render tier is on, FetchGate escalates to a real headless browser and re-checks. It renders, it does not evade: a page still walled after an honest render stays a non-read.

Stated limits

  • content_sha256 is a receipt anchor, not tamper-evidence. A cloaked page hashes cleanly.
  • Provenance is not authenticity. The gate closes fail-silent-on-empty, not fetch-succeeds-on-wrong-content.
  • A read is not an answer. RETRIEVED means enough content arrived, not that it held the fact you need. Claim support is a downstream job.
  • Fail-closed has an availability cost. Anyone who can make your fetch fail can force a refusal. That is the trade for correctness.

What is built

The deterministic core, the offline evaluation with a signed manifest, the build-breaking cheat-test, the MCP server, and the Playwright render tier. Optional extras: [mcp], [http] (httpx redirect chain), [extract] (trafilatura), [browser] (Playwright), [sign] (Ed25519). When installed they are used automatically; the core stays stdlib-only.

MIT licensed.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fetchgate-0.2.0.tar.gz (37.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fetchgate-0.2.0-py3-none-any.whl (33.2 kB view details)

Uploaded Python 3

File details

Details for the file fetchgate-0.2.0.tar.gz.

File metadata

  • Download URL: fetchgate-0.2.0.tar.gz
  • Upload date:
  • Size: 37.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.9

File hashes

Hashes for fetchgate-0.2.0.tar.gz
Algorithm Hash digest
SHA256 85654f1cac868080f30f60ebca9d411570a271171f92b1d1e69fd8a7938acbd9
MD5 e7e6e73935d9ae2f1954ab9d32c4548b
BLAKE2b-256 efe00689db69b0f9ae2f5fcb16c8af4d5d0217cc1f3ab0faf4a81bdfb5ce477d

See more details on using hashes here.

File details

Details for the file fetchgate-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: fetchgate-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 33.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.9

File hashes

Hashes for fetchgate-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e1a742f8ea08dd141fc2f5316bef8a6f24c7d15603c6956b4df706740cf6d4ba
MD5 7d33677d91b1eb81ee7109e773443d8a
BLAKE2b-256 5433329b435b0dbf608262e8213dfe15aaa6df7eba8f634fe8d1930c45774a28

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page