跳至正文
来两杯美式
返回

当 MCP Server 只是"翻译"现成能力:FastMCP + Mem0 + Redis 的选型逻辑

By 来两杯美式
发布于更新于

这篇接着 选型总览 讲。总览里那个”能力适配器”——把企业 IM 消息、用户记忆、时间工具这些现成能力标准化成 MCP 工具的 Python 服务——从框架到部署的全链路选型,都在这里。

它的特点是”翻译型”:能力是现成的,核心是把它们包成 MCP 协议。这个定位,几乎决定了下面每一项的选择——优先高抽象、优先复用 Python AI 生态、优先快速接入。

框架:为什么是 FastMCP

我们没有直接使用官方 mcp SDK 的低层接口,而是用了独立维护的 FastMCP 2.x 高级框架。FastMCP 1.x 的核心曾并入官方 Python SDK,但本文代码里的 from fastmcp import FastMCP 来自独立项目。

对一个”翻译型”服务,裸 SDK 要手写太多协议层样板:工具注册、参数校验、JSON Schema 生成、结果序列化、传输管理。FastMCP 把这些全包了,装饰器一行注册工具:

from fastmcp import FastMCP

mcp = FastMCP("企业内 MCP Server")

@mcp.tool()
def memory_search(query: str, user_id: str, limit: int = 20):
    """搜索用户记忆。"""
    return memory.search(query=query, user_id=user_id, limit=limit)

函数签名和 docstring 直接变成 JSON Schema,Agent 自动就能发现这个工具、知道参数长什么样。这类项目要接十几个工具,装饰器模式省下的样板非常可观。

FastMCP 还顺手解决了几个我们本来要自己造轮子的事:传输层(SSE / Streamable-HTTP / stdio 一行切换)、认证(内置 JWTVerifier、StaticTokenVerifier、OAuthProxy)、ASGI 原生(能直接挂到现有 Starlette 应用)。

选型取向:当一个 MCP Server 的核心是”把现成能力翻译成协议”,高级框架的收益远大于它带来的抽象成本。反过来,如果你要深度嵌入一个已有平台(见 Node.js 栈那篇),裸 SDK 的控制力反而更重要。

版本:稳定优先,不追 v3

FastMCP 我们用的是 2.12,但当时已经出到 3.x 了。3.x 引入了 Apps 架构、交互式 UI、FormInput、GenerativeUI 一堆新东西。

没跟。原因很简单:对这个服务,那些全是锦上添花。它不需要 UI,不需要交互式表单,它只需要稳定地把工具暴露出去。升 v3 要重新验证一遍协议兼容性、要踩早期坑,收益却几乎为零。

这条规律在选型总览里讲过,这里再强调一次:

MCP 生态迭代很快,SDK/框架版本”稳定优先”。新版本的新特性,只有在它真的解决你当前问题时才上。

记忆系统:Mem0 + Redis

记忆是这个服务里的重头戏之一。我们没有自己从零拼”提取 → 向量化 → 存储 → 检索”这一套,而是用了 Mem0——它把记忆的生命周期封装好了,我们只管 memory.search / memory.add

真正花心思选的是向量存储。候选有 Redis(redisvl)、Chroma、Qdrant、Milvus:

对比项Redis(redisvl)ChromaQdrantMilvus
部署复杂度低,复用现有 Redis低,嵌入式中,独立服务高,集群
运维成本最低,无额外组件低,本地文件
向量搜索能力支持多种索引,适合复用 Redis适合本地开发与轻量部署专用向量检索能力完整面向分布式向量检索
容量判断取决于内存、索引和查询负载取决于部署模式与并发取决于节点配置和索引取决于集群配置和索引
本项目适配⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

最后选 Redis,三条理由,按重要性排:

  1. 基础设施复用:内网已经有 Redis 集群,零额外运维成本。为一个记忆功能单独拉一套 Qdrant/Milvus,不值。
  2. Mem0 原生支持:redisvl 是 Mem0 一级支持的向量后端,接起来没坑。
  3. 规模匹配:用本项目的数据量、向量维度、过滤条件和并发做过容量测试后,现有 Redis 能满足目标延迟和内存预算,不需要额外引入专用向量数据库。

