mcp-http-diary-server
README.md
# 🧠 AI 长期记忆系统 —— 自动化维护实施方案
基于 **MCP Streamable HTTP** 的 AI 长期记忆系统,支持「日志提炼 → 长期记忆管理 → 自动化维护」生产级方案。
AI 助手可认识用户、记住信息、理解偏好、学习人格、保存共同经历,并通过定时提炼工作流把每日聊天沉淀为可复用的长期记忆,
同时保证**幂等**(重复运行不产生重复数据)、**可重试**(失败日志保留并重跑)、**可清理**(超期日志自动清理)。
> 核心流程:聊天 → 每日日志 → AI 分类/打标签 → 定时提炼 → memory_key 定位 → insert/update → 验证 → processed → 保留 14 天 → 清理。
## 技术栈(禁止更换)
- Node.js + TypeScript
- Express(HTTP 服务)
- MCP SDK(Streamable HTTP,`@modelcontextprotocol/sdk`)
- Supabase(`@supabase/supabase-js`,anon 发布密钥)
- Zod(参数校验)
---
## 系统架构
```
┌──────────────┐ chat ┌──────────────────────────┐
│ AI 助手/客户端 │ ─────────▶ │ save_daily_log │
└──────────────┘ │ (companion_daily_logs) │
│ processing_status=pending │
└────────────┬───────────────┘
│ get_unprocessed_logs
▼
┌──────────────────────────┐
│ 定时提炼工作流(weekly) │
│ AI 分类 + importance 标签 │
└────────────┬───────────────┘
memory_key 匹配 │
┌─────────────────────────────┼─────────────────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌────────────────────┐ ┌──────────────────┐
│ get_memory_by_key│ │ save_memory │ │ save_shared_memory│
│ 命中→update_memory│ │ (memory_key, 插入) │ │ (幂等 upsert) │
└────────┬────────┘ └─────────┬──────────┘ └─────────┬────────┘
│ │ │
▼ ▼ ▼
companion_memories companion_memories shared_memories
│ │
└──────────── record_extraction_task ───────────────┘
(memory_extraction_logs 仅元数据)
失败:update_log_status(failed, error_reason) → 下一轮重试
成功:update_log_status(processed) → 保留 14 天 → delete_logs 清理
```
> 表名约定:方案中 `memories` / `daily_logs` / `shared_memories` 在本仓库对应既有表
> `companion_memories` / `companion_daily_logs` / `shared_memories`(沿用历史命名以兼容线上数据与旧接口)。
---
## 项目结构
```
mcp-http-diary-server/
├── package.json
├── tsconfig.json
├── .env.example # 环境变量模板(复制为 .env 填写)
├── Dockerfile # 已支持公网部署(Render / Railway / 云服务器)
├── README.md
├── docs/
│ └── AI-memory-workflow.md # 自动化维护工作流说明(流程/幂等/删除策略)
├── src/
│ └── index.ts # MCP HTTP Server 主入口(全部逻辑)
├── scripts/
│ ├── setup-db.mjs # 按顺序应用 supabase/migrations 下所有迁移
│ ├── check-schema.mjs # 检查 diaries.content 列
│ └── test_new_tools.mjs # 走真实 HTTP 链路的自检脚本
├── tests/
│ └── memory-workflow.test.mjs # 自动化维护工作流测试(幂等/失败/去重)
├── supabase/
│ └── migrations/
│ ├── 001_add_content_column.sql
│ ├── 002_companion_memory_columns.sql
│ └── 003_memory_automation.sql # 本次升级:memory_key/处理状态/提炼任务表
└── test-*.js # 旧版手写测试(可选)
```
---
## 快速开始
### 1. 安装
```bash
cd mcp-http-diary-server
npm install
```
### 2. 配置环境变量
```bash
cp .env.example .env
```
编辑 `.env`(必填 `SUPABASE_URL` / `SUPABASE_KEY`):
```
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_KEY=your_supabase_anon_key
PORT=3000
HOST=0.0.0.0
USER_ID=test_couple
LOG_RETENTION_DAYS=14 # 可选,提炼成功后日志保留天数
# DATABASE_URL=postgresql://... # 仅 db:setup 需要
```
### 3. 应用数据库迁移(必须)
```bash
npm run db:setup
```
或直接在 **Supabase SQL Editor** 依次执行 `supabase/migrations/` 下所有 `.sql`
(重点是新增的 `003_memory_automation.sql`,它会为 `companion_memories` 增加 `memory_key`、
为 `companion_daily_logs` 增加 `processing_status` / `error_reason` / `updated_at` / `mood`、
为 `shared_memories` 增加 `created_at` 与 `importance NOT NULL`、并新建 `memory_extraction_logs`)。
### 4. 编译运行
```bash
npm run build
npm start # 生产:node dist/index.js
# 或开发模式(热重载源码)
npm run dev # tsx src/index.ts
```
健康检查:
```bash
curl http://localhost:3000/health
```
---
## MCP 接口说明
共 18 个工具,全部服务于长期记忆与自动化维护(含 V1.1 生命周期闭环 8 个新工具)。
> **工具瘦身说明**:`write_diary` / `get_diaries` 两个「用户主动写日记 / 查看日记」的旧兼容接口已于 V1.2 下线。
> 本服务是 AI 长期记忆系统,不是日记应用;聊天记录统一由 `save_daily_log` 写入 `companion_daily_logs` 后进入提炼流水线。
> 注:仅移除 MCP 工具注册,`diaries` 表与其历史数据保留不动。
| 工具 | 写入/读取表 | 说明 |
|------|--------|------|
| `save_memory(category, content, importance?, confidence?, source?, memory_key?)` | `companion_memories` | 保存长期记忆(可选 memory_key 幂等定位) |
| `get_memory_by_key(memory_key, user_id?)` | `companion_memories` | 按 user_id + memory_key 查询已有记忆 |
| `recall_memory(category?, keyword?, limit?)` | `companion_memories` | 按分类/关键词召回历史记忆 |
| `save_daily_log(...)` | `companion_daily_logs` | 保存每日总结(入队 pending) |
| `save_shared_memory(title, event, importance?)` | `shared_memories` | 保存共同经历(幂等 upsert) |
| `update_memory(id?/memory_key?, ...)` | `companion_memories` | 更新已有记忆(支持 id 或 memory_key) |
| `get_unprocessed_logs(limit?, include_failed?)` | `companion_daily_logs` | 拉取 pending / failed 日志 |
| `update_log_status(id, processing_status, error_reason?)` | `companion_daily_logs` | 更新日志处理状态 |
| `delete_logs(retention_days?, older_than?)` | `companion_daily_logs` | 删除超期 processed 日志 |
| `record_extraction_task(id?, task_id?, log_ids?, status?)` | `memory_extraction_logs` | 记录/更新提炼任务(仅元数据) |
| `claim_daily_log(id)` | `companion_daily_logs` | **V1.1** 原子领取 pending→processing,retry_count 自增(防并发重复消费) |
| `mark_log_processed(id, extract_task_id?)` | `companion_daily_logs` | **V1.1** 标记成功 processed,retry_count 归零,写 processed_at / extract_task_id |
| `mark_log_failed(id, error_reason?)` | `companion_daily_logs` | **V1.1** 失败:retry_count<3 回滚 pending,≥3 标记 failed |
| `recover_stuck_logs(timeout_minutes?)` | `companion_daily_logs` | **V1.1** 超时恢复:stuck processing 按 retry_count 回滚/失败 |
| `reconcile_log_statuses()` | `companion_daily_logs` | **V1.1** 历史修复:无 extraction_results 的 processed 回滚 pending |
| `record_extraction_result(task_id, log_id, memory_id, user_id?)` | `memory_extraction_results` | **V1.1** 写日志↔memory 关联(log,task 唯一,先删后插幂等) |
| `get_shared_memories(user_id?, title?, keyword?, limit?)` | `shared_memories` | **V1.1** 读取共同经历(title 精确 / keyword 模糊,返回含 event) |
| `get_extraction_tasks(user_id?, status?, task_id?, limit?)` | `memory_extraction_logs` | **V1.1** 查询提炼任务(task_id/log_ids/status/时间) |
### save_memory(category, content, importance?, confidence?, source?, memory_key?)
- `category`(必填,枚举):`user_profile` / `preference` / `personality` / `goal` / `skill` / `relationship` / `emotional_pattern` / `conversation_style`
- `content`(必填):记忆正文(≤ 4000 字符)
- `importance`(可选,1–5,默认 3)
- `confidence`(可选,0–1,默认 0.8)
- `source`(可选,默认 `conversation`)
- `memory_key`(可选,`namespace.topic`):省略时自动生成。**命名空间必须由 category 推导**(见下表),禁止自由创建。
### memory_key 命名规范
格式 `namespace.topic`,命名空间与 category 强制绑定:
| category | namespace |
| ------------------ | ------------------ |
| user_profile | profile |
| preference | preference |
| personality | personality |
| goal | goal |
| skill | skill |
| relationship | relationship |
| emotional_pattern | emotional_pattern |
| conversation_style | conversation_style |
示例:`profile.basic_info`、`preference.food`、`relationship.first_meet`、`skill.design`。
### get_memory_by_key(memory_key, user_id?)
- 按 `user_id` + `memory_key` 查询;返回 `{ success, found, memory }`。
- 幂等定位记忆的核心接口,提炼工作流先查后写。
### recall_memory(category?, keyword?, limit?)
- `category`:按分类过滤;`keyword`:对 `content` 做 `ILIKE` 模糊匹配;`limit`(1–100,默认 20)。
### save_daily_log(log_date?, events?, user_state?, mood_changes?, topics?, relationship_state?, mood?)
- 写入 `companion_daily_logs` 并令 `processing_status = 'pending'`(进入提炼队列)。
- `mood`(可选,整数 -2~2):当日心情。
- 同日重复保存仅更新结构化字段,**不重置** `processing_status`(已 processed 的不会被重新入队)。
### save_shared_memory(title, event, importance?)
- 按 `(user_id, title)` **幂等 upsert**:同一标题重复保存更新而非新增。
### update_memory(id? / memory_key?, category?, content?, importance?, confidence?, source?)
- 兼容旧方式:传 `id` 直接更新。
- 新方式:传 `memory_key`,内部先 `get_memory_by_key` 取 id 再更新。
- 用于修正过期记忆(如「喜欢猫」→「喜欢狗」),避免无限累积重复记忆。
### get_unprocessed_logs(limit?, include_failed?)
- 默认返回 `pending` + `failed`(可重试)日志;`include_failed=false` 仅返回 `pending`。
- 供自动化提炼工作流拉取待处理日志。
### update_log_status(id, processing_status, error_reason?)
- `processing_status`:`pending` / `processing` / `processed` / `failed`(四态状态机)。
- **守卫**:存在 `error_reason` 时拒绝标记为 `processed`(失败不能标记成功)。
### delete_logs(retention_days?, older_than?)
- 仅删除 `processing_status = 'processed'` 且 `updated_at` 早于截止日的日志(`failed` 保留以便重试)。
- `retention_days` 默认取 `LOG_RETENTION_DAYS`(14);`older_than` 可覆盖截止日。
### record_extraction_task(id?, task_id?, log_ids?, status?)
- 写入/更新 `memory_extraction_logs`(`pending`/`running`/`success`/`failed`)。
- **只保存任务元数据,不保存 AI 提炼全文**。
---
## 数据库设计
| 表 | 关键列 | 说明 |
|----|--------|------|
| `companion_memories` | `id`, `user_id`, `category`, `content`(≤4000), `importance`, `confidence`, `source`, `memory_key`(NOT NULL, UNIQUE(user_id, memory_key)), `created_at`, `updated_at` | 长期记忆;`memory_key` 用于幂等定位 |
| `companion_daily_logs` | `id`, `user_id`(NOT NULL), `log_date`, `summary`, `events`, `user_state`, `mood_changes`, `topics`, `relationship_state`, `mood`(-2~2), `processing_status`(pending/processing/processed/failed), `error_reason`, `retry_count`(DEFAULT 0), `processing_started_at`, `processed_at`, `extract_task_id`, `created_at`, `updated_at`, UNIQUE(user_id, log_date) | 每日日志;四态状态机驱动提炼闭环(见 V1.1) |
| `shared_memories` | `id`, `user_id`, `title`, `event`(NOT NULL), `importance`(NOT NULL), `created_at`, UNIQUE(user_id, title) | 共同经历;按标题幂等 |
| `memory_extraction_logs` | `id`, `user_id`, `task_id`, `log_ids`(TEXT[]), `status`(pending/running/success/failed), `created_at`, `updated_at` | 提炼任务记录(仅元数据) |
| `memory_extraction_results` | `id`, `user_id`, `task_id`, `log_id`, `memory_id`, `created_at`, UNIQUE(log_id, task_id) | **V1.1** 日志↔memory 关联;保证提炼幂等(不删 memory 本身) |
| `diaries`(已弃用) | `id`, `created_at`, `user_id`, `mood`, `content` | 旧版日志本;V1.2 起无 MCP 工具读写,仅保留历史数据 |
> 时间统一以 UTC 存储(`TIMESTAMPTZ` / Supabase 默认时区)。
> `companion_daily_logs` 的扩展列由迁移 `002` 添加;`003` 新增 `processing_status`/`error_reason`/`updated_at`/`mood` 与 `memory_key` 等。
---
## 自动提炼流程
详见 [docs/AI-memory-workflow.md](docs/AI-memory-workflow.md)。要点:
1. **日志入队**:`save_daily_log` 写入,`processing_status = pending`。
2. **定时提炼**:工作流 `get_unprocessed_logs` 拉取 `pending`/`failed` 日志。
3. **AI 分类 + 打标签**:归入 `category`、`importance`。
4. **memory_key 定位**:按 `category → namespace` 生成 `namespace.topic`。
5. **insert / update**:先 `get_memory_by_key`,命中则 `update_memory`,未命中则 `save_memory`。
6. **验证成功 → `processed`**;失败 → `failed` + `error_reason`,下一轮重试。
7. **删除策略**:`processed` 日志保留 `LOG_RETENTION_DAYS`(默认 14)天后由 `delete_logs` 清理;`failed` 不删除。
8. **幂等**:重复运行不产生重复 `memory` / `shared_memory`(依赖 `memory_key` 与唯一约束)。
---
## 部署方式
服务支持 `HOST` / `PORT` / `LOG_RETENTION_DAYS` 环境变量,MCP 端点为 `https://你的域名/mcp`。
### 方式一:Render(推荐,详细步骤)
项目根目录已提供 `render.yaml`(Blueprint),可一键部署;也可在 Dashboard 手动创建。
1. 把本项目推到 GitHub(`.env` 已被 `.gitignore` 忽略,密钥不会上传)。
2. Render Dashboard → **New** → **Blueprint** → 连接该 GitHub 仓库 → **Apply**。
3. 在创建的服务里,给 `SUPABASE_URL` / `SUPABASE_KEY` 填入真实值(标记为 `sync:false`,需手动填);其余变量已带默认值。
4. 部署完成后,公网 MCP 端点即 `https://<你的服务名>.onrender.com/mcp`。
> Health Check Path:`/health`。免费版有冷启动(约 30–50s),首次调用可能超时属正常。
### 方式二:云服务器(VPS)
上传项目,安装依赖并构建后运行 `npm start`,确保 `HOST=0.0.0.0`、`PORT` 对外开放。
通过 `http://你的公网IP:3000/mcp` 访问。
### 方式三:Docker
```bash
docker build -t ai-memory-mcp .
docker run -d -p 3000:3000 \
-e SUPABASE_URL=... -e SUPABASE_KEY=... \
-e HOST=0.0.0.0 -e LOG_RETENTION_DAYS=14 \
ai-memory-mcp
```
### 方式四:Cloudflare Tunnel / ngrok(临时域名)
```bash
cloudflared tunnel --url http://localhost:3000 # 得到 https://xxx.trycloudflare.com/mcp
# 或
ngrok http 3000 # 得到 https://xxx.ngrok.io/mcp
```
---
## 本地自检
```bash
# 先启动服务(另开终端)
npm run dev
# 全部新工具写读自检(真实链路)
node scripts/test_new_tools.mjs
# 自动化维护工作流测试(幂等/失败/去重,需已应用 003 迁移)
npm run test:workflow
# V1.1 生命周期闭环测试(tools/list + 共享记忆 + 状态流转 + 任务查询,需已应用 004 迁移)
npm run test:v11
```
---
## 环境变量说明
| 变量 | 必填 | 说明 | 默认值 |
|------|------|------|--------|
| `SUPABASE_URL` | ✅ | Supabase 项目 URL | - |
| `SUPABASE_KEY` | ✅ | Supabase anon key | - |
| `PORT` | 否 | HTTP 监听端口 | 3000 |
| `HOST` | 否 | 监听地址 | 0.0.0.0 |
| `USER_ID` | 否 | 记忆归属用户 | test_couple |
| `LOG_RETENTION_DAYS` | 否 | 提炼成功后每日日志保留天数 | 14 |
| `DATABASE_URL` | 仅 db:setup | Supabase 数据库连接串 | - |
| `CORS_ORIGIN` | 否 | CORS 来源(`true` 表示允许全部) | true |
---
## MCP 协议支持
- ✅ initialize / ping / tools/list / tools/call / notifications/
- ✅ 兼容 `POST /` 与 `POST /mcp` 两个入口
## 技术栈
- Express.js + TypeScript
- Supabase JS Client
- MCP Streamable HTTP(无状态,每次请求新建 Server 实例)
- Zod 参数校验
---
## V1.1 变更(记忆生命周期闭环)
> 迁移文件:`supabase/migrations/004_lifecycle_v1.1.sql`(幂等,可重复执行)。
### 修复目标(不改整体架构,仅闭环)
1. **daily_logs 状态机升级为四态**:`pending → processing → processed`,异常 `processing → pending(failed 可重试)` / `processing → failed(retry 耗尽)`。
2. **并发防重**:`claim_daily_log` 走 `UPDATE ... WHERE processing_status='pending'`(原子条件更新)+ `retry_count` 自增,多个 worker 不会重复消费同一条日志。
3. **retry 阈值**:`mark_log_failed` 读取 `retry_count`,`<3` 回滚 `pending`(可重试),`≥3` 标记 `failed` 并记录 `error_reason`。
4. **超时恢复**:`recover_stuck_logs(timeout_minutes=30)` 将卡在 `processing` 超过阈值的日志按 `retry_count` 回滚/失败。
5. **日志↔memory 关联 + 提炼幂等(新表 `memory_extraction_results`)**:`record_extraction_result` 按 `(log_id, task_id)` 唯一约束 + 先删后插,保证重复提炼不产生重复结果;**不删除** `companion_memories` 中的 memory 本身(被其他任务引用则保留)。
6. **历史数据修复**:`reconcile_log_statuses()` 不盲目"summary 存在即 processed"——仅将 `processed` 但无任何 `memory_extraction_results` 关联的日志回滚为 `pending`,交由真实提炼生成结果。
7. **新增查询工具**:`get_shared_memories`(title 精确 / keyword 模糊,返回含 `event`)、`get_extraction_tasks`(按 user_id / status / task_id 过滤)。
### V1.1 新增 8 个工具
`claim_daily_log` · `mark_log_processed` · `mark_log_failed` · `recover_stuck_logs` · `reconcile_log_statuses` · `record_extraction_result` · `get_shared_memories` · `get_extraction_tasks`
### 状态机
```
claim (retry_count+1)
pending ─────────────────────► processing
▲ │ ├─ mark_log_processed ─► processed (retry_count=0, processed_at)
│ │ └─ mark_log_failed
│ retry_count<3 │ │
└─────────────────────────────┘ └─ retry_count>=3 ─► failed
(recover: processing 超时 → <3 pending / >=3 failed)
```
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues