@ccjr1120/memory-one 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,95 +2,64 @@
2
2
 
3
3
  让 Agent 在开始工作前,先记住你的项目约定、个人偏好和已经验证过的经验。
4
4
 
5
- Memory One 是一个运行在本机的长期记忆服务:用 React 工作台管理记忆,用 SQLite 保存数据,并通过 Streamable HTTP MCP 接入 Codex、Claude Code、Cursor 或自建 Agent
5
+ Memory One 是一个运行在本机的长期记忆服务。你可以通过可视化工作台管理记忆,并通过 MCP 将这些记忆提供给 Codex、Claude Code、Cursor 或自建 Agent。所有数据默认保存在本机 SQLite 数据库中。
6
6
 
7
7
  GitHub:https://github.com/ccjr1120/memory-one
8
8
 
9
- ## 先看实际界面
9
+ ## 1. 安装与启动
10
10
 
11
- 记忆总览:搜索、按 Scope 筛选,并查看每条记忆的召回次数和置信度。
11
+ ### 环境要求
12
12
 
13
- ![记忆总览](./docs/screenshots/memory-overview.png)
14
-
15
- MCP 服务页:复制客户端配置、查看服务端点、启用 Codex 全局记忆指令,并查看工具调用统计。
16
-
17
- ![MCP 与 Codex 增强](./docs/screenshots/mcp-codex.png)
18
-
19
- ### 更新截图
13
+ - Node.js 20 或更高版本
14
+ - npm
20
15
 
21
- 截图由 `scripts/capture-screens.mjs` 生成。启动本地服务后,第一次使用需要下载 Chromium:
16
+ ### 安装
22
17
 
23
18
  ```bash
24
- npx --yes playwright@1.55.0 install chromium
25
- npm run screenshots
19
+ npm install --global @ccjr1120/memory-one
26
20
  ```
27
21
 
28
- 脚本默认访问 `http://127.0.0.1:8765`,覆盖 `docs/screenshots/` 下的两张图片。也可以按需调整地址、输出目录、视口和等待时间:
22
+ ### 启动
29
23
 
30
24
  ```bash
31
- npm run screenshots -- \
32
- --base-url http://localhost:5173 \
33
- --output-dir docs/screenshots \
34
- --viewport 1280,900 \
35
- --wait-ms 2000 \
36
- --no-full-page
25
+ memoryone start
37
26
  ```
38
27
 
39
- 对应的环境变量是 `SCREENSHOT_BASE_URL`、`SCREENSHOT_OUTPUT_DIR`、`SCREENSHOT_VIEWPORT` 和 `SCREENSHOT_WAIT_MS`。
28
+ 启动后:
40
29
 
41
- ## 它解决什么问题
42
-
43
- - **把经验保存下来**:偏好、项目决策、工作流程和重要事实都可以保存为长期记忆。
44
- - **让 Agent 先查再做**:每轮任务开始时调用 `memory_get_context`,把相关经验带入当前任务。
45
- - **按项目隔离查看**:用可选的 `scope` 分类项目记忆;不传 `scope` 时仍可搜索全局记忆。
46
- - **本地可控**:默认使用本机 SQLite,不需要云端账号或额外数据库。
47
- - **可观察**:MCP 页面展示总调用次数、成功率、平均耗时和每个工具的使用情况。
48
-
49
- ## 3 分钟启动
50
-
51
- ### 1. 准备环境
52
-
53
- - Node.js 20 或更高版本
54
- - npm
30
+ - 工作台:<http://127.0.0.1:23888/>
31
+ - MCP 地址:<http://127.0.0.1:23888/mcp/>
55
32
 
56
- ### 2. 安装并启动
33
+ 打开工作台:
57
34
 
58
35
  ```bash
59
- git clone https://github.com/ccjr1120/memory-one.git
60
- cd memory-one
61
- npm install
62
- npm run dev
36
+ memoryone open
63
37
  ```
64
38
 
