@memoraone/mcp 0.1.20 → 0.1.22

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/dist/cli.cjs CHANGED
@@ -30,7 +30,7 @@ var require_package = __commonJS({
30
30
  "package.json"(exports2, module2) {
31
31
  module2.exports = {
32
32
  name: "@memoraone/mcp",
33
- version: "0.1.20",
33
+ version: "0.1.22",
34
34
  type: "module",
35
35
  main: "dist/index.cjs",
36
36
  bin: {
@@ -88,6 +88,13 @@ function ensureBaseDir() {
88
88
  var fs2 = __toESM(require("fs/promises"), 1);
89
89
  var path2 = __toESM(require("path"), 1);
90
90
  var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
91
+ function normalizeEnvironment(raw) {
92
+ if (raw === void 0 || raw === null || typeof raw !== "string") {
93
+ return void 0;
94
+ }
95
+ const trimmed = raw.trim();
96
+ return trimmed === "" ? void 0 : trimmed;
97
+ }
91
98
  function parseAndValidateM1(content, markerPath) {
92
99
  let parsed;
93
100
  try {
@@ -104,7 +111,8 @@ function parseAndValidateM1(content, markerPath) {
104
111
  }
105
112
  const apiKeyRaw = parsed?.MEMORAONE_API_KEY ?? parsed?.api_key;
106
113
  const apiKey = apiKeyRaw !== void 0 && apiKeyRaw !== null && typeof apiKeyRaw === "string" && apiKeyRaw.trim() !== "" ? apiKeyRaw.trim() : null;
107
- return { projectId: projectId.trim(), apiKey };
114
+ const environment = normalizeEnvironment(parsed?.environment);
115
+ return environment === void 0 ? { projectId: projectId.trim(), apiKey } : { projectId: projectId.trim(), apiKey, environment };
108
116
  }
109
117
  async function resolveProjectIdFromExplicitM1Path() {
110
118
  const raw = process.env.MEMORAONE_M1_PATH;
@@ -114,8 +122,8 @@ async function resolveProjectIdFromExplicitM1Path() {
114
122
  const markerPath = path2.resolve(raw);
115
123
  try {
116
124
  const content = await fs2.readFile(markerPath, "utf8");
117
- const { projectId, apiKey } = parseAndValidateM1(content, markerPath);
118
- return { projectId, apiKey, foundAt: markerPath };
125
+ const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
126
+ return environment === void 0 ? { projectId, apiKey, foundAt: markerPath } : { projectId, apiKey, environment, foundAt: markerPath };
119
127
  } catch (err) {
120
128
  if (err?.code === "ENOENT") {
121
129
  return null;
@@ -129,9 +137,9 @@ async function findM1WalkingUp(workspaceRoot) {
129
137
  const markerPath = path2.join(current, "memoraone.m1");
130
138
  try {
131
139
  const content = await fs2.readFile(markerPath, "utf8");
132
- const { projectId, apiKey } = parseAndValidateM1(content, markerPath);
140
+ const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
133
141
  const repoRoot = path2.dirname(markerPath);
134
- return { projectId, apiKey, repoRoot, markerPath };
142
+ return environment === void 0 ? { projectId, apiKey, repoRoot, markerPath } : { projectId, apiKey, environment, repoRoot, markerPath };
135
143
  } catch (err) {
136
144
  if (err?.code !== "ENOENT") {
137
145
  throw err;
@@ -191,6 +199,7 @@ async function resolveAuthoritativeBinding(workspaceRoot) {
191
199
  workspaceRoot: path2.dirname(explicitBinding.foundAt),
192
200
  m1Path: explicitBinding.foundAt,
193
201
  apiKey: resolved.apiKey,
202
+ ...explicitBinding.environment !== void 0 ? { environment: explicitBinding.environment } : {},
194
203
  bindingSource: "explicit-m1-path",
195
204
  apiKeySource: resolved.apiKeySource
196
205
  };
@@ -208,6 +217,7 @@ async function resolveAuthoritativeBinding(workspaceRoot) {
208
217
  workspaceRoot: binding.repoRoot,
209
218
  m1Path: binding.markerPath,
210
219
  apiKey: resolved.apiKey,
220
+ ...binding.environment !== void 0 ? { environment: binding.environment } : {},
211
221
  bindingSource: "workspace-search",
212
222
  apiKeySource: resolved.apiKeySource
213
223
  };
@@ -223,6 +233,8 @@ function encodeResolvedBinding(binding) {
223
233
  var fs3 = __toESM(require("fs/promises"), 1);
224
234
  var path3 = __toESM(require("path"), 1);
225
235
  var MANAGED_MARKER = "<!-- MemoraOne managed IDE helper -->";
236
+ var GITIGNORE_MEMORAONE_COMMENT = "# MemoraOne local project binding / API key";
237
+ var GITIGNORE_MEMORAONE_ENTRY = "memoraone.m1";
226
238
  var MEMORAONE_MCP_SERVER = {
227
239
  command: "npx",
228
240
  args: ["-y", "@memoraone/mcp"]
@@ -242,6 +254,40 @@ async function pathExists(filePath) {
242
254
  return false;
243
255
  }
244
256
  }
257
+ function gitignoreAlreadyIgnoresMemoraoneM1(content) {
258
+ for (const line of content.split(/\r?\n/)) {
259
+ const trimmed = line.trim();
260
+ if (!trimmed || trimmed.startsWith("#")) continue;
261
+ if (/^\/?memoraone\.m1\s*$/.test(trimmed)) return true;
262
+ }
263
+ return false;
264
+ }
265
+ function memoraoneGitignoreBlock() {
266
+ return `${GITIGNORE_MEMORAONE_COMMENT}
267
+ ${GITIGNORE_MEMORAONE_ENTRY}
268
+ `;
269
+ }
270
+ async function ensureGitignoreMemoraone(repoRoot, opts) {
271
+ if (opts.noGitignore) return "skipped";
272
+ const abs = path3.join(repoRoot, ".gitignore");
273
+ assertUnderRepoRoot(repoRoot, abs);
274
+ let prior = "";
275
+ let existed = false;
276
+ try {
277
+ prior = await fs3.readFile(abs, "utf8");
278
+ existed = true;
279
+ } catch (err) {
280
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
281
+ if (code !== "ENOENT") throw err;
282
+ }
283
+ if (gitignoreAlreadyIgnoresMemoraoneM1(prior)) return "skipped";
284
+ const block = memoraoneGitignoreBlock();
285
+ const separator = existed && prior.length > 0 ? prior.endsWith("\n") ? "\n" : "\n\n" : "";
286
+ const next = (existed ? prior : "") + separator + block;
287
+ if (opts.dryRun) return existed ? "updated" : "created";
288
+ await fs3.writeFile(abs, next, "utf8");
289
+ return existed ? "updated" : "created";
290
+ }
245
291
  async function findRepoRoot(startDir) {
246
292
  let current = path3.resolve(startDir);
247
293
  const root = path3.parse(current).root;
@@ -270,9 +316,12 @@ This repository uses **MemoraOne** via the MCP server named **user-memoraone** (
270
316
  ### Tools
271
317
 
272
318
  - Before answering questions about **prior decisions**, **remembered facts**, **identity or personal recall**, **preferences**, **repo history**, or **what to do next**, call **\`memora_ask_with_memory\`** so replies stay aligned with MemoraOne memory.
273
- - After **meaningful repository changes** (decisions, fixes, new endpoints or schema, migrations, meaningful wiring), call **\`memora_post_event\`** to append a concise timeline note.
319
+ - Use **\`memora_post_event\`** for durable project decisions, wiring, migrations, fixes, and meaningful product behavior changes. Prefer kind \`note\`, \`content.title\`, \`content.body\` (one concise, fact-promotable statement), and metadata \`source\` (e.g. \`cursor\`), \`purpose\`: \`dev-log\`, \`schema\`: \`v1\`.
320
+ - Use **\`memora_log_change_summary\`** for concise code or feature deltas after implementation.
321
+
322
+ Do not spam events for trivial edits, formatting-only changes, or temporary WIP. Prefer concise, fact-promotable statements.
274
323
 
275
- Keep tool usage proportional to the task; do not spam events for trivial edits.
324
+ Example \`content.body\`: "MemoraOne Studio added a live dashboard summary, replacing the static dashboard placeholder with authenticated project and memory activity data."
276
325
  `;
277
326
  }
278
327
  function copilotAndJetBrainsBody(title) {
@@ -285,9 +334,12 @@ This repo is set up to use **MemoraOne** through MCP where your editor exposes i
285
334
  ## Behavior
286
335
 
287
336
  - For questions about **earlier decisions**, **stored facts**, **personal or identity recall**, **preferences**, **project history**, or **recommended next steps**, use MemoraOne memory tools (e.g. **\`memora_ask_with_memory\`**) when available before answering.
288
- - After **substantive changes** to this codebase (feature work, bugfixes, API/schema changes, migrations, meaningful integration steps), record a short timeline note with **\`memora_post_event\`** when that tool is available.
337
+ - Use **\`memora_post_event\`** for durable project decisions, wiring, migrations, fixes, and meaningful product behavior changes. Prefer kind \`note\`, \`content.title\`, \`content.body\` (one concise, fact-promotable statement), and metadata \`source\` (e.g. your agent name), \`purpose\`: \`dev-log\`, \`schema\`: \`v1\`.
338
+ - Use **\`memora_log_change_summary\`** for concise code or feature deltas after implementation.
289
339
 
290
- Use judgment: skip logging for trivial typo-only edits.
340
+ Do not spam events for trivial edits, formatting-only changes, or temporary WIP. Prefer concise, fact-promotable statements.
341
+
342
+ Example \`content.body\`: "MemoraOne Studio added a live dashboard summary, replacing the static dashboard placeholder with authenticated project and memory activity data."
291
343
  `;
292
344
  }
293
345
  function mcpJsonHeader() {
@@ -372,6 +424,7 @@ function parseSetupIdeFlags(argv) {
372
424
  let all = false;
373
425
  let force = false;
374
426
  let dryRun = false;
427
+ let noGitignore = false;
375
428
  const unknown = [];
376
429
  for (const a of argv) {
377
430
  if (a === "--cursor") cursor = true;
@@ -380,6 +433,7 @@ function parseSetupIdeFlags(argv) {
380
433
  else if (a === "--all") all = true;
381
434
  else if (a === "--force") force = true;
382
435
  else if (a === "--dry-run") dryRun = true;
436
+ else if (a === "--no-gitignore") noGitignore = true;
383
437
  else if (a.startsWith("-")) unknown.push(a);
384
438
  }
385
439
  const specific = cursor || vscode || jetbrains;
@@ -389,7 +443,7 @@ function parseSetupIdeFlags(argv) {
389
443
  } else {
390
444
  targets = { cursor, vscode, jetbrains };
391
445
  }
392
- return { targets, force, dryRun, unknown };
446
+ return { targets, force, dryRun, noGitignore, unknown };
393
447
  }
394
448
  function summarizeOutcomes(outcomes) {
395
449
  const created = [];
@@ -422,6 +476,10 @@ async function runSetupIdeFiles(o) {
422
476
  error: "[setup-ide-files] No repo root found (looked for .git or memoraone.m1)."
423
477
  };
424
478
  }
479
+ outcomes[".gitignore"] = await ensureGitignoreMemoraone(repoRoot, {
480
+ dryRun: o.dryRun,
481
+ noGitignore: o.noGitignore ?? false
482
+ });
425
483
  const cursorContent = `---
426
484
  description: MemoraOne MCP \u2014 IDE agent instructions
427
485
  ---
@@ -458,7 +516,7 @@ description: MemoraOne MCP \u2014 IDE agent instructions
458
516
  return { exitCode: 0, repoRoot, outcomes };
459
517
  }
460
518
  async function cliSetupIdeFiles(argv) {
461
- const { targets, force, dryRun, unknown } = parseSetupIdeFlags(argv);
519
+ const { targets, force, dryRun, noGitignore, unknown } = parseSetupIdeFlags(argv);
462
520
  if (unknown.length) {
463
521
  console.error(`[setup-ide-files] Unknown option(s): ${unknown.join(", ")}`);
464
522
  return 1;
@@ -467,7 +525,8 @@ async function cliSetupIdeFiles(argv) {
467
525
  cwd: process.cwd(),
468
526
  targets,
469
527
  force,
470
- dryRun
528
+ dryRun,
529
+ noGitignore
471
530
  });
472
531
  if (result.error) {
473
532
  console.error(result.error);
@@ -492,7 +551,7 @@ if (args.includes("--version") || args.includes("-v")) {
492
551
  }
493
552
  if (args.includes("--help") || args.includes("-h")) {
494
553
  console.log(
495
- "Usage: memoraone-mcp [--version] [--help] [--daemon --project-id <uuid>]\n memoraone-mcp setup-ide-files [--all|--cursor|--vscode|--jetbrains] [--force] [--dry-run]"
554
+ "Usage: memoraone-mcp [--version] [--help] [--daemon --project-id <uuid>]\n memoraone-mcp setup-ide-files [--all|--cursor|--vscode|--jetbrains] [--force] [--dry-run] [--no-gitignore]"
496
555
  );
497
556
  process.exit(0);
498
557
  }
@@ -558,8 +617,9 @@ if (args[0] === "setup-ide-files") {
558
617
  ensureBaseDir();
559
618
  const binding = await resolveBinding();
560
619
  const socketPath = getSocketPath(binding.projectId);
620
+ const environmentLog = binding.environment !== void 0 ? ` environment=${binding.environment}` : "";
561
621
  log(
562
- `authoritative binding project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}`
622
+ `authoritative binding project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}${environmentLog}`
563
623
  );
564
624
  let socket;
565
625
  try {
package/dist/daemon.cjs CHANGED
@@ -53,6 +53,13 @@ function ensureBaseDir() {
53
53
  var fs2 = __toESM(require("fs/promises"), 1);
54
54
  var path2 = __toESM(require("path"), 1);
55
55
  var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
56
+ function normalizeEnvironment(raw) {
57
+ if (raw === void 0 || raw === null || typeof raw !== "string") {
58
+ return void 0;
59
+ }
60
+ const trimmed = raw.trim();
61
+ return trimmed === "" ? void 0 : trimmed;
62
+ }
56
63
  function parseAndValidateM1(content, markerPath) {
57
64
  let parsed2;
58
65
  try {
@@ -69,7 +76,8 @@ function parseAndValidateM1(content, markerPath) {
69
76
  }
70
77
  const apiKeyRaw = parsed2?.MEMORAONE_API_KEY ?? parsed2?.api_key;
71
78
  const apiKey = apiKeyRaw !== void 0 && apiKeyRaw !== null && typeof apiKeyRaw === "string" && apiKeyRaw.trim() !== "" ? apiKeyRaw.trim() : null;
72
- return { projectId: projectId.trim(), apiKey };
79
+ const environment = normalizeEnvironment(parsed2?.environment);
80
+ return environment === void 0 ? { projectId: projectId.trim(), apiKey } : { projectId: projectId.trim(), apiKey, environment };
73
81
  }
74
82
  async function resolveProjectIdFromExplicitM1Path() {
75
83
  const raw = process.env.MEMORAONE_M1_PATH;
@@ -79,8 +87,8 @@ async function resolveProjectIdFromExplicitM1Path() {
79
87
  const markerPath = path2.resolve(raw);
80
88
  try {
81
89
  const content = await fs2.readFile(markerPath, "utf8");
82
- const { projectId, apiKey } = parseAndValidateM1(content, markerPath);
83
- return { projectId, apiKey, foundAt: markerPath };
90
+ const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
91
+ return environment === void 0 ? { projectId, apiKey, foundAt: markerPath } : { projectId, apiKey, environment, foundAt: markerPath };
84
92
  } catch (err) {
85
93
  if (err?.code === "ENOENT") {
86
94
  return null;
@@ -94,9 +102,9 @@ async function findM1WalkingUp(workspaceRoot) {
94
102
  const markerPath = path2.join(current, "memoraone.m1");
95
103
  try {
96
104
  const content = await fs2.readFile(markerPath, "utf8");
97
- const { projectId, apiKey } = parseAndValidateM1(content, markerPath);
105
+ const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
98
106
  const repoRoot = path2.dirname(markerPath);
99
- return { projectId, apiKey, repoRoot, markerPath };
107
+ return environment === void 0 ? { projectId, apiKey, repoRoot, markerPath } : { projectId, apiKey, environment, repoRoot, markerPath };
100
108
  } catch (err) {
101
109
  if (err?.code !== "ENOENT") {
102
110
  throw err;
@@ -156,6 +164,7 @@ async function resolveAuthoritativeBinding(workspaceRoot) {
156
164
  workspaceRoot: path2.dirname(explicitBinding.foundAt),
157
165
  m1Path: explicitBinding.foundAt,
158
166
  apiKey: resolved.apiKey,
167
+ ...explicitBinding.environment !== void 0 ? { environment: explicitBinding.environment } : {},
159
168
  bindingSource: "explicit-m1-path",
160
169
  apiKeySource: resolved.apiKeySource
161
170
  };
@@ -173,6 +182,7 @@ async function resolveAuthoritativeBinding(workspaceRoot) {
173
182
  workspaceRoot: binding.repoRoot,
174
183
  m1Path: binding.markerPath,
175
184
  apiKey: resolved.apiKey,
185
+ ...binding.environment !== void 0 ? { environment: binding.environment } : {},
176
186
  bindingSource: "workspace-search",
177
187
  apiKeySource: resolved.apiKeySource
178
188
  };
@@ -194,6 +204,7 @@ function decodeResolvedBinding(value) {
194
204
  const workspaceRoot = parsed2?.workspaceRoot;
195
205
  const m1Path = parsed2?.m1Path;
196
206
  const apiKey = parsed2?.apiKey;
207
+ const environment = normalizeEnvironment(parsed2?.environment);
197
208
  const bindingSource = parsed2?.bindingSource;
198
209
  const apiKeySource = parsed2?.apiKeySource;
199
210
  if (!projectId || typeof projectId !== "string" || !uuidRegex.test(projectId.trim())) {
@@ -219,6 +230,7 @@ function decodeResolvedBinding(value) {
219
230
  workspaceRoot,
220
231
  m1Path,
221
232
  apiKey: typeof apiKey === "string" && apiKey.trim() !== "" ? apiKey.trim() : null,
233
+ ...environment !== void 0 ? { environment } : {},
222
234
  bindingSource,
223
235
  apiKeySource
224
236
  };
@@ -541,6 +553,7 @@ async function registerRepoSource(client, projectId, repoPath, ideType) {
541
553
 
542
554
  // src/tools/postEvent.ts
543
555
  var import_v42 = require("zod/v4");
556
+ var postEventDescription = 'Append a durable project-change note to the MemoraOne timeline. Use after meaningful repository or project changes: decisions, fixes, new endpoints, schema changes, migrations, important wiring, or durable product behavior changes \u2014 not for trivial edits, formatting-only changes, or temporary WIP. Recommended shape: kind "note"; content.title (concise title); content.body (one durable, fact-promotable project-change statement); metadata.source (agent name, e.g. "cursor"); metadata.purpose "dev-log"; metadata.schema "v1".';
544
557
  var postEventShape = {
545
558
  kind: import_v42.z.string().min(1),
546
559
  actor: import_v42.z.object({
@@ -649,6 +662,9 @@ var setProjectShape = {
649
662
  projectId: import_v411.z.string().min(1).optional()
650
663
  };
651
664
 
665
+ // src/tools/bindingStatus.ts
666
+ var bindingStatusShape = {};
667
+
652
668
  // src/tools/handlers/postEvent.ts
653
669
  var import_v412 = require("zod/v4");
654
670
  var crypto3 = __toESM(require("crypto"), 1);
@@ -721,6 +737,32 @@ var postEventInputSchema = import_v412.z.object({
721
737
  content: import_v412.z.record(import_v412.z.string(), import_v412.z.any()),
722
738
  metadata: import_v412.z.record(import_v412.z.string(), import_v412.z.any()).optional()
723
739
  });
740
+ function buildPostEventContentFields(content) {
741
+ if (typeof content.message === "string") {
742
+ return { message: content.message };
743
+ }
744
+ if (typeof content.text === "string") {
745
+ return { message: content.text };
746
+ }
747
+ const title = typeof content.title === "string" ? content.title : void 0;
748
+ const body = typeof content.body === "string" ? content.body : void 0;
749
+ if (title !== void 0 || body !== void 0) {
750
+ const message = body ?? title ?? "";
751
+ const structuredContent = {};
752
+ for (const [key, value] of Object.entries(content)) {
753
+ if (key === "message" || key === "text") continue;
754
+ structuredContent[key] = value;
755
+ }
756
+ const new_value = {
757
+ message,
758
+ ...title !== void 0 ? { title } : {},
759
+ ...body !== void 0 ? { body } : {},
760
+ content: structuredContent
761
+ };
762
+ return { message, new_value };
763
+ }
764
+ return { message: JSON.stringify(content) };
765
+ }
724
766
  async function handlePostEvent(client, args) {
725
767
  const nonce = crypto3.randomBytes(8).toString("hex");
726
768
  console.error(
@@ -732,7 +774,7 @@ async function handlePostEvent(client, args) {
732
774
  throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
733
775
  }
734
776
  const content = parsed2.content ?? {};
735
- const message = typeof content.message === "string" ? content.message : typeof content.text === "string" ? content.text : JSON.stringify(content);
777
+ const { message, new_value } = buildPostEventContentFields(content);
736
778
  const body = {
737
779
  kind: parsed2.kind,
738
780
  message,
@@ -742,6 +784,7 @@ async function handlePostEvent(client, args) {
742
784
  identifier: config2.agentName,
743
785
  ...parsed2.actor.id ? { id: parsed2.actor.id } : {}
744
786
  },
787
+ ...new_value ? { new_value } : {},
745
788
  ...parsed2.metadata ? { metadata: parsed2.metadata } : {}
746
789
  };
747
790
  try {
@@ -926,7 +969,8 @@ async function handleAskWithMemory(client, args) {
926
969
  }
927
970
  return {
928
971
  answer: res.answer,
929
- used: res.used ?? {}
972
+ used: res.used ?? {},
973
+ ...res.retrieval !== void 0 ? { retrieval: res.retrieval } : {}
930
974
  };
931
975
  }
932
976
 
@@ -1471,6 +1515,27 @@ async function handleSetProject(args) {
1471
1515
  return { ok: true, projectKey: resolvedProjectKey };
1472
1516
  }
1473
1517
 
1518
+ // src/tools/handlers/bindingStatus.ts
1519
+ function buildBindingStatus(binding) {
1520
+ const status = {
1521
+ projectId: binding.projectId,
1522
+ workspaceRoot: binding.workspaceRoot,
1523
+ m1Path: binding.m1Path,
1524
+ bindingSource: binding.bindingSource,
1525
+ apiKeySource: binding.apiKeySource
1526
+ };
1527
+ if (binding.environment !== void 0) {
1528
+ status.environment = binding.environment;
1529
+ }
1530
+ return status;
1531
+ }
1532
+ function handleBindingStatus(binding) {
1533
+ if (!binding) {
1534
+ throw new Error("[memoraone-mcp] Binding status unavailable (not initialized)");
1535
+ }
1536
+ return buildBindingStatus(binding);
1537
+ }
1538
+
1474
1539
  // src/index.ts
1475
1540
  var notInitializedResult = {
1476
1541
  content: [
@@ -1654,6 +1719,7 @@ async function main(opts = {}) {
1654
1719
  projectId: null,
1655
1720
  apiKeySource: null,
1656
1721
  apiKeyFingerprint: null,
1722
+ authoritativeBinding: opts.authoritativeBinding ?? null,
1657
1723
  ideType: void 0
1658
1724
  };
1659
1725
  let workspaceRoot;
@@ -1703,7 +1769,7 @@ async function main(opts = {}) {
1703
1769
  const registeredToolNames = [];
1704
1770
  server.tool(
1705
1771
  "memora_post_event",
1706
- "Forward an event to MemoraOne timeline",
1772
+ postEventDescription,
1707
1773
  postEventShape,
1708
1774
  async (args) => runWithSessionContext(sessionContext, async () => {
1709
1775
  if (!runtime.client || !runtime.projectId) return notInitializedResult;
@@ -1779,6 +1845,19 @@ async function main(opts = {}) {
1779
1845
  })
1780
1846
  );
1781
1847
  registeredToolNames.push("memora_set_project");
1848
+ server.tool(
1849
+ "memora_status",
1850
+ "Return non-secret project binding metadata for this MCP session",
1851
+ bindingStatusShape,
1852
+ async () => runWithSessionContext(sessionContext, async () => {
1853
+ if (!runtime.authoritativeBinding) return notInitializedResult;
1854
+ const result = handleBindingStatus(runtime.authoritativeBinding);
1855
+ return {
1856
+ content: [{ type: "text", text: JSON.stringify(result) }]
1857
+ };
1858
+ })
1859
+ );
1860
+ registeredToolNames.push("memora_status");
1782
1861
  registerToolWithWorklog(
1783
1862
  server,
1784
1863
  runtime,
@@ -1929,10 +2008,12 @@ async function main(opts = {}) {
1929
2008
  runtime.projectId = projectId;
1930
2009
  runtime.apiKeySource = binding.apiKeySource;
1931
2010
  runtime.apiKeyFingerprint = fingerprintApiKey(apiKeyToUse);
2011
+ runtime.authoritativeBinding = binding;
1932
2012
  runtime.client = new memoraClient_default(config2, projectId, apiKeyToUse);
1933
2013
  workspaceRoot = binding.workspaceRoot;
2014
+ const environmentLog = binding.environment !== void 0 ? ` environment=${binding.environment}` : "";
1934
2015
  process.stderr.write(
1935
- `[memoraone-mcp] registering workspace source bindingSource=${binding.bindingSource} workspaceRoot=${workspaceRoot ?? "(unset)"} m1Path=${binding.m1Path}
2016
+ `[memoraone-mcp] registering workspace source bindingSource=${binding.bindingSource} workspaceRoot=${workspaceRoot ?? "(unset)"} m1Path=${binding.m1Path}${environmentLog}
1936
2017
  `
1937
2018
  );
1938
2019
  await registerRepoSource(
@@ -1946,8 +2027,9 @@ async function main(opts = {}) {
1946
2027
  console.error("[memoraone-mcp][auth] project_id:", projectId);
1947
2028
  console.error("[memoraone-mcp][auth] api_key source:", binding.apiKeySource);
1948
2029
  }
2030
+ const bindingEnvironmentLog = binding.environment !== void 0 ? ` environment=${binding.environment}` : "";
1949
2031
  console.error(
1950
- `[memoraone-mcp] ${sessionLabel} authoritative binding: project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}`
2032
+ `[memoraone-mcp] ${sessionLabel} authoritative binding: project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}${bindingEnvironmentLog}`
1951
2033
  );
1952
2034
  bindingReadyResolve?.(runtime.client);
1953
2035
  return server.server._oninitialize(request);
package/dist/index.cjs CHANGED
@@ -281,6 +281,13 @@ var memoraClient_default = MemoraClient;
281
281
  var fs2 = __toESM(require("fs/promises"), 1);
282
282
  var path2 = __toESM(require("path"), 1);
283
283
  var uuidRegex2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
284
+ function normalizeEnvironment(raw) {
285
+ if (raw === void 0 || raw === null || typeof raw !== "string") {
286
+ return void 0;
287
+ }
288
+ const trimmed = raw.trim();
289
+ return trimmed === "" ? void 0 : trimmed;
290
+ }
284
291
  function parseAndValidateM1(content, markerPath) {
285
292
  let parsed2;
286
293
  try {
@@ -297,7 +304,8 @@ function parseAndValidateM1(content, markerPath) {
297
304
  }
298
305
  const apiKeyRaw = parsed2?.MEMORAONE_API_KEY ?? parsed2?.api_key;
299
306
  const apiKey = apiKeyRaw !== void 0 && apiKeyRaw !== null && typeof apiKeyRaw === "string" && apiKeyRaw.trim() !== "" ? apiKeyRaw.trim() : null;
300
- return { projectId: projectId.trim(), apiKey };
307
+ const environment = normalizeEnvironment(parsed2?.environment);
308
+ return environment === void 0 ? { projectId: projectId.trim(), apiKey } : { projectId: projectId.trim(), apiKey, environment };
301
309
  }
302
310
  async function resolveProjectIdFromExplicitM1Path() {
303
311
  const raw = process.env.MEMORAONE_M1_PATH;
@@ -307,8 +315,8 @@ async function resolveProjectIdFromExplicitM1Path() {
307
315
  const markerPath = path2.resolve(raw);
308
316
  try {
309
317
  const content = await fs2.readFile(markerPath, "utf8");
310
- const { projectId, apiKey } = parseAndValidateM1(content, markerPath);
311
- return { projectId, apiKey, foundAt: markerPath };
318
+ const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
319
+ return environment === void 0 ? { projectId, apiKey, foundAt: markerPath } : { projectId, apiKey, environment, foundAt: markerPath };
312
320
  } catch (err) {
313
321
  if (err?.code === "ENOENT") {
314
322
  return null;
@@ -322,9 +330,9 @@ async function findM1WalkingUp(workspaceRoot) {
322
330
  const markerPath = path2.join(current, "memoraone.m1");
323
331
  try {
324
332
  const content = await fs2.readFile(markerPath, "utf8");
325
- const { projectId, apiKey } = parseAndValidateM1(content, markerPath);
333
+ const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
326
334
  const repoRoot = path2.dirname(markerPath);
327
- return { projectId, apiKey, repoRoot, markerPath };
335
+ return environment === void 0 ? { projectId, apiKey, repoRoot, markerPath } : { projectId, apiKey, environment, repoRoot, markerPath };
328
336
  } catch (err) {
329
337
  if (err?.code !== "ENOENT") {
330
338
  throw err;
@@ -384,6 +392,7 @@ async function resolveAuthoritativeBinding(workspaceRoot) {
384
392
  workspaceRoot: path2.dirname(explicitBinding.foundAt),
385
393
  m1Path: explicitBinding.foundAt,
386
394
  apiKey: resolved.apiKey,
395
+ ...explicitBinding.environment !== void 0 ? { environment: explicitBinding.environment } : {},
387
396
  bindingSource: "explicit-m1-path",
388
397
  apiKeySource: resolved.apiKeySource
389
398
  };
@@ -401,6 +410,7 @@ async function resolveAuthoritativeBinding(workspaceRoot) {
401
410
  workspaceRoot: binding.repoRoot,
402
411
  m1Path: binding.markerPath,
403
412
  apiKey: resolved.apiKey,
413
+ ...binding.environment !== void 0 ? { environment: binding.environment } : {},
404
414
  bindingSource: "workspace-search",
405
415
  apiKeySource: resolved.apiKeySource
406
416
  };
@@ -481,6 +491,7 @@ async function registerRepoSource(client, projectId, repoPath, ideType) {
481
491
 
482
492
  // src/tools/postEvent.ts
483
493
  var import_v42 = require("zod/v4");
494
+ var postEventDescription = 'Append a durable project-change note to the MemoraOne timeline. Use after meaningful repository or project changes: decisions, fixes, new endpoints, schema changes, migrations, important wiring, or durable product behavior changes \u2014 not for trivial edits, formatting-only changes, or temporary WIP. Recommended shape: kind "note"; content.title (concise title); content.body (one durable, fact-promotable project-change statement); metadata.source (agent name, e.g. "cursor"); metadata.purpose "dev-log"; metadata.schema "v1".';
484
495
  var postEventShape = {
485
496
  kind: import_v42.z.string().min(1),
486
497
  actor: import_v42.z.object({
@@ -589,6 +600,9 @@ var setProjectShape = {
589
600
  projectId: import_v411.z.string().min(1).optional()
590
601
  };
591
602
 
603
+ // src/tools/bindingStatus.ts
604
+ var bindingStatusShape = {};
605
+
592
606
  // src/tools/handlers/postEvent.ts
593
607
  var import_v412 = require("zod/v4");
594
608
  var crypto3 = __toESM(require("crypto"), 1);
@@ -661,6 +675,32 @@ var postEventInputSchema = import_v412.z.object({
661
675
  content: import_v412.z.record(import_v412.z.string(), import_v412.z.any()),
662
676
  metadata: import_v412.z.record(import_v412.z.string(), import_v412.z.any()).optional()
663
677
  });
678
+ function buildPostEventContentFields(content) {
679
+ if (typeof content.message === "string") {
680
+ return { message: content.message };
681
+ }
682
+ if (typeof content.text === "string") {
683
+ return { message: content.text };
684
+ }
685
+ const title = typeof content.title === "string" ? content.title : void 0;
686
+ const body = typeof content.body === "string" ? content.body : void 0;
687
+ if (title !== void 0 || body !== void 0) {
688
+ const message = body ?? title ?? "";
689
+ const structuredContent = {};
690
+ for (const [key, value] of Object.entries(content)) {
691
+ if (key === "message" || key === "text") continue;
692
+ structuredContent[key] = value;
693
+ }
694
+ const new_value = {
695
+ message,
696
+ ...title !== void 0 ? { title } : {},
697
+ ...body !== void 0 ? { body } : {},
698
+ content: structuredContent
699
+ };
700
+ return { message, new_value };
701
+ }
702
+ return { message: JSON.stringify(content) };
703
+ }
664
704
  async function handlePostEvent(client, args) {
665
705
  const nonce = crypto3.randomBytes(8).toString("hex");
666
706
  console.error(
@@ -672,7 +712,7 @@ async function handlePostEvent(client, args) {
672
712
  throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
673
713
  }
674
714
  const content = parsed2.content ?? {};
675
- const message = typeof content.message === "string" ? content.message : typeof content.text === "string" ? content.text : JSON.stringify(content);
715
+ const { message, new_value } = buildPostEventContentFields(content);
676
716
  const body = {
677
717
  kind: parsed2.kind,
678
718
  message,
@@ -682,6 +722,7 @@ async function handlePostEvent(client, args) {
682
722
  identifier: config2.agentName,
683
723
  ...parsed2.actor.id ? { id: parsed2.actor.id } : {}
684
724
  },
725
+ ...new_value ? { new_value } : {},
685
726
  ...parsed2.metadata ? { metadata: parsed2.metadata } : {}
686
727
  };
687
728
  try {
@@ -866,7 +907,8 @@ async function handleAskWithMemory(client, args) {
866
907
  }
867
908
  return {
868
909
  answer: res.answer,
869
- used: res.used ?? {}
910
+ used: res.used ?? {},
911
+ ...res.retrieval !== void 0 ? { retrieval: res.retrieval } : {}
870
912
  };
871
913
  }
872
914
 
@@ -1411,6 +1453,27 @@ async function handleSetProject(args) {
1411
1453
  return { ok: true, projectKey: resolvedProjectKey };
1412
1454
  }
1413
1455
 
1456
+ // src/tools/handlers/bindingStatus.ts
1457
+ function buildBindingStatus(binding) {
1458
+ const status = {
1459
+ projectId: binding.projectId,
1460
+ workspaceRoot: binding.workspaceRoot,
1461
+ m1Path: binding.m1Path,
1462
+ bindingSource: binding.bindingSource,
1463
+ apiKeySource: binding.apiKeySource
1464
+ };
1465
+ if (binding.environment !== void 0) {
1466
+ status.environment = binding.environment;
1467
+ }
1468
+ return status;
1469
+ }
1470
+ function handleBindingStatus(binding) {
1471
+ if (!binding) {
1472
+ throw new Error("[memoraone-mcp] Binding status unavailable (not initialized)");
1473
+ }
1474
+ return buildBindingStatus(binding);
1475
+ }
1476
+
1414
1477
  // src/index.ts
1415
1478
  var notInitializedResult = {
1416
1479
  content: [
@@ -1594,6 +1657,7 @@ async function main(opts = {}) {
1594
1657
  projectId: null,
1595
1658
  apiKeySource: null,
1596
1659
  apiKeyFingerprint: null,
1660
+ authoritativeBinding: opts.authoritativeBinding ?? null,
1597
1661
  ideType: void 0
1598
1662
  };
1599
1663
  let workspaceRoot;
@@ -1643,7 +1707,7 @@ async function main(opts = {}) {
1643
1707
  const registeredToolNames = [];
1644
1708
  server.tool(
1645
1709
  "memora_post_event",
1646
- "Forward an event to MemoraOne timeline",
1710
+ postEventDescription,
1647
1711
  postEventShape,
1648
1712
  async (args) => runWithSessionContext(sessionContext, async () => {
1649
1713
  if (!runtime.client || !runtime.projectId) return notInitializedResult;
@@ -1719,6 +1783,19 @@ async function main(opts = {}) {
1719
1783
  })
1720
1784
  );
1721
1785
  registeredToolNames.push("memora_set_project");
1786
+ server.tool(
1787
+ "memora_status",
1788
+ "Return non-secret project binding metadata for this MCP session",
1789
+ bindingStatusShape,
1790
+ async () => runWithSessionContext(sessionContext, async () => {
1791
+ if (!runtime.authoritativeBinding) return notInitializedResult;
1792
+ const result = handleBindingStatus(runtime.authoritativeBinding);
1793
+ return {
1794
+ content: [{ type: "text", text: JSON.stringify(result) }]
1795
+ };
1796
+ })
1797
+ );
1798
+ registeredToolNames.push("memora_status");
1722
1799
  registerToolWithWorklog(
1723
1800
  server,
1724
1801
  runtime,
@@ -1869,10 +1946,12 @@ async function main(opts = {}) {
1869
1946
  runtime.projectId = projectId;
1870
1947
  runtime.apiKeySource = binding.apiKeySource;
1871
1948
  runtime.apiKeyFingerprint = fingerprintApiKey(apiKeyToUse);
1949
+ runtime.authoritativeBinding = binding;
1872
1950
  runtime.client = new memoraClient_default(config2, projectId, apiKeyToUse);
1873
1951
  workspaceRoot = binding.workspaceRoot;
1952
+ const environmentLog = binding.environment !== void 0 ? ` environment=${binding.environment}` : "";
1874
1953
  process.stderr.write(
1875
- `[memoraone-mcp] registering workspace source bindingSource=${binding.bindingSource} workspaceRoot=${workspaceRoot ?? "(unset)"} m1Path=${binding.m1Path}
1954
+ `[memoraone-mcp] registering workspace source bindingSource=${binding.bindingSource} workspaceRoot=${workspaceRoot ?? "(unset)"} m1Path=${binding.m1Path}${environmentLog}
1876
1955
  `
1877
1956
  );
1878
1957
  await registerRepoSource(
@@ -1886,8 +1965,9 @@ async function main(opts = {}) {
1886
1965
  console.error("[memoraone-mcp][auth] project_id:", projectId);
1887
1966
  console.error("[memoraone-mcp][auth] api_key source:", binding.apiKeySource);
1888
1967
  }
1968
+ const bindingEnvironmentLog = binding.environment !== void 0 ? ` environment=${binding.environment}` : "";
1889
1969
  console.error(
1890
- `[memoraone-mcp] ${sessionLabel} authoritative binding: project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}`
1970
+ `[memoraone-mcp] ${sessionLabel} authoritative binding: project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}${bindingEnvironmentLog}`
1891
1971
  );
1892
1972
  bindingReadyResolve?.(runtime.client);
1893
1973
  return server.server._oninitialize(request);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memoraone/mcp",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {
@@ -13,15 +13,6 @@
13
13
  "publishConfig": {
14
14
  "access": "public"
15
15
  },
16
- "scripts": {
17
- "build": "tsup && node scripts/writeBinWrapper.cjs",
18
- "prepublishOnly": "pnpm run build",
19
- "dev": "tsx src/cli.ts",
20
- "lint": "eslint .",
21
- "lint:contracts": "node scripts/lint-contracts.cjs",
22
- "test": "pnpm run lint:contracts && node --import=tsx --test test/*.test.js",
23
- "validate:auth": "node --import=tsx --test test/memoraClient.test.js"
24
- },
25
16
  "dependencies": {
26
17
  "@modelcontextprotocol/sdk": "^1.25.1",
27
18
  "dotenv": "^16.4.5",
@@ -31,5 +22,13 @@
31
22
  "tsx": "^4.21.0",
32
23
  "tsup": "^8.5.1",
33
24
  "typescript": "^5.9.2"
25
+ },
26
+ "scripts": {
27
+ "build": "tsup && node scripts/writeBinWrapper.cjs",
28
+ "dev": "tsx src/cli.ts",
29
+ "lint": "eslint .",
30
+ "lint:contracts": "node scripts/lint-contracts.cjs",
31
+ "test": "pnpm run lint:contracts && node --import=tsx --test test/*.test.js",
32
+ "validate:auth": "node --import=tsx --test test/memoraClient.test.js"
34
33
  }
35
- }
34
+ }