Groundhog
概览
作者提供的说明(英文原文)
Search, read and research the web, with hidden text stripped by default and sources hashed
PopNx MCP 的三大支柱
这三项永远不会合并成一个分数:意见、测量和人气是不同的东西。
- 用户评价i暂无评价评价功能将在后续阶段与账户功能一起开放
- 我们的基础检查i1 项中通过 1 项依据公开信息检查;尚未扫描代码
- 使用情况i非常新↓ 122/周 · ★ –
它能帮你做什么
我们还没有为它写通俗的介绍。下面是我们从注册中心读取到的事实。
- 连接方式: 在你的电脑上运行
- 传输方式: stdio
- 分类: 搜索与 AI 记忆
安装
选择你使用的 AI 应用,然后复制设置。
安装之前
- 基础检查:1 项中通过 1 项
- 在你的电脑上运行,拥有你的用户账户的权限(文件、网络、应用)。
- 注册中心没有列出需要填写的密钥。
你需要
- PyPI已安装 Python 的 uv(用于 uvx 命令)
你用的是哪个应用?
在上方选择你的应用。我们会记住它,下次按钮会显示“安装到 …”。
打开 Claude Desktop → Settings → Developer → Edit Config,把下面的内容粘贴到 claude_desktop_config.json
{
"mcpServers": {
"groundhog": {
"command": "uvx",
"args": [
"groundhog-mcp"
]
}
}
}在终端中运行这条命令
claude mcp add groundhog -- uvx groundhog-mcp或者自己复制设置
粘贴到项目中的 .cursor/mcp.json,或 ~/.cursor/mcp.json
{
"mcpServers": {
"groundhog": {
"command": "uvx",
"args": [
"groundhog-mcp"
]
}
}
}浏览器会先询问是否打开应用,然后应用会显示设置并请你确认。密钥的值会留空,由你自己填写。
或者自己复制设置
粘贴到项目中的 .vscode/mcp.json
{
"servers": {
"groundhog": {
"type": "stdio",
"command": "uvx",
"args": [
"groundhog-mcp"
]
}
}
}浏览器会先询问是否打开应用,然后应用会显示设置并请你确认。密钥的值会留空,由你自己填写。
这个服务器在你的电脑上运行,而 ChatGPT 只能连接互联网上的服务器。请选择其他应用。
你的选择通过 Cookie 保存在这台设备上,无需账户。
这些设置是根据作者在注册中心的条目生成的。如果 README 的写法不同,请以 README 为准。
它能做什么
它在你的电脑上运行;安装后,工具列表会显示在你的 AI 应用中。
基础检查i
我们检查的是任何人都能看到的信息;我们没有阅读代码。不适用的检查项不计入总数。 · 检查于 2026年11月8日
- ✓ 通过
作者与仓库一致
注册名称已通过 GitHub 账户 dmytrome 验证,该账户拥有此仓库
- … 尚未检查
仍在维护
我们还没有从 GitHub 读取这个仓库
- … 尚未检查
有许可证
我们还没有从 GitHub 读取这个仓库
- – 不适用
端点有响应
它在你的电脑上运行,没有互联网端点
- – 不适用
使用 OAuth 登录
在你的电脑上运行,改用本地设置
- … 尚未检查
工具描述中没有隐藏指令
它在你的电脑上运行;我们只能读取互联网端点的工具列表
与“搜索与 AI 记忆”中的其他服务器比较i
- 星标数多于此分类中其他 5726 个服务器的 0%
- 发布到注册中心的时间比此分类中其他 5726 个服务器的 73% 更新
- 基础检查结果等于或优于此分类中其他 5726 个服务器的 100%
与注册中心里这个分类中所有活跃的服务器比较。只有当发布者拥有该仓库时,星标数才计入。
常见问题
Groundhog 安全吗?
它在 1 项适用的基础检查中通过了 1 项。我们没有阅读代码。在添加密钥或令牌之前,请先看看这个服务器要求哪些权限,并使用只具备所需权限的密钥。
它需要哪些密钥或代码?
注册中心没有列出必需的密钥,不过有些服务器会在首次使用时要求你登录。
它在我的电脑上运行还是在云端?
它在你的电脑上运行。你的 AI 应用会在需要时启动它,数据不会经过作者的服务器(除非它自己调用在线服务)。
它还在维护吗?
GitHub 仓库最后更新于–,最新版本(0.22.0)于13天前进入注册中心。
哪些应用可以使用它?
支持 MCP 的应用,例如 Claude Desktop、Claude Code、Cursor 和 VS Code。你的电脑上还需要有 Python (uv)。
有什么可以替代它?
同一分类中有:Context7, Codebase Memory, Exa
替代选择
同一分类中其他作者的服务器,按星标数排序。
如何看卡片:↓ 每周下载量i★ GitHub 星标i三大支柱:评价 · 检查 · 使用情况i更新 = 最新版本发布到注册中心的时间i
Codebase Memory
Codebase knowledge graph for AI agents — 162 languages, sub-ms queries, 99% fewer tokens.
Exa
Connect AI agents to Exa for web search, content fetching, and multi-step research.
作者的 README
打开 README
按原文从作者的仓库复制。PopNx MCP 没有审查或背书其内容。HTML、图片和脚本已被移除。链接会离开本站。
Groundhog
Web search, read and research for AI agents, through a real, stealth-patched Chrome. Groundhog is an MCP server that returns clean Markdown a model can trust: text no human could see is stripped before the model reads it and reported, every source comes with a provenance receipt, and a real browser reads pages that block plain fetchers.
agent / crawler ──MCP──▶ Groundhog (search, read_url, research) ──CDP──▶ stealth Chrome ──▶ the web
Measured against other fetch layers
Twenty-eight pages carry text a human can't see, and three controls carry none. A case passes when the payload doesn't reach the model and the article text still does.
| contained | kept the article | reported it | false positives | |
|---|---|---|---|---|
requests + trafilatura | 25/28 | 28/28 | 0/28 | 0/3 |
| Scrapling (HTTP) | 16/28 | 28/28 | 0/28 | 0/3 |
| Jina Reader | 15/28 | 28/28 | 0/28 | 0/3 |
| Firecrawl | 7/28 | 28/28 | 0/28 | 0/3 |
| Groundhog | 28/28 | 28/28 | 25/28 | 0/3 |
Other extractors often drop hidden text by accident, but none of them tells the caller anything was removed. The three cases Groundhog doesn't report (a hidden input value, a meta description, an iframe srcdoc) never reach any extractor. The corpus is public at dmytrome.github.io/groundhog; the harness and its limits are in benchmark/.
Quick start
Claude Code:
claude mcp add groundhog -- uvx groundhog-mcp
Claude Desktop, Cursor, Windsurf:
{
"mcpServers": { "groundhog": { "command": "uvx", "args": ["groundhog-mcp"] } } }
On the first fetch Groundhog starts the Chrome you already have, headless, in its own profile at ~/.groundhog/chrome. Your own browsing, tabs and logins are never touched. If Chrome is missing, status and the error say what to install.
GROUNDHOG_BROWSER=stealth runs the hardened browser container instead (Docker or Podman; the first pull takes a few minutes). To run it yourself:
docker run -d --rm --name groundhog-browser --shm-size 512m \
-p 127.0.0.1:9222:9222 -- ghcr.io/dmytrome/groundhog:latest
CDP_URL points Groundhog at any browser and is used as given: http:// is probed for /json/version, ws:// or wss:// (what hosted browsers hand out) is connected to directly. Auto-start only runs for a local address that isn't answering, and GROUNDHOG_AUTO_START_BROWSER=false turns it off. From source: cd mcp && uv sync && uv run groundhog-mcp.
All four tools are annotated readOnlyHint: they write nothing and change no remote state. The one side effect is auto-start, which may launch Chrome or, with stealth, run the container after removing an unreachable one named groundhog-browser.
What makes it different
- Hidden text is stripped and reported. A rendered DOM shows what a human would see; everything else goes, and each removal is listed in
threats. It's a strong heuristic, not a proof: see the limits. - Every source carries a receipt: a SHA-256 of the extracted content, canonical URL, language, word count, and author and date when the page declares them.
- Safe by default. An SSRF guard checks every document request, redirects and frames included, with per-domain rate limiting. See Security.
- No automation tell. Groundhog drives Chrome over raw CDP and never enables the
Runtimedomain that anti-bots detect in Puppeteer, Playwright and Selenium. - A real fingerprint. Real Chrome means real TLS, HTTP/2, WebGL and canvas. In headless mode the
HeadlessChrometoken is replaced and client hints are copied from the browser; shared and service workers aren't covered. The stealth image runs headful under Xvfb. - No model, no API key.
researchreturns extracts and your agent does the synthesis.
Tools
read_url(url, format="markdown", max_tokens=None, query=None, include_hidden=False)
| Key | Meaning |
|---|---|
markdown | Extracted content, article first, falling back to the rendered text inside a code fence, so none of it reads as markdown; a PDF comes back as one ## Page N section per page with its text fenced. format may be markdown or text |
title | Page title |
url | The URL you asked for |
final_url | The URL after redirects, re-checked by the SSRF guard. If unusable, the requested URL is reported with a final_url_suppressed threat |
fetched_at | UTC ISO-8601 timestamp |
status | ok, or challenge / blocked / rate_limited / not_found / server_error / unsupported_content when what came back isn't the page you asked for. blocked covers 401, 403 and other 4xx error pages. too_large means the page had over 5 MiB of HTML or 2 MiB of text, or was a PDF over 10 MiB, 100 pages or 20 seconds of parsing, and nothing of it is returned. An encrypted or malformed PDF is unsupported_content. empty means the rendered page showed no text a reader would see (text in closed shadow roots, iframes and alt attributes doesn't count), whatever markdown holds. With include_hidden, text hidden by styling counts as shown; invisible characters never do. unknown means no usable status was observed. Judged on the document the text came from, after client-side redirects |
http_status | The top-level HTTP status, or null |
truncated | Whether the token budget cut the content |
threats | What was stripped; empty when nothing was found |
matches | With query: ranked passages with id, heading, offset, end, score and clamped (cut short to fit max_tokens). offset and end index the page's full markdown, and that span is the text returned; a piece of a long code block comes back with its fence lines added around it. Code is never cut mid-piece: a matching code block or piece larger than max_tokens is left out, and when nothing fits markdown is empty with truncated set, so raise max_tokens |
set_aside | With query: scoring passages the relevance floor dropped |
provenance | Content hash, canonical URL, language, word count, author and date |
Stripped by default: display:none, visibility:hidden, content-visibility:hidden, opacity ≤ 0.05, font-size < 4px, zero-size and display:contents boxes that render nothing, .sr-only-style sub-pixel boxes, clip: rect(...), a clip-path that leaves nothing (circle(0), inset(100%), a polygon on one line), transform: scale(0) on either axis, a filter whose opacity is 0.05 or less, zero-height or zero-width boxes that clip their overflow, transparent or background-coloured text, off-page positioning, collapsed , content, and zero-width, bidi and Unicode Tag characters. A box whose only text is an open popover or modal dialog is kept, because that dialog still shows on screen; any other text in the box is stripped as usual. HTML comments are reported too. In a PDF, text drawn in render mode 3 or 7, which paints nothing, is stripped and reported as hidden_pdf. That includes the OCR layer of a scanned page, so a scan whose only text is that layer is empty unless you pass include_hidden. include_hidden=True keeps the text but still reports it. alt, aria-label, aria-description and title text never appears in markdown or the rendered text; values of 125 characters or more are reported as hidden_attribute with a capped excerpt, because long text there is a known carrier.
Treat threats as untrusted: its excerpts are page-authored text, cleaned and length-capped.
type | Carries |
|---|---|
hidden_css | The hiding reason, an 80-character excerpt and the DOM location |
hidden_attribute | The same, for text in an alt, aria-label, aria-description or title attribute |
hidden_template | The same, for text inside a |
hidden_pdf | The same, for text a PDF drew invisibly, with the page as its location |
zero_width / bidi / tag | A codepoint and count in reason, detected on the served text |
report_truncated | How many entries the cap dropped |
final_url_suppressed | The page's final URL was unusable and not returned |
detection_degraded | Detection ran in the page's own JavaScript world, which the page can tamper with |
strip_incomplete | The text was rebuilt from markup rather than layout, usually because of open shadow roots |
At most 50 findings per page are returned (10 per source in research), with the two classes capped separately so decoys can't bury a real payload. Notices are appended after the cap.
query swaps head-truncation for BM25 passage ranking on the sanitized content, keeping passages within half the best score up to the budget.
search(query, limit=10)
Returns hits (title, url, snippet, engine, score, published) and the backend that answered. Nothing is fetched. With SEARXNG_URL set it uses your SearXNG instance, which needs formats: [html, json]; otherwise it renders a search page through the browser. Hit text is cleaned and capped, and a hit whose URL would change under cleaning, isn't http(s), carries credentials or exceeds 2048 characters is dropped. A backend that can't answer raises an error instead of returning an empty web.
research(query, max_sources=5, max_tokens=None, include_domains=None, exclude_domains=None, published_after=None, aim=None, follow=None)
Searches, reads the top sources and ranks passages from all of them in one pass.
passages:id,text,source_url,heading,score,start,end,clamped,fence.textis exactlymarkdown[start:end];clampedsays it was cut short to fit the budget. A code block is never cut short, only left out. A passage cut from a long code block carries the block's opening line infence: reopen with it whentextdoesn't start with it, and close the block whentextdoesn't end with a closing fence.idstays stable while the page doesn't change.sources:url,title,status(ok/blocked/timeout/error),page_status(as inread_url),threats,threat_total,threat_counts(every finding by type, spans the page's detector dropped ashidden_unclassified),provenance,filtered,depthanddiscovered_via(the candidate it was followed from, with yourwhy). A failed source doesn't fail the call. A challenge, an error page, anemptypage or any page whosemarkdownis blank counts asnot_contentand contributes no passages.- One page per registrable domain unless
include_domainsis set, and then no more pages per site thanGROUNDHOG_MAX_CONCURRENT_PAGES;max_sourcesgoes up to 10.
include_domains reads only those domains and their subdomains, exclude_domains never reads them, and published_after (YYYY-MM-DD) drops pages dated earlier. Up to 8 domains can be included and 50 excluded. A page that redirects into an excluded domain or out of the included ones, is dated too early, or repeats the URL or text of a page already used is listed with filtered and contributes no passages. The query sent to the engine names at most eight domains; further excluded ones are filtered after the search. aim (up to 200 characters) says what the evidence must show and is echoed back without changing the search or the ranking.
receipt holds the executed_query, the distinct hits and hits_by_domain, the hits not fet
只显示了开头部分;其余内容请在仓库中阅读