mcpField
G
Groundhog

Groundhog

本地服务
github.com/dmytrome搜索与检索12 天前更新

关于 Groundhog

Groundhog 是供 Agent 通过 Chrome 搜索、阅读和研究网页的 MCP 服务。它会移除并报告网页中的隐藏文本,提供来源凭据,并将搜索与页面段落排序结合起来。

主要功能

  1. 01搜索网页
  2. 02通过 Chrome 阅读页面
  3. 03移除并报告隐藏文本
  4. 04提供来源溯源凭据
  5. 05为研究段落排序

适用场景

  1. 01跨网页来源研究问题
  2. 02阅读普通抓取器无法访问的页面
  3. 03检查页面中被移除的隐藏文本
  4. 04追溯提取内容的来源

MCP 相关

向 MCP 客户端提供 search、read_url 和 research 工具。它通过 Chrome 浏览器访问页面,并返回提取内容与来源信息。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"dmytrome-groundhog": {
"command": "uvx",
"args": [
"groundhog-mcp"
]
}
}
}
启动命令uvx groundhog-mcp

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypigroundhog-mcp0.22.0

工具

4

收录的工具列表 · 更新于 2 天前

参数类型必填说明
urlstring是绝对 http(s) URL。拒绝私有地址和环回地址。
queryany否设置后,`matches` 会携带与其最相关的段落,每段都包含标题和用于引用的偏移量。
formatenum否'markdown' 提取文章;'text' 返回页面渲染后的文本。markdown | text
max_tokensany否内容的 Token 预算。省略时使用服务器的 GROUNDHOG_MAX_TOKENS(默认值为 20000)。必须为正数。
include_hiddenboolean否保留人类读者不可见的文本。无论如何,这些文本都会在 `threats` 中报告;此设置仅控制它们是否保留在内容中。

详细介绍

Groundhog

Conformance Glama

通过真实且经过隐身补丁处理的 Chrome,为 AI Agent 提供网页搜索、阅读和研究功能。 Groundhog 是一个 MCP 服务,能返回大模型可信赖的纯净 Markdown:人类看不到的文本会在大模型读取前被剔除并报告;每个来源都附带来源凭证;真实浏览器还能读取会阻止普通抓取器访问的网页。

agent / crawler  ──MCP──▶  Groundhog (search, read_url, research)  ──CDP──▶  stealth Chrome  ──▶  the web

与其他抓取层的实测对比

28 个页面包含人类看不到的文本,另有 3 个对照页面不含此类文本。当载荷未传到大模型且文章文本仍然保留时,该案例即为通过。

已拦截保留文章已报告误报
requests + trafilatura25/2828/280/280/3
Scrapling (HTTP)16/2828/280/280/3
Jina Reader15/2828/280/280/3
Firecrawl7/2828/280/280/3
Groundhog28/2828/2825/280/3

其他提取器通常会意外丢弃隐藏文本,但都不会告知调用方删除了内容。Groundhog 未报告的 3 种情况(隐藏的输入值、元描述、iframe srcdoc)不会到达任何提取器。语料库公开于 dmytrome.github.io/groundhog;测试框架及其限制见 benchmark/。

快速开始

Claude Code:

claude mcp add groundhog -- uvx groundhog-mcp

Claude Desktop、Cursor、Windsurf:

{
  "mcpServers": {
    "groundhog": {
      "command": "uvx",
      "args": ["groundhog-mcp"]
    }
  }
}

首次抓取时,Groundhog 会在独立配置目录 ~/.groundhog/chrome 中,以无头模式启动你现有的 Chrome。不会触碰你自己的浏览活动、标签页或登录状态。如果未安装 Chrome,status 和错误信息会说明需要安装什么。

GROUNDHOG_BROWSER=stealth 会改为运行经过强化的浏览器容器(Docker 或 Podman;首次拉取需要几分钟)。如需自行运行:

docker run -d --rm --name groundhog-browser --shm-size 512m \
  -p 127.0.0.1:9222:9222 -- ghcr.io/dmytrome/groundhog:latest

CDP_URL 会按原样将 Groundhog 指向任意浏览器:对 http:// 地址会探测 /json/version,对 ws:// 或 wss:// 地址(托管浏览器提供的地址)则会直接连接。只有本地地址无响应时才会自动启动;GROUNDHOG_AUTO_START_BROWSER=false 可将其关闭。从源码运行:cd mcp && uv sync && uv run groundhog-mcp。

这四个工具都标注为 readOnlyHint:不会写入任何内容,也不会更改远程状态。唯一的副作用是自动启动,它可能会启动 Chrome;使用 stealth 时,也可能会在移除名为 groundhog-browser 且无法访问的容器后运行该容器。

