Metadata-Version: 2.4
Name: fetchgate
Version: 0.2.0
Summary: A deterministic gate at the fetch boundary: a 200 is transport success, not a read.
Project-URL: Homepage, https://github.com/MoAz06/FetchGate
Project-URL: Repository, https://github.com/MoAz06/FetchGate
Project-URL: Issues, https://github.com/MoAz06/FetchGate/issues
Author: Mohamed Azahrioui
License: MIT
License-File: LICENSE
Keywords: ai-agents,guardrails,llm,mcp,provenance,retrieval
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.11
Provides-Extra: browser
Requires-Dist: playwright>=1.44; extra == 'browser'
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Provides-Extra: extract
Requires-Dist: trafilatura>=1.9; extra == 'extract'
Provides-Extra: http
Requires-Dist: httpx>=0.27; extra == 'http'
Provides-Extra: mcp
Requires-Dist: mcp>=1.2; extra == 'mcp'
Provides-Extra: sign
Requires-Dist: cryptography>=42; extra == 'sign'
Description-Content-Type: text/markdown

# 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

```bash
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):

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

## Easiest: one double-click (Claude Desktop)

Download `fetchgate.mcpb` from the [Releases](https://github.com/MoAz06/FetchGate/releases)
page and double-click it. Claude Desktop shows an Install button, no JSON editing.
The bundle source is in [bundle/](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](cursor://anysphere.cursor-deeplink/mcp/install?name=fetchgate&config=eyJjb21tYW5kIjogInV2eCIsICJhcmdzIjogWyItLWZyb20iLCAiZmV0Y2hnYXRlW21jcF0iLCAiZmV0Y2hnYXRlLW1jcCJdfQ==)

**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)

```bash
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](docs/mcp.md)):

```json
{ "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]"`:

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

See [docs/claude-code-plugin.md](docs/claude-code-plugin.md).

## Use it in your own agent

```python
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.
