dsh-memory-mcp
by ihuskyyyy
README.md
# dsh-memory-mcp —— 一份大脑,多个宿主
把**一个目录里的长期记忆**(`MEMORY.md` + `AGENTS.md` + `skills/*/SKILL.md` + 演化账本)以
**stdio MCP** 暴露出去:DSH、Codex、Claude Code、CodeBuddy、ZCode 等任何支持 MCP 的 Agent
读同一份脑、走同一条写入闸门。
- **零依赖**:只用 `node:fs` / `node:path`,单个 `server.mjs`,Windows 与 POSIX 同一份代码,Node ≥ 18。
- **单一真源**:记忆就是磁盘上的 Markdown,没有第二个数据库、没有向量索引、没有同步进程。
- **单一写者**:外部宿主只能「提案」(写 `memory/inbox.md`),是否并进 `MEMORY.md` 由你自己的策展流程决定。
```
DSH / Codex / Claude Code / CodeBuddy / ZCode
│ stdio JSON-RPC(一行一条 JSON)
▼
server.mjs ──读──▶ MEMORY.md · AGENTS.md · skills/*/SKILL.md · memory/curator/ledger.md · audit.json
──写──▶ memory/inbox.md(提案队列,默认唯一写路径)
──写──▶ MEMORY.md 的「自动沉淀」区(仅当带写令牌,可选)
```
## 30 秒跑起来
```bash
git clone https://github.com/ihuskyyyy/dsh-memory-mcp.git
cd dsh-memory-mcp
# 1) 自检:用内置示例工作区打印工具表 + 真跑一次检索
DSH_MEMORY_WORKSPACE=./examples/demo-workspace node server.mjs --selftest
# 2) 协议自检:像宿主那样拉起 server,走 initialize → tools/list → tools/call(8 项断言)
DSH_MEMORY_WORKSPACE=./examples/demo-workspace node test-client.mjs
```
Windows(PowerShell):
```powershell
$env:DSH_MEMORY_WORKSPACE = "$PWD\examples\demo-workspace"
node server.mjs --selftest
node test-client.mjs
```
真接自己的记忆库,就把 `DSH_MEMORY_WORKSPACE` 指到那个目录(见下「目录约定」)。
## 目录约定
`DSH_MEMORY_WORKSPACE` 指向的目录里,按需放这些文件——**全部可选**,缺哪个对应工具就返回空/提示:
| 路径 | 用途 | 相关工具 |
| --- | --- | --- |
| `MEMORY.md` | 长期记忆正文(一条一行的事实/口径) | `memory_search`、`memory_get`、`memory_write` |
| `AGENTS.md` | 会话注入的工作区说明 | `memory_get` |
| `skills/<名>/SKILL.md` | 技能(YAML frontmatter 的 `description` 会被加权检索) | `memory_search`、`skill_list`、`memory_get` |
| `memory/inbox.md` | 提案收件箱(自动创建) | `memory_propose` |
| `memory/curator/ledger.md` | 演化账本(`## ` 分节,字段 `pattern` / `missing` / `evidence`) | `ledger_list` |
| `memory/curator/audit.json` | 体检结果(`issues[]` + `counts` + `stats`) | `memory_audit` |
不设 `DSH_MEMORY_WORKSPACE` 时,server 以自身位置推 `../..` 作为工作区;宿主清洗环境变量时建议**显式传**。
## 工具面
| 工具 | 作用 | 写盘 |
| --- | --- | --- |
| `memory_search(query, limit)` | MEMORY.md(行分 ×2)+ 技能描述(×3)+ 技能正文(×1)关键词检索,返回 `file:line` 与分数 | 否 |
| `memory_get(target, offset, limit)` | 取原文:`MEMORY.md` / `AGENTS.md` / `skill:<name>`,按行区间返回 | 否 |
| `skill_list()` | 技能库清单(名称 / 描述 / 体积 / 行数) | 否 |
| `memory_propose(text, kind, source, evidence)` | 提交待确认提案,追加到 `memory/inbox.md` | **是**(仅 inbox) |
| `memory_audit()` | 读最近一次体检结果(漂移 / 重复 / 技能形态 / 裸凭据) | 否 |
| `ledger_list(limit)` | 读演化账本最近条目(规律 / 缺失条件 / 证据) | 否 |
| `memory_write(text, source, evidence, token)` | 直接追加到 `MEMORY.md` 的「自动沉淀」区(写前整文件备份) | **是**(仅带写令牌时出现) |
检索是**确定性的**:小写化、按标点切词、丢弃 2 字以下的词与停用词,逐行计分,按分数降序截断。
没有 embedding、没有隐藏状态——同一份文件、同一个 query,永远同样的输出,便于审计与复现。
## 为什么写入是「提案制」
`MEMORY.md` 通常是**单一写者**文件:由你的策展流程(例如定时复盘 + 暂存副本 + diff 提交)在维护。
外部宿主若能直写,格式(日期前缀、`[8 位会话 id]`、受管区块边界)迟早被写坏,而它又是每个会话
都要注入的上下文。所以:
1. 默认只有 `memory_propose`:写 `memory/inbox.md`,带 `kind` / `source` / `evidence` / `status: pending`;
2. 采纳与否由你自己的流程决定;
3. 确实需要直写时,给这一个 server 进程设 `MEMORY_MCP_WRITE_TOKEN=<随机串>`:
- `memory_write` **才会出现在工具表里**(能力是「列不列这个工具」,不是提示词约束);
- 调用时还必须带上同一个 token,不一致直接拒绝;
- 写入只新增到「`## … 自动沉淀 …`」这一节的末尾,找不到该节就拒绝,绝不碰正文区;
- 写前把整个 `MEMORY.md` 复制到 `memory/backups/<时间戳>-mcp-write/`。
## 各宿主接法
### 通用 MCP(`mcpServers` JSON)
```json
{
"mcpServers": {
"memory": {
"command": "node",
"args": ["<repo>/server.mjs"],
"env": { "DSH_MEMORY_WORKSPACE": "<你的记忆目录>" }
}
}
}
```
Claude Code(项目根 `.mcp.json` 或 `claude mcp add`)、CodeBuddy(`~/.codebuddy/mcp.json`)、
ZCode(MCP 设置或 `.mcp.json`)都用这一份。
### Codex
```powershell
codex mcp add memory --env DSH_MEMORY_WORKSPACE=<你的记忆目录> -- node <repo>/server.mjs
codex mcp get memory # 核对
codex mcp remove memory # 撤销
```
等价手写(`~/.codex/config.toml`):
```toml
[mcp_servers.memory]
command = 'node'
args = ['<repo>/server.mjs']
[mcp_servers.memory.env]
DSH_MEMORY_WORKSPACE = '<你的记忆目录>'
```
### DSH(`cordis.patch.yml`)
```yaml
- insert:
- id: mcp-memory
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: memory
transport: stdio
command: 'node'
args: ['<repo>/server.mjs']
cwd: '<repo>'
toolCallTimeoutMs: 20000
env:
DSH_MEMORY_WORKSPACE: '<你的记忆目录>'
```
装载后在宿主里看工具名形如 `mcp__memory__memory_search`;`cordis_inspect_query` 的 `Tool.listTools`
可直接看到已注册的那几个。
## 自检
```bash
node server.mjs --selftest # 打印工作区、记忆/技能计数、工具表,并真跑一次检索
node test-client.mjs # 像宿主一样拉起 server:initialize → tools/list → tools/call(7 项断言)
```
`test-client.mjs` 覆盖:握手、工具表、写能力与 env 是否一致(有无 `MEMORY_MCP_WRITE_TOKEN`
决定 `memory_write` 出现与否)、检索、技能清单、取段、体检、未知工具返回 `isError`。
带令牌再跑一次即可验证写能力:`MEMORY_MCP_WRITE_TOKEN=demo node test-client.mjs` → 7 个工具、8/8 通过。
## 协议与踩坑
- **stdio 用换行分隔的 JSON-RPC**:不要 Content-Length 头,响应必须一行一条;stdout 只留给协议,日志走 stderr。
- **环境变量会被清洗**:宿主只继承少量系统变量,工作区路径务必显式传 `DSH_MEMORY_WORKSPACE`,别赌 `cwd`。
- **配置里别转义反斜杠**:Windows 路径在 YAML 用单引号、在 JSON 用双反斜杠。
- **先只读后写**:先用 `memory_search` 验证连通,再上 `memory_propose`(它会落盘)。
- **改代码要重启宿主**:改 `server.mjs` 需重启;只改配置(patch / JSON / TOML)不用。
- **`--selftest` 不读 stdin**:它直接跑一次函数并退出,适合放进 CI 或发布前的冒烟。
## 设计来源
写入闸门与 `inbox` 提案队列的做法借鉴自灵枢(lingshu)那套「AI 只提议、你确认」;
但存储层没有引入第二套库——直接以磁盘上的 Markdown 为真源,换个 Agent 也读得懂。
## License
[MIT](LICENSE) © 2026 ihuskyyyy
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo recent community issues