@ccjr1120/memory-one 0.1.12 → 0.1.13

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.
Files changed (52) hide show
  1. package/README.md +15 -7
  2. package/dist/agent/providers.js +113 -0
  3. package/dist/agent/providers.js.map +1 -0
  4. package/dist/agent/run.js +98 -0
  5. package/dist/agent/run.js.map +1 -0
  6. package/dist/agent/types.js +2 -0
  7. package/dist/agent/types.js.map +1 -0
  8. package/dist/api/client.js +26 -0
  9. package/dist/api/client.js.map +1 -0
  10. package/dist/app.js +36 -0
  11. package/dist/app.js.map +1 -0
  12. package/dist/integrations/codex.js +143 -0
  13. package/dist/integrations/codex.js.map +1 -0
  14. package/dist/lib/copy.js +203 -0
  15. package/dist/lib/copy.js.map +1 -0
  16. package/dist/lib/format.js +11 -0
  17. package/dist/lib/format.js.map +1 -0
  18. package/dist/lib/kinds.js +5 -0
  19. package/dist/lib/kinds.js.map +1 -0
  20. package/dist/lib/views.js +31 -0
  21. package/dist/lib/views.js.map +1 -0
  22. package/dist/mcp/context-cache.js +14 -0
  23. package/dist/mcp/context-cache.js.map +1 -0
  24. package/dist/mcp/server.js +80 -0
  25. package/dist/mcp/server.js.map +1 -0
  26. package/dist/routes/agent.js +268 -0
  27. package/dist/routes/agent.js.map +1 -0
  28. package/dist/routes/data.js +18 -0
  29. package/dist/routes/data.js.map +1 -0
  30. package/dist/routes/integrations.js +22 -0
  31. package/dist/routes/integrations.js.map +1 -0
  32. package/dist/routes/mcp.js +22 -0
  33. package/dist/routes/mcp.js.map +1 -0
  34. package/dist/routes/memories.js +46 -0
  35. package/dist/routes/memories.js.map +1 -0
  36. package/dist/routes/settings.js +36 -0
  37. package/dist/routes/settings.js.map +1 -0
  38. package/dist/routes/system.js +25 -0
  39. package/dist/routes/system.js.map +1 -0
  40. package/dist/server.js +2 -762
  41. package/dist/server.js.map +1 -1
  42. package/dist/storage.js +218 -11
  43. package/dist/storage.js.map +1 -1
  44. package/dist/types.js +2 -0
  45. package/dist/types.js.map +1 -0
  46. package/package.json +4 -6
  47. package/public/assets/{highlighted-body-KPVGNVTW-DUlL1c8y.js → highlighted-body-KPVGNVTW-jxw5D7vG.js} +1 -1
  48. package/public/assets/index-C3mrwXZF.css +1 -0
  49. package/public/assets/index-DWgbu5Rx.js +190 -0
  50. package/public/index.html +2 -2
  51. package/public/assets/index-Corxd8nB.css +0 -1
  52. package/public/assets/index-KtcznaoB.js +0 -190
