内容
# Fetch Guard
[](https://pypi.org/project/fetch-guard/)
[](https://pepy.tech/project/fetch-guard)
[](https://github.com/Erodenn/fetch-guard/actions/workflows/ci.yml)
[](https://pypi.org/project/fetch-guard/)
[](LICENSE)
<a href="https://glama.ai/mcp/servers/@Erodenn/fetch-guard">
<img width="380" height="200" src="https://glama.ai/mcp/servers/@Erodenn/fetch-guard/badge" alt="fetch-guard MCP server" />
</a>
MCP服务器和命令行工具,获取网址并返回干净的、适合LLM的markdown。专有的提取管道净化HTML,提取结构化元数据,检测提示注入尝试,并处理破坏简单获取器的边缘情况:机器人阻止、付费墙、登录墙、非HTML内容类型和需要JavaScript渲染的页面。
核心问题很简单:LLM需要网页内容,但原始HTML噪音大且可能具有敌意。获取的页面可能包含隐藏文本、不可见Unicode、屏幕外元素和直接嵌入内容中的提示注入尝试。该管道在内容到达模型之前会剥离所有这些。
三个层次专门处理注入防御:
1. **提取前净化** 在内容提取之前移除隐藏元素(`display:none`、`visibility:hidden`、`opacity:0`、`font-size:0`、`transform:scale(0)`、`clip:rect(0,0,0,0)`、零高度溢出容器和具有匹配前景和背景颜色的元素)、通过`<style>`标签中的CSS类/ID规则隐藏的元素、屏幕外定位的内容、`aria-hidden`元素、`<noscript>`和`<template>`标签,以及26类非打印Unicode字符,包括双向隔离和Unicode标签。
2. **模式扫描** 对提取的文本和元数据字段进行四阶段扫描。第一阶段应用50个编译后的正则表达式模式,涵盖系统覆盖、忽略前指令、角色注入、假对话标签和隐藏指令标记,支持英语、西班牙语、法语、德语、日语、简体中文和葡萄牙语。第二阶段通过NFKC和混淆字符映射规范化文本,然后重新扫描以捕获同形异义绕过(西里尔字母或数学Unicode字符替换拉丁字母等)。第三阶段查找base64、十六进制编码和URL百分比编码块,解码它们并扫描高严重性模式。第四阶段用ROT13解码整个文档并扫描高严重性模式。元数据字段(标题、描述、og:title等)独立扫描,匹配名称限定为其源字段。
3. **会话加盐输出包装** 为每次调用生成一个随机的8字符十六进制盐,并用`<fetch-content-{salt}>`标签包装正文。由于盐是不可预测的,注入的内容无法伪造包装边界。
## 一个工具
这是一个单一工具的MCP服务器。它公开一个工具——`fetch`——在一致的接口后面运行完整的提取管道。无需工具选择,无需路由,无需多步骤工作流。一个网址输入,一个结构化结果输出,可通过参数配置。
## 快速入门
### 先决条件
- Python 3.10+
- pip
### 安装
```bash
pip install fetch-guard
```
对于JavaScript渲染(可选):
```bash
pip install 'fetch-guard[js]' && playwright install chromium
```
### 配置MCP客户端
将以下内容添加到您的MCP客户端配置中。适用于Claude Code、Claude Desktop、Cursor或任何MCP兼容的客户端。
**通过uvx(推荐):**
```json
{
"mcpServers": {
"fetch-guard": {
"command": "uvx",
"args": ["fetch-guard"]
}
}
}
```
**通过pip安装:**
```json
{
"mcpServers": {
"fetch-guard": {
"command": "fetch-guard"
}
}
}
```
**从源代码:**
```json
{
"mcpServers": {
"fetch-guard": {
"command": "python",
"args": ["-m", "fetch_guard.server"]
}
}
}
```
**通过Docker:**
```json
{
"mcpServers": {
"fetch-guard": {
"command": "docker",
"args": ["run", "-i", "--rm", "sterlsnyc/fetch-guard"]
}
}
}
```
> **注意:** Docker镜像不包含Playwright。JavaScript渲染(`js: true`)在通过Docker运行时不可用。如果需要JS渲染,请使用`uvx`或`pip`安装。
### 验证
询问您的AI助手获取任意网址。如果它返回具有状态头、元数据和风险评估的结构化内容,则表示您已连接。
### 命令行界面
```bash
fetch-guard-cli <url> [options]
# 或:python -m fetch_guard.cli <url> [options]
```
| 标志 | 默认值 | 描述 |
|---|---|---|
| `--timeout N` | 180 | 请求超时(秒) |
| `--max-words N` | 无 | 提取正文内容的字数上限。也禁用自动大小保护 |
| `--js` | 关闭 | 对JavaScript渲染的页面使用Playwright |
| `--strict` | 关闭 | 高风险注入时退出代码2 |
| `--links MODE` | `domains` | `domains`表示唯一外部域名,`full`表示带有锚文本的所有URL |
| `--header KEY:VALUE` | 无 | 自定义HTTP头(可重复) |
## 工具参数
MCP `fetch`工具接受以下参数:
| 参数 | 类型 | 默认值 | 描述 |
|-----------|------|---------|-------------|
| `url` | 字符串 | 必需 | 要获取的URL |
| `timeout` | 整数 | 180 | 请求超时(秒)。确保工具始终返回 - 无挂起获取 |
| `max_words` | 整数 | 无 | 提取正文内容的字数上限。也禁用自动大小保护 - 在您需要对截断有明确控制权时使用,而不影响默认限制 |
| `strict` | 布尔值 | false | 当为true且检测到高风险注入时,响应被标记为错误 |
| `js` | 布尔值 | false | 对JavaScript渲染的页面使用Playwright(需要`fetch-guard[js]`) |
| `links` | `"domains"` \| `"full"` | `"domains"` | `"domains"`表示唯一外部域名,`"full"`表示带有锚文本的所有URL |
| `auth_token` | 字符串 | 无 | `Authorization`头的Bearer令牌(例如`"my-api-key"`)。用于GitHub的身份验证API和其他需要身份验证的端点 |
| `headers` | 对象 | 无 | **已弃用。** 改用`auth_token`。将在下一个版本中删除 |
### Claude Code技能
将`resources/fetch-guard/`复制到项目中的`.claude/skills/fetch-guard/`,或将`resources/fetch-guard.md`用作Claude Code命令。
## 它做什么
管道运行从URL到结构化输出的13步序列:
1. **`/llms.txt`预检** 在完整获取之前检查域名根目录下的`/llms.txt`。如果请求的URL是域名根目录且存在`/llms.txt`,则该内容完全取代正常的HTML管道。这遵循LLM友好型网站摘要的新兴约定。
2. **获取** 通过`requests`进行静态HTTP请求,或在设置`--js`时通过Playwright驱动的浏览器渲染。两者之间没有自动回退:`--js`是明确选择加入。
3. **边缘检测** 将响应分类为机器人阻止(Cloudflare挑战,403/429/503带有阻止签名,LinkedIn的自定义999)、付费墙(订阅提示,高级叠加层)和登录墙(登录重定向,仅限成员模式)。
4. **自动重试** 机器人阻止触发一次重试,带有完整的Chrome用户代理字符串,然后再报告。付费墙和登录墙立即报告,无需重试。
5. **内容类型路由** 非HTML响应获得快速路径:JSON渲染为围栏代码块,RSS/Atom馈送解析为结构化摘要,CSV变为markdown表格(上限为2000行),纯文本直接通过。 二进制内容类型被拒绝。
6. **HTML净化** 剥离隐藏元素(包括扩展的CSS可见性技术、颜色匹配文本和`<template>`标签)、屏幕外定位内容、`aria-hidden`节点、`<noscript>`标签和非打印Unicode。 返回删除的所有内容的统计数据。
7. **内容提取** trafilatura将净化的HTML转换为带有链接保留的markdown。
8. **元数据提取** 从三个来源按优先级顺序提取标题、作者、日期、描述、规范URL和图像:JSON-LD、Open Graph,然后是元标签。
9. **链接提取** 两种模式:`domains`返回唯一外部域名的排序列表,`full`返回带有锚文本的所有外部URL,按域名分组。
10. **注入扫描** 四阶段扫描:原始文本针对所有50个模式(英语 + 6种额外语言),NFKC规范化文本用于同形异义绕过,解码并扫描base64/十六进制/URL百分比编码有效载荷,整个文档用ROT13扫描。 元数据字段独立扫描,匹配限定为其源字段。 每个匹配项记录模式名称、严重性(高/中)和60个字符的上下文片段。
11. **大小保护 + 截断** 默认情况下,超过2MB(提取前)或20KB(提取后)的內容会引发错误,并建议`max_words`值。 设置`--max-words`会禁用两个限制并截断 - 在您需要对到达模型的內容有明确控制权时使用。
12. **加盐包装** 正文被包装在会话加盐标签中,以加强防御。
13. **输出格式化** 命令行界面生成五个纯文本部分(状态头、正文、元数据、链接、注入详细信息)。MCP服务器返回具有相同数据的结构化JSON字典。
## 输出
### 命令行界面
五个部分,打印到stdout:
- **状态头:** URL、获取时间戳、风险标志(`OK` 或 `INJECTION WARNING`)、净化统计数据、如果检测到边缘情况则显示边缘情况信息
- **正文:** 用`<fetch-content-{salt}>`标签包装的干净markdown
- **元数据:** 带有标题、作者、日期、描述、规范URL、图像的JSON块
- **外部链接:** 域名列表或按域名分组的完整URL分解
- **注入详细信息:** 每个匹配项的模式名称、严重性和上下文片段(仅当检测到模式时存在)
### MCP服务器
返回结构化字典:
```
status, url, fetched_at, body, content_type, metadata, links, links_mode,
risk_level, injection_matches, edge_cases, sanitization,
llms_txt_available, llms_txt_replaced, js_rendered, js_hint,
retried, truncated_at
```
`status`是一个快速查看的摘要字符串,设计为无需扩展完整结果即可阅读:
```
"OK | html"
"HIGH | html | edge:paywall | sanitized:193 | retried | truncated:500"
```
始终包含`risk`和`content_type`。 非默认值为(`edge`、`sanitized > 0`、`retried`、`js`、`truncated`)仅在存在时追加。
当设置`--strict`且风险级别为`HIGH`时,命令行界面退出,返回代码为2,MCP服务器引发错误响应。 在这两种情况下,完整结果仍然可用。
## 退出代码
| 代码 | 含义 |
|---|---|
| 0 | 成功 |
| 1 | 获取错误(网络故障、空响应、二进制内容) |
| 2 | 检测到高风险注入(仅限`--strict`) |
## 架构
```
fetch_guard/
├── pipeline.py # 核心编排 - 13步序列,由CLI和服务器共享
├── cli.py # CLI入口点 - 参数解析、管道调用、输出
├── server.py # MCP服务器 - FastMCP包装器,覆盖相同的管道
│
├── http/ # HTTP获取层
│ ├── client.py # 通过requests进行静态HTTP获取
│ ├── playwright.py # 通过Playwright进行JavaScript渲染(可选)
│ └── llms_txt.py # /llms.txt预检检查
│
├── extraction/ # 内容提取和边缘检测
│ ├── content.py # trafilatura包装器 - HTML到markdown
│ ├── content_type.py # 非HTML路由 - JSON、XML/RSS、CSV、纯文本
│ ├── edges.py # 机器人阻止、付费墙、登录墙分类
│ ├── links.py # 外部链接提取(域名列表或完整URL)
│ └── metadata.py # JSON-LD、Open Graph、元标签提取
│
├── security/ # 注入防御
│ ├── guard.py # 盐生成、内容包装、四阶段扫描、元数据扫描、合并API
│ ├── normalize.py # 用于同形异义检测的NFKC + 混淆字符规范化
│ ├── patterns.py # 14个英语 + 36个多语言编译正则表达式模式 - 唯一真实来源
│ ├── multilingual_patterns.json # 多语言注入模式(ES、FR、DE、JA、ZH、PT)
│ └── sanitizer.py # 隐藏元素、CSS规则、颜色匹配和非打印字符移除
│
└── output/ # 格式化
└── formatter.py # CLI输出组装
```
每个模块都是一个单一责任单元,具有一个公共函数作为其接口。 `pipeline.py`是共享的核心:`cli.py`和`server.py`都调用`pipeline.run()`并以自己的方式处理结果。
## 测试
测试套件有两个层级。
**单元测试** (424个,所有模拟 - 无网络调用):
```bash
pytest
```
每个模块都有对应的测试文件。CI在每次推送和PR时都会在Python 3.10、3.12和3.13上运行完整的套件。
**实时集成测试** (54个条目,真实网络):
```bash
pytest -m live -v
```
而不是硬编码的测试函数,实时套件是数据驱动的:`tests/catalogs/`中的五个YAML目录定义了带有类型化断言的URL条目。单个参数化运行者(`test_catalog.py`)评估所有这些。
| 目录 | 条目 | 涵盖内容 |
|---|---|---|
| `html.yaml` | 13 | 丰富的元数据页面,非英语内容,重定向,政府/学术网站 |
| `injection.yaml` | 9 | OWASP备忘录,arXiv论文,受控高风险有效载荷gist |
| `edge_cases.yaml` | 10 | 登录墙(GitHub,Reddit,Steam),机器人阻止(LinkedIn,Glassdoor,WSJ) |
| `content_types.yaml` | 12 | RSS/Atom馈送,GitHub API JSON,原始文本文件,XML网站地图 |
| `llms_txt.yaml` | 11 | 具有`/llms.txt`的域名(替换或可用),确认否定 |
实时测试在发布时通过单独的`live-tests.yml`工作流自动运行。
## 开发
```bash
# 运行测试(414个单元测试,所有模拟 - 无网络调用)
pytest
# 运行实时集成测试(访问真实网址)
pytest -m live
# 整理
ruff check fetch_guard/ tests/
```
CI在推送和PR到`main`时运行,使用GitHub Actions,针对Python 3.10、3.12和3.13进行测试。
## 致谢
与[Claude Code](https://claude.ai/code)一起开发。
## 许可证
[MIT](LICENSE)