Skip to content

feat(memory-mcp): 专家记忆 MCP Server(用户级 token 鉴权) - #2887

Open
ArvinQi wants to merge 21 commits into
TencentCloud:developfrom
ArvinQi:feat/memory-mcp-extension-develop
Open

ArvinQi wants to merge 21 commits into
TencentCloud:developfrom
ArvinQi:feat/memory-mcp-extension-develop

Conversation

@ArvinQi

@ArvinQi ArvinQi commented Nov 8, 2026 •

Copy link
Copy Markdown

Summary(摘要)

本次按 #2887 上的 review 重做了核心与鉴权;随后把另一分支上已验证的本地终端接入(hooks + 安装器 + DSH 原生插件)脱敏后一并并入本 PR:

  • 用户级 token 鉴权:token 绑定用户,可命名客户端(codex@macbook)、可限定专家范围、可单独吊销;服务端只存 sha256,hmac.compare_digest 校验;调用者身份只从 token 推出,不再信任 X-Octop-User-Id

  • 按请求校验专家权限:每次请求都按 owner / 共享专家规则重新检查(与 HTTP 路由同一套规则),改 header 无法越权

  • 写入走应用层:memory_capture → MemoryRuntime.capture(脱敏、回声保护、角色/长度策略生效),memory_save → MemoryService.save(人工审核调度器能收到通知)

  • 工具面重做:9 个工具全部 async def + asyncio.to_thread;日常 4 个(search/get/save/capture)任何 token 可用,审核/运维 5 个(raws/candidates/extract/promote/reject)仅专家所有者

  • 默认进审核队列:外部写入不再固定 promote=True,跟随专家提炼配置;后台提炼按专家串行、保存任务引用、不会为了提炼自动启动停用的专家

  • 归属写进 metadata:caller / source_host 落到原始事件 payload,不再把 <user>说: 拼进正文

  • lifespan 不再被覆盖:把 MCP session manager 串进原来的 FastAPI lifespan

  • 挂载 fail-closed:OCTOP_MEMORY_MCP_ENABLED=1(显式开启)或已存在有效 token 才挂载

  • Dashboard token 管理:个人记忆页新增「MCP 接入」tab,创建(名称 + 单选上报专家)、查看(掩码/最近使用/状态)、切换专家、撤销;明文只在创建时显示一次,页面同时给出可直接复制的 curl 一键安装命令

  • 本地终端接入:/mcp/memory/setup(含 ?project= 项目级、?os=windows PowerShell 版)检测并接入 Claude Code / Kiro / DSH / Codex / Cursor;安装器不内置凭据,支持 curl ... | OCTOP_MEMORY_TOKEN=omcp_… sh 一条命令配置

  • token 即唯一凭据:token 绑定一个专家,客户端只带 Authorization(不再需要 X-Octop-Agent-Id);可随时切换专家,下一次请求生效;不限/多专家 token 仍支持带头指定

  • 安装与配置指引:新增 docs/memory-mcp-setup.md(创建 token、一键命令、手动配置、验证、排障)

  • DSH 改原生插件:octop-memory-native(vendored,可离线 link: 安装)在 DSH 进程内完成召回/上报,不再依赖 hook 桥接;旧 preset/hook 自动清理

  • transcript 导入:终端 hook 上传会话 JSONL(本地文件或 gzip+base64),服务端按终端格式解析成 L0;导入前做密钥脱敏,原会话归档到 workspace

仅开源以下 5 个终端;不附带任何域名、凭据或账号信息。

拆分(按 review 建议)

PR 内容 目标仓库 本次状态
a MCP 核心 + 用户级鉴权 + 工具面 Octop 本 PR
d 终端 hooks 与安装器(5 个终端、项目级、Windows、DSH 原生插件、安装指引) Octop 已脱敏并入本 PR
b 召回质量补丁(实体后缀 / 强 token 排序) octop-memory 从本 PR 移除,随后单独提
c 对话记录解析 octop-memory 暂留 Octop(hooks 依赖),随后下沉到 octop-memory 适配层
— DSH 预设(cordis.patch.yml) — 不进入开源仓库

被移除的内容保留在 fork 分支 archive/pr2887-pre-split,b/c/d 提交时会复用。

对 review 各条的回应

