memento-mcp 0.2.5 → 0.3.0

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
@@ -10,7 +10,7 @@ AI agents have anterograde amnesia — every session starts blank. The Memento P
10
10
  npx memento-mcp init
11
11
  ```
12
12
 
13
- This creates `.memento.json`, configures Claude Code hooks, and sets up the MCP server all in one command. Restart Claude Code to load the new config.
13
+ This creates `.memento.json`, detects your agent (Claude Code, Codex, Gemini CLI, OpenCode), writes the correct MCP config, and sets up hooks (Claude Code only) all in one command.
14
14
 
15
15
  ---
16
16
 
@@ -43,29 +43,55 @@ Save the `api_key` from the response — you'll need it next.
43
43
 
44
44
  ### Step 2: Configure your MCP client
45
45
 
46
- **Claude Code (project-level):** Create `.mcp.json` in your project root.
46
+ **Claude Code** `.mcp.json` in your project root (or `~/.claude.json` globally):
47
47
 
48
- **Claude Code (global):** Add to `~/.claude.json` under `"mcpServers"`.
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "memento": {
52
+ "command": "npx",
53
+ "args": ["-y", "memento-mcp"]
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ **OpenAI Codex** — `.codex/config.toml`:
60
+
61
+ ```toml
62
+ [mcp_servers.memento]
63
+ command = "npx"
64
+ args = ["-y", "memento-mcp"]
65
+ ```
49
66
 
50
- **Claude Desktop:** Add to your `claude_desktop_config.json`.
67
+ **Gemini CLI** `.gemini/settings.json`:
51
68
 
52
69
  ```json
