MCP 记忆服务器:Model Context Protocol 下的持久记忆
MCP(Model Context Protocol)让 AI 客户端用一套统一协议接入外部能力。把「记忆」做成 MCP 服务器,意味着任何支持该协议的客户端——Claude Desktop、Cursor、以及后续接入的 IDE 与 Agent 框架——都能获得持久记忆,而不用为每个客户端写一遍集成。本文讲清协议层发生了什么、52 个工具分别解决什么问题、两种传输怎么选,以及记忆类 MCP 服务器特有的安全要求。
一句话版本:MCP 解决「怎么接」,记忆服务器解决「记住什么」。两者结合后,客户端换一个、模型换一个,用户的历史记忆仍然在。
一、MCP 解决的是什么问题
在 MCP 之前,每接一个外部服务都要写一次定制集成:给 Claude 写一套、给 Cursor 写一套、给自己的 Agent 再写一套。N 个客户端 × M 个服务 = N×M 份集成代码。
MCP 把这个矩阵压成 N+M:客户端实现一次协议(客户端侧),服务实现一次协议(服务端侧),任意组合都能工作。
| 层 | 职责 | 在本项目里的对应 |
|---|---|---|
| 传输层 | 怎么传字节:stdio 管道 或 HTTP | stdio + Streamable HTTP 两种都支持 |
| 协议层 | JSON-RPC 2.0 消息格式、握手、能力协商 | initialize / tools/list / tools/call |
| 能力层 | 暴露什么:tools / resources / prompts | 主要用 tools,共 52 个 |
| 业务层 | 工具实际做什么 | 记忆写入、召回、图谱、压缩、审计… |
一次调用的完整流程
# 1. 握手:协商协议版本与能力
{"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2024-11-05","capabilities":{},
"clientInfo":{"name":"my-client","version":"1.0"}}}
# 2. 列出工具
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
# 3. 调用工具
{"jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{"name":"retain",
"arguments":{"content":"Alice 偏好深色模式","mtype":"semantic"}}}
实测握手返回(mnemosyne-os 8.1.0):
{"jsonrpc":"2.0","id":1,
"result":{"protocolVersion":"2024-11-05",
"serverInfo":{"name":"mnemosyne-memory","version":"8.1.0"},
"capabilities":{"tools":{}}}}
二、52 个工具怎么分类
工具数量多不是卖点,分类清晰才是。41 个原生工具 + 11 个兼容别名,按用途分四组。
A. 检索与治理(23 个)
| 工具 | 做什么 | 什么时候用 |
|---|---|---|
retain | 存一条记忆 | 最基础的写入 |
recall | 按查询召回 | 构造提示词之前 |
retain_batch | 批量写入,约 15× 加速 | 导入历史对话 |
forget | 遗忘:按 id 或自然语言定位 | 用户要求删除 |
capsule | 压成 指针+事实+原子 | 上下文预算紧张时 |
expand | 按指针还原原文,逐字节校验 | 需要原文时 |
consolidate | 合并近重复记忆 | 库里出现大量同义条目 |
reflect | 统计、高频实体、冲突检测 | 定期体检 |
dedup | 检测重复与近重复 | 导入数据后 |
graph_query | 知识图谱遍历 | 多跳关系问题 |
temporal_query | 版本链查询 | 「这个值什么时候变的」 |
doctor | 健康检查 | 排障第一站 |
audit | 审计链查询 | 合规举证 |
confidence_history | 置信度轨迹 | 判断某条是否可信 |
recall_health | 召回质量指标 | 判断召回是否退化 |
stats | 运行统计 | 容量规划 |
list_projects | 列出隔离项目 | 多项目管理 |
| 另有 6 个导出/导入工具(Memory Exchange Protocol)与别名 | ||
B. 记忆形成层(9 个,8.1.0 新增)
这一层解决「记忆怎么从对话里长出来」,而不是「怎么存取」。
| 工具 | 解决什么 |
|---|---|
session_summarize | 七要素会话摘要(目标/约束/决策/未决/产物/失败/偏好)+ 锚点原文,未决事项单独成条 |
session_recall | 召回上次聊到哪,让下次对话能接上 |
conflict_resolve | 六类冲突判定 + 四种动作;scope=audit 扫全库 |
task_record | 任务事实(只追加),按 goal_type 供同类检索 |
playbook_save | 存可复用经验,前置条件必填 |
playbook_recall | 带前置条件校验的检索(applicable / blocked_by) |
code_change_record | 代码变更事件:意图、约束、影响面、被否决的方案 |
code_archaeology | 代码考古 / 坑位预警 |
memory_policy | 敏感度分级、写入准入、历史审计 |
为什么「前置条件必填」很重要:一条没有适用条件的经验,在条件不匹配时会把 Agent 带沟里。强制写清 must / must_not / typical_env,检索时就能算出这条在当前环境下适不适用,不适用的直接丢弃——防止负迁移。
C. 运行时层(12 个,8.1.0 新增)
这一层解决「记忆怎么进上下文」,是 token 预算紧张时的关键。
| 工具 | 解决什么 |
|---|---|
profile_card | 用户画像卡:稳定身份 / 偏好约束 / 当前情境三段 + 未决事项,可直接拼进 system prompt。max_tokens 是硬上限,超了按段裁剪,不截断单条 |
memory_inject | 注入流水线:查询构造→四路召回→RRF 融合→重排→预算内注入 |
recall_catalog | 渐进披露·目录层:只给标题+一句话+展开成本 |
expand_memory | 渐进披露·展开层:正文 + 信封 + 图邻居 + 证据链 |
memory_envelope | 统一信封:八类分类 + 四维作用域 + 证据链 + 双时间轴 + TTL |
memory_consolidate | 巩固三路径:情景→语义、情景→程序、失败→负记忆 |
memory_metrics | 指标:数据不足返回 insufficient_data 而非 0 |
memory_cascade_delete | 级联删除:软删原始 → 失效派生 → 清缓存 → 标 stale |
memory_mask | 掩码:正文换占位符但保留骨架,不破坏 KV-Cache |
topic_summarize | L3 主题摘要:把多条 L2 会话摘要合成「项目现在什么状态」 |
memory_trace | 指针链回溯:L3→L2→L1,回答「这结论从哪句话来」 |
memory_hot_path | 热路径缓冲:异步写入,flush 作读一致性屏障 |
一个设计细节值得单独说:memory_metrics 在数据不足时返回 {"value":null,"status":"insufficient_data"},而不是填 0。因为「0 次错误」和「还没跑过」是两件事,混为一谈会让人误判系统状态。这个取舍在工程上比多一个指标更有价值。
D. 客户端兼容(11 个)
沿用 Mem0 的常规工具名,让已有的 MCP 客户端不改提示词就能切换:
add_memory · search_memories · get_memories · get_memory · update_memory · delete_memory · delete_all_memories · delete_entities · list_entities · list_events · get_event_status
三、两种传输怎么选
| 维度 | stdio | Streamable HTTP |
|---|---|---|
| 进程由谁启动 | 客户端拉起子进程 | 独立部署,客户端连过去 |
| 需要网络 | 不需要 | 需要 |
| 鉴权 | 无(本机进程) | Bearer token / API key |
| 多客户端共享 | 不行(各起各的) | 可以 |
| 多租户隔离 | 不需要 | 必须 |
| 适合 | Claude Desktop、Cursor 本地使用 | 团队共享、远程 Agent、SaaS |
stdio 配置(Claude Desktop 示例)
{
"mcpServers": {
"mnemosyne": {
"command": "python",
"args": ["-m", "mnemosyne.webui.mcp_server",
"--brain-dir", "/Users/me/mem",
"--namespace", "default"],
"env": { "MNEMOSYNE_MCP_TOKEN": "change-me" }
}
}
}
Streamable HTTP 配置
# 启动服务端
export MNEMOSYNE_MCP_TOKEN="a-long-random-secret"
python -m mnemosyne.webui.mcp_server --transport http --port 8788 --brain-dir /data --namespace default
# 客户端调用
curl -X POST https://your-host/mcp -H "Authorization: Bearer a-long-random-secret" -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
四、记忆类 MCP 服务器特有的安全要求
普通 MCP 服务器(查天气、读文件)出问题最多是不好用。记忆服务器出问题是数据泄露。以下四条不是可选项。
1. 握手就要鉴权
很多实现在 initialize 阶段不校验 token,到 tools/call 才查。这给了攻击者一个探针:能握手就说明服务活着、能读 tools/list 就拿到完整能力清单。正确做法是所有方法一律先鉴权,未授权直接 401。
实测(本项目线上端点):无凭据调用 initialize / tools/list / tools/call / resources/list / prompts/list / ping,全部返回 401。
2. 命名空间由服务端决定,不接受客户端传值
这是最容易出的越权漏洞。如果 tools/call 的 arguments.namespace 被采信,那么持有任意有效 token 的人都能读别人的库。
正确实现:服务端从 token 反查归属,强制覆盖客户端传来的 namespace。实测确认本项目行为:非管理令牌携带 X-Mnemosyne-Namespace 头会被忽略并回落默认值。
3. 令牌分级
| 令牌类型 | 能做什么 | 不该能做什么 |
|---|---|---|
| 管理令牌 | 跨命名空间查看、运维 | 不应发给任何客户端 |
| 用户令牌 | 只读写自己那一个命名空间 | 不能指定别人的 namespace |
如果只有一种令牌,那么每个客户端都等价于全站管理员。
4. 日志不记记忆正文
记忆内容属用户隐私,而日志会进可观测平台、会被长期保留、会被多人查看。工具日志只应记 method、工具名、作用域,不记 arguments 里的正文。
五、性能实测
本机(Windows / Python 3.13.12 / 8.1.0.post1 / 默认零配置):
| 操作 | 耗时 | 说明 |
|---|---|---|
| 引擎初始化 | 0.24 ms | 建库 + 加载 |
retain 单条 | 11.91 ms | 含规则抽取 |
recall 一次 | 3.924 ms | 300 次采样中位 |
recall P95 | 5.955 ms |
注意这是「库内 200 条记忆」的量级。记忆量到万级时,向量打分成为主要开销——这也是为什么官方把 numpy 列为可选依赖:它把 1024 维余弦从「每条 1024 次纯 Python 乘加」变成一次矩阵乘法。
六、工具选择策略:52 个不必全暴露
工具数量多对开发者是能力,对模型是负担。研究与实践都指向同一个结论:候选工具越多,选择准确率越低。以下是按场景裁剪的建议。
| 场景 | 建议暴露的工具 | 数量 |
|---|---|---|
| 日常对话助手 | retain recall profile_card memory_inject | 4 |
| 客服 / 销售 | 上列 + session_summarize session_recall get_all | 7 |
| 编码助手 | retain recall code_change_record code_archaeology playbook_save playbook_recall | 6 |
| 运维排障 | retain recall task_record doctor stats | 5 |
| 数据治理 | dedup consolidate reflect memory_metrics memory_cascade_delete | 5 |
| 全量(调试用) | 全部 | 52 |
实现方式:MCP 服务端的 tools/list 返回是可控的,按配置裁剪即可。这不违反协议——客户端只应依赖服务端实际声明的能力。
七、一次典型的记忆生命周期
把工具串起来看,比逐个介绍更清楚。以下是一次完整交互中会发生什么。
阶段 1:用户说了一句话
用户:「我下周要搬到上海了,之前北京那套地址别用了」
阶段 2:召回相关历史(在构造提示词前)
memory_inject(query="用户搬家 地址", budget_tokens=400)
→ 返回:
【画像】时区 Asia/Shanghai;偏好简洁回答
【相关】[2026-08-12] 用户住在北京朝阳区…(置信度 0.9)
【未决】上次提到的合同地址待确认
这里用 memory_inject 而不是 recall,因为它直接给出可拼进提示词的文本 + 配额账目。
阶段 3:写入新事实
retain(content="用户将于下周搬到上海;北京旧地址作废",
mtype="semantic", tags=["住址","时效"], confidence=0.9)
阶段 4:冲突处理
新地址与旧地址是「时间更新」关系,不是矛盾。触发:
conflict_resolve(content="用户将于下周搬到上海", scope="single")
判定为「时间更新」→ 旧记录标记为 superseded(不删除),新记录成为当前事实。这样历史仍可追溯(「他之前住北京」),但检索时不会把旧地址当现况。
阶段 5:会话结束
session_summarize(session_id="2026-10-27-001",
messages=[...], write_pending=true)
产出七要素摘要:目标 / 约束 / 决策 / 未决 / 产物 / 失败 / 偏好。其中「未决」独立成条,供下次对话主动追问。
阶段 6:下次对话
session_recall(query="上次聊到哪", include_pending=true)
→ 「上次你提到要搬到上海,合同地址还没确认」
这个流程的价值:用户不用重复交代背景。注意阶段 4 的取舍——旧地址被标记而非删除。因为「他之前住北京」本身可能有用(比如处理历史订单),而「当前住址」必须是上海。区分「历史事实」与「当前事实」是记忆系统的核心能力之一。
八、错误处理与幂等
| 情形 | 行为 | 调用方该怎么做 |
|---|---|---|
| 重复写入同一内容 | 可能产生多条近重复记录 | 定期跑 dedup;或在写入前先 search 判断 |
| 写入超时 | 可能已成功但未收到响应 | 用返回的 event_id 调 get_event_status 确认 |
| 召回返回空 | 正常结果,不是错误 | 不要重试,直接告诉模型「没有记录」 |
| 作用域不匹配 | 返回空而非报错 | 先用 get_all 确认数据确实在这个作用域 |
| 并发写入 | SQLite 单写者模型,可能等待 | 批量用 retain_batch;高并发用 memory_hot_path 异步队列 |
关于「召回返回空」:这是设计选择。系统宁可不返回,也不返回弱相关内容。如果你的业务需要「总是给点东西」,应该放宽 threshold,而不是抱怨返回空。
九、性能实测
本机(Windows / Python 3.13.12 / 8.1.0.post1 / 默认零配置):
| 操作 | 耗时 | 说明 |
|---|---|---|
| 引擎初始化 | 0.24 ms | 建库 + 加载 |
retain 单条 | 11.91 ms | 含规则抽取 |
recall 一次 | 3.924 ms | 300 次采样中位 |
recall P95 | 5.955 ms |
注意这是「库内 200 条记忆」的量级。记忆量到万级时,向量打分成为主要开销——这也是为什么官方把 numpy 列为可选依赖:它把 1024 维余弦从「每条 1024 次纯 Python 乘加」变成一次矩阵乘法。
十、常见问题
MCP 服务器和 REST API 要都做吗?
看目标。面向「已经支持 MCP 的客户端」,做 MCP 就能零代码接入;面向「自己的后端服务」,REST 更直接。两者共用同一套引擎与存储,不冲突。
工具太多会不会让模型选错?
会。52 个工具全暴露给模型,选择成本很高。见第六节的裁剪建议。
为什么 tools/list 返回 55 个但说 52 个能力?
因为有 3 个是给旧客户端的废弃别名(memory/export-v1 等),它们解析到与 memory_export-v1 相同的处理器。去重后的实际能力是 52 个。这个细节在文档里写清楚,比含糊说「50 多个」更可信。
stdio 模式还需要 token 吗?
严格说不需要(进程由本地客户端拉起,没有网络暴露)。但项目仍支持设置 MNEMOSYNE_MCP_TOKEN,理由是:同一台机器上可能有其他进程,设置 token 能防止误连。建议设。
能不能只暴露只读工具?
可以,按配置裁剪 tools/list。对审计场景很实用——给外部审查方一个只能 recall / audit 的端点,不给写入权限。