云计算百科
云计算领域专业知识百科平台

MCP Fetch 服务器实战指南:从 URL 抓取到 Markdown 转换的完整实现

1. 为什么你需要一个自己的 MCP Fetch 服务器?

如果你用过 Claude、Cursor 这类 AI 工具,肯定遇到过这样的场景:想让 AI 帮你分析一篇最新的技术文章,或者总结某个产品官网的更新日志。你通常怎么做?手动复制粘贴网页内容,然后扔给 AI。这个过程不仅繁琐,遇到长文章还得分段复制,更别提网页里那些烦人的广告、导航栏和无关信息了,它们会严重干扰 AI 的理解。

这就是 MCP Fetch 服务器要解决的痛点。MCP,全称 Model Context Protocol,你可以把它理解为 AI 工具的“通用插座”。它定义了一套标准协议,让不同的 AI 应用(客户端)能够以统一的方式调用外部工具(服务器)。而 Fetch 服务器,就是专门负责“上网”抓取内容的那个工具。

自己动手实现一个 Fetch 服务器,好处远不止是“不用再复制粘贴”这么简单。首先,你获得了完全的控制权。你可以定制内容抓取的规则,比如只抓取特定区域的内容,或者对抓取到的内容进行预处理。其次,你能更好地控制成本和性能。使用第三方 API 往往有次数限制和费用,而自建服务器,你可以根据需求配置代理池、连接池,优化抓取速度。最重要的是,你能确保合规性。一个负责任的网络爬虫必须尊重网站的 robots.txt 协议,自建服务器让你可以精细地控制这一行为,避免对目标网站造成不必要的负担,甚至引发法律风险。

我刚开始接触这个需求时,也尝试过一些现成的方案,但总感觉不够顺手。要么功能太简单,要么配置太复杂。后来我决定自己实现一个,踩过不少坑,也积累了很多实战经验。今天,我就把这些从零开始构建一个健壮、高效、合规的 MCP Fetch 服务器的完整过程分享给你,包含大量你在官方文档里看不到的细节和优化技巧。

2. 项目初始化与环境搭建

万事开头难,但把环境搭好,后面就顺了。我们选择 Python 作为实现语言,因为它有极其丰富的网络爬虫和文本处理生态。我会带你一步步走,确保你的开发环境是干净且可复现的。

2.1 创建项目与虚拟环境

我强烈建议为每个项目使用独立的虚拟环境,这能避免依赖包之间的版本冲突。这里我用 uv 这个新兴的包管理工具,它比传统的 pip 快得多,也能很好地管理虚拟环境。如果你还没安装,可以用 pip install uv 来获取。

# 创建项目目录并进入
mkdir mcp-fetch-server
cd mcp-fetch-server

# 使用 uv 初始化项目并创建虚拟环境
uv init
uv venv

# 激活虚拟环境
# 在 Windows 上:.venv\\Scripts\\activate
# 在 macOS/Linux 上:source .venv/bin/activate

激活虚拟环境后,你的命令行提示符前面通常会显示 (.venv),这表示你已经在这个独立的环境中了。

2.2 安装核心依赖

我们的服务器核心依赖以下几个库,它们各有分工:

  • mcp: 这是实现 MCP 协议的官方 Python SDK,是我们与 AI 客户端通信的桥梁。
  • httpx: 一个现代化、异步的 HTTP 客户端库,性能比 requests 更好,尤其适合高并发抓取。
  • pydantic: 用于数据验证和设置管理。它能确保我们接收到的参数都是合法、安全的,这是构建健壮 API 的第一步。
  • markdownify: 负责将 HTML 转换为 Markdown。
  • readabilipy: 基于 Mozilla 的 Readability 算法,能智能地从杂乱 HTML 中提取出文章主体内容。

使用 uv 一次性安装它们:

uv add mcp httpx pydantic markdownify readabilipy

安装完成后,我们可以创建一个 pyproject.toml 文件来正式定义我们的项目。这个文件就像是项目的“身份证”和“说明书”。

# pyproject.toml
[project]
name = \”mcp-fetch-server\”
version = \”0.1.0\”
description = \”A custom MCP server for fetching and converting web content.\”
readme = \”README.md\”
requires-python = \”>=3.9\”
dependencies = [
\”mcp>=1.0.0\”,
\”httpx>=0.25.0\”,
\”pydantic>=2.0.0\”,
\”markdownify>=0.11.0\”,
\”readabilipy>=0.2.0\”,
]

[project.scripts]
fetch-server = \”mcp_fetch_server.main:serve\”

最后一行定义了一个命令行入口,以后我们可以直接用 fetch-server 命令来启动服务,非常方便。

3. 定义数据模型:用 Pydantic 筑起第一道防线

在开始写网络请求代码之前,我们必须先定义好“游戏规则”。客户端会传过来哪些参数?每个参数有什么要求?这一步做得好,能避免后续处理中大量的边界错误和安全隐患。Pydantic 库就是干这个的,它通过类型注解和验证器,让参数校验变得既严格又优雅。

3.1 构建 Fetch 请求模型

我们定义一个 Fetch 类,它对应客户端调用 fetch 工具时传递的参数。每个字段我都加了详细的注释和验证规则。

from pydantic import BaseModel, Field, AnyUrl, validator
from typing import Annotated
from urllib.parse import urlparse

class Fetch(BaseModel):
\”\”\”URL获取请求的参数模型\”\”\”
url: Annotated[
AnyUrl,
Field(
description=\”目标URL地址,必须包含协议头(如 https://)\”,
examples=[\”https://example.com/article\”]
)
]
max_length: Annotated[
int,
Field(
default=5000,
ge=1,
le=1_000_000,
description=\”返回内容的最大字符数,防止内存溢出。最小1,最大100万。\”
)
] = 5000
start_index: Annotated[
int,
Field(
default=0,
ge=0,
description=\”分页获取的起始字符索引。从0开始计数。\”
)
] = 0
raw: Annotated[
bool,
Field(
default=False,
description=\”是否返回原始HTML内容。为False时,会自动转换为Markdown。\”
)
] = False

@validator(\”url\”)
def validate_url_scheme(cls, v):
\”\”\”自定义验证器:确保URL使用HTTP或HTTPS协议\”\”\”
parsed = urlparse(str(v))
if parsed.scheme not in [\”http\”, \”https\”]:
raise ValueError(\”URL协议必须是 http 或 https\”)
return v

我来解释一下几个关键设计点:

  • AnyUrl 类型:Pydantic 自带的这个类型会自动校验字符串是否符合 URL 格式,比如是否包含协议头。这省去了我们手写正则表达式的麻烦。
  • Field 的约束:ge 和 le 分别代表“大于等于”和“小于等于”。我把 max_length 限制在 1 到 100 万字符之间。这个范围是经过考虑的:太小了可能抓不到完整内容,太大了容易导致内存问题或被目标网站视为攻击。
  • 自定义验证器 validate_url_scheme:虽然 AnyUrl 能校验格式,但它允许像 ftp:// 或 file:// 这样的协议。对于网页抓取,我们只关心 http 和 https。这个验证器确保了安全性,防止有人尝试访问本地文件或其他非网络资源。
  • 3.2 模型的实际威力

    这个模型不仅仅是个“摆设”。当我们在服务器主逻辑中接收到客户端传来的 JSON 参数时,可以这样使用:

    try:
    # 这行代码会自动触发所有字段的验证
    fetch_args = Fetch(**arguments_from_client)
    except ValidationError as e:
    # 如果验证失败,e.errors() 会包含详细的错误信息
    return f\”参数错误:{e.errors()[0][\’msg\’]}\”

    这样一来,任何非法的、超出范围的参数都会在第一时间被拦截,并返回清晰的错误信息给客户端。这比在业务逻辑里到处写 if 判断要清爽和可靠得多。我强烈建议你在任何涉及外部输入的地方都使用 Pydantic 模型,这是写出健壮代码的好习惯。

    4. 核心抓取引擎:异步、稳健、可配置

    参数校验通过后,就进入最核心的环节:从网络获取内容。这里我们选择 httpx 的异步客户端,因为它能更好地处理并发请求,不会在等待网络响应时阻塞整个程序。

    4.1 实现 fetch_url 函数

    这个函数是服务器的心脏,它负责发起 HTTP 请求、处理各种网络异常、判断内容类型,并决定是否进行格式转换。

    import asyncio
    from httpx import AsyncClient, HTTPError, TimeoutException, ConnectError
    from typing import Tuple
    from mcp.shared.exceptions import McpError

    async def fetch_url(
    url: str,
    user_agent: str,
    force_raw: bool = False,
    proxy_url: str | None = None,
    timeout: float = 30.0
    ) -> Tuple[str, str]:
    \”\”\”
    异步抓取URL内容的核心函数。

    参数:
    url: 目标地址
    user_agent: 用户代理字符串
    force_raw: 强制返回原始HTML
    proxy_url: 代理服务器地址
    timeout: 请求超时时间(

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » MCP Fetch 服务器实战指南:从 URL 抓取到 Markdown 转换的完整实现
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!