53
70
  {
54
71
  "mcpServers": {
55
72
  "memento": {
56
- "command": "node",
57
- "args": ["/home/you/memento-protocol/src/index.js"],
58
- "env": {
59
- "MEMENTO_API_KEY": "mp_live_your_key_here",
60
- "MEMENTO_API_URL": "https://memento-api.myrakrusemark.workers.dev",
61
- "MEMENTO_WORKSPACE": "my-project"
62
- }
73
+ "command": "npx",
74
+ "args": ["-y", "memento-mcp"]
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ **OpenCode** — `opencode.json`:
81
+
82
+ ```json
83
+ {
84
+ "mcp": {
85
+ "memento": {
86
+ "type": "local",
87
+ "command": ["npx", "-y", "memento-mcp"],
88
+ "enabled": true
63
89
  }
64
90
  }
65
91
  }
66
92
  ```
67
93
 
68
- > **Tip:** Replace the path with the actual absolute path to `src/index.js` in your clone. Run `echo "$(pwd)/src/index.js"` from inside the repo to get it.
94
+ Set credentials via `.memento.json` (created by `npx memento-mcp init`) or environment variables `MEMENTO_API_KEY`, `MEMENTO_API_URL`, `MEMENTO_WORKSPACE`.
69
95
 
70
96
  ### Step 3: Restart your client
71
97
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memento-mcp",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "mcpName": "io.github.myrakrusemark/memento-protocol",
5
5
  "description": "The Memento Protocol — persistent memory for AI agents",
6
6
  "type": "module",
package/src/cli.js CHANGED
@@ -96,6 +96,14 @@ function httpsPost(url, body) {
96
96
  // File writers
97
97
  // ---------------------------------------------------------------------------
98
98
 
99
+ function readJsonFile(filePath) {
100
+ try {
101
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
106
+
99
107
  function writeJsonFile(filePath, data) {
100
108
  const dir = path.dirname(filePath);
101
109
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
@@ -103,14 +111,7 @@ function writeJsonFile(filePath, data) {
103
111
  }
104
112
 
105
113
  function mergeJsonFile(filePath, data) {
106
- let existing = {};
107
- if (fs.existsSync(filePath)) {
108
- try {
109
- existing = JSON.parse(fs.readFileSync(filePath, "utf8"));
110
- } catch {
111
- // Corrupt file — overwrite
112
- }
113
- }
114
+ const existing = readJsonFile(filePath) || {};
114
115
  const merged = deepMerge(existing, data);
115
116
  writeJsonFile(filePath, merged);
116
117
  }
@@ -127,6 +128,106 @@ function appendToGitignore(cwd, line) {
127
128
  return true;
128
129
  }
129
130
 
131
+ // ---------------------------------------------------------------------------
132
+ // Agent registry — per-agent MCP config writers
133
+ // ---------------------------------------------------------------------------
134
+
135
+ const MCP_SERVER_ENTRY = {
136
+ command: "npx",
137
+ args: ["-y", "memento-mcp"],
138
+ };
139
+
140
+ function writeMcpJson(cwd) {
141
+ const filePath = path.join(cwd, ".mcp.json");
142
+ const existing = readJsonFile(filePath) || {};
143
+ deepMerge(existing, { mcpServers: { memento: MCP_SERVER_ENTRY } });
144
+ writeJsonFile(filePath, existing);
145
+ return ".mcp.json";
146
+ }
147
+
148
+ function writeCodexToml(cwd) {
149
+ const dir = path.join(cwd, ".codex");
150
+ fs.mkdirSync(dir, { recursive: true });
151
+ const filePath = path.join(dir, "config.toml");
152
+
153
+ let content = "";
154
+ try {
155
+ content = fs.readFileSync(filePath, "utf8");
156
+ } catch {
157
+ /* file doesn't exist */
158
+ }
159
+
160
+ // Skip if memento section already exists
161
+ if (/\[mcp_servers\.memento\]/.test(content)) {
162
+ return ".codex/config.toml (already configured)";
163
+ }
164
+
165
+ const section = `\n[mcp_servers.memento]\ncommand = "npx"\nargs = ["-y", "memento-mcp"]\n`;
166
+ const separator = content && !content.endsWith("\n") ? "\n" : "";
167
+ fs.writeFileSync(filePath, content + separator + section);
168
+ return ".codex/config.toml";
169
+ }
170
+
171
+ function writeGeminiJson(cwd) {
172
+ const dir = path.join(cwd, ".gemini");
173
+ fs.mkdirSync(dir, { recursive: true });
174
+ const filePath = path.join(dir, "settings.json");
175
+ const existing = readJsonFile(filePath) || {};
176
+ deepMerge(existing, { mcpServers: { memento: MCP_SERVER_ENTRY } });
177
+ writeJsonFile(filePath, existing);
178
+ return ".gemini/settings.json";
179
+ }
180
+
181
+ function writeOpencodeJson(cwd) {
182
+ const filePath = path.join(cwd, "opencode.json");
183
+ const existing = readJsonFile(filePath) || {};
184
+ deepMerge(existing, {
185
+ mcp: {
186
+ memento: {
187
+ type: "local",
188
+ command: ["npx", "-y", "memento-mcp"],
189
+ enabled: true,
190
+ },
191
+ },
192
+ });
193
+ writeJsonFile(filePath, existing);
194
+ return "opencode.json";
195
+ }
196
+
197
+ const AGENTS = {
198
+ "claude-code": {
199
+ name: "Claude Code",
200
+ detect: (cwd) => fs.existsSync(path.join(cwd, ".claude")),
201
+ configWriter: writeMcpJson,
202
+ hasHooks: true,
203
+ nextSteps: "Restart Claude Code to activate.",
204
+ },
205
+ codex: {
206
+ name: "OpenAI Codex",
207
+ detect: (cwd) => fs.existsSync(path.join(cwd, ".codex")),
208
+ configWriter: writeCodexToml,
209
+ hasHooks: false,
210
+ nextSteps: "Run `codex` in this directory — memento tools load automatically.",
211
+ },
212
+ gemini: {
213
+ name: "Gemini CLI",
214
+ detect: (cwd) => fs.existsSync(path.join(cwd, ".gemini")),
215
+ configWriter: writeGeminiJson,
216
+ hasHooks: false,
217
+ nextSteps: "Run `gemini` in this directory — memento tools load automatically.",
218
+ },
219
+ opencode: {
220
+ name: "OpenCode",
221
+ detect: (cwd) => fs.existsSync(path.join(cwd, "opencode.json")),
222
+ configWriter: writeOpencodeJson,
223
+ hasHooks: false,
224
+ nextSteps: "Run `opencode` in this directory — memento tools load automatically.",
225
+ },
226
+ };
227
+
228
+ // Exported for testing
229
+ export { AGENTS, writeMcpJson, writeCodexToml, writeGeminiJson, writeOpencodeJson };
230
+
130
231
  // ---------------------------------------------------------------------------
131
232
  // CLI
132
233
  // ---------------------------------------------------------------------------
@@ -193,26 +294,76 @@ async function runInit() {
193
294
  false,
194
295
  );
195
296
 
196
- // 4. Hooks
197
- console.log("\nClaude Code hooks (automate recall + distillation):");
198
- const enableUserPrompt = await askYesNo(
199
- rl,
200
- " UserPromptSubmit — recall on every message?",
201
- true
202
- );
203
- const enableStop = await askYesNo(rl, " Stop — autonomous recall after responses?", true);
204
- const enablePreCompact = await askYesNo(
205
- rl,
206
- " PreCompact — distill memories before context compression?",
207
- true
208
- );
297
+ // 4. Agent detection + selection
298
+ const agentKeys = Object.keys(AGENTS);
299
+ const detected = agentKeys.filter((key) => AGENTS[key].detect(cwd));
300
+
301
+ console.log("\nDetected agents:");
302
+ const markers = {
303
+ "claude-code": ".claude/",
304
+ codex: ".codex/",
305
+ gemini: ".gemini/",
306
+ opencode: "opencode.json",
307
+ };
308
+ for (const key of agentKeys) {
309
+ const agent = AGENTS[key];
310
+ const isDetected = detected.includes(key);
311
+ const mark = isDetected ? "✓" : " ";
312
+ const hint = isDetected ? ` (${markers[key]} found)` : "";
313
+ console.log(` ${mark} ${agent.name}${hint}`);
314
+ }
315
+
316
+ console.log("\n Configure for which agents?");
317
+ agentKeys.forEach((key, i) => {
318
+ const mark = detected.includes(key) ? " ✓" : "";
319
+ console.log(` ${i + 1}. ${AGENTS[key].name}${mark}`);
320
+ });
321
+
322
+ const defaultSelection =
323
+ detected.length > 0
324
+ ? detected.map((key) => agentKeys.indexOf(key) + 1).join(",")
325
+ : "1";
326
+ const selectionStr = await ask(rl, "\n Enter numbers, comma-separated", defaultSelection);
327
+
328
+ const selectedIndices = selectionStr
329
+ .split(",")
330
+ .map((s) => parseInt(s.trim(), 10))
331
+ .filter((n) => n >= 1 && n <= agentKeys.length);
332
+ const selectedAgents = [...new Set(selectedIndices.map((i) => agentKeys[i - 1]))];
333
+
334
+ if (selectedAgents.length === 0) {
335
+ console.log(" No agents selected. Defaulting to Claude Code.");
336
+ selectedAgents.push("claude-code");
337
+ }
338
+
339
+ const hasClaude = selectedAgents.includes("claude-code");
340
+
341
+ // 5. Hooks — only if Claude Code selected
342
+ let enableUserPrompt = false;
343
+ let enableStop = false;
344
+ let enablePreCompact = false;
209
345
  let enableSessionStart = false;
210
- if (enableIdentity) {
211
- enableSessionStart = await askYesNo(
346
+
347
+ if (hasClaude) {
348
+ console.log("\nClaude Code hooks (automate recall + distillation):");
349
+ enableUserPrompt = await askYesNo(
350
+ rl,
351
+ " UserPromptSubmit — recall on every message?",
352
+ true,
353
+ );
354
+ enableStop = await askYesNo(rl, " Stop — autonomous recall after responses?", true);
355
+ enablePreCompact = await askYesNo(
212
356
  rl,
213
- " SessionStartinject identity + active items at startup?",
214
- true
357
+ " PreCompactdistill memories before context compression?",
358
+ true,
215
359
  );
360
+ if (enableIdentity) {
361
+ enableSessionStart = await askYesNo(
362
+ rl,
363
+ " SessionStart — inject identity + active items at startup?",
364
+ true,
365
+ );
366
+ }
216
367
  }
217
368
 
218
369
  rl.close();
@@ -221,6 +372,7 @@ async function runInit() {
221
372
  const config = {
222
373
  apiKey,
223
374
  workspace,
375
+ agents: selectedAgents,
224
376
  features: {
225
377
  images: enableImages,
226
378
  identity: enableIdentity,
@@ -235,15 +387,16 @@ async function runInit() {
235
387
 
236
388
  const created = [];
237
389
 
238
- // 5. Write .memento.json
390
+ // 6. Write .memento.json
239
391
  const configPath = path.join(cwd, ".memento.json");
240
392
  writeJsonFile(configPath, config);
241
393
  created.push(".memento.json");
242
394
 
243
- // 6. Copy hook scripts into .memento/scripts/ for stable paths
244
- // (pointing into the npx cache would break on cache clear or update)
245
- const anyHookEnabled = enableUserPrompt || enableStop || enablePreCompact || enableSessionStart;
246
- if (anyHookEnabled) {
395
+ // 7. Copy hook scripts + write Claude Code settings — gated on hasClaude
396
+ const anyHookEnabled =
397
+ enableUserPrompt || enableStop || enablePreCompact || enableSessionStart;
398
+
399
+ if (hasClaude && anyHookEnabled) {
247
400
  const pkgScriptsDir = path.resolve(__dirname, "..", "scripts");
248
401
  const localScriptsDir = path.join(cwd, ".memento", "scripts");
249
402
  if (!fs.existsSync(localScriptsDir))
@@ -264,7 +417,7 @@ async function runInit() {
264
417
  }
265
418
  created.push(".memento/scripts/");
266
419
 
267
- // 7. Write .claude/settings.local.json (hooks)
420
+ // 8. Write .claude/settings.local.json (hooks)
268
421
  const hooks = {};
269
422
  if (enableUserPrompt) {
270
423
  hooks.UserPromptSubmit = [
@@ -305,14 +458,16 @@ async function runInit() {
305
458
  },
306
459
  ];
307
460
  }
308
-
309
461
  if (enableSessionStart) {
310
462
  hooks.SessionStart = [
311
463
  {
312
464
  hooks: [
313
465
  {
314
466
  type: "command",
315
- command: path.join(localScriptsDir, "memento-sessionstart-identity.sh"),
467
+ command: path.join(
468
+ localScriptsDir,
469
+ "memento-sessionstart-identity.sh",
470
+ ),
316
471
  timeout: 10000,
317
472
  },
318
473
  ],
@@ -325,30 +480,29 @@ async function runInit() {
325
480
  created.push(".claude/settings.local.json");
326
481
  }
327
482
 
328
- // 8. Write .mcp.json
329
- const mcpPath = path.join(cwd, ".mcp.json");
330
- mergeJsonFile(mcpPath, {
331
- mcpServers: {
332
- memento: {
333
- command: "npx",
334
- args: ["-y", "memento-mcp"],
335
- },
336
- },
337
- });
338
- created.push(".mcp.json");
483
+ // 9. Per-agent config files
484
+ for (const agentKey of selectedAgents) {
485
+ const agent = AGENTS[agentKey];
486
+ const result = agent.configWriter(cwd);
487
+ created.push(result);
488
+ }
339
489
 
340
- // 9. Add .memento.json and .memento/scripts/ to .gitignore
490
+ // 10. Add .memento.json and .memento/scripts/ to .gitignore
341
491
  let gitignoreUpdated = false;
342
492
  if (appendToGitignore(cwd, ".memento.json")) gitignoreUpdated = true;
343
493
  if (appendToGitignore(cwd, ".memento/scripts/")) gitignoreUpdated = true;
344
494
  if (gitignoreUpdated) created.push(".gitignore (updated)");
345
495
 
346
- // 10. Summary
496
+ // 11. Summary
347
497
  const labels = {
348
498
  ".memento.json": "workspace config + credentials",
349
499
  ".memento/scripts/": "hook scripts (recall + distillation)",
350
500
  ".claude/settings.local.json": "hooks registered with Claude Code",
351
- ".mcp.json": "MCP server registered",
501
+ ".mcp.json": "MCP server registered (Claude Code)",
502
+ ".codex/config.toml": "MCP server registered (Codex)",
503
+ ".codex/config.toml (already configured)": "MCP server (Codex, skipped)",
504
+ ".gemini/settings.json": "MCP server registered (Gemini CLI)",
505
+ "opencode.json": "MCP server registered (OpenCode)",
352
506
  ".gitignore (updated)": "credentials excluded from git",
353
507
  };
354
508
  const colWidth = Math.max(...created.map((f) => f.length)) + 2;
@@ -358,14 +512,25 @@ async function runInit() {
358
512
  const label = labels[f] || "";
359
513
  console.log(` ${f.padEnd(colWidth)}${label}`);
360
514
  }
361
- console.log("\n Restart Claude Code to activate.");
515
+
516
+ // Per-agent next steps
517
+ console.log("\n Next steps:");
518
+ for (const agentKey of selectedAgents) {
519
+ const agent = AGENTS[agentKey];
520
+ console.log(` · ${agent.name}: ${agent.nextSteps}`);
521
+ }
362
522
  console.log(" Your agent will wake up remembering.\n");
363
523
 
364
- // 11. CLAUDE.md boilerplate
524
+ // 12. CLAUDE.md / AGENTS.md boilerplate
525
+ const hasNonClaude = selectedAgents.some((k) => k !== "claude-code");
526
+ const docTarget = hasNonClaude
527
+ ? "your CLAUDE.md, AGENTS.md, or equivalent"
528
+ : "your CLAUDE.md";
529
+
365
530
  console.log("─".repeat(60));
366
531
  console.log(`
367
- One more step: paste the following into your CLAUDE.md,
368
- or hand it to Claude and ask it to add it. This teaches
532
+ One more step: paste the following into ${docTarget},
533
+ or hand it to your agent and ask it to add it. This teaches
369
534
  your agent the memory discipline Memento expects.
370
535
 
371
536
  ── paste below this line ──────────────────────────────
@@ -392,32 +557,39 @@ before compaction. Trust the hooks. Focus on writing good memories.
392
557
  }
393
558
 
394
559
  // ---------------------------------------------------------------------------
395
- // Entrypoint
560
+ // Entrypoint — only run when this module is the entry point (not imported)
396
561
  // ---------------------------------------------------------------------------
397
562
 
398
- const args = process.argv.slice(2);
563
+ const isMain =
564
+ process.argv[1] &&
565
+ (process.argv[1].endsWith("/cli.js") || process.argv[1].endsWith("memento-mcp"));
399
566
 
400
- if (args[0] === "init") {
401
- runInit().catch((err) => {
402
- console.error(err);
403
- process.exit(1);
404
- });
405
- } else if (args.length === 0) {
406
- // No args — start the MCP server (this is what .mcp.json invokes)
407
- // Must call main() explicitly because the isMainModule guard in index.js
408
- // checks process.argv[1] which still points to cli.js, not index.js.
409
- const { main } = await import("./index.js");
410
- await main();
411
- } else {
412
- console.log(`
567
+ if (isMain) {
568
+ const args = process.argv.slice(2);
569
+
570
+ if (args[0] === "init") {
571
+ runInit().catch((err) => {
572
+ console.error(err);
573
+ process.exit(1);
574
+ });
575
+ } else if (args.length === 0) {
576
+ // No args start the MCP server (this is what .mcp.json invokes)
577
+ // Must call main() explicitly because the isMainModule guard in index.js
578
+ // checks process.argv[1] which still points to cli.js, not index.js.
579
+ const { main } = await import("./index.js");
580
+ await main();
581
+ } else {
582
+ console.log(`
413
583
  Memento Protocol CLI
414
584
 
415
585
  Usage:
416
586
  npx memento-mcp init Set up Memento in the current project
417
587
  npx memento-mcp Start the MCP server (used by .mcp.json)
418
588
 
419
- This creates .memento.json, configures Claude Code hooks,
420
- and sets up the MCP server — all in one command.
589
+ This creates .memento.json, configures your agent's MCP client,
590
+ and sets up hooks (Claude Code) — all in one command.
591
+ Supports Claude Code, Codex, Gemini CLI, and OpenCode.
421
592
  `);
422
- process.exit(1);
593
+ process.exit(1);
594
+ }
423
595
  }
package/src/config.js CHANGED
@@ -14,6 +14,7 @@ import path from "node:path";
14
14
  export const DEFAULTS = {
15
15
  apiUrl: "https://memento-api.myrakrusemark.workers.dev",
16
16
  workspace: "default",
17
+ agents: [],
17
18
  features: { images: false, identity: false },
18
19
  hooks: {
19
20
  "userprompt-recall": { enabled: true, limit: 5, maxLength: 200 },
@@ -69,5 +70,7 @@ export function resolveConfig(startDir = process.cwd()) {
69
70
  hooks[key] = { ...defaults, ...(fileConfig.hooks?.[key] || {}) };
70
71
  }
71
72
 
72
- return { apiKey, apiUrl, workspace, features, hooks };
73
+ const agents = fileConfig.agents || DEFAULTS.agents;
74
+
75
+ return { apiKey, apiUrl, workspace, agents, features, hooks };
73
76
  }