65
- `npm run dev` 会先清理开发端口 `8765` 和 `5173`,然后同时启动 API 服务与 Vite 前端。
39
+ 如果端口 `23888` 已被占用,启动命令会询问是否终止占用进程。
66
40
 
67
- ### 3. 打开工作台
41
+ ## 2. 配置 MCP
68
42
 
69
- 访问 <http://127.0.0.1:5173/>。
43
+ 打开工作台后,先将 Memory One MCP 接入你的 Agent:
70
44
 
71
- 建议第一次按这个顺序体验:
45
+ 1. 在左侧进入 **MCP 服务** 页面。
46
+ 2. 默认开启 Bearer Key 认证。保持认证时,先在 **MCP Key 管理** 中创建 Key,并选择该 Agent 可以使用的工具。
47
+ 3. 使用 Codex 时,在 **Codex → 连接与增强** 中选择 Key 并配置连接,然后在同一卡片中启用 **Codex 增强**。
48
+ 4. 使用其他 Agent 时,复制页面中的 MCP 服务地址,并按客户端要求填写 Key。
49
+ 5. 重启 Agent 或开启新会话。
72
50
 
73
- 1. 点击右上角 **新建记忆**,保存一条你希望 Agent 长期遵守的偏好。
74
- 2. 在左侧进入 **全部记忆**,搜索刚刚保存的内容,并点击卡片查看详情。
75
- 3. 进入 **MCP 服务**,复制配置并接入你的 Agent。
76
- 4. 如果你使用 Codex,继续完成下面的“Codex 增强”步骤。
51
+ Memory One 会检测 `~/.codex/config.toml` 中现有的 `memory-one` MCP 配置。单击配置后,只会替换该 MCP 服务的配置,并保留 Codex 的其他设置。
77
52
 
78
- ## 接入 MCP 客户端
53
+ 关闭 Bearer Key 认证后,整个 MCP 服务都不再要求 Key。Codex 自动配置也会移除 Authorization Header,此时无需创建或选择 Key。
79
54
 
80
- 本地开发时的 MCP 地址是:
81
-
82
- ```text
83
- http://127.0.0.1:8765/mcp/
84
- ```
85
-
86
- MCP 服务默认要求 Bearer Key。进入 **MCP 服务 → MCP Key 管理** 创建 Key 后,使用页面的一次性配置复制按钮。配置格式如下:
55
+ Memory One 默认要求客户端通过 Bearer Key 访问 MCP 服务。复制的配置格式如下:
87
56
 
88
57
  ```json
89
58
  {
90
59
  "mcpServers": {
91
60
  "memory-one": {
92
61
  "type": "http",
93
- "url": "http://127.0.0.1:8765/mcp/",
62
+ "url": "http://127.0.0.1:23888/mcp/",
94
63
  "headers": {
95
64
  "Authorization": "Bearer <你的 Key>"
96
65
  }
@@ -99,149 +68,160 @@ MCP 服务默认要求 Bearer Key。进入 **MCP 服务 → MCP Key 管理** 创
99
68
  }
100
69
  ```
101
70
 
102
- 把这段配置放进客户端的 MCP 设置后,重启客户端或新开一个会话。平台 Agent、Codex 和其他客户端建议分别创建独立 Key,并按最小权限选择工具。新建 Key 的明文会保存在本地数据库中,之后可以再次复制。Memory One 默认只监听本机;如果要暴露到其他设备,请同时配置网络层和 Key 管理。
71
+ 建议为不同 Agent 分别创建 Key,并只授予所需工具权限。Key 保存在本机数据库中,可以随时回到管理页面复制或撤销。
103
72
 
104
- ## Codex 增强:在哪里、为什么、怎么用
73
+ Memory One 默认仅监听本机;如需从其他设备访问,还需要自行配置网络访问方式。
105
74
 
