fluxio-mcp 开发手记:从需求到开源

这篇文章记录 fluxio-mcp 从想法到开源的完整过程——一个给 LLM 提供"可编程信息获取能力"的 MCP Server。为什么做它、怎么设计、测试怎么覆盖、开源踩了什么坑。

一、为什么做

背景:做信息流工具(Fluxio)时,我发现一个真实痛点——

我 L : L 请 M 整 : 理 我 今 的 天 知 识 A 截 I 止 到 领 域 X 的 重 月 要 , 新 无 闻 法 获 取 最 新 信 息

LLM 的通用搜索能力(如果提供)和"信息流"差很远:

通 信 用 息 搜 流 索 场 : 景 搜 : " 需 关 要 键 的 词 是 " " 批 → 量 抓 返 取 回 网 U 页 R L → → L L 提 M 取 正 自 文 己 读 → 结 构 化 "

需求成型:

给 L 1 2 3 4 L . . . . M 批 提 转 批 一 量 取 量 组 抓 网 M 处 工 取 页 a 理 具 正 r ( : U 文 k 一 R ( d 次 L 去 o 喂 ( 广 w 多 多 告 n 个 个 / ( 链 源 导 L 接 ) 航 L ) ) M 友 好 )

定位:不是搜索,是"读取器"——LLM 已经有链接,需要的是把链接变成可读的正文。

二、架构设计

L f H L l T M u T ( │ ▼ x ├ ├ ├ └ │ ▼ P C i ─ ─ ─ ─ l o 请 a M - t t t t 求 u C m o o o o ( d P c o o o o h e p l l l l t 协 ( s s s s t / 议 P / / / / p y f e r s x 任 t e x e e 意 h t t a a + o c r d r M n h a _ c 重 C _ c b h 试 P + u t a _ r _ t n + C F l m c e l a s d h w 限 i s s 速 e t ) n M t C 批 网 批 信 ) P 量 页 量 息 ) 抓 读 流 取 → 取 源 聚 U M + 合 R a L r 摘 ( k 要 并 d 发 o ) w n 正 文

设计决策:

决策 原因
FastMCP 装饰器极简、中间件、Python 生态
httpx 并发 批量场景 IO 密集,并发是核心收益
可配置 UA 部分站点反爬,UA 是基本尊重
超时 + 重试 网络不可靠,工具不能挂
输出纯 Markdown LLM 消费友好(HTML 会污染上下文)

三、核心实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@mcp.tool()
async def fetch_urls(urls: list[str]) -> list[dict]:
    """批量抓取 URL 内容,返回标题+正文(Markdown)。
    Args:
        urls: 要抓取的 URL 列表(最多 10 个)
    """
    async with httpx.AsyncClient(
        timeout=15,
        headers={"User-Agent": UA},
        follow_redirects=True,
    ) as client:
        results = await asyncio.gather(
            *(fetch_one(client, u) for u in urls[:10]),
            return_exceptions=True,
        )
    return [normalize(r) for r in results]

关键点:

  1. 并发:asyncio.gather 10 个 URL 并发,总耗时 ≈ 最慢的一个
  2. 容错:单个失败不影响整体(return_exceptions)
  3. 上限:urls[:10] 防滥用(工具级限流)
  4. 降级:正文提取失败 → 返回元数据(title/url),不让 LLM 拿空结果

四、测试怎么覆盖

开源项目没有测试 = 没有可信度。fluxio-mcp 的测试设计:

1
2
3
4
5
6
7
8
# tests/test_fetch.py
class TestFetchUrls:
    async def test_single_url(self): ...
    async def test_multiple_urls(self): ...      # 并发正确性
    async def test_invalid_url(self): ...        # 容错
    async def test_timeout_fallback(self): ...   # 超时降级
    async def test_max_limit(self): ...          # 数量上限
    async def test_markdown_extraction(self): ... # 正文提取质量

测试策略:

✅ ✅ ✅ 用 覆 覆 本 盖 盖 地 " " 边 契 H 界 约 T " " T : : P 空 输 列 出 测 表 结 试 、 构 服 非 稳 务 法 定 ( ( 不 U L 依 R L 赖 L M 真 、 实 超 依 网 时 赖 站 、 , 超 s C 大 c I 页 h 面 e 稳 m 定 a ) )

CI:GitHub Actions,push 自动跑 pytest——开源项目的门面是 CI 绿。

五、开源的坑

坑 1:README 是门面,别随便写

❌ ✅ 只 清 有 晰 一 的 句 定 " 位 给 + L L 安 M 装 用 + 的 抓 快 取 速 工 开 具 始 " + 工 具 清 单 + 截 图

坑 2:License 必须先定

M A G I p P T a L : c : 最 h 传 宽 e 染 松 - ( , 2 不 别 . 适 人 0 合 能 : 工 放 带 具 心 专 类 用 利 ) ( 条 推 款 荐 ( 开 大 源 公 起 司 步 友 ) 好 )

坑 3:示例比文档有用

1
2
3
# README 里给"10 秒跑起来"的示例
uvx fluxio-mcp 或 clone 后 python -m fluxio_mcp
# 让用户 30 秒内看到效果,比 1000 字文档有用

坑 4:版本与发布

✅ ✅ ✅ 语 发 C 义 布 H 化 到 A 版 N 本 P G ( y E 0 P L . I O 1 ( G . p 0 i 记 p 录 → 每 i 次 0 n 变 . s 更 2 t . a 0 l l → 即 1 用 . ) 0 . 0 )

六、收获

工 面 程 试 上 一 ( 价 " — : 个 设 值 我 — 完 计 : 开 后 整 源 者 的 → 过 是 产 一 知 品 实 个 识 级 现 , M 前 M → C 者 C P 是 P 测 证 试 工 据 S 具 e → " r v 文 v e 档 s r → " 的 我 打 发 了 磨 布 解 流 ) 程 M C P "

总结

fluxio-mcp 的核心认知:

  1. 开源项目的起点是"自己的真实痛点"——不是追热点,是解决问题
  2. 设计决策要写出来:为什么 FastMCP、为什么并发、为什么 Markdown 输出
  3. 测试是开源的门面:CI 绿 + 边界覆盖 = 可信
  4. README/License/示例是开源的"交付物":代码好但没人用 = 白写

开源不是"把代码放网上",是"把问题、决策、过程一起交付"。 过程的价值不比代码低。