与众不同之处

  • 隐藏文本会被剔除并报告。 渲染后的 DOM 展示人类可见的内容;其余内容都会被移除,并逐项列入 threats。这是一种有力的启发式方法,并非确凿证明:参见隐藏文本检测的局限。
  • 每个来源都附带凭证:包括提取内容的 SHA-256、规范 URL、语言、字数,以及网页声明的作者和日期。
  • 默认安全。 SSRF 防护会检查每个文档请求,包括重定向和框架,并按域名进行速率限制。参见安全性。
  • 不暴露自动化特征。 Groundhog 通过原始 CDP 驱动 Chrome,且从不启用 Puppeteer、Playwright 和 Selenium 中会被反机器人机制检测到的 Runtime 域。
  • 真实的指纹。 使用真实 Chrome,意味着使用真实的 TLS、HTTP/2、WebGL 和 canvas。在无头模式下,HeadlessChrome 标记会被替换,客户端提示会从浏览器复制;共享工作线程和服务工作线程不在覆盖范围内。隐身镜像会在 Xvfb 下以有头模式运行。
  • 无需大模型,也无需 API 密钥。 research 会返回提取结果,由你的 Agent 负责综合分析。

工具

read_url(url, format="markdown", max_tokens=None, query=None, include_hidden=False)
Key含义
markdown提取的内容,优先使用文章内容;如果没有,则回退为代码围栏内的渲染文本,因此其中的内容都不会被解析为 Markdown;PDF 会按每页返回一个 ## Page N 分节,并将文本放在代码围栏中。format 可以是 markdown 或 text
title页面标题
url你请求的 URL
final_url重定向后的 URL,会由 SSRF 防护机制重新检查。如果不可用,则报告请求的 URL,并附带 final_url_suppressed 威胁
fetched_atUTC ISO-8601 时间戳
statusok;如果返回的不是你请求的页面,则为 challenge / blocked / rate_limited / not_found / server_error / unsupported_content。blocked 包括 401、403 和其他 4xx 错误页面。too_large 表示页面包含超过 5 MiB 的 HTML 或超过 2 MiB 的文本,或者 PDF 超过 10 MiB、100 页或 20 秒的解析时限,此时不会返回任何内容。加密或格式错误的 PDF 会标记为 unsupported_content。empty 表示渲染后的页面没有读者能看到的文本(封闭 Shadow DOM、iframe 和 alt 属性中的文本不计),无论 markdown 中有什么内容。使用 include_hidden 时,样式隐藏的文本也会计为可见;不可见字符始终不计。unknown 表示没有观察到可用的状态。状态根据文本来源文档判断,并在客户端重定向之后进行
http_status顶层 HTTP 状态,或 null
truncated内容是否因 token 预算用尽而被截断
threats被剥离的内容;未发现内容时为空
matches使用 query 时:按排名排列的段落,包含 id、heading、offset、end、score 和 clamped(为适应 max_tokens 而截短)。offset 和 end 是页面完整 Markdown 中的索引,该范围对应返回的文本;长代码块的一部分会在前后补上围栏行。代码不会在片段中间截断:如果匹配的代码块或片段超过 max_tokens,则会被省略;如果没有内容适合返回,markdown 会为空并设置 truncated,因此请调高 max_tokens
set_aside使用 query 时:因相关性低于阈值而被舍弃的评分段落
provenance内容哈希、规范 URL、语言、字数、作者和日期

默认会剥离:display:none、visibility:hidden、content-visibility:hidden、 opacity ≤ 0.05、font-size < 4px、零尺寸以及渲染为空的 display:contents 盒子、.sr-only 样式的亚像素盒子、clip: rect(...)、会将内容完全裁掉的 clip-path(circle(0)、inset(100%)、单行多边形)、任一轴上使用 transform: scale(0)、透明度不大于 0.05 的 filter、裁切溢出内容的零高度或零宽度盒子、透明或与背景同色的文本、位于页面之外的内容、折叠的 <details>、<template> 内容,以及零宽度字符、双向控制字符和 Unicode Tag 字符。如果盒子中唯一的文本是已打开的弹出框或模态对话框,则会保留,因为该对话框仍显示在屏幕上;盒子中的其他文本照常剥离。HTML 注释也会报告。 在 PDF 中,使用渲染模式 3 或 7 绘制、不会显示任何内容的文本会被剥离,并报告为 hidden_pdf。这包括扫描页面的 OCR 层,因此,如果扫描件的唯一文本就是该层,则会标记为 empty,除非你传入 include_hidden。 include_hidden=True 会保留文本,但仍会报告该文本。alt、aria-label、aria-description 和 title 中的文本不会出现在 markdown 或渲染文本中;长度达到 125 个字符或以上的值会报告为 hidden_attribute,并附上截短的摘录,因为已知长文本可能被用作载体。

请将 threats 视为不可信内容:其中的摘录来自页面,由页面作者编写,经过清理且长度受限。

type内容
hidden_css隐藏原因、80 个字符的摘录和 DOM位置
hidden_attribute同上,适用于 alt、aria-label、aria-description 或 title 属性中的文本
hidden_template同上,适用于 <template> 中的文本
hidden_pdf同上,适用于 PDF 以不可见方式绘制的文本,页面作为其位置
zero_width / bidi / tag在提供的文本中检测到的原因中的码点和计数
report_truncated达到上限后丢弃的条目数
final_url_suppressed页面最终 URL 不可用,因此未返回
detection_degraded检测在页面自身的 JavaScript 环境中运行,页面可以篡改该环境
strip_incomplete文本是根据标记而非布局重建的,通常是因为存在开放的影子根

每个页面最多返回 50 条发现(research 中每个来源最多 10 条),两类发现分别设定上限,以免诱饵内容淹没真实载荷。通知会在达到上限后追加。

query 会将从头部截断改为对清理后的内容进行 BM25 段落排序,并在预算范围内保留得分达到最高得分一半的段落。

search(query, limit=10)

返回命中项(title、url、snippet、engine、score、published)以及提供结果的backend。不会抓取任何内容。设置 SEARXNG_URL 后,会使用你的 SearXNG 实例,该实例需要配置 formats: [html, json];否则会通过浏览器呈现搜索页面。命中项文本会经过清理并限制长度;若命中项的 URL 在清理后会发生变化、不是 http(s)、包含凭据或长度超过 2048 个字符,则会被丢弃。无法提供结果的后端会引发错误,而不会返回空网页。

research(query, max_sources=5, max_tokens=None, include_domains=None, exclude_domains=None, published_after=None, aim=None, follow=None)

搜索并读取排名靠前的来源,然后一次性对所有来源的段落进行排序。

  • passages:id、text、source_url、heading、score、start、end、clamped、 fence。text 与 markdown[start:end] 完全一致;clamped 表示为满足预算而截短了文本。代码块不会被截短,只会被排除。若段落从长代码块中截取而来,fence 会包含代码块的起始行:当 text 不以该行开头时,请用它重新打开代码块;当 text 不以闭合围栏结尾时,请闭合代码块。只要页面未发生变化,id 就保持稳定。
  • sources:url、title、status(ok / blocked / timeout / error)、page_status (与 read_url 中相同)、threats、threat_total、threat_counts(按类型统计的所有发现,包括检测器丢弃为 hidden_unclassified 的页面片段)、provenance、filtered、 depth 和 discovered_via(跟进该来源所依据的候选项,并包含你的 why)。来源获取失败不会导致整个调用失败。挑战页面、错误页面、empty 页面或任何 markdown 为空的页面都会被视为 not_content,且不会贡献段落。
  • 除非设置了 include_domains,否则每个可注册域名仅取一个页面;设置后,每个站点的页面数不得超过 GROUNDHOG_MAX_CONCURRENT_PAGES;max_sources 最高可设为 10。

include_domains 只读取这些域名及其子域名,exclude_domains 不会读取这些域名,而 published_after(YYYY-MM-DD)会排除日期更早的页面。最多可包含 8 个域名、排除 50 个域名。若页面重定向至被排除的域名或跳出所包含的域名范围、日期过早,或其 URL 或文本与已使用页面重复,则会在 filtered 中列出,且不会贡献段落。发送给搜索引擎的查询最多会注明 8 个域名;更多排除域名会在搜索后进行过滤。aim(最多 200 个字符)说明证据必须证明什么,并会原样回传,不会改变搜索或排序结果。 receipt 中包含 executed_query、去重后的命中项和 hits_by_domain、未抓取的命中项(hits_excluded、hits_outside_domains、hits_before_date、hits_same_domain、超过来源上限而未读取的 hits_unread)、各来源只计为 failed、not_content、filtered 或 usable 其中之一的计数、outside_domains、unknown_dates,以及被 omitted、set_aside 或 clamped 的段落。在得出网页上没有答案的结论前,请先阅读这些内容。

