Purpose
Groundhog is a secure, self-hosted web grounding tool for AI agents and crawlers, in the form of an MCP server. Through a real, stealth-patched Chrome browser, it provides agents with three types of capabilities: web search, page reading, and cross-origin research, returning clean Markdown that models can trust.
Main Capabilities
Hidden text stripping: Groundhog renders the real DOM, can evaluate computed styles, determine what a real person can see, and by default strips text that a real person cannot see, with each stripping reported in threats along with its signal type and a brief excerpt. It recognizes twelve categories of signals, including display:none / visibility:hidden, content-visibility: hidden, opacity ≤ 0.05, font size smaller than 4px, zero-size elements, display: contents with content not rendered, sub-pixel boxes of .sr-only / .visually-hidden classes, legacy clip: rect(...), fully transparent text color, text color with a contrast close to 1:1 against the background color, elements positioned outside the rendered page (such as left: -9999px), and content that the browser lays out but refuses to render (such as collapsed <details>). In addition, it strips character-level hidden content: zero-width characters, bidirectional marks and RTL overrides, and the Unicode Tag block (an invisible ASCII mirror, a typical prompt injection smuggling channel). Non-trivial HTML comments and <template> content are also reported. Text in alt, aria-label, aria-description, and title attributes is removed from the markup and reported when it exceeds a length threshold.
Provenance credentials: Every source comes with credentials, including the SHA-256 hash of the extracted content, the canonical URL, the language, the word count, and the author/date when declared by the page, enabling downstream assertions to be traced back to exactly what was read. read_url also returns the fetched_at fetch time.
Secure defaults: SSRF protection resolves every host before navigation, rejecting URL content that returns redirects to private addresses. Read-only, with per-domain rate limiting. This is more important in research scenarios, because URLs are chosen by third parties.
Anti-automationdetection: Puppeteer/Playwright/Selenium enable CDP's Runtime domain, which can be detected by anti-bot systems (isAutomatedWithCDP). Groundhog drives the browser through raw CDP and never enables Runtime / Console, so this signal does not exist.
Real fingerprint: It uses real Chrome, with a real TLS/HTTP2 fingerprint and real WebGL/canvas, rather than a Python HTTP client, so fingerprint-driven blocking disappears, and cheap proxies can also work in places where they otherwise could not.
No model, no API key: research returns extracted snippets rather than summaries, and the calling agent performs the synthesis. It is self-hosted and MIT licensed, and scraped pages never leave the user's infrastructure.
Usage
Add to an MCP client: Add Groundhog in Claude Desktop / Cursor / Windsurf (claude_desktop_config.json or equivalent configuration). On the first scrape, Groundhog will start the Chrome already present on the machine, running in headless mode with a separate profile at ~/.groundhog/chrome: no Docker, no repository checkout, no window, so it will not steal focus while you work. It stays running for the next scrape. Chrome (since 136) refuses remote control of daily-use profiles, so the user's own browsing, tabs, and logins are never touched.
When a hardened browser is preferred, setting GROUNDHOG_BROWSER=stealth will pull and start the stealth-browser container (requires Docker or Podman). This path first removes a stale container named groundhog-browser at runtime; an accessible browser is never touched.
uvx will fetch groundhog-mcp from PyPI on first run. The first scrape will pull the browser image (once, a few minutes); subsequent scrapes are immediate. No container runtime? The status tool and any errors will explain how to install one—or point CDP_URL at a hosted browser for zero-install usage.
Manage the browser yourself: Start it, and Groundhog will be used directly. 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.
Using any CDP browser: CDP_URL accepts the address given by the browser and uses it exactly as given. http:// addresses are probed at /json/version; ws:// or wss:// addresses (those given by hosted browsers, often with a key in the query) are connected to directly. Auto-start only runs when a local address does not respond, so the browser pointed to by CDP_URL is managed by the user. status omits the query and credentials when reporting the address.
All four tools are annotated with readOnlyHint, which lets clients run them without confirmation on every call. They are read-only with respect to data: they write nothing and change no remote state. The only exception: when auto-start is on, the first call may launch Chrome in Groundhog's own configuration file—or, when GROUNDHOG_BROWSER=stealth, pull and run a browser container, first removing any unreachable groundhog-browser container. A reachable browser is never touched, and GROUNDHOG_AUTO_START_BROWSER=false disables that path entirely.
Tool descriptions
read_url(url, format="markdown", max_tokens=None, query=None, include_hidden=False): Fetches a page and returns clean content plus provenance. Passing include_hidden=True preserves stripped text, and threats will still be populated. At most 50 findings are returned per page (10 per source in research); beyond that a report_truncated note is appended stating the number discarded, rather than silently truncating. The two kinds of findings have independent limits. Passing query replaces crude head truncation with relevance-ranked passage selection: content is chunked by Markdown structure, ranked by lexical (BM25) relevance, and passages scoring within the best half are returned up to the token budget. matches gives each passage's heading, character offset, and score for downstream citation, set_aside counts scored passages discarded by the threshold. Ranking runs on sanitized content, so hidden-text injection payloads cannot influence which passages surface (except with include_hidden=True).
search(query, limit=10): Finds pages for a query and returns ranked hits—title, url, snippet, engine, score, published—plus the responding backend. Hits are only links: nothing is fetched until a URL is passed to read_url. Two backends are selected automatically: setting SEARXNG_URL uses your own SearXNG instance (best results; requires formats: [html, json] enabled in its settings.yml); when no instance is configured, Groundhog renders the search page through a stealth browser. Force with GROUNDHOG_SEARCH_BACKEND=searxng|serp. All text fields in hits may be attacker-influenced, so they undergo the same invisible-character stripping as page content and are length-limited. URLs are never rewritten; if sanitization would change the URL, the URL is not http/https, it carries credentials, or it exceeds 2048 characters, the hit is dropped outright.
research(query, max_sources=5, max_tokens=None): Does "find information about X" in one call: search, read top sources through a stealth browser, return the passages most relevant to query, ranked once across all sources. Returns passages (each with text, source_url, heading, score) and sources (each with url, title, status, page_status, threats, provenance). status is the fetch result (ok/blocked/timeout/error); page_status is what the loaded page actually is, and is null when the fetch produced no page. Sources whose page_status indicates the body is an interstitial or error page contribute no passages but still appear in sources to explain why. At most one page per registrable domain to ensure source diversity. Passages are extracted, not summarized. A source failing does not fail the call; it will be reported as blocked/timeout/error appears in sources. max_sources is capped at 10. Because search results are selected by third parties and SEO poisoning is a documented real attack, every fetched URL goes through the same SSRF protection and hidden-text stripping as read_url. Failed sources have null provenance. threats is capped at 10 per source. It is slower than API-based research tools: a real browser renders each source, which is the price of reading pages that block ordinary scrapers and revealing what is hidden in them.
status: Reports whether Groundhog can reach the stealth browser, returning browser_reachable, cdp_url, and remediation hints when unreachable. Endpoints are reported only by scheme, host, and port.
Applicable scenarios
Suitable for AI agents and crawler scenarios that need secure, self-hosted web grounding, especially research tasks where pages block ordinary scrapers, where it is necessary to determine whether a page hides prompt-injection content, and where traceable source provenance is needed.
Limitations
Hidden-text detection is a strong heuristic rather than proof; shared and service workers are not covered. The stealth image runs Chrome in headful mode under Xvfb. Without a container runtime, it cannot point to a hosted browser. threats entries should be treated as untrusted. research is slower than API-driven tools. Other specific pricing, platform, or performance information not mentioned is not currently provided.