向量库选型最容易踩的坑,是”为了用上更强的引擎而引入额外基建”。先看你的数据规模和现有基础设施,够用就不折腾。

Mem0 的配置(脱敏后)大概长这样,重点看它怎么把 LLM、Embedding、向量库三段都声明出来:

config = {
    "llm": {"provider": "openai", "config": {
        "model": os.environ["MEM_MODEL_NAME"],
        "openai_base_url": os.environ["OPENAI_BASE_URL"],
        "temperature": 0.2,
    }},
    "embedder": {"provider": "openai", "config": {
        "model": os.environ["MEM_EMBED_MODEL_NAME"],
        "embedding_dims": int(os.environ["MEM_EMBED_DIMS"]),
    }},
    "vector_store": {"provider": "redis", "config": {
        "collection_name": "mem",
        "embedding_model_dims": int(os.environ["MEM_EMBED_DIMS"]),
        "redis_url": os.environ["REDIS_URL"],
    }},
}
memory = Memory.from_config(config)

temperature 设 0.2 是这个模型和评测集上的起始配置,用于减少采样随机性;它不能保证抽取正确,也不是所有模型都支持。上线前仍要用记忆召回率、误写率和重复率做回归评测。

LLM 与 Embedding:OpenAI 兼容才是关键

LLM 用的是百度千帆 + Qwen 系列(Qwen3-Coder 做提取,Qwen3-Embedding 做向量)。

选千帆的首要原因是合规——这是境内要用的服务。但选型里真正有长期价值的,不是千帆本身,而是它走的是 OpenAI 兼容接口

这意味着 HTTP 客户端层通常可以先通过环境变量切换端点和模型名:

# 千帆
OPENAI_BASE_URL=https://qianfan.example.com/v2
MEM_MODEL_NAME=qwen3-coder-instruct

# 换成 OpenAI
OPENAI_BASE_URL=https://api.openai.com/v1
MEM_MODEL_NAME=gpt-4o

# 换成本地 Ollama
OPENAI_BASE_URL=http://localhost:11434/v1
MEM_MODEL_NAME=qwen3:32b

这一点在记忆系统里尤其重要:Mem0 的 provider 可以继续使用 "openai" 适配器,再通过 base URL 指向兼容服务。但“接口兼容”只减少传输层改动,不代表模型可以无验证替换。不同 Provider 可能在模型名、认证、参数支持、工具调用、限流和错误格式上有差异;Embedding 模型切换还必须同步核对向量维度,并重建不兼容的向量索引。

OpenAI 兼容接口可以降低客户端迁移成本,但不能消除 Provider 锁定。切换前要跑契约测试和任务评测,并把 Embedding 维度、向量库 Schema 与迁移方案一并纳入变更。

HTTP 客户端:httpx

调企业内部那些 HTTP 接口(消息推送等),用的是 httpx,不是 requests 也不是 aiohttp。

对比项httpxaiohttprequests
异步原生 async/await原生仅同步
HTTP/2支持不支持不支持
SSEhttpx-sse 扩展需自实现不支持
与异步工具协同原生异步原生异步直接调用会阻塞事件循环

选 httpx 最直接的理由,是它同时提供同步和异步 API,适合在异步工具中复用连接池。它的 API 风格和 requests 相近,但并不相同;迁移时仍要处理超时、流式响应、连接复用和异常类型差异。

部署:Docker + uv

部署用了 Docker 容器化,依赖管理从老的 pip + requirements.txt 迁到了 uv + pyproject.toml。这一步的收益是实打实的:

镜像里还做了两件事:非 root 用户运行(安全基线),以及用 uv 直接跑(uv run --no-dev python -m ...),不污染系统 Python。

传输层走 SSE 时,Nginx 反向代理有几条必须的配置(关 buffering、拉长超时),这块坑比较多,我放到 传输演进那篇 统一讲。