106
- ### 在哪里用
75
+ Codex 增强用于告诉 Codex 主动使用 Memory One,而不只是连接 MCP。在 Codex 卡片中单击 **启用全局记忆**;已有旧版指令时,单击 **更新全局指令**。
107
76
 
108
- 打开 Memory One 工作台,进入左侧 **MCP 服务** 页面,在 **Codex增强 / 全局任务前置记忆** 区域操作。
77
+ 启用后,Codex 会被要求:
109
78
 
110
- ### 为什么要用
79
+ - 每项任务开始前调用一次 `memory_get_context`。
80
+ - 在 Git 项目中使用仓库根目录的绝对路径作为 `scope`。
81
+ - 将用户表达的长期偏好、决定、纠正和项目约定及时写入或更新到 Memory One。
111
82
 
112
- 仅仅把 MCP 服务配置给 Codex,并不能保证每个任务都会先读取记忆。Codex 增强会把一小段全局工作指令写入 `~/.codex/AGENTS.md`,明确要求 Codex:
83
+ Memory One 只会替换 `~/.codex/AGENTS.md` 中由自己管理的 `memory-one:codex` 标记区块,不会覆盖其他内容。该操作只会在你主动单击按钮后执行。
113
84
 
114
- > 开始任何任务前,先调用一次 `memory_get_context`;在 Git 项目中使用仓库根目录的绝对路径作为 `scope`。
85
+ ## 3. 开始使用
115
86
 
116
- 这样做的价值是把“先查记忆”变成稳定的任务前动作,而不是依赖你每次手动提醒。它适合经常在多个仓库之间切换、需要持续遵守项目约定,或希望 Agent 记住修正意见的场景。
87
+ 完成 MCP 配置后:
117
88
 
118
- ### 怎么用
89
+ 1. 在工作台右上角单击 **新建记忆**。
90
+ 2. 保存一条希望 Agent 长期遵守的偏好或项目约定。
91
+ 3. 在 Agent 中开始一个新任务,确认它调用了 `memory_get_context`。
92
+ 4. 回到 **MCP 服务** 页面查看调用统计,或在 **全部记忆** 中检查新增和召回的记忆。
119
93
 
120
- 1. 在 **MCP Key 管理** 创建一个给 Codex 使用的 Key,至少勾选 `memory_get_context`;需要读写时再勾选对应工具。
121
- 2. 把带 `Authorization` Header 的 MCP 配置放进 Codex。
122
- 3. 打开 **MCP 服务**,找到 **Codex增强** 区域。
123
- 4. 点击 **启用全局记忆**;如果已有旧版本指令,按钮会显示 **更新全局指令**。
124
- 5. 重新启动 Codex,或开启一个新会话。
125
- 6. 在 Codex 中直接提出任务,例如“检查这个项目的部署配置”。正常情况下,任务开始阶段会先出现 `memory_get_context` 调用。
94
+ ## 主要能力
126
95
 
127
- Memory One 只会替换自己管理的 `memory-one:codex` 标记区块,保留 `~/.codex/AGENTS.md` 中其他内容。写入动作必须由你在网页中主动点击完成,不会在安装或启动时自动修改 Codex 配置。
96
+ - **长期保存经验**:记录偏好、事实、项目决策、工作流程和纠正意见。
97
+ - **任务前自动召回**:通过 `memory_get_context` 在 Agent 开始工作前读取相关经验。
98
+ - **区分项目与通用记忆**:使用可选的 `scope` 对记忆分类,同时支持项目记忆与全局记忆联合召回。
99
+ - **可视化管理**:搜索、筛选、新建、编辑和删除记忆,并查看召回次数与置信度。
100
+ - **本地存储**:默认仅监听本机地址,数据保存在本地 SQLite 数据库中。
101
+ - **调用可观察**:查看 MCP 调用次数、成功率、平均耗时和各工具使用情况。
128
102
 
129
- ## 内置记忆管家
103
+ ## 界面预览
130
104
 
