关于 web-fetcher
web-fetcher 是只读 MCP 服务,可将网页转换为 Markdown、结构化元数据和分类链接,供大模型工作流使用。抓取页面时支持分页,并提供 SSRF 防护。
主要功能
- 01将网页内容转换为简洁 Markdown
- 02分页读取长页面
- 03提取结构化元数据和标题
- 04分类并筛选链接
- 05阻止访问内网和回环地址
适用场景
- 01排除页面杂项以阅读文章
- 02不抓取正文而检查页面元数据
- 03收集站内或站外链接
- 04在响应长度限制后继续读取页面
MCP 相关
通过 stdio 提供三个只读 MCP 工具:获取页面 Markdown、提取元数据和提取链接。每个工具接受绝对 HTTP 或 HTTPS URL,并返回结构化结果。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
npx -y mcp-server-web-fetcherWEB_FETCHER_TIMEOUT_MSRequest timeout in milliseconds (1000-120000). Defaults to 15000.WEB_FETCHER_MAX_BYTESMaximum bytes downloaded per response. Defaults to 5000000.WEB_FETCHER_USER_AGENTUser-Agent header sent with every request.WEB_FETCHER_ALLOW_PRIVATE_HOSTSSet to true to allow fetching loopback and private network addresses. Disables the SSRF guard.WEB_FETCHER_RESPECT_ROBOTSSet to true to enforce robots.txt before fetching.本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
3收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| url | string | 是 | 要获取页面的绝对 http(s) URL。 |
| maxLength | integer | 否 | 单次调用返回的 Markdown 字符数上限。 |
| timeoutMs | integer | 否 | 请求超时时间,单位为毫秒(1000-120000)。 |
| startIndex | integer | 否 | 起始字符偏移量。使用 `nextStartIndex` 翻页获取较长页面。 |
| includeLinks | boolean | 否 | 保留 Markdown 链接。为 false 时,链接文本将以内联纯文本形式呈现。 |
| includeImages | boolean | 否 | 将图片保留为 Markdown 图片语法。 |
| includeMetadata | boolean | 否 | 包含简短的元数据摘要(标题、描述、规范链接、语言)。 |
| mainContentOnly | boolean | 否 | 移除导航、页眉、页脚和侧边栏,仅保留内容最密集的区块。 |
详细介绍
mcp-server-web-fetcher
一个快速、依赖精简的 Model Context Protocol 服务,可将任意网页转换为大模型真正可读的内容:整洁的 Markdown、结构化元数据和分类后的链接列表。
网页有 90% 都是无关内容。脚本、Cookie 横幅、导航栏、侧边栏和跟踪像素会占用上下文并干扰推理。这个服务会在获取网页时剔除这些内容,让模型只看到文章本身。
┌────────────┐ stdio/JSON-RPC ┌──────────────────────┐ HTTPS ┌──────────┐
│ MCP client │ ─────────────────► │ mcp-server-web- │ ────────► │ web page │
│ (Claude, │ ◄───────────────── │ fetcher │ ◄──────── │ │
│ Kiro, …) │ Markdown + JSON │ fetch → clean → md │ └──────────┘
└────────────┘ └──────────────────────┘
功能特性
- 整洁的 Markdown 输出。 采用类似 Readability 的正文检测、GFM 表格、带语言标记的代码围栏和绝对链接。没有残留 HTML,复杂的嵌套表格也不例外。
- 感知上下文窗口。 长网页会通过
startIndex/nextStartIndex分页,而不是被悄悄截断。 - 结构化元数据。 标题、描述、canonical、
lang、作者、发布日期和修改日期、Open Graph、Twitter 卡片、JSON-LD、hreflang替代版本、RSS/Atom 订阅源、h1–h6 大纲和原始 HTTP 标头。 - 链接智能处理。 绝对 URL、锚文本、
rel、nofollow、内部与外部分类、范围筛选和去重。 - SSRF 安全加固。 每次重定向都会拦截环回、私有、链路本地和云元数据地址,而不仅仅是首次请求。URL 中的凭据会被剥除。
- 可预测的失败处理。 每个错误都包含稳定的机器可读
code(TIMEOUT、HTTP_ERROR、BLOCKED_HOST、RESPONSE_TOO_LARGE等)、是否可重试标记和恢复提示,让模型能够自行纠正,而不是猜测。 - 端到端类型安全。 严格的 Zod 输入架构和已发布的
outputSchema,让客户端获得经过验证的structuredContent,而无需重新解析文字内容。 - 运行可靠。 字节数上限、超时、重定向次数限制、字符集检测(包括旧式编码)、小型 TTL 缓存、瞬时故障重试和可选的
robots.txt强制执行。 - 115 项测试,无需网络。 Vitest 单元测试和内存中的 MCP 协议测试。
快速开始
需要 Node.js 20.18.1 或更高版本(这是从 cheerio 继承的要求,因为它需要 undici 7)。
# run it without installing
npx -y mcp-server-web-fetcher
# or install globally
npm install -g mcp-server-web-fetcher
mcp-server-web-fetcher
该服务通过 stdio 使用 MCP,因此单独运行时只会等待客户端连接。将客户端指向它:
Claude Desktop
macOS 上的 ~/Library/Application Support/Claude/claude_desktop_config.json,
Windows 上的 %APPDATA%\Claude\claude_desktop_config.json:
{
"mcpServers": {
"web-fetcher": {
"command": "npx",
"args": ["-y", "mcp-server-web-fetcher"]
}
}
}
配置完成后,使用全局安装:
{
"mcpServers": {
"web-fetcher": {
"command": "mcp-server-web-fetcher",
"env": {
"WEB_FETCHER_TIMEOUT_MS": "20000",
"WEB_FETCHER_RESPECT_ROBOTS": "true"
}
}
}
}
重启 Claude Desktop,然后让它总结某个 URL。
Claude Code
claude mcp add web-fetcher -- npx -y mcp-server-web-fetcher
Kiro、Cursor、Windsurf 和其他 MCP 客户端
格式相同,配置在 .kiro/settings/mcp.json 或客户端的 MCP 配置文件中:
{
"mcpServers": {
"web-fetcher": {
"command": "npx",
"args": ["-y", "mcp-server-web-fetcher"],
"disabled": false,
"autoApprove": ["fetch_page_markdown", "extract_metadata", "extract_links"]
}
}
}
MCP Registry
该服务已列入官方 MCP Registry,名称为 io.github.vojtisprime11/web-fetcher,因此可直接被读取 Registry 的客户端发现:
curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.vojtisprime11/web-fetcher"
Docker
docker build -t mcp-server-web-fetcher .
{
"mcpServers": {
"web-fetcher": {
"command": "docker",
"args": ["run", "--rm", "-i", "mcp-server-web-fetcher"]
}
}
}
镜像以非特权 node 用户运行,且只包含生产依赖。CI 会构建镜像,并使用真实 MCP 客户端驱动它,因此已验证它能够启动并响应内省请求。
从源代码构建
git clone https://github.com/vojtisprime11/mcp-server-web-fetcher.git
cd mcp-server-web-fetcher
npm install
npm run build
node dist/index.js # or: npm run inspect
{
"mcpServers": {
"web-fetcher": {
"command": "node",
"args": ["/absolute/path/to/mcp-server-web-fetcher/dist/index.js"]
}
}
}
工具
这三个工具均为只读、幂等且开放世界(在协议中标注为此类),并且都接受绝对 http(s) URL。
fetch_page_markdown
下载页面并返回干净、适合大模型处理的 Markdown。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
url | string | 必填 | 绝对 http(s) URL。 |
maxLength | integer 500–1000000 | 25000 | 每次调用返回的 Markdown 字符数。 |
startIndex | integer ≥ 0 | 0 | 字符偏移量;使用上次调用返回的 nextStartIndex。 |
mainContentOnly | boolean | true | 删除导航栏、页眉、页脚和侧边栏,保留内容最密集的区块。 |
includeLinks | boolean | true | 保留 Markdown 链接(false 时仅保留文本)。 |
includeImages | boolean | false | 将图片保留为 。 |
includeMetadata | boolean | true | 附加简短的元数据摘要。 |
timeoutMs | integer 1000–120000 | env / 15000 | 每个请求的超时时间。 |
请求:
{
"name": "fetch_page_markdown",
"arguments": {
"url": "https://example.com/blog/caching",
"maxLength": 8000,
"mainContentOnly": true,
"includeImages": false
}
}
structuredContent:
{
"url": "https://example.com/blog/caching",
"requestedUrl": "https://example.com/blog/caching",
"status": 200,
"contentType": "text/html; charset=utf-8",
"title": "How caching works",
"markdown": "# How caching works\n\nCaching is the art of **not** doing work twice...",
"markdownLength": 7984,
"totalLength": 21874,
"startIndex": 0,
"endIndex": 7984,
"nextStartIndex": 7984,
"truncated": true,
"wordCount": 1203,
"bytesDownloaded": 148213,
"elapsedMs": 412,
"fromCache": false,
"redirects": [],
"metadata": {
"title": "How caching works",
"description": "A deep dive into HTTP caching.",
"canonical": "https://example.com/blog/caching",
"language": "en",
"author": "Ada Lovelace",
"publishedTime": "2026-01-15T09:00:00Z"
}
}
要读取剩余内容,请再次调用并传入 "startIndex": 7984。只要 nextStartIndex 不为
null,就继续调用。
extract_metadata
提取模型对页面进行分类所需的全部信息,无需耗费上下文处理正文。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
url | string | 必填 | 绝对 http(s) URL。 |
includeJsonLd | boolean | true | 包含解析后的 JSON-LD 区块(跳过格式错误的区块)。 |
includeHeadings | boolean | true | 包含 h1–h6 大纲。 |
includeHttpHeaders | boolean | true | 包含响应标头,并将其转换为小写。 |
timeoutMs | integer 1000–120000 | env / 15000 | 每个请求的超时时间。 |
{
"name": "extract_metadata",
"arguments": { "url": "https://example.com/blog/caching", "includeJsonLd": true }
}
structuredContent(节选):
{
"url": "https://example.com/blog/caching",
"status": 200,
"charset": "utf-8",
"title": "How caching works",
"description": "A deep dive into HTTP caching.",
"canonical": "https://example.com/blog/caching",
"language": "en",
"author": "Ada Lovelace",
"publishedTime": "2026-01-15T09:00:00Z",
"modifiedTime": null,
"robots": "index, follow",
"favicon": "https://example.com/favicon.ico",
"openGraph": {
"title": "How caching works",
"type": "article",
"image": "https://example.com/img/cover.png"
},
"twitter": { "card": "summary_large_image", "site": "@example" },
"alternates": [{ "hreflang": "de", "href": "https://example.com/de/blog/caching" }],
"feeds": [
{ "title": "Feed", "href": "https://example.com/feed.xml", "type": "application/rss+xml" }
],
"jsonLd": [{ "@type": "Article", "headline": "How caching works" }],
"headings": [
{ "level": 1, "text": "How caching works", "id": null },
{ "level": 2, "text": "Directives", "id": "directives" }
],
"httpHeaders": { "content-type": "text/html; charset=utf-8", "x-cache": "HIT" },
"wordCount": 1203,
"redirects": [],
"fromCache": false
}
extract_links
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
url | string | 必填 | 绝对 http(s) URL。 |
scope | all | internal | external | all | 同站、站外或两者。 |
includeAnchors | boolean | false | 包含页面内的 #fragment 链接。 |
deduplicate | boolean | true | 合并重复的 URL。 |
limit | integer 1–2000 | 200 | 返回链接的上限。 |
timeoutMs | integer 1000–120000 | env / 15000 | 每个请求的超时时间。 |
{
"name": "extract_links",
"arguments": { "url": "https://example.com/blog/caching", "scope": "external", "limit": 50 }
}
structuredContent:
{
"url": "https://example.com/blog/caching",
"status": 200,
"totalFound": 2,
"returned": 2,
"internalCount": 0,
"externalCount": 2,
"truncated": false,
"links": [
{
"url": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching",
"text": "MDN article",
"title": null,
"rel": "noopener nofollow",
"internal": false,
"nofollow": true
}
],
"fromCache": false
}
www.example.com 和 example.com 视为同一站点;blog.example.com 则不视为同一站点。
错误处理
失败会作为 MCP 工具错误返回(isError: true),不会静默返回空结果:
HTTP_ERROR: 404 Client Error for https://example.com/missing
Retryable: no
Hint: Check the URL, or retry later if the status is 429/5xx.
structuredContent.error 包含与 JSON 相同的信息:{ code, message, retryable, url, status }。
| Code | 含义 |
|---|---|
INVALID_URL | 不是绝对 URL。 |
BLOCKED_SCHEME | Scheme 不是 http/https。 |
BLOCKED_HOST | 环回、私有、链路本地或元数据地址(SSRF 防护)。 |
DNS_FAILURE | 主机无法解析。 |
TIMEOUT | 请求超出 timeoutMs。 |
HTTP_ERROR | 响应状态 ≥ 400,或重定向缺少 Location。 |
TOO_MANY_REDIRECTS | 重定向链超过限制。 |
RESPONSE_TOO_LARGE | 声明的正文大于字节上限。 |
UNSUPPORTED_CONTENT_TYPE | 不是文本/HTML/XML/JSON 文档。 |
ROBOTS_DISALLOWED | 被 robots.txt 阻止(仅在启用强制执行时)。 |
NETWORK_ERROR | 连接重置、TLS 故障等。 |
PARSE_ERROR | 无法解析文档。 |
超时、5xx、408 和 429 响应会在错误返回前自动重试一次。
配置
所有配置均为可选;默认值是安全的。
| 变量 | 默认值 | 描述 |
|---|---|---|
WEB_FETCHER_USER_AGENT | mcp-server-web-fetcher/<version> (+repo url) | 发出的 User-Agent。 |
WEB_FETCHER_TIMEOUT_MS | 15000 | 默认超时时间(1000–120000)。 |
WEB_FETCHER_MAX_BYTES | 5000000 | 单个响应的字节上限(10000–50000000)。 |
WEB_FETCHER_MAX_REDIRECTS | 5 | 允许的重定向次数(0–20)。 |
WEB_FETCHER_ALLOW_PRIVATE_HOSTS | false | 仅在有意获取 localhost/LAN URL 时设为 true。 |
WEB_FETCHER_RESPECT_ROBOTS | false | 获取前强制执行 robots.txt(RFC 9309 子集)。 |
WEB_FETCHER_CACHE_TTL_MS | 60000 | 响应缓存 TTL;0 表示禁用缓存。 |
WEB_FETCHER_CACHE_MAX_ENTRIES | 50 | 缓存响应的最大数量。 |
安全
- 默认启用 SSRF 防护。 主机名会被解析,并检查是否属于环回、RFC 1918、CGNAT、链路本地(包括
169.254.169.254)、多播和 IPv4 映射 IPv6 地址范围——对初始 URL 和每个重定向目标都会进行检查。localhost、*.localhost和*.internal会被直接拒绝。关闭防护需要明确选择。 - 限制字节数和时间。 响应以流的形式传输,并在达到字节数上限时截断;每个请求都会在超时时间到达时中止。若 Content-Length 大于上限,则会在下载前拒绝。
- 不执行代码。 永远不会运行 JavaScript;转换前会移除
<script>、<style>、<iframe>等标签。抓取的内容是数据,不是指令——请将传递给大模型的页面文本视为不可信输入。 - 移除凭据。 发起请求前会从 URL 中移除
user:pass@。 - 只读。 此服务不提供写入、文件系统或 shell 接口,也不保留任何认证状态。
发现漏洞?请参阅 SECURITY.md。
架构
src/
├── index.ts # stdio entry point (logs to stderr only)
├── server.ts # transport-agnostic server factory + public API
├── types.ts # Zod input/output schemas for every tool
├── tools/
│ ├── fetchPageMarkdown.ts
│ ├── extractMetadata.ts
│ ├── extractLinks.ts
│ ├── shared.ts # result shaping, dependency injection
│ └── index.ts
└── lib/
├── config.ts # env-driven configuration
├── errors.ts # typed error codes + recovery hints
├── net.ts # URL parsing, SSRF guard, link resolution
├── http.ts # fetch pipeline: timeouts, caps, redirects, charset, cache
├── robots.ts # optional robots.txt support
├── html.ts # cheerio cleanup, metadata + link extraction
├── markdown.ts # turndown configuration, tables, pagination
└── cache.ts # TTL + LRU cache
tests/ # unit tests + in-memory MCP protocol tests
工具处理程序接受可注入的 fetchImpl,因此所有测试都可以通过脚本化的 HTTP 存根离线运行。该库也可以直接导入:
import { runFetchPageMarkdown } from 'mcp-server-web-fetcher';
const page = await runFetchPageMarkdown({ url: 'https://example.com' });
console.log(page.markdown);
开发
最低支持的 Node.js 版本为 20.18.1,这是由 cheerio 决定的(它依赖 undici 7,而 undici 7 需要在 Node 20 中引入的 File 全局对象)。CI 会在 Node 20/22/24 上运行测试套件,并设有单独的任务,使用恰好为 20.18.1 的版本与构建后的服务完成 MCP 握手,因此声明的最低版本经过了验证,而非凭空假定。
npm install
npm run dev # tsx watch
npm run typecheck
npm run lint
npm test # vitest run
npm run test:coverage # thresholds enforced
npm run build
npm run inspect # MCP Inspector against dist/index.js
node scripts/smoke.mjs https://example.com # live end-to-end check
欢迎贡献——请参阅 CONTRIBUTING.md 和 CODE_OF_CONDUCT.md。
限制
- 仅支持静态 HTML。客户端渲染的 SPA 会返回服务器发送的内容;不使用无头浏览器。
- 不支持提取 PDF 或 Office 文档。
- 受机器人防护的网站(Cloudflare 验证页面、登录墙)会返回 403。
- 主内容检测采用文本密度启发式算法。若页面结构特殊,请使用
mainContentOnly: false。
路线图
search_page工具:仅返回与查询匹配的章节。- 提供可选的无头渲染开关,以支持仅含 JS 的页面。
- 在 stdio 之外增加可流式 HTTP 传输。
- 在缓存层支持条件请求(
ETag/If-Modified-Since)。
致谢
基于 MCP TypeScript SDK、 cheerio、turndown 和 zod 构建。
许可证
MIT © Vojta Holes 和贡献者
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。