candidates 是返回段落中的链接,最多 12 个,每页不超过 4 个,每个域名不超过 3 个。每个候选项都包含 token、url、domain、label(页面写出的链接文本)、parent_source_url、parent_passage_id、depth 和 flags(parent_strip_incomplete、parent_detection_degraded、parent_threats=N、likely_unsupported_format、same_domain_pages=N)。不会提供指向当前页面、此次调用中已读取页面、域名筛选范围之外的页面,或来自链接深度为 3 的页面的链接。若要读取其中一些链接,请使用相同的 query 和筛选条件再次调用,并传入 follow=[{"token": ..., "why": ...}](最多 8 个,每个 why 最长 160 个字符):不同调用之间不会保留任何信息。跟进操作不会执行搜索,followed 会说明每个条目的状态:read、not_content、failed、filtered、duplicate、超过 same_domain_limit(截止时间前每个站点最多可以开始读取的页面数)、invalid_token 或 expired。令牌由服务端签名,并会在 30 分钟后或服务重启时过期。

响应的 JSON 文本块不会超过 40,000 个字符;MCP 结果还会在 structuredContent 中再次返回同一对象。若内容将超出上限,会依次优先保留卡片、威胁摘录、段落和元数据,并在收据中分别计数(candidates_omitted、threat_excerpts_omitted、passages_omitted、sources_omitted、metadata_compacted);被省略来源的安全计数会保留在 omitted_safety 中,notes 也会说明这一点。对某个来源使用 read_url 会返回其页面;缩小查询范围或减少来源数量可以腾出更多空间。

status()

返回 browser_reachable、cdp_url(仅包含 scheme、host 和 port)以及浏览器无法连接时的 hint。

配置

MCP 服务:

环境变量默认值用途
CDP_URLhttp://127.0.0.1:9222CDP 端点,可为本地或远程。未经过身份验证,请妥善保密
GROUNDHOG_BROWSERchrome自动启动时启动的浏览器:chrome 或 stealth。设置了 GROUNDHOG_COMPOSE_FILE 或 GROUNDHOG_BROWSER_IMAGE 时,默认为 stealth
GROUNDHOG_CHROME_PATH(found)Chrome 可执行文件
GROUNDHOG_CHROME_PROFILE~/.groundhog/chrome配置文件目录;在此处登录后,后续会复用该登录状态
GROUNDHOG_BLOCK_PRIVATE_IPStrueSSRF 防护
GROUNDHOG_MIN_DELAY_MS5000对同一域名的请求间隔
GROUNDHOG_MAX_TOKENS20000Token 限额
GROUNDHOG_MAX_CONCURRENT_PAGES4并发标签页数
SEARXNG_URL(unset)用于 search 的 SearXNG 实例
GROUNDHOG_SEARCH_BACKENDautoauto、searxng 或 serp
GROUNDHOG_AUTO_START_BROWSERtrue本地 CDP_URL 无响应时启动浏览器
GROUNDHOG_BROWSER_IMAGEghcr.io/dmytrome/groundhog:lateststealth 使用的镜像
GROUNDHOG_COMPOSE_FILE(none)改为使用 docker compose -f <file> up -d 启动

浏览器容器:

环境变量默认值用途
USER_AGENT根据已安装的 Chrome 生成启动时设置 UA,每个作用域中的值都不相同
PROXY(none)上游代理 http://user:pass@host:port;时区和区域设置与出口 IP 一致
TZUTC备用时区
WINDOW_SIZE1920,1080Chrome 窗口大小
XVFB_WHD1920x1080x24虚拟显示器

隐身 Chrome 容器

在 Xvfb 下运行带界面的 Chrome,并提供 CDP 端点,可供任何 CDP 客户端使用。它报告为 Chrome 而非 HeadlessChrome,将 navigator.webdriver 设为 false,并在启动时设置 UA。 使用 PROXY 时,时区和区域设置会与出口 IP 匹配,凭据通过本地 tinyproxy 传递,WebRTC 会固定使用该代理。WebGL 使用检测到的 GPU,否则使用 Mesa llvmpipe。

在全新容器(Chrome 151,无代理)上通过原始 CDP 测试:

检测器结果
deviceandbrowserinfo非机器人(isBot: false,零项标记)
browserscan正常
bot.sannysoft.com31 / 31 项检查通过

iphey 会将任何数据中心 IP 标记出来,因此会跟踪但不计分。实时结果表见 RESULTS.md。Puppeteer、Playwright 和 Selenium 仍会被标记,因为它们会启用 Runtime;examples/ 中有对应的客户端示例,更多工具见 OTHER_TOOLS.md。

安全

CDP 端点未经过身份验证:任何能访问该端口的人都可以控制浏览器。请将其限制在 localhost 或专用网络中。隐身镜像使用 --no-sandbox 运行,因此请确保容器隔离。请通过 SECURITY.md 报告漏洞。