风险与改进:把”配了没开”讲清楚

诚实地说,这个服务现在背着几笔债,列出来也是选型的一部分——知道哪里留了缺口,比假装没有强:

风险等级说明建议
认证未启用🔴 高生产无认证,谁都能调所有工具启用 JWTVerifier
SSE 是 legacy🟡 中官方已标记为旧版规划迁 Streamable-HTTP
FastMCP 版本滞后🟡 中2.12 vs 3.x评估升级路径
JWT 密钥硬编码🟡 中写在源码里迁到环境变量
Redis 无高可用🟡 中单节点连接接 Sentinel/Cluster
无监控可观测性🟡 中缺 metrics/tracing接 OpenTelemetry

其中最该拿出来说的是第一条:JWT 配了,但没开

源码里 JWTVerifier 是写好的,issuer、audience、algorithm 都配齐了,唯独 auth=verifier 那行被注释掉了,服务实际跑在无认证状态。这是个典型的反例——内部服务、调用方受控,于是图省事先不开,结果认证能力形同虚设。

别学我们这一步。网络隔离只能减少暴露面,不能证明调用者身份。即使全部调用方都是内部 Agent,也应启用服务身份认证、最小权限和密钥轮换;在完成这些基线前,不应把服务视为可安全上线。

动手验证

这篇的载体是脱敏的最小片段(agent-runtime-lab 是 Node 栈,Python 部分用最小可复现示例)。三段就够了,感受一下这套栈的”薄”:

# 1. FastMCP 装饰器注册(上面已贴)
# 2. Mem0 最小配置(上面已贴)
# 3. 传输一行切换
mcp.run(transport="sse", host="0.0.0.0", port=8000)
# 想换 Streamable-HTTP,把 "sse" 改成 "http" 即可

整个服务的核心代码量,比想象中少得多——这正是 FastMCP + Mem0 这套组合的价值:把协议层和记忆工程都吃掉,业务只管”接工具”。

AI 工程化相关阅读


分享这篇文章:
通过邮件分享这篇文章✓ 链接已复制
查看系列全部文章
  1. 01.MCP Server 选型决策树:从语言到审计,六个维度一次说清
  2. 02.当 MCP Server 只是"翻译"现成能力:FastMCP + Mem0 + Redis 的选型逻辑
  3. 03.当 MCP Server 要"嵌进"对外平台:Node.js + 官方 SDK + API Key 的选型逻辑
  4. 04.SSE 已被 MCP 判为 legacy:存量服务怎么迁、新服务怎么选
  5. 05.MCP 权限怎么分级?只读、可操作和高风险动作(附完整源码)
  6. 06.MCP 工具权限码怎么设计?read、write、dangerous 三层模型(附完整源码)
  7. 07.自建 SearXNG + MCP:给 AI 工具接入可控的网页搜索
  8. 08.Agent Skill 是快照,快照会腐烂:我把业务流程全部搬到了服务端
  9. 09.能力目录与协议下发:把接口设计和控制流一起交给 Agent
  10. 10.四套版本号:把 Agent Skill 的版本管理挪回服务端之后
  11. 11.让用户自定义 Agent 能力,而不打开注入的后门
  12. 12.MCP 2026-07-28 改了什么:从有状态会话到无状态核心
  13. 13.Anthropic 发布 Agent Skills:AI Agent 的能力该如何被打包
  14. 14.Agent Skills 设计模式:用文件夹给 Agent 装上专业能力
  15. 15.Agent Skills 最佳实践:从评测、结构拆分到安全审查
  16. 16.MCP Server 升级 2026-07-28:不是换依赖,而是重画协议边界
  17. 17.MCP Client 升级 2026-07-28:服务端能留兼容窗口,我们偏不兼容旧协议
  18. 18.MCP 爆炸之后,Agent 怎么从一堆工具里选对那一个

上一篇
当 MCP Server 要"嵌进"对外平台:Node.js + 官方 SDK + API Key 的选型逻辑
下一篇
RAG 实战:核心组件、调优与问题排查