131
- 右下角的悬浮按钮会打开记忆管家。它可以搜索、保存、更新、软删除记忆,也可以总结你的偏好和特点。
105
+ 记忆总览:
132
106
 
133
- 如果要使用它:
107
+ ![记忆总览](./docs/screenshots/memory-overview.png)
134
108
 
135
- 1. 点击右下角的 Agent 按钮;首次打开会进入配置页。
136
- 2. 填写 Base URL,选择请求格式、获取模型列表并填写 API Key。
137
- 3. 可选填写默认 Scope,并决定每轮对话是否自动读取上下文。
138
- 4. 点击 **保存配置**,再从右下角打开记忆管家。
109
+ MCP 服务与 Codex 配置:
139
110
 
140
- 模型配置保存在本地 SQLite 数据库;记忆的读写仍通过 Memory One MCP 工具完成。
111
+ ![MCP 服务与 Codex 配置](./docs/screenshots/mcp-codex.png)
112
+
113
+ ## 使用内置记忆管家
114
+
115
+ 点击工作台右下角的悬浮按钮可以打开记忆管家。它能够搜索、保存、更新和软删除记忆,也可以根据已有记忆总结你的偏好与特点。
116
+
117
+ 首次使用需要完成模型配置:
118
+
119
+ 1. 打开右下角的 Agent 面板并进入 **配置**。
120
+ 2. 填写模型服务的 Base URL。
121
+ 3. 选择请求格式并选择或填写模型。
122
+ 4. 非本地模型服务需要填写 API Key。
123
+ 5. 按需设置默认 Scope,以及是否在每轮对话前自动读取上下文。
124
+ 6. 保存配置后开始对话。
125
+
126
+ 模型配置保存在本机 SQLite 数据库中;记忆操作仍通过 Memory One 的 MCP 工具完成。
141
127
 
142
128
  ## MCP 工具
143
129
 
144
130
  | 工具 | 用途 |
145
131
  | --- | --- |
146
- | `memory_get_context` | 每项任务开始时检索相关经验 |
132
+ | `memory_get_context` | 在任务开始时检索相关经验 |
147
133
  | `memory_search` | 按关键词搜索记忆,可选 `scope` |
148
134
  | `memory_store` | 保存偏好、事实、决策、流程或纠正意见 |
149
- | `memory_get` | 按 ID 读取单条记忆 |
135
+ | `memory_get` | 按 ID 读取单条完整记忆 |
150
136
  | `memory_list` | 列出记忆,可选 `scope` |
151
- | `memory_update` | 更新记忆内容或元数据 |
137
+ | `memory_update` | 更新指定记忆的内容或元数据 |
152
138
  | `memory_delete` | 软删除指定记忆,仅在用户明确要求时使用 |
153
- | `memory_feedback` | 记录某条记忆是否有帮助,改善后续召回 |
154
-
155
- ### Scope 怎么填
139
+ | `memory_feedback` | 记录记忆是否有帮助,以改善后续召回 |
156
140
 
157
- `scope` 是分类字段,不是安全隔离边界。项目记忆使用仓库根目录的绝对路径,例如 `/Users/name/code/memory-one`;通用偏好省略 `scope`。`memory_get_context` 传入项目 `scope` 时联合召回当前项目与全局记忆,不传时只召回全局记忆;`memory_search` 不传 `scope` 时仍可跨分类搜索。
141
+ ## Scope 使用建议
158
142
 
159
- ## 数据与配置
143
+ `scope` 是可选的分类字段,不是安全隔离边界。
160
144
 
161
- - 开发数据库:`data/memory.db`
162
- - 修改数据库路径:设置 `MEMORY_DB_PATH`
163
- - 修改服务端口:设置 `MEMORY_PORT`
164
- - 前端开发地址:<http://127.0.0.1:5173/>
165
- - 后端 API 地址:<http://127.0.0.1:8765/>
166
- - MCP 地址:<http://127.0.0.1:8765/mcp/>
145
+ - **项目记忆**:使用 Git 仓库根目录的绝对路径,例如 `/Users/name/code/my-project`。
146
+ - **通用记忆**:省略 `scope`。
147
+ - **其他分类**:也可以使用 `work`、`personal` 等自定义值。
167
148
 