# Review 意见 处理
1 /setup 不鉴权却内置实例 token ✅ 安装器完全不内置凭据(default_token="",安装时粘贴用户 token);实例共享 token 已删除
2 鉴权要落到用户 ✅ 用户级 token(表 + repo + /api/memory-mcp/tokens),身份从 token 推出;专家权限每次请求用 owner / expert_visible_to 规则校验;只存 hash + 常量时间比较
3 写入要走 octop-memory 应用层 ✅ capture → MemoryRuntime.capture;save → MemoryService.save(supersedes 仍用 deprecate_atom 标记旧记忆)
4 同步工具阻塞事件循环 ✅ 9 个工具全部 async def,阻塞体 asyncio.to_thread
5 memory_recall_patch.py 拿出去 ✅ 已从本 PR 删除(连同 server.py 的启动挂钩),后续作为 octop-memory PR b
6 工具分层 ✅ 审核/运维 5 个工具仅专家所有者可调用(非所有者 403)
7 不要跳过审核 ✅ promote 跟随专家提炼配置,默认 False(外部写入进审核队列)
8 后台提炼 ✅ 任务保存强引用、按专家 asyncio.Lock 串行、专家停止时不自动启动
9 来源不要写进正文 ✅ 写进事件 payload:caller / source_host / source;正文不再加前缀
10 lifespan ✅ 串接原 lifespan,不再整体覆盖
11 Codex 没配 MCP ✅ 安装器注册 hooks;Codex 的 MCP 段由它自己管理,文档给出 [mcp_servers.octop-memory] 配置与首次信任说明

说明:Dashboard 的 token 管理页已在本次一并补上(个人记忆页 →「MCP 接入」);API 仍可直接调用(文档给了 curl 示例)。

Verification(本地验证)

  • ✅ uv run ruff check src tests
  • ✅ uv run ruff format --check src tests
  • ✅ uv run mypy src/octop — 657 source files
  • ✅ uv run pytest -n auto -m "not live" — 5619 passed / 40 skipped
  • ✅ lint / format / mypy:660 source files
  • ✅ Dashboard:tsc -b、vitest run src/pages/Agent/Memory(86 条)、eslint / prettier、en/zh 文案键树一致性
  • ✅ 真实环境联调(Codex):同一个 token 下召回 hook hits=5 http=200、上报 hook parsed=2 imported=2 http=200;多专家 token 不带头发请求返回 400

新增测试覆盖 review 点名的四类:

  • 越权:用户 A 的 token 访问用户 B 的专家 → 403;token 限定专家范围 → 403
  • 伪造 X-Octop-User-Id:不影响写入身份(身份只从 token 推出)
  • 密钥过滤:memory_capture 写入 sk-… → 落库为 [REDACTED]
  • 并发:工具在 worker 线程执行(不阻塞事件循环),全部工具为 async def
  • 另含:token 吊销 / 一次性明文 / 列表不含明文、lifespan 串接、fail-closed 挂载

Compatibility(兼容性)

  • 破坏性:移除了实例共享 OCTOP_MEMORY_MCP_TOKEN 鉴权,改用用户级 token;升级后需要创建 token 并重启(或设置 OCTOP_MEMORY_MCP_ENABLED=1)
  • 数据库迁移 026(SQLite + PostgreSQL 成对),schema 版本 25 → 26
  • 未开启时 /mcp/memory 不挂载,对现有部署零影响
  • /setup、/uninstall 仍位于 token 校验之前(只返回脚本、不发放凭据);部署应只对可信网络暴露 /mcp
  • 安装器支持项目级与 Windows PowerShell;DSH 原生插件随本仓 vendored 发版
  • 依赖不变(mcp>=1.9,<2、octop-memory>=1.0.3)

Target branch

  • Base is develop
  • Base is main

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Refactor / chore
  • Release / hotfix

Test plan

  • uv run ruff check src tests
  • uv run ruff format --check src tests
  • uv run mypy src/octop
  • uv run pytest -n auto -m "not live"
  • Added/updated tests

Checklist

  • Updated CHANGELOG.md (user-facing)
  • README / docs updated(docs/memory-mcp.md 重写)

Dashboard「MCP 接入」

个人记忆页(/personalization/memory)新增 tab(Experts 抽屉不显示):

  • 列表:名称 / 掩码(token_hint)/ 上报专家 / 创建时间 / 最近使用 / 状态
  • 创建:名称必填 + 单选上报专家(可改选「不限专家」);创建成功弹窗只显示一次明文,并给出带该 token 的 curl 一键安装命令
  • 切换专家:行内「切换专家」→ PATCH,下一次请求立即生效;已撤销 token 不可编辑
  • 页面顶部常驻「一条命令接入」提示 + 可复制的安装命令
  • 文案 en/zh 齐全,组件测试 6 条

安装与配置指引

新增 docs/memory-mcp-setup.md,覆盖:

  • 创建 token(Dashboard / API)、绑定与切换上报专家
  • 一键安装(macOS/Linux + Windows PowerShell、OCTOP_MEMORY_TOKEN / OCTOP_TERMINALS / ?project=)
  • 各终端落点与手动配置(Claude Code / Codex / Cursor / DSH 插件)
  • 验证(Dashboard 最近使用、MCP 握手、hooks 日志)
  • 排障(401/400/403/404、Codex hook trust、DSH 重启、项目级安装)