package/dist/server.js CHANGED
@@ -1,765 +1,5 @@
1
- import Fastify from "fastify";
2
- import fastifyStatic from "@fastify/static";
3
- import { mkdir, readFile, writeFile } from "node:fs/promises";
4
- import { readFileSync } from "node:fs";
5
- import { homedir } from "node:os";
6
- import { dirname, join } from "node:path";
7
- import { fileURLToPath } from "node:url";
8
- import { randomUUID } from "node:crypto";
9
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
11
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
12
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
13
- import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
14
- import { z } from "zod";
15
- import { MemoryStore } from "./storage.js";
16
- const store = new MemoryStore();
17
- const internalMcpToken = randomUUID();
18
- let agentTurnQueue = Promise.resolve();
19
- const json = (value) => ({ content: [{ type: "text", text: JSON.stringify(value) }] });
20
- const storageScope = "user";
21
- const exposeMemory = (memory) => { if (!memory || typeof memory !== "object" || "error" in memory)
22
- return memory; const { project, ...rest } = memory; return { ...rest, scope: project ?? "global" }; };
23
- const exposeMemories = (memories) => memories.map(exposeMemory);
24
- const toStorageInput = (input) => { const { scope, ...rest } = input; return { ...rest, scope: storageScope, project: typeof scope === "string" && scope !== "global" ? scope : null }; };
25
- const toStoragePatch = (patch) => { const { scope, ...rest } = patch; return "scope" in patch ? { ...rest, project: typeof scope === "string" && scope !== "global" ? scope : null } : rest; };
26
- const agentSystemPrompt = "你是 Memory One 的记忆管家,首要职责是结合已读取的相关记忆直接回答用户的问题。你可以主动搜索和读取记忆来提高回答准确性,但不得因为普通对话、提问、纠正回答、顺带提到的偏好或项目细节而新增、更新或删除记忆。只有当用户明确要求‘记住/保存’某项内容、明确要求修改某条记忆,或明确要求‘忘记/删除’某条记忆时,才调用 memory_store、memory_update 或 memory_delete。执行更新或删除前先确认目标唯一,不要编造记忆。回复使用中文,简洁但可以使用 Markdown。";
27
- const toolLabels = { "memory-get-context": "读取固定上下文", "memory-search": "搜索记忆", memory_get: "读取记忆", memory_list: "列出记忆", memory_store: "保存记忆", memory_update: "更新记忆", memory_delete: "删除记忆", memory_feedback: "记录反馈" };
28
- const isMemoryOverviewRequest = (message) => /(?:有哪些|所有记忆|全部记忆|列出(?:全部)?|查看(?:全部)?|浏览全部|总结(?:下)?(?:我的)?记忆|总结我的特点|概括我的特点|我的画像|我的偏好和特点|我的记忆(?:有什么)?特点|记忆特点)/.test(message);
29
- function sseEvent(type, payload, id) { return `${id === undefined ? "" : `id: ${id}\n`}event: ${type}\ndata: ${JSON.stringify(payload)}\n\n`; }
30
- async function readSse(response, onEvent) {
31
- if (!response.body)
32
- throw new Error("provider_empty_stream");
33
- const reader = response.body.getReader();
34
- const decoder = new TextDecoder();
35
- let buffer = "";
36
- while (true) {
37
- const { value, done } = await reader.read();
38
- buffer += decoder.decode(value ?? new Uint8Array(), { stream: !done });
39
- const chunks = buffer.split(/\r?\n\r?\n/);
40
- buffer = chunks.pop() ?? "";
41
- for (const chunk of chunks) {
42
- const lines = chunk.split(/\r?\n/);
43
- const event = lines.find((line) => line.startsWith("event:"))?.slice(6).trim() ?? "message";
44
- const data = lines.filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).join("\n");
45
- if (data && await onEvent({ event, data }) === false) {
46
- await reader.cancel().catch(() => undefined);
47
- return;
48
- }
49
- }
50
- if (done)
51
- break;
52
- }
53
- }
54
- async function connectMemoryMcp() {
55
- const client = new Client({ name: "memory-one-agent", version: "0.1.0" });
56
- const transport = new StreamableHTTPClientTransport(new URL(`http://127.0.0.1:${process.env.MEMORY_PORT ?? 8765}/mcp`), { requestInit: { headers: { "x-memory-one-internal": internalMcpToken } } });
57
- await client.connect(transport);
58
- return { client, transport };
59
- }
60
- function normalizeTools(tools) {
61
- return tools.map((tool) => ({ type: "function", function: { name: tool.name, description: tool.description ?? "", parameters: tool.inputSchema } }));
62
- }
63
- async function openAiRound(config, messages, tools, emit) {
64
- const base = (config.base_url?.trim() || "").replace(/\/$/, "");
65
- const response = await fetch(`${base}/chat/completions`, { method: "POST", headers: { "content-type": "application/json", ...(config.api_key ? { authorization: `Bearer ${config.api_key}` } : {}) }, body: JSON.stringify({ model: config.model || "", messages, tools, stream: true }) });
66
- if (!response.ok)
67
- throw new Error(`provider_http_${response.status}: ${await response.text()}`);
68
- let content = "";
69
- const calls = new Map();
70
- await readSse(response, ({ data }) => {
71
- if (data === "[DONE]")
72
- return false;
73
- const chunk = JSON.parse(data);
74
- const delta = chunk.choices?.[0]?.delta;
75
- if (delta?.content) {
76
- content += delta.content;
77
- emit({ type: "delta", text: delta.content });
78
- }
79
- for (const call of delta?.tool_calls ?? []) {
80
- const existing = calls.get(call.index) ?? { id: call.id ?? "", name: call.function?.name ?? "", arguments: "" };
81
- existing.id ||= call.id ?? "";
82
- existing.name ||= call.function?.name ?? "";
83
- existing.arguments += call.function?.arguments ?? "";
84
- calls.set(call.index, existing);
85
- }
86
- });
87
- return { content, toolCalls: [...calls.values()] };
88
- }
89
- function responsesInput(messages) {
90
- return messages.filter((message) => message.role !== "system").flatMap((message) => {
91
- if (message.role === "tool")
92
- return [{ type: "function_call_output", call_id: message.tool_call_id, output: message.content }];
93
- if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
94
- return message.tool_calls.map((call) => ({ type: "function_call", call_id: call.id, name: call.function.name, arguments: call.function.arguments }));
95
- }
96
- return [{ role: message.role, content: message.content }];
97
- });
98
- }
99
- async function openAiResponsesRound(config, messages, tools, emit) {
100
- const base = (config.base_url?.trim() || "").replace(/\/$/, "");
101
- const system = messages.find((message) => message.role === "system")?.content;
102
- const response = await fetch(`${base}/responses`, { method: "POST", headers: { "content-type": "application/json", ...(config.api_key ? { authorization: `Bearer ${config.api_key}` } : {}) }, body: JSON.stringify({ model: config.model || "", instructions: system, input: responsesInput(messages), tools: tools.map((tool) => ({ type: "function", name: tool.function.name, description: tool.function.description, parameters: tool.function.parameters })), stream: false }) });
103
- if (!response.ok)
104
- throw new Error(`provider_http_${response.status}: ${await response.text()}`);
105
- const payload = await response.json();
106
- let content = "";
107
- const toolCalls = [];
108
- for (const item of payload.output ?? []) {
109
- if (item.type === "message") {
110
- for (const part of item.content ?? [])
111
- if (part.type === "output_text" && part.text)
112
- content += part.text;
113
- }
114
- if (item.type === "function_call")
115
- toolCalls.push({ id: item.call_id || item.id, name: item.name, arguments: item.arguments || "{}" });
116
- }
117
- if (content)
118
- emit({ type: "delta", text: content });
119
- return { content, toolCalls };
120
- }
121
- async function anthropicRound(config, messages, tools, emit) {
122
- const base = (config.base_url?.trim() || "").replace(/\/$/, "");
123
- const system = messages.find((message) => message.role === "system")?.content;
124
- const response = await fetch(`${base}/messages`, { method: "POST", headers: { "content-type": "application/json", "anthropic-version": "2023-06-01", ...(config.api_key ? { "x-api-key": config.api_key } : {}) }, body: JSON.stringify({ model: config.model || "claude-3-5-sonnet-latest", max_tokens: 4096, system, messages: messages.filter((message) => message.role !== "system"), tools: tools.map((tool) => ({ name: tool.function.name, description: tool.function.description, input_schema: tool.function.parameters })), stream: true }) });
125
- if (!response.ok)
126
- throw new Error(`provider_http_${response.status}: ${await response.text()}`);
127
- let content = "";
128
- const calls = [];
129
- let current = null;
130
- await readSse(response, ({ event, data }) => {
131
- if (event === "message_stop")
132
- return false;
133
- const item = JSON.parse(data);
134
- if (event === "content_block_start" && item.content_block?.type === "tool_use") {
135
- current = { id: item.content_block.id, name: item.content_block.name, arguments: "" };
136
- calls.push(current);
137
- }
138
- if (event === "content_block_delta") {
139
- if (item.delta?.type === "text_delta") {
140
- content += item.delta.text;
141
- emit({ type: "delta", text: item.delta.text });
142
- }
143
- if (item.delta?.type === "input_json_delta" && current)
144
- current.arguments += item.delta.partial_json;
145
- }
146
- });
147
- return { content, toolCalls: calls };
148
- }
149
- async function runAgent(request, emit) {
150
- const provider = (request.provider || "").toLowerCase();
151
- if (!request.message?.trim()) {
152
- emit({ type: "delta", text: "告诉我你想查找、保存、修改、删除,还是总结哪些记忆?" });
153
- emit({ type: "done", toolCalls: [] });
154
- return;
155
- }
156
- if (!["openai", "openai-compatible", "anthropic", "local"].includes(provider))
157
- throw new Error("unsupported_provider");
158
- if (!request.base_url?.trim())
159
- throw new Error("missing_base_url");
160
- if (provider !== "local" && !request.api_key)
161
- throw new Error("missing_api_key");
162
- const { client, transport } = await connectMemoryMcp();
163
- const toolResult = await client.listTools();
164
- const tools = normalizeTools(toolResult.tools);
165
- const toolCalls = [];
166
- const history = (request.history ?? []).slice(-12).filter((message) => message.content).map((message) => ({ role: message.role, content: message.content }));
167
- const messages = [{ role: "system", content: `${agentSystemPrompt}${request.scope ? ` 默认 Scope:${request.scope}` : ""}${request.auto_context === false ? " 不需要自动读取上下文。" : " 每个新任务开始时先调用 memory-get-context;后续如需具体历史信息,使用 memory-search。"}` }, ...history];
168
- if (!history.some((message) => message.role === "user" && message.content === request.message))
169
- messages.push({ role: "user", content: request.message });
170
- try {
171
- if (request.auto_context !== false) {
172
- const overviewRequest = isMemoryOverviewRequest(request.message);
173
- const context = await client.callTool({ name: "memory-get-context", arguments: { scope: request.scope || null, limit: overviewRequest ? 50 : 10 } }, CallToolResultSchema);
174
- const contextText = context.content?.filter((item) => item.type === "text").map((item) => item.text ?? "").join("\n") || "{}";
175
- const parsed = (() => { try {
176
- return JSON.parse(contextText);
177
- }
178
- catch {
179
- return contextText;
180
- } })();
181
- toolCalls.push({ name: "memory-get-context", label: toolLabels["memory-get-context"], count: Array.isArray(parsed) ? parsed.length : parsed?.memories?.length });
182
- emit({ type: "tool", tool: toolCalls.at(-1) });
183
- messages[0] = { role: "system", content: `${messages[0].content}\n\n已自动读取 memory-get-context,结果如下。请基于这些固定上下文回答;如需查找具体历史信息,请调用 memory-search,不要重复调用 memory-get-context:\n${contextText}` };
184
- if (overviewRequest) {
185
- const listed = await client.callTool({ name: "memory_list", arguments: { scope: request.scope || null, limit: 100 } }, CallToolResultSchema);
186
- const listedText = listed.content?.filter((item) => item.type === "text").map((item) => item.text ?? "").join("\n") || "[]";
187
- const listedParsed = (() => { try {
188
- return JSON.parse(listedText);
189
- }
190
- catch {
191
- return [];
192
- } })();
193
- const listCall = { name: "memory_list", label: toolLabels.memory_list, count: Array.isArray(listedParsed) ? listedParsed.length : undefined };
194
- toolCalls.push(listCall);
195
- emit({ type: "tool", tool: listCall });
196
- messages[0] = { role: "system", content: `${messages[0].content}\n\n这是针对“有哪些记忆/总结特点”问题通过 MCP memory_list 获取的完整记忆列表。请直接基于它回答,不要声称没有上下文:\n${listedText}` };
197
- }
198
- }
199
- for (let round = 0; round < 6; round += 1) {
200
- const result = provider === "anthropic" ? await anthropicRound(request, messages, tools, emit) : provider === "openai" ? await openAiResponsesRound(request, messages, tools, emit) : await openAiRound(request, messages, tools, emit);
201
- if (!result.toolCalls.length) {
202
- emit({ type: "done", toolCalls });
203
- return;
204
- }
205
- if (provider === "anthropic")
206
- messages.push({ role: "assistant", content: [...(result.content ? [{ type: "text", text: result.content }] : []), ...result.toolCalls.map((call) => ({ type: "tool_use", id: call.id, name: call.name, input: JSON.parse(call.arguments || "{}") }))] });
207
- else
208
- messages.push({ role: "assistant", content: result.content || null, tool_calls: result.toolCalls.map((call) => ({ id: call.id, type: "function", function: { name: call.name, arguments: call.arguments } })) });
209
- for (const call of result.toolCalls) {
210
- const args = JSON.parse(call.arguments || "{}");
211
- if (request.scope && ["memory-get-context", "memory-search", "memory_list", "memory_store"].includes(call.name) && args.scope == null)
212
- args.scope = request.scope;
213
- if (request.scope && call.name === "memory_update" && args.patch && typeof args.patch === "object" && args.patch.scope == null)
214
- args.patch = { ...args.patch, scope: request.scope };
215
- const output = await client.callTool({ name: call.name, arguments: args }, CallToolResultSchema);
216
- const outputContent = output.content;
217
- const text = outputContent?.filter((item) => item.type === "text").map((item) => item.text ?? "").join("\n") || JSON.stringify(output);
218
- if (provider === "anthropic")
219
- messages.push({ role: "user", content: [{ type: "tool_result", tool_use_id: call.id, content: text }] });
220
- else
221
- messages.push({ role: "tool", tool_call_id: call.id, content: text });
222
- const count = (() => { try {
223
- const parsed = JSON.parse(text);
224
- return Array.isArray(parsed) ? parsed.length : parsed?.memories?.length ?? undefined;
225
- }
226
- catch {
227
- return undefined;
228
- } })();
229
- const latest = { name: call.name, label: toolLabels[call.name] ?? call.name, ...(count === undefined ? {} : { count }) };
230
- toolCalls.push(latest);
231
- emit({ type: "tool", tool: latest });
232
- }
233
- }
234
- throw new Error("agent_tool_loop_limit");
235
- }
236
- finally {
237
- await transport.close().catch(() => undefined);
238
- }
239
- }
240
- const codexAgentsPath = join(process.env.CODEX_HOME ?? join(homedir(), ".codex"), "AGENTS.md");
241
- const codexConfigPath = join(process.env.CODEX_HOME ?? join(homedir(), ".codex"), "config.toml");
242
- const codexGuidanceStart = "<!-- memory-one:codex:start -->";
243
- const codexGuidanceEnd = "<!-- memory-one:codex:end -->";
244
- const codexGuidance = `${codexGuidanceStart}
245
- ## Memory One
246
-
247
- Before starting any new user task, call the Memory One MCP tool \`memory-get-context\` once to retrieve persistent context.
248
-
249
- - When working in a project, resolve the Git repository root and pass its absolute directory path as \`scope\`. Use the same scope when storing project-specific memory; omit \`scope\` for general preferences and knowledge.
250
- - Apply the returned persistent preferences and project conventions before planning, answering, editing files, or calling task-specific tools.
251
- - Do not repeat \`memory-get-context\` during follow-up turns of the same task. Use \`memory-search\` when a specific historical preference, decision, fact, or project convention is needed.
252
- - Only save a memory when the user explicitly asks to remember or save it; if a durable preference, decision, project convention, personal fact, or correction seems worth keeping but the user did not ask, ask for confirmation instead. Use \`memory_update\` only after the user explicitly asks to change an existing memory. Do not save clearly transient, one-off details.
253
- - Do not skip retrieval merely because the task appears self-contained.
254
- ${codexGuidanceEnd}`;
255
- async function readCodexAgents() {
256
- try {
257
- return await readFile(codexAgentsPath, "utf8");
258
- }
259
- catch (error) {
260
- if (error.code === "ENOENT")
261
- return "";
262
- throw error;
263
- }
264
- }
265
- function removeManagedCodexGuidance(content) {
266
- const start = content.indexOf(codexGuidanceStart);
267
- const end = content.indexOf(codexGuidanceEnd, start + codexGuidanceStart.length);
268
- if (start < 0 || end < 0)
269
- return content;
270
- const before = content.slice(0, start).trimEnd();
271
- const after = content.slice(end + codexGuidanceEnd.length).trimStart();
272
- return [before, after].filter(Boolean).join("\n\n");
273
- }
274
- async function getCodexIntegration() {
275
- const content = await readCodexAgents();
276
- const start = content.indexOf(codexGuidanceStart);
277
- const end = content.indexOf(codexGuidanceEnd, start + codexGuidanceStart.length);
278
- const installed = start >= 0 && end >= 0;
279
- const managed = installed ? content.slice(start, end + codexGuidanceEnd.length) : "";
280
- return { path: codexAgentsPath, status: installed ? managed === codexGuidance ? "configured" : "update_available" : "not_configured" };
281
- }
282
- async function installCodexIntegration() {
283
- const content = await readCodexAgents();
284
- const preserved = removeManagedCodexGuidance(content);
285
- await mkdir(dirname(codexAgentsPath), { recursive: true });
286
- await writeFile(codexAgentsPath, `${preserved ? `${preserved}\n\n` : ""}${codexGuidance}\n`, "utf8");
287
- return getCodexIntegration();
288
- }
289
- async function readCodexConfig() {
290
- try {
291
- return await readFile(codexConfigPath, "utf8");
292
- }
293
- catch (error) {
294
- if (error.code === "ENOENT")
295
- return "";
296
- throw error;
297
- }
298
- }
299
- function isMemoryOneCodexSection(name) {
300
- const normalized = name.replace(/"memory-one"/g, "memory-one");
301
- return normalized === "mcp_servers.memory-one" || normalized.startsWith("mcp_servers.memory-one.");
302
- }
303
- function getMemoryOneCodexSections(content) {
304
- const sections = [];
305
- let current = null;
306
- for (const line of content.split("\n")) {
307
- const section = line.match(/^\s*\[([^\]]+)]\s*(?:#.*)?$/);
308
- if (section) {
309
- if (current)
310
- sections.push(current.join("\n"));
311
- current = isMemoryOneCodexSection(section[1].trim()) ? [line] : null;
312
- }
313
- else if (current)
314
- current.push(line);
315
- }
316
- if (current)
317
- sections.push(current.join("\n"));
318
- return sections.join("\n");
319
- }
320
- function removeMemoryOneCodexSections(content) {
321
- const lines = [];
322
- let removing = false;
323
- for (const line of content.split("\n")) {
324
- const section = line.match(/^\s*\[([^\]]+)]\s*(?:#.*)?$/);
325
- if (section)
326
- removing = isMemoryOneCodexSection(section[1].trim());
327
- if (!removing)
328
- lines.push(line);
329
- }
330
- return lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
331
- }
332
- function readTomlString(source, key) {
333
- const match = source.match(new RegExp(`(?:^|[,{]\\s*)${key}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*"|'[^']*')`, "im"));
334
- if (!match)
335
- return null;
336
- if (match[1].startsWith("'"))
337
- return match[1].slice(1, -1);
338
- try {
339
- return JSON.parse(match[1]);
340
- }
341
- catch {
342
- return null;
343
- }
344
- }
345
- async function getCodexMcpIntegration(expectedEndpoint) {
346
- const content = await readCodexConfig();
347
- const sections = getMemoryOneCodexSections(content);
348
- const endpoint = readTomlString(sections, "url");
349
- const authorization = readTomlString(sections, "Authorization");
350
- const key = authorization?.match(/^Bearer\s+(.+)$/i)?.[1]?.trim();
351
- const keyRecord = key ? store.verifyMcpKey(key) : null;
352
- const authRequired = store.getMcpConfig().use_bearer_key;
353
- const configured = endpoint === expectedEndpoint && (authRequired ? Boolean(keyRecord) : !authorization);
354
- return {
355
- path: codexConfigPath,
356
- detected: Boolean(content),
357
- endpoint,
358
- auth_required: authRequired,
359
- configured_key_id: keyRecord?.id ?? null,
360
- status: configured ? "configured" : sections ? "update_available" : "not_configured",
361
- };
362
- }
363
- async function installCodexMcpIntegration(endpoint, keyId) {
364
- const authRequired = store.getMcpConfig().use_bearer_key;
365
- const key = authRequired && keyId ? store.getMcpKey(keyId) : null;
366
- if (authRequired && (!key?.secret || key.revoked_at))
367
- throw new Error("mcp_key_not_found");
368
- const content = await readCodexConfig();
369
- const preserved = removeMemoryOneCodexSections(content);
370
- const section = `[mcp_servers.memory-one]\nurl = ${JSON.stringify(endpoint)}${authRequired ? `\nhttp_headers = { Authorization = ${JSON.stringify(`Bearer ${key.secret}`)} }` : ""}`;
371
- await mkdir(dirname(codexConfigPath), { recursive: true });
372
- await writeFile(codexConfigPath, `${preserved ? `${preserved}\n\n` : ""}${section}\n`, "utf8");
373
- return getCodexMcpIntegration(endpoint);
374
- }
375
- async function trackToolCall(toolName, action) {
376
- const startedAt = performance.now();
377
- try {
378
- const result = await action();
379
- store.recordToolCall(toolName, true, Math.round((performance.now() - startedAt) * 100) / 100);
380
- return result;
381
- }
382
- catch (error) {
383
- store.recordToolCall(toolName, false, Math.round((performance.now() - startedAt) * 100) / 100);
384
- throw error;
385
- }
386
- }
387
- const mcpToolNames = ["memory_store", "memory-search", "memory-get-context", "memory_get", "memory_list", "memory_update", "memory_delete", "memory_feedback"];
388
- function createMcpServer(allowedTools) {
389
- const mcp = new McpServer({ name: "memory-one", version: "0.1.0" }, {
390
- instructions: "Memory One provides durable experience for every task. At the start of each new user task, call memory-get-context before planning, answering, editing, or using task-specific tools. It returns persistent preferences and project conventions without a query. Do not repeat it during follow-up turns; use memory-search for focused retrieval of specific historical information. Only save a memory when the user explicitly asks to remember or save it; if a durable preference, decision, project convention, personal fact, or correction seems worth keeping but the user did not ask, ask for confirmation instead. Use memory_update only after the user explicitly asks to change an existing memory. Do not save clearly transient, one-off details. Use memory_feedback after retrieved memories prove useful or unhelpful. Only use memory_delete when the user explicitly asks to forget a specific memory. Scope is optional."
391
- });
392
- const registerTool = (name, description, schema, handler) => {
393
- if (!allowedTools || allowedTools.has(name))
394
- mcp.tool(name, description, schema, handler);
395
- };
396
- registerTool("memory_store", "Use only when the user explicitly asks to remember or save a durable preference, decision, project convention, personal fact, or correction. If the information may be useful but the user did not ask to save it, ask for confirmation first. Do not store transient chatter or one-off task details. Scope is an optional category such as a project directory, work area, or session.", {
397
- content: z.string(), kind: z.string().default("fact"), scope: z.string().nullable().optional(),
398
- session_id: z.string().nullable().optional(), source: z.string().nullable().optional(), occurred_at: z.string().nullable().optional(),
399
- confidence: z.number().default(1), importance: z.number().default(0.5), metadata: z.record(z.string(), z.unknown()).nullable().optional(),
400
- }, async (input) => trackToolCall("memory_store", () => json(exposeMemory(store.create(toStorageInput(input))))));
401
- registerTool("memory-search", "Use when a specific historical preference, decision, fact, or project convention may matter. Search with 5–12 concise high-signal concepts or identifiers, not the full user message; use scope as an optional category filter.", { query: z.string(), scope: z.string().nullable().optional(), limit: z.number().int().default(20) }, async ({ query, scope, limit }) => trackToolCall("memory-search", () => json(exposeMemories(store.recordRecalls(store.search(query, storageScope, scope && scope !== "global" ? scope : null, limit))))));
402
- registerTool("memory-get-context", "Use once at the beginning of each new user task. Return persistent memories marked with metadata.always_include=true, prioritizing the current project before global memories. Do not pass a query; use memory-search for focused follow-up retrieval. When working in a Git repository, pass the repository root's absolute directory path as scope. Scope is optional.", { scope: z.string().nullable().optional(), limit: z.number().int().default(10) }, async ({ scope, limit }) => trackToolCall("memory-get-context", () => { const category = scope && scope !== "global" ? scope : null; const memories = store.persistentContext(storageScope, category, limit); return json({ scope: scope ?? "global", strategy: category ? "persistent_project_plus_global" : "persistent_global", memories: exposeMemories(store.recordRecalls(memories)) }); }));
403
- registerTool("memory_get", "Use after memory-search or memory-get-context returns a memory ID and you need the complete record before relying on or updating it.", { memory_id: z.string() }, async ({ memory_id }) => trackToolCall("memory_get", () => { const memory = store.get(memory_id); return json(exposeMemory(memory ? store.recordRecalls([memory])[0] : { error: "memory_not_found" })); }));
404
- registerTool("memory_list", "Use when reviewing recent memories, auditing what has been saved, or preparing context without a specific search query. Scope is an optional category filter.", { scope: z.string().nullable().optional(), limit: z.number().int().default(50) }, async ({ scope, limit }) => trackToolCall("memory_list", () => json(exposeMemories(store.list(storageScope, scope && scope !== "global" ? scope : null, limit)))));
405
- registerTool("memory_update", "Use only when the user explicitly asks to correct, refine, or supersede a stored memory. Fetch the record first when needed, then update only the changed fields. Scope is an optional category.", { memory_id: z.string(), patch: z.record(z.string(), z.unknown()) }, async ({ memory_id, patch }) => trackToolCall("memory_update", () => json(exposeMemory(store.update(memory_id, toStoragePatch(patch)) ?? { error: "memory_not_found" }))));
406
- registerTool("memory_delete", "Use only when the user explicitly asks to forget or delete a specific memory. This is a soft delete.", { memory_id: z.string() }, async ({ memory_id }) => trackToolCall("memory_delete", () => json({ deleted: store.delete(memory_id) })));
407
- registerTool("memory_feedback", "Use after applying a retrieved memory or when the user indicates that a memory was useful or not useful. Record that relevance signal so future retrieval can improve.", { memory_id: z.string(), useful: z.boolean() }, async ({ memory_id, useful }) => {
408
- return trackToolCall("memory_feedback", () => {
409
- const item = store.get(memory_id);
410
- if (!item)
411
- return json({ error: "memory_not_found" });
412
- return json(exposeMemory(store.update(memory_id, { importance: Math.max(0, Math.min(1, item.importance + (useful ? 0.05 : -0.05))) })));
413
- });
414
- });
415
- return mcp;
416
- }
417
- const app = Fastify({ logger: true });
418
- const publicDir = join(fileURLToPath(new URL(".", import.meta.url)), "../public");
419
- const packageJsonPath = join(fileURLToPath(new URL(".", import.meta.url)), "../package.json");
420
- const packageVersion = JSON.parse(readFileSync(packageJsonPath, "utf8")).version;
421
- app.register(fastifyStatic, { root: publicDir, prefix: "/" });
422
- for (const frontendRoute of ["/", "/timeline", "/preferences", "/scopes", "/tags", "/settings", "/mcp-service"]) {
423
- app.get(frontendRoute, async (_, reply) => reply.sendFile("index.html"));
424
- }
425
- app.get("/api/memories", async (request) => { const q = request.query; return store.list(q.scope ?? "user", q.project ?? null, Number(q.limit ?? 50)); });
426
- app.get("/api/version", async () => {
427
- try {
428
- const response = await fetch("https://registry.npmjs.org/@ccjr1120%2Fmemory-one/latest", { signal: AbortSignal.timeout(2000), headers: { accept: "application/json" } });
429
- if (!response.ok)
430
- return { current: packageVersion, latest: null, updateAvailable: false };
431
- const latest = String((await response.json()).version ?? "");
432
- return { current: packageVersion, latest: latest || null, updateAvailable: Boolean(latest && latest !== packageVersion) };
433
- }
434
- catch {
435
- return { current: packageVersion, latest: null, updateAvailable: false };
436
- }
437
- });
438
- app.get("/api/search", async (request) => { const q = request.query; return store.recordRecalls(store.search(q.query, q.scope ?? "user", q.project ?? null, Number(q.limit ?? 20))); });
439
- app.get("/api/memories/most-recalled", async (request) => { const q = request.query; return store.mostRecalled(q.scope ?? "user", q.project ?? null, Number(q.limit ?? 5)); });
440
- app.get("/api/app-config", async () => store.getAppConfig());
441
- app.put("/api/app-config", async (request, reply) => {
442
- const language = request.body?.language;
443
- if (language !== "zh" && language !== "en")
444
- return reply.code(422).send({ detail: "language_required" });
445
- return store.saveAppConfig(language);
446
- });
447
- app.get("/api/mcp/stats", async () => store.getToolStats());
448
- app.get("/api/mcp/config", async () => store.getMcpConfig());
449
- app.put("/api/mcp/config", async (request) => store.saveMcpConfig(Boolean(request.body?.use_bearer_key)));
450
- app.get("/api/mcp/keys", async () => store.listMcpKeys());
451
- app.post("/api/mcp/keys", async (request, reply) => {
452
- const body = request.body ?? {};
453
- const name = body.name?.trim() || "未命名 Key";
454
- const allowedTools = [...new Set((body.allowed_tools ?? []).filter((tool) => mcpToolNames.includes(tool)))];
455
- const created = store.createMcpKey(name, allowedTools);
456
- return reply.code(201).send({ key: created.key, key_record: created.keyRecord });
457
- });
458
- app.patch("/api/mcp/keys/:id", async (request, reply) => {
459
- const { id } = request.params;
460
- const body = request.body ?? {};
461
- const patch = {
462
- ...(body.name === undefined ? {} : { name: body.name.trim() || "未命名 Key" }),
463
- ...(body.allowed_tools === undefined ? {} : { allowed_tools: [...new Set(body.allowed_tools.filter((tool) => mcpToolNames.includes(tool)))] }),
464
- };
465
- const key = store.updateMcpKey(id, patch);
466
- return key ? key : reply.code(404).send({ detail: "mcp_key_not_found" });
467
- });
468
- app.delete("/api/mcp/keys/:id", async (request, reply) => {
469
- const { id } = request.params;
470
- return { revoked: store.revokeMcpKey(id) };
471
- });
472
- function emitExecutionEvent(executionId, type, data, reply) {
473
- const event = store.appendAgentExecutionEvent(executionId, type, data);
474
- if (reply && !reply.raw.destroyed)
475
- reply.raw.write(sseEvent(type, data, event.id));
476
- return event;
477
- }
478
- app.get("/api/agent/config", async () => store.getAgentConfig());
479
- app.put("/api/agent/config", async (request, reply) => {
480
- const body = request.body ?? {};
481
- return reply.send(store.saveAgentConfig(body));
482
- });
483
- app.get("/api/agent/messages", async () => store.listAgentMessages());
484
- app.post("/api/agent/messages", async (request, reply) => {
485
- const body = request.body;
486
- if (!body?.id || !body.role || typeof body.content !== "string")
487
- return reply.code(422).send({ detail: "message_required" });
488
- return store.saveAgentMessage({ id: body.id, role: body.role, content: body.content, toolCalls: body.toolCalls });
489
- });
490
- app.post("/api/agent/executions", async (request, reply) => {
491
- const body = request.body ?? {};
492
- if (!body.message?.trim())
493
- return reply.code(422).send({ detail: "message_required" });
494
- const userMessageId = randomUUID();
495
- const assistantMessageId = randomUUID();
496
- const history = (body.history ?? []).slice(-12);
497
- store.saveAgentMessage({ id: userMessageId, role: "user", content: body.message });
498
- store.saveAgentMessage({ id: assistantMessageId, role: "assistant", content: "", toolCalls: [] });
499
- const execution = store.createAgentExecution({ messageIds: [userMessageId, assistantMessageId] });
500
- const turn = agentTurnQueue.then(async () => {
501
- let content = "";
502
- let toolCalls = [];
503
- try {
504
- await runAgent({ ...body, history }, (event) => {
505
- if (event.type === "delta")
506
- content += event.text;
507
- if (event.type === "tool")
508
- toolCalls = [...toolCalls, event.tool];
509
- if (event.type === "done")
510
- toolCalls = event.toolCalls;
511
- store.saveAgentMessage({ id: assistantMessageId, role: "assistant", content, toolCalls });
512
- });
513
- store.updateAgentExecution(execution.id, { status: "completed" });
514
- }
515
- catch (error) {
516
- const errorMessage = error instanceof Error ? error.message : "agent_request_failed";
517
- store.updateAgentExecution(execution.id, { status: "failed", error: errorMessage });
518
- emitExecutionEvent(execution.id, "status", { status: "failed", error: errorMessage });
519
- }
520
- });
521
- agentTurnQueue = turn.catch(() => undefined);
522
- return reply.code(202).send(store.getAgentExecution(execution.id));
523
- });
524
- app.get("/api/agent/executions/:executionId/events", async (request, reply) => {
525
- const { executionId } = request.params;
526
- if (!store.getAgentExecution(executionId))
527
- return reply.code(404).send({ detail: "execution_not_found" });
528
- const header = request.headers["last-event-id"];
529
- const query = request.query;
530
- const after = Number(header ?? query.after ?? 0) || 0;
531
- reply.hijack();
532
- reply.raw.writeHead(200, { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache, no-transform", connection: "keep-alive", "x-accel-buffering": "no" });
533
- let closed = false;
534
- request.raw.on("close", () => { closed = true; });
535
- const send = () => { for (const event of store.listAgentExecutionEvents(executionId, after))
536
- if (!closed)
537
- reply.raw.write(sseEvent(event.type, event.data, event.id)); };
538
- send();
539
- const timer = setInterval(() => { if (closed) {
540
- clearInterval(timer);
541
- return;
542
- } send(); }, 250);
543
- request.raw.on("close", () => { clearInterval(timer); if (!reply.raw.destroyed)
544
- reply.raw.end(); });
545
- });
546
- app.get("/api/agent/executions/:executionId", async (request, reply) => {
547
- const { executionId } = request.params;
548
- const execution = store.getAgentExecution(executionId);
549
- return execution ? execution : reply.code(404).send({ detail: "execution_not_found" });
550
- });
551
- app.post("/api/agent/executions/:executionId/messages", async (request, reply) => {
552
- const { executionId } = request.params;
553
- const execution = store.getAgentExecution(executionId);
554
- const body = request.body ?? {};
555
- if (!execution)
556
- return reply.code(404).send({ detail: "execution_not_found" });
557
- if (execution.status !== "completed" && execution.status !== "failed" && execution.status !== "cancelled")
558
- return reply.code(409).send({ detail: "execution_in_progress" });
559
- if (!body.message?.trim())
560
- return reply.code(422).send({ detail: "message_required" });
561
- const userMessageId = randomUUID();
562
- const assistantMessageId = randomUUID();
563
- store.saveAgentMessage({ id: userMessageId, role: "user", content: body.message });
564
- store.saveAgentMessage({ id: assistantMessageId, role: "assistant", content: "", toolCalls: [] });
565
- store.updateAgentExecution(executionId, { status: "running", messageIds: [...execution.messageIds, userMessageId, assistantMessageId], error: null });
566
- const history = [...execution.messages, { id: userMessageId, role: "user", content: body.message }].slice(-12).map((message) => ({ role: message.role, content: message.content }));
567
- const turn = agentTurnQueue.then(async () => {
568
- let content = "";
569
- let toolCalls = [];
570
- try {
571
- await runAgent({ ...body, history }, (event) => { if (event.type === "delta")
572
- content += event.text; if (event.type === "tool")
573
- toolCalls = [...toolCalls, event.tool]; if (event.type === "done")
574
- toolCalls = event.toolCalls; store.saveAgentMessage({ id: assistantMessageId, role: "assistant", content, toolCalls }); });
575
- store.updateAgentExecution(executionId, { status: "completed" });
576
- }
577
- catch (error) {
578
- store.updateAgentExecution(executionId, { status: "failed", error: error instanceof Error ? error.message : "agent_request_failed" });
579
- }
580
- });
581
- agentTurnQueue = turn.catch(() => undefined);
582
- return reply.code(202).send(store.getAgentExecution(executionId));
583
- });
584
- app.post("/api/agent/executions/:executionId/cancel", async (request, reply) => {
585
- const { executionId } = request.params;
586
- const execution = store.getAgentExecution(executionId);
587
- if (!execution)
588
- return reply.code(404).send({ detail: "execution_not_found" });
589
- if (execution.status === "running") {
590
- store.updateAgentExecution(executionId, { status: "cancelled" });
591
- emitExecutionEvent(executionId, "status", { status: "cancelled" });
592
- }
593
- return store.getAgentExecution(executionId);
594
- });
595
- app.post("/api/agent/executions/:executionId/retry", async (request, reply) => {
596
- const { executionId } = request.params;
597
- const execution = store.getAgentExecution(executionId);
598
- if (!execution)
599
- return reply.code(404).send({ detail: "execution_not_found" });
600
- if (execution.status === "running")
601
- return reply.code(409).send({ detail: "execution_in_progress" });
602
- const lastUser = [...execution.messages].reverse().find((message) => message.role === "user");
603
- if (!lastUser)
604
- return reply.code(422).send({ detail: "message_required" });
605
- const body = request.body ?? {};
606
- const messageIds = [...execution.messageIds, randomUUID(), randomUUID()];
607
- const userMessageId = messageIds.at(-2);
608
- const assistantMessageId = messageIds.at(-1);
609
- store.saveAgentMessage({ id: userMessageId, role: "user", content: lastUser.content });
610
- store.saveAgentMessage({ id: assistantMessageId, role: "assistant", content: "", toolCalls: [] });
611
- store.updateAgentExecution(executionId, { status: "running", messageIds, error: null });
612
- const history = [...execution.messages.filter((message) => message.id !== lastUser.id), { role: "user", content: lastUser.content }].slice(-12).map((message) => ({ role: message.role, content: message.content }));
613
- const turn = agentTurnQueue.then(async () => {
614
- let content = "";
615
- let toolCalls = [];
616
- try {
617
- await runAgent({ ...body, message: lastUser.content, history }, (event) => { if (event.type === "delta")
618
- content += event.text; if (event.type === "tool")
619
- toolCalls = [...toolCalls, event.tool]; if (event.type === "done")
620
- toolCalls = event.toolCalls; store.saveAgentMessage({ id: assistantMessageId, role: "assistant", content, toolCalls }); });
621
- store.updateAgentExecution(executionId, { status: "completed" });
622
- }
623
- catch (error) {
624
- store.updateAgentExecution(executionId, { status: "failed", error: error instanceof Error ? error.message : "agent_request_failed" });
625
- }
626
- });
627
- agentTurnQueue = turn.catch(() => undefined);
628
- return reply.code(202).send(store.getAgentExecution(executionId));
629
- });
630
- app.post("/api/agent/chat", async (request, reply) => {
631
- try {
632
- let text = "";
633
- const toolCalls = [];
634
- await runAgent(request.body ?? {}, (event) => { if (event.type === "delta")
635
- text += event.text; if (event.type === "done")
636
- toolCalls.push(...event.toolCalls); });
637
- return { reply: text, toolCalls };
638
- }
639
- catch (error) {
640
- request.log.error(error);
641
- return reply.code(500).send({ detail: error instanceof Error ? error.message : "agent_request_failed" });
642
- }
643
- });
644
- app.post("/api/agent/models", async (request, reply) => {
645
- const body = request.body ?? {};
646
- const provider = (body.provider || "").toLowerCase();
647
- if (!["openai", "openai-compatible", "anthropic", "local"].includes(provider))
648
- return reply.code(422).send({ detail: "unsupported_provider" });
649
- const base = (body.base_url?.trim() || "").replace(/\/$/, "");
650
- if (!base)
651
- return reply.code(422).send({ detail: "base_url_required" });
652
- const headers = { accept: "application/json" };
653
- if (provider === "anthropic") {
654
- headers["anthropic-version"] = "2023-06-01";
655
- if (body.api_key?.trim())
656
- headers["x-api-key"] = body.api_key.trim();
657
- }
658
- else if (body.api_key?.trim()) {
659
- headers.authorization = `Bearer ${body.api_key.trim()}`;
660
- }
661
- try {
662
- const response = await fetch(`${base}/models`, { headers });
663
- const text = await response.text();
664
- let payload = null;
665
- try {
666
- payload = text ? JSON.parse(text) : null;
667
- }
668
- catch {
669
- payload = null;
670
- }
671
- if (!response.ok)
672
- return reply.code(response.status >= 400 && response.status < 600 ? response.status : 502).send({ detail: payload?.error?.message || payload?.message || text || "model_list_failed" });
673
- const entries = Array.isArray(payload?.data) ? payload.data : Array.isArray(payload) ? payload : [];
674
- const models = [...new Set(entries.map((item) => typeof item === "string" ? item : item?.id).filter((item) => typeof item === "string" && item.trim().length > 0))];
675
- return { models };
676
- }
677
- catch (error) {
678
- request.log.error(error);
679
- return reply.code(502).send({ detail: "model_list_unreachable" });
680
- }
681
- });
682
- app.post("/api/agent/stream", async (request, reply) => {
683
- const body = (request.body ?? {});
684
- const userMessageId = body.user_message_id;
685
- const assistantMessageId = body.assistant_message_id;
686
- if (userMessageId && body.message)
687
- store.saveAgentMessage({ id: userMessageId, role: "user", content: body.message });
688
- if (assistantMessageId)
689
- store.saveAgentMessage({ id: assistantMessageId, role: "assistant", content: "", toolCalls: [] });
690
- let assistantContent = "";
691
- let assistantTools = [];
692
- reply.hijack();
693
- reply.raw.writeHead(200, { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache, no-transform", connection: "keep-alive", "x-accel-buffering": "no" });
694
- const turn = agentTurnQueue.then(async () => {
695
- const allMessages = store.listAgentMessages(1000).filter((item) => item.content);
696
- const currentIndex = userMessageId ? allMessages.findIndex((item) => item.id === userMessageId) : allMessages.length - 1;
697
- const storedHistory = allMessages.slice(0, Math.max(0, currentIndex)).map((item) => ({ role: item.role, content: item.content })).slice(-12);
698
- await runAgent({ ...body, history: storedHistory }, (event) => {
699
- if (event.type === "delta")
700
- assistantContent += event.text;
701
- if (event.type === "tool")
702
- assistantTools = [...assistantTools, event.tool];
703
- if (event.type === "done")
704
- assistantTools = event.toolCalls;
705
- if (assistantMessageId)
706
- store.saveAgentMessage({ id: assistantMessageId, role: "assistant", content: assistantContent, toolCalls: assistantTools });
707
- if (!reply.raw.destroyed)
708
- reply.raw.write(sseEvent(event.type, event.type === "delta" ? { text: event.text } : event.type === "tool" ? event.tool : { toolCalls: event.toolCalls }));
709
- });
710
- });
711
- agentTurnQueue = turn.catch(() => undefined);
712
- try {
713
- await turn;
714
- }
715
- catch (error) {
716
- request.log.error(error);
717
- reply.raw.write(sseEvent("error", { detail: error instanceof Error ? error.message : "agent_request_failed" }));
718
- }
719
- finally {
720
- reply.raw.end();
721
- }
722
- });
723
- app.get("/api/integrations/codex", async () => getCodexIntegration());
724
- app.post("/api/integrations/codex/install", async () => installCodexIntegration());
725
- app.get("/api/integrations/codex/mcp", async (request) => {
726
- const { endpoint = "" } = request.query;
727
- return getCodexMcpIntegration(endpoint);
728
- });
729
- app.post("/api/integrations/codex/mcp/install", async (request, reply) => {
730
- const body = request.body ?? {};
731
- if (!body.endpoint)
732
- return reply.code(422).send({ detail: "codex_mcp_config_required" });
733
- try {
734
- return await installCodexMcpIntegration(body.endpoint, body.key_id);
735
- }
736
- catch (error) {
737
- if (error instanceof Error && error.message === "mcp_key_not_found")
738
- return reply.code(404).send({ detail: error.message });
739
- throw error;
740
- }
741
- });
742
- 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" }); });
743
- app.post("/api/memories", async (request, reply) => { const payload = request.body; if (!payload?.content)
744
- return reply.code(422).send({ detail: "content_required" }); return store.create(payload); });
745
- app.patch("/api/memories/:id", async (request, reply) => { const { id } = request.params; const item = store.update(id, request.body); return item ? item : reply.code(404).send({ detail: "memory_not_found" }); });
746
- app.delete("/api/memories/:id", async (request) => { const { id } = request.params; return { deleted: store.delete(id) }; });
747
- app.all("/mcp", async (request, reply) => reply.redirect("/mcp/", 307));
748
- app.all("/mcp/", async (request, reply) => {
749
- const internal = request.headers["x-memory-one-internal"] === internalMcpToken;
750
- const authRequired = store.getMcpConfig().use_bearer_key;
751
- const authorization = request.headers.authorization;
752
- const secret = authorization?.match(/^Bearer\s+(.+)$/i)?.[1]?.trim();
753
- const key = authRequired && secret ? store.verifyMcpKey(secret) : null;
754
- if (!internal && authRequired && (!secret || !key))
755
- return reply.code(401).header("www-authenticate", "Bearer").send({ error: secret ? "invalid_mcp_key" : "mcp_key_required" });
756
- const allowedTools = key && key.allowed_tools.length ? new Set(key.allowed_tools) : undefined;
757
- const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
758
- const mcp = createMcpServer(allowedTools);
759
- await mcp.connect(transport);
760
- await transport.handleRequest(request.raw, reply.raw, request.body);
761
- reply.hijack();
762
- });
1
+ import { createApp } from "./app.js";
763
2
  const port = Number(process.env.MEMORY_PORT ?? 8765);
3
+ const { app } = createApp();
764
4
  app.listen({ host: "127.0.0.1", port }).then(() => console.log(`Memory One listening on http://127.0.0.1:${port}`));
765
5
  //# sourceMappingURL=server.js.map