168
- ## 本地生产模式
149
+ `memory_get_context` 传入项目 `scope` 时,会联合召回该项目和全局记忆;省略 `scope` 时,只召回全局记忆。`memory_search` 省略 `scope` 时,可以跨分类搜索。
169
150
 
170
- 通过 npm 全局安装后,可以使用 CLI 管理本地服务:
151
+ ## CLI 命令
171
152
 
172
153
  ```bash
173
- npm install -g @ccjr1120/memory-one
174
- memoryone start
175
- memoryone status
176
- memoryone open
177
- memoryone update
178
- memoryone stop
154
+ memoryone start # 启动本地服务
155
+ memoryone stop # 停止本地服务
156
+ memoryone status # 查看运行状态
157
+ memoryone open # 在浏览器中打开工作台
158
+ memoryone update # 更新到最新版本
179
159
  ```
180
160
 
181
- 服务默认运行在 <http://127.0.0.1:23888/>,MCP 地址为 <http://127.0.0.1:23888/mcp/>。数据库、PID 和日志保存在 `~/.local/share/memory-one`,不会写入 npm 包目录。执行 `memoryone update` 会更新全局 CLI,并在服务运行时自动重启服务。若启动时端口 `23888` 已被占用,CLI 会询问是否终止占用进程。
161
+ 执行 `memoryone update` 时,如果服务正在运行,Memory One 会先停止服务,更新完成后再自动启动。
182
162
 
183
- 从源码 checkout 安装并启动:
184
-
185
- ```bash
186
- npm run install:local
187
- ```
163
+ ## 数据与备份
188
164
 
189
- 该命令会安装依赖、构建前端和后端,清理端口 `23888`,并在后台启动服务。
165
+ 运行数据默认保存在:
190
166
 
191
- - 工作台:<http://127.0.0.1:23888/>
192
- - MCP:<http://127.0.0.1:23888/mcp/>
193
- - 部署目录:`~/.local/share/memory-one`
194
- - 部署数据库:`~/.local/share/memory-one/data/memory.db`
195
- - PID:`data/memory-one.pid`
196
- - 日志:`data/memory-one.log`
197
-
198
- 生产服务请使用 `npm run install:local`,它会在临时目录构建并把版本复制到上面的部署目录后启动。
167
+ ```text
168
+ ~/.local/share/memory-one/
169
+ ```
199
170
 
200
- ## 发布到 npm
171
+ 其中:
201
172
 
202
- 仓库已经包含 GitHub Actions 发布流程:每次推送 `main` 时,工作流会自动递增一个 patch 版本,npm 发布前会运行测试和构建,发布成功后再提交新的 `package.json` 与 `package-lock.json`。自动生成的版本提交不会再次触发发布循环。
173
+ - 数据库:`~/.local/share/memory-one/data/memory.db`
174
+ - PID:`~/.local/share/memory-one/memory-one.pid`
175
+ - 日志:`~/.local/share/memory-one/memory-one.log`
203
176
 
204
- 首次配置需要完成两件事:
177
+ 备份记忆时,建议先执行:
205
178
 
206
- 1. 在 npm 登录 `ccjr1120` 账号,进入 **Access Tokens** 创建一个具备发布权限的 token;包名是 scoped 包 `@ccjr1120/memory-one`。
207
- 2. 在 GitHub 仓库的 **Settings → Secrets and variables → Actions → New repository secret** 中添加:
179
+ ```bash
180
+ memoryone stop
181
+ ```
208
182
 
209
- - Name:`NPM_TOKEN`
210
- - Secret:刚才复制的 npm token
183
+ 然后复制 `memory.db` 文件。恢复时,在服务停止状态下用备份文件替换原数据库,再重新启动服务。
211
184
 
