@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 +123 -143
- package/dist/server.js +107 -2
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/public/assets/{highlighted-body-KPVGNVTW-BpWplPka.js → highlighted-body-KPVGNVTW-DipOcK5P.js} +1 -1
- package/public/assets/{index-DUFjREc6.css → index-BmWkq8Dr.css} +1 -1
- package/public/assets/{index-CkQQQDOY.js → index-DyaH_e9T.js} +49 -49
- package/public/index.html +2 -2
package/README.md
CHANGED
|
@@ -2,95 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
让 Agent 在开始工作前,先记住你的项目约定、个人偏好和已经验证过的经验。
|
|
4
4
|
|
|
5
|
-
Memory One
|
|
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
|
-
|
|
11
|
+
### 环境要求
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
MCP 服务页:复制客户端配置、查看服务端点、启用 Codex 全局记忆指令,并查看工具调用统计。
|
|
16
|
-
|
|
17
|
-

|
|
18
|
-
|
|
19
|
-
### 更新截图
|
|
13
|
+
- Node.js 20 或更高版本
|
|
14
|
+
- npm
|
|
20
15
|
|
|
21
|
-
|
|
16
|
+
### 安装
|
|
22
17
|
|
|
23
18
|
```bash
|
|
24
|
-
|
|
25
|
-
npm run screenshots
|
|
19
|
+
npm install --global @ccjr1120/memory-one
|
|
26
20
|
```
|
|
27
21
|
|
|
28
|
-
|
|
22
|
+
### 启动
|
|
29
23
|
|
|
30
24
|
```bash
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
+
打开工作台:
|
|
57
34
|
|
|
58
35
|
```bash
|
|
59
|
-
|
|
60
|
-
cd memory-one
|
|
61
|
-
npm install
|
|
62
|
-
npm run dev
|
|
36
|
+
memoryone open
|
|
63
37
|
```
|
|
64
38
|
|
|
65
|
-
|
|
39
|
+
如果端口 `23888` 已被占用,启动命令会询问是否终止占用进程。
|
|
66
40
|
|
|
67
|
-
|
|
41
|
+
## 2. 配置 MCP
|
|
68
42
|
|
|
69
|
-
|
|
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
|
-
|
|
74
|
-
2. 在左侧进入 **全部记忆**,搜索刚刚保存的内容,并点击卡片查看详情。
|
|
75
|
-
3. 进入 **MCP 服务**,复制配置并接入你的 Agent。
|
|
76
|
-
4. 如果你使用 Codex,继续完成下面的“Codex 增强”步骤。
|
|
51
|
+
Memory One 会检测 `~/.codex/config.toml` 中现有的 `memory-one` MCP 配置。单击配置后,只会替换该 MCP 服务的配置,并保留 Codex 的其他设置。
|
|
77
52
|
|
|
78
|
-
|
|
53
|
+
关闭 Bearer Key 认证后,整个 MCP 服务都不再要求 Key。Codex 自动配置也会移除 Authorization Header,此时无需创建或选择 Key。
|
|
79
54
|
|
|
80
|
-
|
|
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:
|
|
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
|
-
|
|
71
|
+
建议为不同 Agent 分别创建 Key,并只授予所需工具权限。Key 保存在本机数据库中,可以随时回到管理页面复制或撤销。
|
|
103
72
|
|
|
104
|
-
|
|
73
|
+
Memory One 默认仅监听本机;如需从其他设备访问,还需要自行配置网络访问方式。
|
|
105
74
|
|
|
106
|
-
|
|
75
|
+
Codex 增强用于告诉 Codex 主动使用 Memory One,而不只是连接 MCP。在 Codex 卡片中单击 **启用全局记忆**;已有旧版指令时,单击 **更新全局指令**。
|
|
107
76
|
|
|
108
|
-
|
|
77
|
+
启用后,Codex 会被要求:
|
|
109
78
|
|
|
110
|
-
|
|
79
|
+
- 每项任务开始前调用一次 `memory_get_context`。
|
|
80
|
+
- 在 Git 项目中使用仓库根目录的绝对路径作为 `scope`。
|
|
81
|
+
- 将用户表达的长期偏好、决定、纠正和项目约定及时写入或更新到 Memory One。
|
|
111
82
|
|
|
112
|
-
|
|
83
|
+
Memory One 只会替换 `~/.codex/AGENTS.md` 中由自己管理的 `memory-one:codex` 标记区块,不会覆盖其他内容。该操作只会在你主动单击按钮后执行。
|
|
113
84
|
|
|
114
|
-
|
|
85
|
+
## 3. 开始使用
|
|
115
86
|
|
|
116
|
-
|
|
87
|
+
完成 MCP 配置后:
|
|
117
88
|
|
|
118
|
-
|
|
89
|
+
1. 在工作台右上角单击 **新建记忆**。
|
|
90
|
+
2. 保存一条希望 Agent 长期遵守的偏好或项目约定。
|
|
91
|
+
3. 在 Agent 中开始一个新任务,确认它调用了 `memory_get_context`。
|
|
92
|
+
4. 回到 **MCP 服务** 页面查看调用统计,或在 **全部记忆** 中检查新增和召回的记忆。
|
|
119
93
|
|
|
120
|
-
|
|
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
|
-
|
|
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
|
+

|
|
134
108
|
|
|
135
|
-
|
|
136
|
-
2. 填写 Base URL,选择请求格式、获取模型列表并填写 API Key。
|
|
137
|
-
3. 可选填写默认 Scope,并决定每轮对话是否自动读取上下文。
|
|
138
|
-
4. 点击 **保存配置**,再从右下角打开记忆管家。
|
|
109
|
+
MCP 服务与 Codex 配置:
|
|
139
110
|
|
|
140
|
-
|
|
111
|
+

|
|
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
|
-
|
|
141
|
+
## Scope 使用建议
|
|
158
142
|
|
|
159
|
-
|
|
143
|
+
`scope` 是可选的分类字段,不是安全隔离边界。
|
|
160
144
|
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
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
|
-
|
|
151
|
+
## CLI 命令
|
|
171
152
|
|
|
172
153
|
```bash
|
|
173
|
-
|
|
174
|
-
memoryone
|
|
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
|
-
|
|
161
|
+
执行 `memoryone update` 时,如果服务正在运行,Memory One 会先停止服务,更新完成后再自动启动。
|
|
182
162
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
```bash
|
|
186
|
-
npm run install:local
|
|
187
|
-
```
|
|
163
|
+
## 数据与备份
|
|
188
164
|
|
|
189
|
-
|
|
165
|
+
运行数据默认保存在:
|
|
190
166
|
|
|
191
|
-
|
|
192
|
-
-
|
|
193
|
-
|
|
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
|
-
|
|
171
|
+
其中:
|
|
201
172
|
|
|
202
|
-
|
|
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
|
-
|
|
207
|
-
|
|
179
|
+
```bash
|
|
180
|
+
memoryone stop
|
|
181
|
+
```
|
|
208
182
|
|
|
209
|
-
|
|
210
|
-
- Secret:刚才复制的 npm token
|
|
183
|
+
然后复制 `memory.db` 文件。恢复时,在服务停止状态下用备份文件替换原数据库,再重新启动服务。
|
|
211
184
|
|
|
212
|
-
|
|
185
|
+
## 更新与卸载
|
|
213
186
|
|
|
214
|
-
|
|
187
|
+
更新到最新版本:
|
|
215
188
|
|
|
216
189
|
```bash
|
|
217
|
-
|
|
190
|
+
memoryone update
|
|
218
191
|
```
|
|
219
192
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
本地全局安装后,使用下面的命令检查并更新到 npm 上的最新版本;如果服务正在运行,更新完成后会自动重启:
|
|
193
|
+
卸载程序:
|
|
223
194
|
|
|
224
195
|
```bash
|
|
225
|
-
memoryone
|
|
196
|
+
memoryone stop
|
|
197
|
+
npm uninstall --global @ccjr1120/memory-one
|
|
226
198
|
```
|
|
227
199
|
|
|
228
|
-
|
|
200
|
+
卸载 npm 包不会自动删除 `~/.local/share/memory-one/` 中的数据库和配置。
|
|
229
201
|
|
|
230
202
|
## 常见问题
|
|
231
203
|
|
|
232
|
-
|
|
204
|
+
### 页面打不开
|
|
205
|
+
|
|
206
|
+
运行 `memoryone status` 检查服务状态;如果服务未运行,执行 `memoryone start`。启动失败时,查看:
|
|
233
207
|
|
|
234
|
-
|
|
208
|
+
```text
|
|
209
|
+
~/.local/share/memory-one/memory-one.log
|
|
210
|
+
```
|
|
235
211
|
|
|
236
|
-
|
|
212
|
+
### Agent 已连接 MCP,但没有主动读取记忆
|
|
237
213
|
|
|
238
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
221
|
+
### 记忆管家提示配置不完整
|
|
222
|
+
|
|
223
|
+
打开右下角 Agent 面板,在 **配置** 中检查 Base URL、请求格式、模型和 API Key。使用不需要鉴权的本地模型时,可以不填写 API Key。
|
|
224
|
+
|
|
225
|
+
### 如何确认 MCP 已连接
|
|
246
226
|
|
|
247
|
-
|
|
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 });
|