终端接入(本次新增)

终端 接入方式 备注
Claude Code hooks(UserPromptSubmit 召回 / Stop 上报)+ MCP 条目 项目级写到 .claude/settings.local.json
Kiro hooks + MCP 条目 项目级 MCP 含凭据,跳过并提示
DSH 原生插件 octop-memory-native 进程内召回/上报,旧 hook/preset 自动清理
Codex hooks + ~/.codex/config.toml 的 [mcp_servers] 项目级写 <proj>/.codex/config.toml
Cursor hooks + ~/.cursor/hooks.json 项目级写 <proj>/.cursor/hooks.json

本 PR 不包含任何域名、凭据或个人信息。

@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

This pull request was closed by a temporary author restriction.

Pull requests may only be opened by:

  • users who already have a merged pull request in this repository
  • users with admin, maintain, or write access
  • users listed in .github/pr-author-allowlist.txt or the PR_AUTHOR_ALLOWLIST repository variable

Ask a maintainer to add your GitHub login to that allowlist, then reopen the pull request.


此 Pull Request 已被临时的作者限制自动关闭。

当前仅允许以下用户创建 PR:

  • 已有 Pull Request 被合入本仓库的贡献者
  • 拥有 admin、maintain 或 write 权限的用户
  • 写在 .github/pr-author-allowlist.txt 或仓库变量 PR_AUTHOR_ALLOWLIST 中的用户

如需在首次合入前贡献,请联系维护者把 GitHub 用户名加入允许名单,然后再重新打开这个 PR。

维护者可将仓库变量 PR_AUTHOR_GUARD 设为 off 以关闭此限制。

@github-actions github-actions Bot closed this Nov 8, 2026
@ArvinQi

ArvinQi commented Nov 8, 2026

Copy link
Copy Markdown
Author