212
- 这个 workflow 当前读取的就是 GitHub Actions 的 **Repository secret**,不会把 token 写入代码。若 npm 账号并不是 `ccjr1120`,则不能发布这个 scope,需要先使用自己账号对应的 scope。
185
+ ## 更新与卸载
213
186
 
214
- 首次配置完成后,日常发布不需要手动切换分支或运行版本脚本,直接推送 `main` 即可:
187
+ 更新到最新版本:
215
188
 
216
189
  ```bash
217
- git push origin main
190
+ memoryone update
218
191
  ```
219
192
 
220
- 例如当前版本为 `0.1.0` 时,推送一次 `main` 会自动发布 `@ccjr1120/memory-one@0.1.1`。`npm run v` 仍保留给需要手动发布 minor、major 或特殊 release 分支的场景。
221
-
222
- 本地全局安装后,使用下面的命令检查并更新到 npm 上的最新版本;如果服务正在运行,更新完成后会自动重启:
193
+ 卸载程序:
223
194
 
224
195
  ```bash
225
- memoryone update
196
+ memoryone stop
197
+ npm uninstall --global @ccjr1120/memory-one
226
198
  ```
227
199
 
228
- 打开记忆工作台时,前端会检查当前版本和 npm 最新版本;发现新版本时会在顶部显示 3 秒通知条。
200
+ 卸载 npm 包不会自动删除 `~/.local/share/memory-one/` 中的数据库和配置。
229
201
 
230
202
  ## 常见问题
231
203
 
232
- **页面打不开?** 确认 `npm run dev` 仍在运行,并检查 `8765`、`5173` 是否被其他程序占用。开发脚本会自动清理这两个端口。
204
+ ### 页面打不开
205
+
206
+ 运行 `memoryone status` 检查服务状态;如果服务未运行,执行 `memoryone start`。启动失败时,查看:
233
207
 
234
- **Codex 没有读取记忆?** 确认 MCP 地址已配置,Codex 增强按钮已显示“已启用”,然后重启 Codex 或新开会话。
208
+ ```text
209
+ ~/.local/share/memory-one/memory-one.log
210
+ ```
235
211
 
236
- **记忆管家提示配置不完整?** 打开右下角 Agent 面板,在 **配置** Tab 填写 Base URL、请求格式和模型;非本地模型还需要填写 API Key。
212
+ ### Agent 已连接 MCP,但没有主动读取记忆
237
213
 
238
- **想备份记忆?** 开发版备份 `data/memory.db`,生产版备份 `~/.local/share/memory-one/data/memory.db`;服务停止后操作最稳妥。
214
+ 确认以下事项:
239
215
 
240
- ## 开发
216
+ 1. Agent 已配置 Memory One MCP 地址。
217
+ 2. 如果开启了 Bearer Key 认证,确认客户端已填写有效 Key,且 Key 已授权 `memory_get_context`。
218
+ 3. 使用 Codex 时,确认已在 Codex 卡片中启用 **全局任务前置记忆**。
219
+ 4. 启用后已经重启 Agent 或开启新会话。
241
220
 
242
- ```bash
243
- npm run dev # API + Vite 前端
244
- npm run install:local # 构建、复制并启动本地生产服务
245
- ```
221
+ ### 记忆管家提示配置不完整
222
+
223
+ 打开右下角 Agent 面板,在 **配置** 中检查 Base URL、请求格式、模型和 API Key。使用不需要鉴权的本地模型时,可以不填写 API Key。
224
+
225
+ ### 如何确认 MCP 已连接
246
226
 
247
- 前端使用 React、Vite `lucide-react`,后端使用 Fastify、MCP SDK better-sqlite3。界面约定记录在 [DESIGN.md](./DESIGN.md)。
227
+ Agent 中调用一次 `memory_get_context`,然后打开工作台的 **MCP 服务** 页面查看调用统计。也可以让 Agent 保存一条测试记忆,再到 **全部记忆** 中搜索确认。
package/dist/server.js CHANGED
@@ -234,6 +234,7 @@ async function runAgent(request, emit) {
234
234
  }
