Safe, self-hosted web grounding for AI agents and crawlers over a stealth browser
Copy the AI prompt to install this server into Claude Code, Cursor, or another agent β or use 1-click editor setup below.
We haven't yet run this listing's install command through our automated sandbox check. This isn't a red flag β we're steadily working through the catalog.
π‘ Paste the JSON block into your client's configuration file under mcpServers, then restart the application.
Web search, read and research for AI agents β through a real, stealth-patched Chrome. Groundhog is an MCP server that finds pages, reads them, and researches across them, returning clean Markdown a model can trust: text no human could see is stripped by default before the model reads it, every source comes back with a provenance receipt, and a real browser reads pages that block plain fetchers β without the SSRF holes of naive fetch tools.
Eighteen pages, each carrying text a human reader cannot see, plus one clean control. A case passes when the payload does not reach the model and the article text still does β a fetcher that returns nothing contains every payload and is useless.
| contained | kept the article | reported it | |
|---|---|---|---|
requests + trafilatura | 16/18 | 18/18 | 0/18 |
| Scrapling (HTTP) | 10/18 | 18/18 | 0/18 |
| Jina Reader | 8/18 | 18/18 | 0/18 |
| Firecrawl | 4/18 | 18/18 | 0/18 |
| Groundhog | 18/18 | 18/18 | 18/18 |
Containment happens by accident all the time β an article extractor prunes a hidden <div>
because its heuristics dislike it, not because anything asked whether a reader could see
it. That is why the last column matters: nothing else tested tells the caller that
anything was removed, so an empty threat list and a page with nothing hidden look
identical.
The corpus is published at
dmytrome.github.io/groundhog, so any fetcher can
be measured against the same pages. Harness, per-case results and the limits of what this
proves are in benchmark/ β including that hosted services are a snapshot of
the date recorded in RESULTS.md.
Add Groundhog to your MCP client β that's it. On the first fetch, Groundhog pulls and
starts the stealth-browser container for you (Docker or Podman required); no repo checkout,
no manual steps. When the default (non-compose) auto-start path has to run, any stale
container named groundhog-browser is removed first; a reachable browser is never touched.
Claude Code:
Claude Desktop / Cursor / Windsurf (claude_desktop_config.json or equivalent):
uvx fetches groundhog-mcp from PyPI on first run. The first fetch pulls the browser
image (once, a few minutes); later fetches are instant. No container runtime? The status
tool and any error say how to install one β or point CDP_URL at a hosted browser for
zero-install use.
Prefer to manage the browser yourself? Start it and Groundhog will just use it:
Set GROUNDHOG_AUTO_START_BROWSER=false to disable auto-start. To run the MCP server from
source: cd mcp && uv sync && uv run groundhog-mcp.
All four tools are annotated readOnlyHint, which is what lets a client run them without a
per-call confirmation. That describes what they do to your data: nothing is written, and no
remote state is changed. Worth knowing, because it is the one exception: with auto-start on,
the first call may pull and run the browser container, and remove an unreachable container
named groundhog-browser first. A reachable one is never touched, and
GROUNDHOG_AUTO_START_BROWSER=false turns the whole path off.
threats. A strong heuristic, not a proof β see
the limits of hidden-text detection, and
the benchmark for how that compares. The twelve signals,
the threats caveat and the include_hidden exception are documented under read_url.read_url returns the fetch time alongside it as
fetched_at.research, where a third party chooses the URLs.
See Security for the full blocklist and the guard's limits.Runtime domain,
which anti-bots detect (isAutomatedWithCDP). Groundhog drives the browser over raw CDP
and never enables Runtime/Console, so that signal is absent β a clean session that
full automation libraries can't produce over connect_over_cdp.HeadlessChrome
token) β authentic TLS/HTTP2 fingerprint, real WebGL/canvas β not a Python HTTP client,
so fingerprint-driven blocks go away and cheap proxies work where they otherwise wouldn't.research returns extracts, not summaries; your agent does the
synthesis. Self-hosted and MIT β the pages you fetch never leave your infrastructure.read_url(url, format="markdown", max_tokens=None, query=None, include_hidden=False)Fetches a page and returns clean content plus provenance.
| Key | Meaning |
|---|---|
markdown | Extracted content (article-first, falls back to full text); format may be markdown or text |
title | Page title |
url | The URL you asked for |
final_url | The URL after redirects (re-checked against the SSRF guard). Never rewritten: if the page's own final URL is unusable, the requested URL is reported and a final_url_suppressed threat says so |
fetched_at | UTC ISO-8601 timestamp |
status | What actually came back: ok, or challenge / blocked / rate_limited / not_found / server_error / unsupported_content when the content is not the page you asked for β so a Cloudflare interstitial, a 403, or a PDF is not read as if it were the real page. blocked covers 401/403 and every other 4xx that serves an error page (451, 400, 405β¦), with the exact code in http_status. unknown means no response β or no usable status β was observed for the document that was read: it is reported rather than assumed to be fine. A challenge is recognised from vendor mitigation markers β a header that exists only to announce it, or a request for an asset only a challenge loads β so it works whatever language the page is in; page wording is a last resort and only counts on a page too empty to be content. See the limits of block detection The verdict describes the document the text came from, so a page that redirects client-side (meta-refresh, location.href) is judged on where it landed, not where it started |
http_status | The top-level response's HTTP status code, or null when it could not be observed |
truncated | Whether the content was cut to fit the token budget |
threats | Signals detected: hidden-CSS nodes and invisible-character classes; empty when none found |
matches | When query is set: ranked passages with heading, offset, and score for citation |
provenance | Content hash, canonical URL, language, word count, and author/date metadata when present |
No reviews yet β be the first to share how this listing worked for you.
Showcase your server listing on GitHub or your project documentation. Embed this dynamic SVG badge to highlight official listing status and live engagement.
[](https://allmcps.com/mcp/groundhog-mcp)<a href="https://allmcps.com/mcp/groundhog-mcp"><img src="https://allmcps.com/api/badge/groundhog-mcp?style=directory" alt="Groundhog MCP on AllMCPs" /></a>