CI 里的 CI workflow 目前是「required approval but was not approved before it expired」(fork 外部贡献者的首次运行需要 maintainer 批准;旧 PR #445 的 CI 也是同样原因从未真正跑起来)。麻烦 maintainer 在 Checks 页点一下 Approve and run。

本地已跑完同等的门禁,供参考:

  • uv run ruff check src tests — All checks passed
  • uv run ruff format --check src tests — 1253 files already formatted
  • uv run mypy src/octop — Success: no issues found in 658 source files
  • uv run pytest -n auto -m "not live" — 5631 passed, 41 skipped(11m34s)

@Bosheng0422 Bosheng0422 reopened this Nov 9, 2026
@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

This pull request was closed by a temporary author restriction.

Pull requests may only be opened by:

  • users who already have a merged pull request in this repository
  • users with admin, maintain, or write access
  • users listed in .github/pr-author-allowlist.txt or the PR_AUTHOR_ALLOWLIST repository variable

Ask a maintainer to add your GitHub login to that allowlist, then reopen the pull request.


此 Pull Request 已被临时的作者限制自动关闭。

当前仅允许以下用户创建 PR:

  • 已有 Pull Request 被合入本仓库的贡献者
  • 拥有 admin、maintain 或 write 权限的用户
  • 写在 .github/pr-author-allowlist.txt 或仓库变量 PR_AUTHOR_ALLOWLIST 中的用户

如需在首次合入前贡献,请联系维护者把 GitHub 用户名加入允许名单,然后再重新打开这个 PR。

维护者可将仓库变量 PR_AUTHOR_GUARD 设为 off 以关闭此限制。

@github-actions github-actions Bot closed this Nov 9, 2026
@Bosheng0422 Bosheng0422 self-assigned this Nov 9, 2026
@Bosheng0422 Bosheng0422 reopened this Nov 9, 2026
@Bosheng0422 Bosheng0422 added the Memory Issue about memory plz add this label to help filter label Nov 9, 2026
@Bosheng0422

Copy link
Copy Markdown
Collaborator

感谢 @ArvinQi 把这块从 #356、#445 一路推到现在 🙏 让外部 agent 通过 MCP 读写专家记忆,是我们非常想要的能力:Claude Code、Codex 和 Octop 专家共享同一份记忆,正是 octop-memory 接下来的方向。

我们整体读了一遍代码,方向认可,但目前的状态还不能合并。下面按优先级列出问题,并给出改法建议。

✅ 我们想保留的设计

  • 用 Streamable HTTP + stateless_http=True,用 X-Octop-Agent-Id 选择专家,URL 统一,服务重启后客户端不会因为会话失效而报错
  • 没配置 token 时不挂载接口,对现有部署零影响
  • 审核类操作复用 Dashboard 用的 Bridge,MCP 和记忆面板的审核规则不会分叉
  • 外部写入能走完提炼流程(原始事件 → 候选 → 长期记忆);召回内容重复写回和同一内容重复写入的处理很细
  • 客户端 hook(提问前召回、会话结束上报)的思路

🔴 合并前必须修

1. /setup 不需要鉴权,但返回的安装脚本里写着实例 token

_InstallerApp 用 default_token=_memory_mcp_token() 把 OCTOP_MEMORY_MCP_TOKEN 写进了脚本,而 /setup 位于鉴权之前,能访问到这个地址的人都能拿到 token。(类的说明写着「不发放任何凭据」,和代码注释「谁连得到它就拿到这个 token」也是矛盾的。)
建议:安装脚本里不带 token,由用户在 Dashboard 登录后获取,安装时粘贴进去。

2. 鉴权需要落到「用户」

现在整个实例共用一个 token,X-Octop-Agent-Id 和 X-Octop-User-Id 都由调用方自己填。Octop 是多用户系统,这样做会出现:

  • 用户 A 改一下请求头,就能读写用户 B 的专家记忆,绕过了 Octop 现有的专家归属检查
  • X-Octop-User-Id 可以随便填,「调用者追溯」的结果不能作为依据
  • 只要一个客户端泄露了 token,所有专家都会暴露,而且没法只吊销某一个人

建议(改动不大):

  • token 绑定到用户,不按专家或实例共用。一个用户可以有多个 token,每个带一个客户端名字,比如 codex@macbook,这样可以按客户端分别吊销,也能从 token 可靠地知道写入来源;创建时可以选择只允许访问某几个专家
  • 用户身份从 token 推出,不信任 X-Octop-User-Id
  • 每次请求用现成的 octop.api.common.agent.assert_agent_access(server, agent_id, user) 检查权限
  • 服务端只存 token 的 hash,比较时用 hmac.compare_digest。可以参考 api/routers/internal_mcp.py 里 verify_internal_token 的做法

3. 写入要走 octop-memory 的应用层,不要直接调 core

  • memory_capture 直接调 Memory.add_raw / add_raw_batch,绕过了 MemoryRuntime.capture,敏感信息过滤(redact)不生效。coding agent 的对话记录里很常见 API key 和 .env 内容,这一点风险最大。(PR 里 memory_capture 的使用示例恰好就写着这个问题 😄)
  • memory_save 直接调 memory.store / deprecate_atom,绕过了 MemoryService.save,不会通知人工审核调度器,和 harness 内置 memory_save 的行为(包括 fix(memory): preserve explicit save classifications octop-memory#42 刚修的显式分类)也会越走越远

建议:capture 改走 MemoryRuntime.capture(host 填 claude-code / codex),这样自带过滤和重复写回检测,MCP 这边自己实现的 echo 过滤可以删掉;save 改走 MemoryService.save。

4. 工具是同步函数,会阻塞 Octop 的事件循环

9 个 @mcp.tool() 都是普通 def。mcp SDK(1.28.1,func_metadata.call_fn_with_arg_validation)对同步函数是直接在事件循环里调用的,每次读写数据库都会卡住整个 Octop。
建议:改成 async def,阻塞的操作用 asyncio.to_thread 包起来,和 internal_mcp.py、agent_files.py 的写法保持一致。

5. memory_recall_patch.py 请从这个 PR 里拿出去

它在启动时全局替换了 octop-memory 的三个私有函数(router.route、_per_token_atom_search、_gather_atoms),影响的是所有专家的召回,不只是 MCP 这个入口;_ENTITY_INDEX 一旦建好就不再刷新,新实体要重启后才认得;而且 octop-memory 一升级,这些替换就可能悄悄失效。
这几个召回改进本身很有价值,欢迎带上评测数据直接给 octop-memory 提 PR,我们在那边评审。

🟡 建议一起修

  1. 工具分层:外部 agent 只开放 memory_search / memory_get / memory_save / memory_capture。memory_candidates / memory_promote / memory_reject / memory_extract / memory_raws 属于审核和运维操作,Dashboard 已经有了;如果要保留,只对专家所有者开放,否则外部 agent 可以自己晋升自己写的内容。
  2. 不要跳过审核:_trigger_extract 里固定用了 promote=True。建议跟着专家自己的提炼配置走,外部写入默认进入审核队列。
  3. 后台提炼:
    • asyncio.create_task 启动后没保存引用,任务可能被回收(同类问题见 fix(acp): keep a strong reference to scheduled close tasks octop-harness#197)
    • 直接调用正在运行的 service.extract,可能和 MemoryMiddleware 自己的提炼线程同时操作同一个 MemoryService,建议每个专家串行执行
    • 专家停用时会被自动启动,可能带来意外的模型调用费用,建议不启动或加开关
  4. 来源标记不要写进正文:<user>说: 前缀最终会进到长期记忆的文本里。建议先写进原始事件的 metadata(比如 source_host / caller),这两个字段我们会在 octop-memory 里正式加上。
  5. lifespan:app.router.lifespan_context = _memory_mcp_lifespan 会覆盖原来的 lifespan。develop 目前没有用 on_event,所以暂时没影响,但以后加的启动和关闭钩子会被悄悄吞掉。建议改成标准的 APIRouter 路由(参考 internal_mcp.py),或者把原 lifespan 串进去。
  6. Codex 没配置 MCP:安装器只写了 hook,没有写 ~/.codex/config.toml 的 [mcp_servers];hook 第一次需要在 Codex 里手动信任,这一点也请写进文档。

📦 拆分建议

11k 行放在一个 PR 里很难评审,建议拆成:

PR 内容 目标仓库
a MCP 核心 + 鉴权(上面 1–4、6–10 的改动都在这里) Octop
b 召回优化 octop-memory
c 对话记录解析(Claude Code / Kiro / DSH JSONL → 原始事件),和宿主无关 建议放 octop-memory
d 客户端 hook 和安装器 先放 Octop,之后挪到 octop-memory plugins/
— DSH 预设(cordis.patch.yml) 建议不放进开源仓库

长期来看,MCP 工具层也会下沉到 octop-memory(adapters/mcp,和现有的 adapters/bridge 平级),Octop 只负责挂载和鉴权。a 先合进来不受这件事影响。

🧪 测试补充

  • 越权:用户 A 的 token 访问用户 B 的专家,应该被拒绝
  • 伪造 X-Octop-User-Id 不应改变写入的身份
  • capture 写入的内容里,密钥会被过滤掉
  • 并发调用工具时,其他接口(比如 /api/health)不会被卡住

流程

  • 自动检查的机器人会因为作者限制关掉这个 PR,我们这边会把你加进 .github/pr-author-allowlist.txt
  • CI 需要维护者点 Approve and run,我们会处理
  • CHANGELOG 和 develop 有冲突,rebase 一下就好

拆分方式如果有不同想法,欢迎一起讨论。改完 a 之后我们会优先评审。再次感谢!

@ArvinQi
ArvinQi force-pushed the feat/memory-mcp-extension-develop branch from 7884039 to 5a473b3 Compare November 9, 2026 13:48
按 review 把"实例共享 token"换成"用户级 token"的存储与发放层:

- 迁移 026 建 memory_mcp_tokens 表:token 绑定 user,带客户端名、可限定专家
  范围、可单独吊销;服务端只存 sha256(明文只在创建时返回一次)
- MemoryMcpTokenRepo(create / list_for_user / get_by_token_hash / get_for_user /
  revoke / touch / any_active)+ RepoBundle / SharedServices 接线
- token 管理 API:POST/GET/DELETE /api/memory-mcp/tokens,创建时逐个校验专家
  存在且当前用户可访问,撤销只能撤自己的
- 迁移版本升到 26:同步 8 处 schema 断言
鉴权
- MCP 中间件从 Bearer token 推出用户(sha256 + hmac.compare_digest,忽略
  X-Octop-User-Id);每次请求按 owner / 共享专家规则重新校验专家访问权,
  并支持 token 的专家范围限制
- 挂载条件:OCTOP_MEMORY_MCP_ENABLED=1 或已存在有效 token;lifespan 串接原
  FastAPI lifespan,不再覆盖

工具面
- capture 改走 MemoryRuntime.capture:脱敏、回声保护、角色/长度策略生效;
  调用者写进事件 metadata(caller / source_host),不再拼 `<user>说:`
- save 改走 MemoryService.save:人工审核调度器能收到通知
- 9 个工具全部 async,阻塞调用一律 asyncio.to_thread
- 审核/运维工具(raws/candidates/extract/promote/reject)仅专家所有者
- 外部写入默认不进晋升(跟随专家提炼配置);后台提炼串行、保存任务引用,
  不自动启动停用的专家

测试:越权、伪造 header、密钥脱敏、工具不阻塞事件循环、lifespan 串接。
- docs/memory-mcp.md 重写为 PR a 的实际形态:用户级 token 的创建/吊销、挂载
  开关、9 个工具(4 个日常 + 5 个仅专家所有者)、客户端配置与拆分计划
- CHANGELOG 改为描述用户级 token 与审核队列
@ArvinQi
ArvinQi force-pushed the feat/memory-mcp-extension-develop branch from 5a473b3 to ecfc292 Compare November 9, 2026 14:15
@ArvinQi ArvinQi changed the title feat(memory-mcp): 专家记忆 MCP Server——外部 Agent 读写专家记忆 + 本地终端接入 feat(memory-mcp): 专家记忆 MCP Server(用户级 token 鉴权) Nov 9, 2026
DSH 的记忆接入从 hook 桥接换成进程内原生插件(v0.1.2):每轮开始前召回并注入
共享记忆,轮末上报本轮增量,不再依赖外部子进程 / hook 方言 / `--use-system-ca`。
插件源码随本仓发版(vendored,`link:` 安装,离线可装)。

开源版相比闭源版本副本的差异只有身份与文案:去掉 userId / X-Octop-User-Id、把闭源专有鉴权头换成通用 Bearer 逻辑、README 的内网域名/员工号替换为公开占位符。
- `/mcp/memory/setup`、`/uninstall`:检测 Claude Code / Kiro / DSH / Codex /
  Cursor,写入 hooks(提问前召回、会话结束上报)与 MCP 条目;token 由用户在
  运行时粘贴(用户级 omcp_ token),安装器不内置任何凭据
- 项目级安装(`?project=` / `OCTOP_PROJECT_DIR`):项目里只写注册,hook 脚本与
  含 token 的 config.json 留在用户目录;Kiro 项目级 MCP 因含凭据而跳过
- Windows 原生 PowerShell 版(`?os=windows`):Read-Host/Read-Secret + .cmd
  包装,不需要 Git Bash
- DSH 走原生插件(见上一 commit),安装器负责写 bundle/profile 配置并清理旧的
  hook 桥接与 preset
- `memory_capture` 恢复终端 transcript 形态:本地文件或 gzip+base64 上传,按
  终端格式解析成 L0(user/assistant/tool 事件),导入前做密钥脱敏;整份原文
  归档到 `{workspace}/.octop/memory-sessions/`
- 移除已无引用的 dsh-memory-share preset
- 新增一键安装器章节(macOS/Linux + Windows PowerShell、项目级安装、5 个终端
  的 hooks/注册位置)
- 拆分计划更新:d 已并入本 PR,c 仍计划下沉到 octop-memory
- CHANGELOG 补上终端接入(DSH 原生插件 / 项目级 / Windows)
@ArvinQi
ArvinQi force-pushed the feat/memory-mcp-extension-develop branch from 825c264 to e55d3b7 Compare November 9, 2026 14:47
移除断言里对闭源产品名与私有域名的硬编码,避免测试文件本身携带这些词;
安装器的脱敏由 `grep` 自检与其余用例覆盖。
个人记忆页(/personalization/memory)新增 tab:列出用户级 MCP token(名称、
掩码、专家范围、创建/最近使用/状态),创建时名称必填、可限定专家,明文只显示
一次并提供复制,支持撤销。只对个人记忆页展示(Experts 抽屉不出现)。

- 新增 api/modules/memoryMcp.ts(list/create/revoke,snake_case 对齐 TokenOut)
- 新增 pages/Agent/Memory/McpAccess.tsx + 4 条组件测试
- MemoryPanel 增加受 showMcpTab 控制的 mcp tab
- en/zh 文案(memory.tabs.mcp / memory.mcp)
@Bosheng0422

Copy link
Copy Markdown
Collaborator

感谢 @ArvinQi 这么快就完成了第二轮 🙏 我们对照代码逐条核对过,第一轮提的核心问题基本都解决了:

  • ✅ 安装脚本里不再写 token,实例共享 token 也已删除
  • ✅ 用户级 token:只存 sha256、常量时间比较、可以吊销、可以限定专家范围;身份由 token 决定;每次请求都检查专家权限
  • ✅ 9 个工具都改成了 async,阻塞操作放进了线程
  • ✅ 文本 capture 走 MemoryRuntime.capture,save 走 MemoryService.save
  • ✅ recall patch 已移除;审核和运维类工具只给所有者;默认进审核队列;不会自动启动专家;lifespan 改成了串接
  • ✅ 新增测试覆盖了越权访问、伪造身份、敏感信息过滤、异步执行等场景

鉴权这部分做得很扎实。下面是我们这一轮的一个主要建议,以及几处小问题。


1. 这个 PR 只保留服务端,客户端部分拆出去

第一轮我们建议客户端 hook 和安装器「先放 Octop」。看到完整实现之后,我们重新评估了 Octop 的维护边界,觉得放在客户端那一侧更合适。这是我们调整了建议,不是实现有问题,抱歉让你多改一轮。

我们按文件统计了一下:现在的 1.24 万行里,约 68%(≈8.5k 行)是客户端那一侧的代码,包括 5 种客户端的安装器(client_setup.py)、hook 脚本、对话记录解析(claude_transcript.py)、会话原文归档、DSH 原生插件。服务端(MCP 接口 + token)加上 UI 和文档,大约只有 4k 行。

我们希望 Octop 只负责服务端,原因有三个:

  • 变化节奏不同:Claude Code、Codex、Cursor 的配置格式和 hook 协议经常变化。客户端代码放在 Octop 里,客户端一变就只能等 Octop 发版才能修。
  • 维护面:替 5 个客户端维护安装、卸载、Windows 版本和项目级配置,和 Octop「托管专家」的核心职责关系不大。
  • 服务端单独就能用:docs/memory-mcp.md §2.1 和 §2.2 里,Claude Code 一条 claude mcp add、Codex 在 config.toml 里写几行就能接上。hook 带来的自动召回和自动上报是增强功能,不是使用前提。

具体建议:

内容 去向
MCP 服务端、token、token 管理 UI、文档 留在本 PR
安装器、hook 脚本、DSH 原生插件 提到 octop-memory 的 plugins/<client>/(和现有的 plugins/hermes、plugins/openclaw 保持一致),或者单独一个仓库,可以独立发版
对话记录解析(claude_transcript.py) 跟着客户端走:由客户端把对话记录解析成事件,再用 memory_capture 的 events 形式上传。这样服务端的 capture 只剩一条路径,统一走 MemoryRuntime.capture,脱敏、重复写回检测、长度限制都会自动生效。现在对话记录这条路径是直接 add_raw_batch,脱敏靠私有函数 _privacy_cfg 和 _redact_text,取不到配置时会静默跳过脱敏。拆出去之后这个问题也就自然消失了
会话原文归档(.octop/memory-sessions/) 这次先不加。它会持续占用工作区磁盘,以后需要再单独讨论

被拆出去的部分欢迎直接给 octop-memory 提 PR,我们在那边评审。

2. 留在本 PR 的几处小问题

  • 来源用 token 名字:source_host 现在用的是客户端传的 source 参数,而 token 名字已经存进了 _current_token_name,却没有被用到。建议改成以 token 名字为准,这样「是哪个客户端写的」就不用靠客户端自报了,改动大约一行。
  • 第一个 token 要重启才能用:只有启动时就存在有效 token,接口才会挂载,所以在 Dashboard 创建第一个 token 后要重启 Octop,页面上却没有提示。建议改成只看 OCTOP_MEMORY_MCP_ENABLED,或者至少在 UI 上提示需要重启。
  • PR 描述第 11 条:描述里说安装器会写 Codex 的 [mcp_servers],但 do_codex() 实际打印的是「这里不动」。安装器拆出去之后,这一条在本 PR 里改成「文档 §2.2 给出了手动配置方法」就行。

3. 合并后再单独开 issue 的(不阻塞本 PR)

  • CLI 管理 token:加一组 octop memory token create / list / revoke(参数如 --user、--name、--expert),和现有的 octop user 管理命令保持一致,复用已有的 token 存储代码。用得上的场景:没有浏览器的服务器、管理员给 CI 或机器人发 token、直接在终端里完成客户端接入
  • 专家没在运行时,上报的记忆不会被提炼,会一直停在原始事件层
  • MCP 这边的提炼锁和 MemoryMiddleware 的提炼没有互斥
  • 每次请求都调 touch() 写一次库,建议降低频率;中间件里的数据库查询也是同步执行的
  • 专家权限判断 _user_may_access_agent 和 api 层的检查函数是两套实现,以后可能不一致
  • 读取提炼配置用的 _runtime._extraction_cfg 是 octop-memory 的私有接口(对话记录拆出去之后,_redact_text 和 _privacy_cfg 也就不再需要了)。我们会在 octop-memory 那边提供公开接口
  • 远期:如果以后知识库、定时任务等功能也要开放给外部调用,可以把现在的 token 通用化(表名去掉 memory、加 scopes 字段)。现在的用户级 token 加专家范围对记忆这个场景已经够用,这次不用改

更正

第一轮里我们说「DSH 预设不要放进开源仓库」,这是我们判断错了:DSH 是 DeepSeek Harness,是公开的工具,没有保密问题。DSH 插件和其他客户端一样,按第 1 点拆到客户端那一侧就好。

流程

  • 白名单我们会加上,免得机器人再关掉这个 PR
  • 你推送新的提交之后,我们会触发 CI

拆分方式如果有不同想法,欢迎直接讨论。服务端这部分改完,我们会尽快评审合并。再次感谢!

- 路由:token 恰好绑定 1 个专家时,客户端只带 Authorization 即可;不限/多专家
  且未带 X-Octop-Agent-Id 时返回 400(附允许列表),带头保持原行为
- 每次请求重读 token 行:Dashboard 切换专家 / 撤销 token 对下一次请求立即生效
- 新增 PATCH /api/memory-mcp/tokens/{token_id}:改名 / 换专家 / 清空为不限;
  仅本人未撤销可改,他人 404、已撤销 409,专家校验与创建一致
- repo 增加 update()(仅更新未撤销、属于该用户的行)
- 创建 token:专家单选(默认当前专家),最后一个选项「不限定专家」
- 列表:「上报专家」列(单专家显示名称、不限显示 Tag、多专家显示数量)
- 行内「切换专家」→ PATCH 换绑,下一次 MCP 请求即生效;已撤销 token 禁用
- 文案 en/zh 同步(memory.mcp 47 键)
- config.json / MCP 条目 / DSH 插件配置只剩 url + token(+ tokenHeader);
  hooks 与原生插件不再发 X-Octop-Agent-Id,激活判据改为 endpoint && token
- 安装器不再询问专家 ID,非交互只认 OCTOP_MEMORY_TOKEN;支持
  `curl .../setup | OCTOP_MEMORY_TOKEN=omcp_… sh` 一条命令配置
- hooks 的「未配置」判定同步放宽,避免删掉 agentId 后静默不工作
- 文档:token 绑定专家、可切换;客户端只需 Authorization;补快速配置命令
- 新增 docs/memory-mcp-setup.md:创建 token、绑定/切换专家、macOS/Linux 与
  Windows 一键安装、5 个终端落点、手动配置、验证、排障、安全说明
- 主文档 linked 到安装指引,并说明 token 绑定专家、客户端只带 Authorization
- Dashboard「MCP 接入」:页面顶部常驻「一条命令接入」提示(可复制),创建成功
  弹窗直接给出带该 token 的 curl 命令
- CHANGELOG 补充 token 绑定/切换专家
@ArvinQi
ArvinQi force-pushed the feat/memory-mcp-extension-develop branch from 6b6ae6f to da39471 Compare November 10, 2026 03:53
Claude Code 2.1.x 校验 settings.json 时要求事件下是 matcher 组、组内再套
`hooks` 数组;安装器此前写的是「事件直接挂 handler」的简写形态,
`claude doctor` 报 `Hook matcher "hooks" must be an array of hook entries`
并把该 hook 静默忽略(表现为安装后 Claude 里召回/上报都不生效)。

改成 `[{"hooks": [{type, command}]}]`,重装即可修复;旧形态仍会被识别并替换。
- 创建成功弹窗:Token 输入框旁直接「复制 Token」,下面紧挨着带该 Token 的
  安装命令 + 「复制命令」,无需手拼
- 弹窗底部的复制按钮改为「复制命令」(带 token 可直接粘贴执行)
- hooks / DSH 原生插件 / 安装器写入并发送 X-Octop-Client(claude-code、codex、
  cursor、kiro、dsh);服务端按 token 记录 first_seen / last_seen / count
- token 新增 disabled_clients:命中的接入端直接 403(其它接入端不受影响),
  迁移 026 折叠新增 disabled_clients / client_stats 两列
- API:TokenOut 暴露两个字段,PATCH 支持 disabled_clients
- Dashboard:列表新增「接入端」列,展开可看每个客户端的最近使用/次数并逐个
  开关(已撤销 token 不可改)
- 文档补充接入端识别与禁用后的 403 说明
- MemoryPanel 把「MCP 接入」tab 移到「设置」之前
- 标题旁增加「接入说明」链接(docs/memory-mcp-setup.md)
- 每行新增「接入命令」:本次会话创建的 token 直接复制带 token 的 curl 命令;
  更早的 token 因服务端只存 hash,走「重新生成并复制」(rotate 旧值立即失效)
- 后端新增 POST /api/memory-mcp/tokens/{id}/rotate:为本人未撤销的 token 换发
  新明文(hint 更新、last_used_at 清空),旧明文立即 401
- 创建成功弹窗:Token 段与「一键安装命令」段都带标题与各自的复制按钮
- 重新生成(rotate)弹窗:生成结果同样展示新 Token 与带该 Token 的命令两段,
  不再只有命令
@Bosheng0422

Copy link
Copy Markdown
Collaborator

https://github.com/TencentCloud/octop-memory
octop-memory 仓库连接如上

- source_host 改为 token 名字(服务端已知的客户端身份),不再信任客户端自报的
  source;caller 仍是 token 所有者
- /mcp/memory 只在 OCTOP_MEMORY_MCP_ENABLED=1 时挂载,token 存在与否不再影响
  启动期挂载(创建 token 后无需重启)
- transcript 导入不再直接 add_raw_batch:解析后的候选事件交给
  MemoryRuntime.capture 写入,脱敏 / 回声 / 角色与长度过滤统一生效;删除对
  octop-memory 私有 `_privacy_cfg` / `_redact_text` 的依赖
- memory_capture 新增 `events` 入参:客户端可自行解析对话记录后上传事件,
  content/source 变为可选;dry_run 同样支持
- 文档同步(挂载条件、来源字段、events 形式)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Memory Issue about memory plz add this label to help filter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants