Mnemosyne OS

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 管道 或 HTTPstdio + 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_summarizeL3 主题摘要:把多条 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

三、两种传输怎么选

维度stdioStreamable 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 ms300 次采样中位
recall P955.955 ms

注意这是「库内 200 条记忆」的量级。记忆量到万级时,向量打分成为主要开销——这也是为什么官方把 numpy 列为可选依赖:它把 1024 维余弦从「每条 1024 次纯 Python 乘加」变成一次矩阵乘法。

六、工具选择策略:52 个不必全暴露

工具数量多对开发者是能力,对模型是负担。研究与实践都指向同一个结论:候选工具越多,选择准确率越低。以下是按场景裁剪的建议。

场景建议暴露的工具数量
日常对话助手retain recall profile_card memory_inject4
客服 / 销售上列 + session_summarize session_recall get_all7
编码助手retain recall code_change_record code_archaeology playbook_save playbook_recall6
运维排障retain recall task_record doctor stats5
数据治理dedup consolidate reflect memory_metrics memory_cascade_delete5
全量(调试用)全部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 ms300 次采样中位
recall P955.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 的端点,不给写入权限。

十一、下一步

看完整接入指南 →GitHub 源码