SSRF 防护会解析每个主机,并拒绝环回、私有、链路本地(包括 169.254.169.254)、保留、多播、未指定、CGNAT 和 IPv4 映射的 IPv6 地址、 非 http(s) 协议,以及包含凭据的 URL。它会检查 Chrome 即将发送的每个文档请求,包括重定向和跨站框架,并再次检查 final_url。 子资源由 Chrome 的本地网络访问规则处理,而 Groundhog 启动的 Chrome 已关闭预加载功能。

防护的限制:

  • 只检查文档。较旧的 Chrome,或在配置文件中获准访问本地网络的网站,其子资源不受此防护覆盖。
  • 隐身容器和你提供的浏览器中,预加载功能仍处于开启状态。
  • Groundhog 和 Chrome 会分别解析 DNS,因此仍存在短 TTL 的 DNS 重绑定窗口。
  • 抓取操作共用一个配置文件,因此一个页面设置的 Cookie 会保留到后续抓取中,且页面 JavaScript 可以从该配置文件自行发出请求。

GROUNDHOG_BLOCK_PRIVATE_IPS=false 会关闭防护;仅当确实需要访问内部地址时才使用。

隐藏文本检测的限制

threats 列表为空并不意味着万无一失。检测在页面无法篡改的隔离环境中运行,且不会修改实时页面。但:

  • 阈值可以设置得恰好略低于检测值(opacity: 0.06、transform: scale(0.01)),并且只有在能够证明某种样式完全不绘制内容时,才会将其视为隐藏,因此以下情况可以通过检测: text-indent、inset(50%)(Chrome 会对其报告的值进行取整)、以像素为单位或通过 url()/path() 指定的 clip-path、3D 旋转、mask-image、被其他元素覆盖绘制的文本或堆叠在其他元素后方的文本,以及放置在裁剪溢出内容的框外的定位子元素。
  • 在 PDF 中,只检测渲染模式 3 和 7。白色或微小文本、页面外的文本、隐藏图层以及图像下方的文本都能通过检测,并且每个 PDF 结果都会在 detection_degraded 通知中说明这一点。如果不可见文本使用的字体无法测量字形宽度(竖排文本、非 Identity-H 的 CMap),则会返回 unsupported_content,而不会返回含有该文本的结果;除非传入 include_hidden。解析在子进程中运行,20 秒后会终止。在 Linux 上,内存使用也限制为 1 GiB;在其他系统上,则仅受时间上限和解码流 256 MiB 的上限约束。
  • 不可见字符集采用拒绝列表。
  • 无法读取封闭的 Shadow Root,因此会完全省略其中的内容。
  • 当页面在层叠优先级上胜过隐藏样式表,或隐藏了自己的 <body> 时,文本会根据标记重建,换行符为推测值,并会通过 strip_incomplete 说明这一情况。
拦截检测的限制
  • 如果供应商响应头(cf-mitigated、 x-vercel-mitigated、x-amzn-waf-action、x-dd-b、x-datadome-cid)或质询资源 (Cloudflare、DataDome、PerimeterX、Imperva)表明存在质询,则会确定为质询。其他供应商则根据近乎空白页面中的英文措辞进行判断。
  • 软拦截(返回 200,但悄悄扣留数据)会被视为内容。
  • unknown 不等于 ok,但也不会被视为拦截。
  • 会有意忽略供应商标识(server: cloudflare、cf-ray)。

关于“隐身”的说明

尽力而为。它可以绕过常见的开源检测器,但无法绕过依据 IP 信誉和行为进行拦截的商业反机器人系统。请将其用于合法且经授权的自动化操作。

隐私政策

Groundhog 是可自托管的软件,不会与项目运行的任何服务通信。它不会收集任何信息:没有遥测数据、分析数据、崩溃报告或账户。

页面由你设备上的浏览器获取,并返回给 MCP 客户端。服务不保留数据库,也不写入日志。浏览器配置文件会持久保存 Cookie 和存储数据:本地 Chrome 使用 ~/.groundhog/chrome(删除该文件夹即可清除),stealth 使用容器(运行 docker rm -f groundhog-browser)。

你的流量会到达你获取内容的网站、你的搜索后端(你的 SearXNG;如果未配置,则使用公共搜索引擎),以及仅当设置了 PROXY 时用于查询代理出口 IP 的 IP 地理位置服务;安装时还会访问 PyPI 和 GHCR。

如有疑问,请参阅 SECURITY.md 或访问 https://github.com/dmytrome/groundhog/issues。

许可证

MIT

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。