235
235
  }
236
236
  const codexAgentsPath = join(process.env.CODEX_HOME ?? join(homedir(), ".codex"), "AGENTS.md");
237
+ const codexConfigPath = join(process.env.CODEX_HOME ?? join(homedir(), ".codex"), "config.toml");
237
238
  const codexGuidanceStart = "<!-- memory-one:codex:start -->";
238
239
  const codexGuidanceEnd = "<!-- memory-one:codex:end -->";
239
240
  const codexGuidance = `${codexGuidanceStart}
@@ -281,6 +282,92 @@ async function installCodexIntegration() {
281
282
  await writeFile(codexAgentsPath, `${preserved ? `${preserved}\n\n` : ""}${codexGuidance}\n`, "utf8");
282
283
  return getCodexIntegration();
283
284
  }
285
+ async function readCodexConfig() {
286
+ try {
287
+ return await readFile(codexConfigPath, "utf8");
288
+ }
289
+ catch (error) {
290
+ if (error.code === "ENOENT")
291
+ return "";
292
+ throw error;
293
+ }
294
+ }
295
+ function isMemoryOneCodexSection(name) {
296
+ const normalized = name.replace(/"memory-one"/g, "memory-one");
297
+ return normalized === "mcp_servers.memory-one" || normalized.startsWith("mcp_servers.memory-one.");
298
+ }
299
+ function getMemoryOneCodexSections(content) {
300
+ const sections = [];
301
+ let current = null;
302
+ for (const line of content.split("\n")) {
303
+ const section = line.match(/^\s*\[([^\]]+)]\s*(?:#.*)?$/);
304
+ if (section) {
305
+ if (current)
306
+ sections.push(current.join("\n"));
307
+ current = isMemoryOneCodexSection(section[1].trim()) ? [line] : null;
308
+ }
309
+ else if (current)
310
+ current.push(line);
311
+ }
312
+ if (current)
313
+ sections.push(current.join("\n"));
314
+ return sections.join("\n");
315
+ }
316
+ function removeMemoryOneCodexSections(content) {
317
+ const lines = [];
318
+ let removing = false;
319
+ for (const line of content.split("\n")) {
320
+ const section = line.match(/^\s*\[([^\]]+)]\s*(?:#.*)?$/);
321
+ if (section)
322
+ removing = isMemoryOneCodexSection(section[1].trim());
323
+ if (!removing)
324
+ lines.push(line);
325
+ }
326
+ return lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
327
+ }
328
+ function readTomlString(source, key) {
329
+ const match = source.match(new RegExp(`(?:^|[,{]\\s*)${key}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*"|'[^']*')`, "im"));
330
+ if (!match)
331
+ return null;
332
+ if (match[1].startsWith("'"))
333
+ return match[1].slice(1, -1);
334
+ try {
335
+ return JSON.parse(match[1]);
336
+ }
337
+ catch {
338
+ return null;
339
+ }
340
+ }
341
+ async function getCodexMcpIntegration(expectedEndpoint) {
342
+ const content = await readCodexConfig();
343
+ const sections = getMemoryOneCodexSections(content);
344
+ const endpoint = readTomlString(sections, "url");
345
+ const authorization = readTomlString(sections, "Authorization");
346
+ const key = authorization?.match(/^Bearer\s+(.+)$/i)?.[1]?.trim();
347
+ const keyRecord = key ? store.verifyMcpKey(key) : null;
348
+ const authRequired = store.getMcpConfig().use_bearer_key;
349
+ const configured = endpoint === expectedEndpoint && (authRequired ? Boolean(keyRecord) : !authorization);
350
+ return {
351
+ path: codexConfigPath,
352
+ detected: Boolean(content),
353
+ endpoint,
354
+ auth_required: authRequired,
355
+ configured_key_id: keyRecord?.id ?? null,
356
+ status: configured ? "configured" : sections ? "update_available" : "not_configured",
357
+ };
358
+ }
359
+ async function installCodexMcpIntegration(endpoint, keyId) {
360
+ const authRequired = store.getMcpConfig().use_bearer_key;
361
+ const key = authRequired && keyId ? store.getMcpKey(keyId) : null;
362
+ if (authRequired && (!key?.secret || key.revoked_at))
363
+ throw new Error("mcp_key_not_found");
364
+ const content = await readCodexConfig();
365
+ const preserved = removeMemoryOneCodexSections(content);
366
+ const section = `[mcp_servers.memory-one]\nurl = ${JSON.stringify(endpoint)}${authRequired ? `\nhttp_headers = { Authorization = ${JSON.stringify(`Bearer ${key.secret}`)} }` : ""}`;
367
+ await mkdir(dirname(codexConfigPath), { recursive: true });
368
+ await writeFile(codexConfigPath, `${preserved ? `${preserved}\n\n` : ""}${section}\n`, "utf8");
369
+ return getCodexMcpIntegration(endpoint);
370
+ }
284
371
  async function trackToolCall(toolName, action) {
285
372
  const startedAt = performance.now();
286
373
  try {
@@ -435,6 +522,23 @@ app.post("/api/agent/stream", async (request, reply) => {
435
522
  */
436
523
  app.get("/api/integrations/codex", async () => getCodexIntegration());
437
524
  app.post("/api/integrations/codex/install", async () => installCodexIntegration());
525
+ app.get("/api/integrations/codex/mcp", async (request) => {
526
+ const { endpoint = "" } = request.query;
527
+ return getCodexMcpIntegration(endpoint);
528
+ });
529
+ app.post("/api/integrations/codex/mcp/install", async (request, reply) => {
530
+ const body = request.body ?? {};
531
+ if (!body.endpoint)
532
+ return reply.code(422).send({ detail: "codex_mcp_config_required" });
533
+ try {
534
+ return await installCodexMcpIntegration(body.endpoint, body.key_id);
535
+ }
536
+ catch (error) {
537
+ if (error instanceof Error && error.message === "mcp_key_not_found")
538
+ return reply.code(404).send({ detail: error.message });
539
+ throw error;
540
+ }
541
+ });
438
542
  app.get("/api/memories/:id", async (request, reply) => { const { id } = request.params; const item = store.get(id); return item ? item : reply.code(404).send({ detail: "memory_not_found" }); });
439
543
  app.post("/api/memories", async (request, reply) => { const payload = request.body; if (!payload?.content)
440
544
  return reply.code(422).send({ detail: "content_required" }); return store.create(payload); });
@@ -443,10 +547,11 @@ app.delete("/api/memories/:id", async (request) => { const { id } = request.para
443
547
  app.all("/mcp", async (request, reply) => reply.redirect("/mcp/", 307));
444
548
  app.all("/mcp/", async (request, reply) => {
445
549
  const internal = request.headers["x-memory-one-internal"] === internalMcpToken;
550
+ const authRequired = store.getMcpConfig().use_bearer_key;
446
551
  const authorization = request.headers.authorization;
447
552
  const secret = authorization?.match(/^Bearer\s+(.+)$/i)?.[1]?.trim();
448
- const key = secret ? store.verifyMcpKey(secret) : null;
449
- if (!internal && (!secret || !key))
553
+ const key = authRequired && secret ? store.verifyMcpKey(secret) : null;
554
+ if (!internal && authRequired && (!secret || !key))
450
555
  return reply.code(401).header("www-authenticate", "Bearer").send({ error: secret ? "invalid_mcp_key" : "mcp_key_required" });
451
556
  const allowedTools = key && key.allowed_tools.length ? new Set(key.allowed_tools) : undefined;
452
557
  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });