@memoraone/mcp 0.1.14 → 0.1.16

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 (3) hide show
  1. package/dist/cli.cjs +535 -77
  2. package/dist/index.cjs +530 -77
  3. package/package.json +8 -8
package/dist/index.cjs CHANGED
@@ -114,6 +114,40 @@ var config = {
114
114
  };
115
115
 
116
116
  // src/client/memoraClient.ts
117
+ var crypto2 = __toESM(require("crypto"), 1);
118
+
119
+ // src/runContext.ts
120
+ var crypto = __toESM(require("crypto"), 1);
121
+ var currentRunId = null;
122
+ var currentProjectId = null;
123
+ function setCurrentRunId(id) {
124
+ currentRunId = id;
125
+ }
126
+ function getCurrentProjectId() {
127
+ return currentProjectId;
128
+ }
129
+ function setCurrentProjectId(id) {
130
+ currentProjectId = id;
131
+ }
132
+ function resolveRunId(passed) {
133
+ if (passed) {
134
+ return passed;
135
+ }
136
+ return currentRunId;
137
+ }
138
+ function generateRunId() {
139
+ return crypto.randomBytes(16).toString("hex");
140
+ }
141
+
142
+ // src/client/memoraClient.ts
143
+ var parseBooleanFlag2 = (value) => {
144
+ if (!value) {
145
+ return false;
146
+ }
147
+ const normalized = value.trim().toLowerCase();
148
+ return ["1", "true", "yes", "on"].includes(normalized);
149
+ };
150
+ var debugEnabled = parseBooleanFlag2(process.env.MEMORAONE_DEV_MODE);
117
151
  async function requestJson(url, method, headers, body) {
118
152
  const res = await fetch(url, {
119
153
  method,
@@ -137,9 +171,21 @@ var MemoraOneHttpError = class extends Error {
137
171
  }
138
172
  };
139
173
  var MemoraClient = class {
140
- constructor(cfg) {
174
+ constructor(cfg, projectKey) {
175
+ if (!projectKey?.trim()) {
176
+ throw new Error("[memoraone-mcp] Invalid projectKey for MemoraClient");
177
+ }
141
178
  this.baseUrl = cfg.apiUrl;
142
179
  this.apiKey = cfg.apiKey;
180
+ this.projectKey = projectKey;
181
+ }
182
+ resolveProjectKey() {
183
+ const selected = getCurrentProjectId();
184
+ const projectKey = (selected ?? this.projectKey)?.trim();
185
+ if (!projectKey) {
186
+ throw new Error("Missing projectKey: select a project first");
187
+ }
188
+ return projectKey;
143
189
  }
144
190
  buildHeaders(options) {
145
191
  return {
@@ -148,30 +194,66 @@ var MemoraClient = class {
148
194
  ...options?.headers ?? {}
149
195
  };
150
196
  }
151
- async post(path2, body, options) {
152
- const url = `${this.baseUrl}${path2.startsWith("/") ? path2 : `/${path2}`}`;
153
- if (options?.log !== false) {
154
- process.stderr.write(
155
- `[memoraone-mcp] http url=${url} apiKeyPrefix=${String(this.apiKey).slice(0, 8)} apiKeyLen=${String(this.apiKey).length}
156
- `
197
+ async post(path5, body, options) {
198
+ console.error(
199
+ `[memoraone-mcp][info] MemoraClient.post ENTER path=${path5}`
200
+ );
201
+ const nonce = crypto2.randomBytes(8).toString("hex");
202
+ const url = `${this.baseUrl}${path5.startsWith("/") ? path5 : `/${path5}`}`;
203
+ this.resolveProjectKey();
204
+ console.error(
205
+ `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_fetch method=POST url=${url}`
206
+ );
207
+ const res = await requestJson(url, "POST", this.buildHeaders(options), body);
208
+ if (debugEnabled && options?.log !== false) {
209
+ const snippet = res.text.length > 200 ? `${res.text.slice(0, 200)}...` : res.text;
210
+ console.error(
211
+ `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_response_log`
212
+ );
213
+ const line = `[memoraone-mcp][info] http response method=POST url=${url} status=${res.status} body=${snippet}`;
214
+ console.error(line);
215
+ console.error(
216
+ `[memoraone-mcp][info] requestJson nonce=${nonce} stage=after_response_log`
157
217
  );
158
218
  }
159
- const res = await requestJson(url, "POST", this.buildHeaders(options), body);
160
219
  if (!res.ok) {
220
+ const snippet = res.text.length > 200 ? `${res.text.slice(0, 200)}...` : res.text;
221
+ process.stderr.write(
222
+ `[memoraone-mcp][error] http error method=POST url=${url} status=${res.status} body=${snippet}
223
+ `
224
+ );
161
225
  throw new MemoraOneHttpError(res.status, res.statusText, res.text);
162
226
  }
227
+ console.error(
228
+ `[memoraone-mcp][info] MemoraClient.post EXIT path=${path5}`
229
+ );
163
230
  return res.text ? JSON.parse(res.text) : null;
164
231
  }
165
- async get(path2, options) {
166
- const url = `${this.baseUrl}${path2.startsWith("/") ? path2 : `/${path2}`}`;
167
- if (options?.log !== false) {
168
- process.stderr.write(
169
- `[memoraone-mcp] http url=${url} apiKeyPrefix=${String(this.apiKey).slice(0, 8)} apiKeyLen=${String(this.apiKey).length}
170
- `
232
+ async get(path5, options) {
233
+ const nonce = crypto2.randomBytes(8).toString("hex");
234
+ const url = `${this.baseUrl}${path5.startsWith("/") ? path5 : `/${path5}`}`;
235
+ this.resolveProjectKey();
236
+ console.error(
237
+ `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_fetch method=GET url=${url}`
238
+ );
239
+ const res = await requestJson(url, "GET", this.buildHeaders(options));
240
+ if (debugEnabled && options?.log !== false) {
241
+ const snippet = res.text.length > 200 ? `${res.text.slice(0, 200)}...` : res.text;
242
+ console.error(
243
+ `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_response_log`
244
+ );
245
+ const line = `[memoraone-mcp][info] http response method=GET url=${url} status=${res.status} body=${snippet}`;
246
+ console.error(line);
247
+ console.error(
248
+ `[memoraone-mcp][info] requestJson nonce=${nonce} stage=after_response_log`
171
249
  );
172
250
  }
173
- const res = await requestJson(url, "GET", this.buildHeaders(options));
174
251
  if (!res.ok) {
252
+ const snippet = res.text.length > 200 ? `${res.text.slice(0, 200)}...` : res.text;
253
+ process.stderr.write(
254
+ `[memoraone-mcp][error] http error method=GET url=${url} status=${res.status} body=${snippet}
255
+ `
256
+ );
175
257
  throw new MemoraOneHttpError(res.status, res.statusText, res.text);
176
258
  }
177
259
  return res.text ? JSON.parse(res.text) : null;
@@ -179,11 +261,339 @@ var MemoraClient = class {
179
261
  };
180
262
  var memoraClient_default = MemoraClient;
181
263
 
264
+ // src/repoFingerprint.ts
265
+ var fs2 = __toESM(require("fs"), 1);
266
+ var path2 = __toESM(require("path"), 1);
267
+ var crypto3 = __toESM(require("crypto"), 1);
268
+ var parseBooleanFlag3 = (value) => {
269
+ if (!value) {
270
+ return false;
271
+ }
272
+ const normalized = value.trim().toLowerCase();
273
+ return ["1", "true", "yes", "on"].includes(normalized);
274
+ };
275
+ var debugEnabled2 = parseBooleanFlag3(process.env.MEMORAONE_DEV_MODE);
276
+ var debugLog = (message) => {
277
+ if (!debugEnabled2) {
278
+ return;
279
+ }
280
+ process.stderr.write(`[memoraone-mcp][debug] ${message}
281
+ `);
282
+ };
283
+ var normalizeRemoteUrl = (remoteUrl) => {
284
+ let normalized = remoteUrl.trim();
285
+ normalized = normalized.replace(/^[a-z]+:\/\//i, "");
286
+ normalized = normalized.replace(/^git@([^:]+):/i, "$1/");
287
+ normalized = normalized.replace(/\.git$/i, "");
288
+ normalized = normalized.replace(/\/+$/, "");
289
+ return normalized.toLowerCase();
290
+ };
291
+ var sha256 = (value) => {
292
+ return crypto3.createHash("sha256").update(value).digest("hex");
293
+ };
294
+ var resolveGitDir = (gitPath) => {
295
+ try {
296
+ const stat = fs2.statSync(gitPath);
297
+ if (stat.isDirectory()) {
298
+ return gitPath;
299
+ }
300
+ if (stat.isFile()) {
301
+ const content = fs2.readFileSync(gitPath, "utf8");
302
+ const match = content.match(/^gitdir:\s*(.+)$/m);
303
+ if (match) {
304
+ const gitDir = match[1].trim();
305
+ return path2.resolve(path2.dirname(gitPath), gitDir);
306
+ }
307
+ }
308
+ } catch {
309
+ return null;
310
+ }
311
+ return null;
312
+ };
313
+ var findGitRoot = (start) => {
314
+ let current = path2.resolve(start);
315
+ while (true) {
316
+ const gitPath = path2.join(current, ".git");
317
+ if (fs2.existsSync(gitPath)) {
318
+ const gitDir = resolveGitDir(gitPath);
319
+ if (gitDir) {
320
+ return { gitRoot: current, gitDir };
321
+ }
322
+ }
323
+ const parent = path2.dirname(current);
324
+ if (parent === current) {
325
+ break;
326
+ }
327
+ current = parent;
328
+ }
329
+ return null;
330
+ };
331
+ var readOriginRemote = (gitDir) => {
332
+ const configPath = path2.join(gitDir, "config");
333
+ try {
334
+ const content = fs2.readFileSync(configPath, "utf8");
335
+ const lines = content.split(/\r?\n/);
336
+ let inOrigin = false;
337
+ for (const line of lines) {
338
+ const sectionMatch = line.match(/^\s*\[(.+)]\s*$/);
339
+ if (sectionMatch) {
340
+ inOrigin = sectionMatch[1].trim() === 'remote "origin"';
341
+ continue;
342
+ }
343
+ if (inOrigin) {
344
+ const urlMatch = line.match(/^\s*url\s*=\s*(.+)\s*$/);
345
+ if (urlMatch) {
346
+ return urlMatch[1].trim();
347
+ }
348
+ }
349
+ }
350
+ } catch {
351
+ return null;
352
+ }
353
+ return null;
354
+ };
355
+ function resolveRepoFingerprint(cwd2) {
356
+ const found = findGitRoot(cwd2);
357
+ if (!found) {
358
+ const fallbackPath = path2.resolve(cwd2);
359
+ const fingerprint2 = sha256(fallbackPath);
360
+ debugLog(`repo fingerprint=${fingerprint2} source=path-fallback`);
361
+ return {
362
+ fingerprint: fingerprint2,
363
+ gitRoot: fallbackPath,
364
+ source: "path-fallback"
365
+ };
366
+ }
367
+ const { gitRoot, gitDir } = found;
368
+ const remoteUrl = readOriginRemote(gitDir);
369
+ if (remoteUrl) {
370
+ const normalized = normalizeRemoteUrl(remoteUrl);
371
+ const fingerprint2 = sha256(normalized);
372
+ debugLog(`repo fingerprint=${fingerprint2} source=git-remote`);
373
+ return {
374
+ fingerprint: fingerprint2,
375
+ gitRoot,
376
+ remoteUrl,
377
+ source: "git-remote"
378
+ };
379
+ }
380
+ const fingerprint = sha256(path2.resolve(gitRoot));
381
+ debugLog(`repo fingerprint=${fingerprint} source=path-fallback`);
382
+ return {
383
+ fingerprint,
384
+ gitRoot,
385
+ source: "path-fallback"
386
+ };
387
+ }
388
+
389
+ // src/workspaceMap.ts
390
+ var fs3 = __toESM(require("fs/promises"), 1);
391
+ var path3 = __toESM(require("path"), 1);
392
+ var import_node_os = __toESM(require("os"), 1);
393
+ var parseBooleanFlag4 = (value) => {
394
+ if (!value) {
395
+ return false;
396
+ }
397
+ const normalized = value.trim().toLowerCase();
398
+ return ["1", "true", "yes", "on"].includes(normalized);
399
+ };
400
+ var debugEnabled3 = parseBooleanFlag4(process.env.MEMORAONE_DEV_MODE);
401
+ var debugLog2 = (message) => {
402
+ if (!debugEnabled3) {
403
+ return;
404
+ }
405
+ process.stderr.write(`[memoraone-mcp][debug] ${message}
406
+ `);
407
+ };
408
+ var fingerprintRegex = /^[0-9a-f]{64}$/i;
409
+ function getWorkspaceMapPath() {
410
+ return path3.join(import_node_os.default.homedir(), ".memoraone", "workspaces.json");
411
+ }
412
+ var ensureWorkspaceDir = async () => {
413
+ const dir = path3.dirname(getWorkspaceMapPath());
414
+ await fs3.mkdir(dir, { recursive: true });
415
+ };
416
+ var validateWorkspaceMap = (map, filePath) => {
417
+ if (!map || typeof map !== "object" || Array.isArray(map)) {
418
+ throw new Error(
419
+ `[memoraone-mcp] Invalid workspace map schema in ${filePath}`
420
+ );
421
+ }
422
+ for (const [fingerprint, entry] of Object.entries(map)) {
423
+ if (!fingerprintRegex.test(fingerprint)) {
424
+ throw new Error(
425
+ `[memoraone-mcp] Invalid workspace fingerprint in ${filePath}`
426
+ );
427
+ }
428
+ if (typeof entry === "string") {
429
+ if (!entry.trim()) {
430
+ throw new Error(
431
+ `[memoraone-mcp] Invalid workspace projectKey in ${filePath}`
432
+ );
433
+ }
434
+ continue;
435
+ }
436
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
437
+ throw new Error(
438
+ `[memoraone-mcp] Invalid workspace projectKey in ${filePath}`
439
+ );
440
+ }
441
+ const projectKey = entry.projectKey ?? entry.project_id;
442
+ if (!projectKey || !projectKey.trim()) {
443
+ throw new Error(
444
+ `[memoraone-mcp] Invalid workspace projectKey in ${filePath}`
445
+ );
446
+ }
447
+ const source = entry.source;
448
+ if (source !== void 0 && typeof source !== "string") {
449
+ throw new Error(
450
+ `[memoraone-mcp] Invalid workspace source in ${filePath}`
451
+ );
452
+ }
453
+ const linkedAt = entry.linked_at;
454
+ if (linkedAt !== void 0 && typeof linkedAt !== "string") {
455
+ throw new Error(
456
+ `[memoraone-mcp] Invalid workspace linked_at in ${filePath}`
457
+ );
458
+ }
459
+ }
460
+ };
461
+ async function readWorkspaceMap() {
462
+ const filePath = getWorkspaceMapPath();
463
+ try {
464
+ const content = await fs3.readFile(filePath, "utf8");
465
+ const parsed2 = JSON.parse(content);
466
+ validateWorkspaceMap(parsed2, filePath);
467
+ const typed = parsed2;
468
+ let migrated = false;
469
+ const normalized = {};
470
+ for (const [fingerprint, entry] of Object.entries(typed)) {
471
+ if (typeof entry === "string") {
472
+ normalized[fingerprint] = entry;
473
+ continue;
474
+ }
475
+ const projectKey = entry.projectKey ?? entry.project_id ?? "";
476
+ if (entry.project_id && !entry.projectKey) {
477
+ migrated = true;
478
+ }
479
+ normalized[fingerprint] = {
480
+ ...projectKey ? { projectKey } : {},
481
+ ...entry.source ? { source: entry.source } : {},
482
+ ...entry.linked_at ? { linked_at: entry.linked_at } : {}
483
+ };
484
+ }
485
+ if (migrated) {
486
+ await writeWorkspaceMap(normalized);
487
+ }
488
+ debugLog2(
489
+ `workspace map loaded path=${filePath} entries=${Object.keys(normalized).length}`
490
+ );
491
+ return normalized;
492
+ } catch (err) {
493
+ if (err?.code === "ENOENT") {
494
+ const emptyMap = {};
495
+ debugLog2(`workspace map loaded path=${filePath} entries=0`);
496
+ return emptyMap;
497
+ }
498
+ if (err instanceof SyntaxError) {
499
+ throw new Error(
500
+ `[memoraone-mcp] Failed to parse workspace map at ${filePath}`
501
+ );
502
+ }
503
+ throw err;
504
+ }
505
+ }
506
+ async function writeWorkspaceMap(map) {
507
+ const filePath = getWorkspaceMapPath();
508
+ validateWorkspaceMap(map, filePath);
509
+ await ensureWorkspaceDir();
510
+ const tempPath = `${filePath}.tmp`;
511
+ const content = JSON.stringify(map, null, 2);
512
+ await fs3.writeFile(tempPath, content, "utf8");
513
+ await fs3.rename(tempPath, filePath);
514
+ }
515
+ async function getProjectIdForFingerprint(fingerprint) {
516
+ if (!fingerprintRegex.test(fingerprint)) {
517
+ throw new Error("[memoraone-mcp] Invalid fingerprint");
518
+ }
519
+ const map = await readWorkspaceMap();
520
+ const entry = map[fingerprint];
521
+ if (!entry) {
522
+ return { projectKey: null, source: "unknown" };
523
+ }
524
+ if (typeof entry === "string") {
525
+ return { projectKey: entry, source: "unknown" };
526
+ }
527
+ return {
528
+ projectKey: entry.projectKey ?? entry.project_id ?? null,
529
+ source: entry.source ?? "unknown"
530
+ };
531
+ }
532
+ async function setProjectIdForFingerprint(args) {
533
+ const { fingerprint, projectKey, source, linked_at } = args;
534
+ if (!fingerprintRegex.test(fingerprint)) {
535
+ throw new Error("[memoraone-mcp] Invalid fingerprint");
536
+ }
537
+ if (!projectKey.trim()) {
538
+ throw new Error("[memoraone-mcp] Invalid projectKey");
539
+ }
540
+ const map = await readWorkspaceMap();
541
+ map[fingerprint] = {
542
+ projectKey,
543
+ ...source ? { source } : {},
544
+ linked_at: linked_at ?? (/* @__PURE__ */ new Date()).toISOString()
545
+ };
546
+ await writeWorkspaceMap(map);
547
+ debugLog2(
548
+ `workspace map set fingerprint=${fingerprint} projectKey=${projectKey}`
549
+ );
550
+ }
551
+
552
+ // src/projectBinding.ts
553
+ var fs4 = __toESM(require("fs/promises"), 1);
554
+ var path4 = __toESM(require("path"), 1);
555
+ function readRepoProjectIdPath(gitRoot) {
556
+ return path4.join(gitRoot, ".memoraone-project");
557
+ }
558
+ async function readRepoProjectId(gitRoot) {
559
+ const filePath = readRepoProjectIdPath(gitRoot);
560
+ try {
561
+ const content = await fs4.readFile(filePath, "utf8");
562
+ const value = content.trim();
563
+ return value ? value : null;
564
+ } catch (err) {
565
+ if (err?.code === "ENOENT") {
566
+ return null;
567
+ }
568
+ throw err;
569
+ }
570
+ }
571
+ function resolveProjectIdOrThrow(args) {
572
+ const envProjectKey = args.envProjectKey?.trim();
573
+ if (envProjectKey) {
574
+ return { projectKey: envProjectKey, source: "env" };
575
+ }
576
+ const repoFileProjectKey = args.repoFileProjectKey?.trim();
577
+ if (repoFileProjectKey) {
578
+ return { projectKey: repoFileProjectKey, source: "repo-file" };
579
+ }
580
+ const workspaceProjectKey = args.workspaceProjectKey?.trim();
581
+ if (workspaceProjectKey) {
582
+ return { projectKey: workspaceProjectKey, source: "workspace-map" };
583
+ }
584
+ throw new Error(
585
+ `Repo not linked to a MemoraOne project. Set MEMORAONE_PROJECT_ID or create ${args.repoFilePath} containing a project key.`
586
+ );
587
+ }
588
+
182
589
  // src/tools/postEvent.ts
183
590
  var import_v42 = require("zod/v4");
184
591
  var postEventShape = {
185
592
  kind: import_v42.z.string().min(1),
186
- actor: import_v42.z.object({ identifier: import_v42.z.string().min(1) }),
593
+ actor: import_v42.z.object({
594
+ identifier: import_v42.z.string().min(1),
595
+ id: import_v42.z.string().min(1).optional()
596
+ }),
187
597
  content: import_v42.z.record(import_v42.z.string(), import_v42.z.any()),
188
598
  metadata: import_v42.z.record(import_v42.z.string(), import_v42.z.any()).optional()
189
599
  };
@@ -256,58 +666,59 @@ var listProjectsShape = {};
256
666
  // src/tools/setProject.ts
257
667
  var import_v48 = require("zod/v4");
258
668
  var setProjectShape = {
259
- projectId: import_v48.z.string().min(1)
669
+ projectKey: import_v48.z.string().min(1).optional(),
670
+ projectId: import_v48.z.string().min(1).optional()
260
671
  };
261
672
 
262
673
  // src/tools/handlers/postEvent.ts
263
674
  var import_v49 = require("zod/v4");
264
-
265
- // src/runContext.ts
266
- var crypto = __toESM(require("crypto"), 1);
267
- var currentRunId = null;
268
- var currentProjectId = null;
269
- function setCurrentRunId(id) {
270
- currentRunId = id;
271
- }
272
- function getCurrentProjectId() {
273
- return currentProjectId;
274
- }
275
- function setCurrentProjectId(id) {
276
- currentProjectId = id;
277
- }
278
- function resolveRunId(passed) {
279
- if (passed) {
280
- return passed;
281
- }
282
- return currentRunId;
283
- }
284
- function generateRunId() {
285
- return crypto.randomBytes(16).toString("hex");
286
- }
287
-
288
- // src/tools/handlers/postEvent.ts
675
+ var crypto4 = __toESM(require("crypto"), 1);
289
676
  var postEventInputSchema = import_v49.z.object({
290
677
  kind: import_v49.z.string().min(1),
291
- actor: import_v49.z.object({ identifier: import_v49.z.string().min(1) }),
678
+ actor: import_v49.z.object({
679
+ identifier: import_v49.z.string().min(1),
680
+ id: import_v49.z.string().min(1).optional()
681
+ }),
292
682
  content: import_v49.z.record(import_v49.z.string(), import_v49.z.any()),
293
683
  metadata: import_v49.z.record(import_v49.z.string(), import_v49.z.any()).optional()
294
684
  });
295
685
  async function handlePostEvent(client, args) {
686
+ const nonce = crypto4.randomBytes(8).toString("hex");
687
+ console.error(
688
+ `[memoraone-mcp][debug] tool=memora_post_event toolCallId=unknown nonce=${nonce} stage=before_post`
689
+ );
296
690
  const parsed2 = postEventInputSchema.parse(args ?? {});
297
- const projectId = getCurrentProjectId();
298
- if (!projectId) {
691
+ const projectKey = getCurrentProjectId();
692
+ if (!projectKey) {
299
693
  throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
300
694
  }
695
+ const content = parsed2.content ?? {};
696
+ const message = typeof content.message === "string" ? content.message : typeof content.text === "string" ? content.text : JSON.stringify(content);
301
697
  const body = {
302
698
  kind: parsed2.kind,
303
- actor: { type: config.agentType, identifier: parsed2.actor.identifier },
304
- content: parsed2.content,
305
- metadata: {
306
- source: config.source,
307
- ...parsed2.metadata
308
- }
699
+ message,
700
+ projectKey,
701
+ actor: {
702
+ type: "agent",
703
+ identifier: config.agentName,
704
+ ...parsed2.actor.id ? { id: parsed2.actor.id } : {}
705
+ },
706
+ ...parsed2.metadata ? { metadata: parsed2.metadata } : {}
309
707
  };
310
- await client.post("/timeline/events", body, { projectId });
708
+ try {
709
+ await client.post("/timeline/events", body);
710
+ console.error(
711
+ `[memoraone-mcp][debug] tool=memora_post_event toolCallId=unknown nonce=${nonce} stage=after_post`
712
+ );
713
+ } catch (err) {
714
+ if (err instanceof MemoraOneHttpError) {
715
+ const bodyText = typeof err.body === "string" ? err.body : JSON.stringify(err.body ?? "");
716
+ throw new Error(
717
+ `memora_post_event failed: ${err.status} ${err.message} ${bodyText}`.trim()
718
+ );
719
+ }
720
+ throw err;
721
+ }
311
722
  return { ok: true, forwarded: true };
312
723
  }
313
724
 
@@ -326,17 +737,18 @@ function isAskWithMemoryResponse(value) {
326
737
  }
327
738
  async function handleAskWithMemory(client, args) {
328
739
  const parsed2 = askWithMemoryInputSchema.parse(args ?? {});
329
- const projectId = getCurrentProjectId();
330
- if (!projectId) {
740
+ const projectKey = getCurrentProjectId();
741
+ if (!projectKey) {
331
742
  throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
332
743
  }
333
744
  const payload = {
334
- question: parsed2.question
745
+ question: parsed2.question,
746
+ projectKey
335
747
  };
336
748
  if (parsed2.code_context) {
337
749
  payload.code_context = parsed2.code_context;
338
750
  }
339
- const res = await client.post("/agent/ask-with-memory", payload, { projectId });
751
+ const res = await client.post("/agent/ask-with-memory", payload);
340
752
  if (!isAskWithMemoryResponse(res)) {
341
753
  const err = new Error("Unexpected response from MemoraOne");
342
754
  err.status = 502;
@@ -360,8 +772,8 @@ var logIntentInputSchema = import_v411.z.object({
360
772
  });
361
773
  async function handleLogIntent(client, args) {
362
774
  const parsed2 = logIntentInputSchema.parse(args ?? {});
363
- const projectId = getCurrentProjectId();
364
- if (!projectId) {
775
+ const projectKey = getCurrentProjectId();
776
+ if (!projectKey) {
365
777
  throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
366
778
  }
367
779
  const intent = parsed2.intent;
@@ -381,6 +793,7 @@ async function handleLogIntent(client, args) {
381
793
  concept,
382
794
  // MUST be TOP-LEVEL so it populates timeline_events.concept
383
795
  message,
796
+ projectKey,
384
797
  metadata: {
385
798
  source: config.source,
386
799
  purpose,
@@ -390,7 +803,7 @@ async function handleLogIntent(client, args) {
390
803
  ...context ? { context } : {}
391
804
  }
392
805
  };
393
- await client.post("/timeline/events", body, { projectId });
806
+ await client.post("/timeline/events", body);
394
807
  return { ok: true, run_id };
395
808
  }
396
809
 
@@ -410,8 +823,8 @@ var logChangeSummaryInputSchema = import_v412.z.object({
410
823
  });
411
824
  async function handleLogChangeSummary(client, args) {
412
825
  const parsed2 = logChangeSummaryInputSchema.parse(args ?? {});
413
- const projectId = getCurrentProjectId();
414
- if (!projectId) {
826
+ const projectKey = getCurrentProjectId();
827
+ if (!projectKey) {
415
828
  throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
416
829
  }
417
830
  const { summary, scope, files, stats, commit } = parsed2;
@@ -422,6 +835,7 @@ async function handleLogChangeSummary(client, args) {
422
835
  concept: "concept:change_summary",
423
836
  actor: { type: config.agentType, name: config.agentName },
424
837
  message,
838
+ projectKey,
425
839
  metadata: {
426
840
  source: config.source,
427
841
  purpose: "change_summary",
@@ -433,7 +847,7 @@ async function handleLogChangeSummary(client, args) {
433
847
  ...run_id ? { run_id } : {}
434
848
  }
435
849
  };
436
- await client.post("/timeline/events", body, { projectId });
850
+ await client.post("/timeline/events", body);
437
851
  return { ok: true };
438
852
  }
439
853
 
@@ -452,8 +866,8 @@ var logToolResultInputSchema = import_v413.z.object({
452
866
  });
453
867
  async function handleLogToolResult(client, args) {
454
868
  const parsed2 = logToolResultInputSchema.parse(args ?? {});
455
- const projectId = getCurrentProjectId();
456
- if (!projectId) {
869
+ const projectKey = getCurrentProjectId();
870
+ if (!projectKey) {
457
871
  throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
458
872
  }
459
873
  const { tool, status, summary, duration_ms, error_code, error_message, error_kind, stats } = parsed2;
@@ -464,6 +878,7 @@ async function handleLogToolResult(client, args) {
464
878
  concept: "concept:tool_result",
465
879
  actor: { type: config.agentType, name: config.agentName },
466
880
  message,
881
+ projectKey,
467
882
  metadata: {
468
883
  source: config.source,
469
884
  purpose: "tool_result",
@@ -478,7 +893,7 @@ async function handleLogToolResult(client, args) {
478
893
  ...stats ? { stats } : {}
479
894
  }
480
895
  };
481
- await client.post("/timeline/events", body, { projectId });
896
+ await client.post("/timeline/events", body);
482
897
  return { ok: true };
483
898
  }
484
899
 
@@ -495,8 +910,8 @@ var logCommandInputSchema = import_v414.z.object({
495
910
  });
496
911
  async function handleLogCommand(client, args) {
497
912
  const parsed2 = logCommandInputSchema.parse(args ?? {});
498
- const projectId = getCurrentProjectId();
499
- if (!projectId) {
913
+ const projectKey = getCurrentProjectId();
914
+ if (!projectKey) {
500
915
  throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
501
916
  }
502
917
  const { cmd, summary, cwd: cwd2, exit_code, duration_ms, stats } = parsed2;
@@ -507,6 +922,7 @@ async function handleLogCommand(client, args) {
507
922
  concept: "concept:command",
508
923
  actor: { type: config.agentType, name: config.agentName },
509
924
  message,
925
+ projectKey,
510
926
  metadata: {
511
927
  source: config.source,
512
928
  purpose: "command",
@@ -519,7 +935,7 @@ async function handleLogCommand(client, args) {
519
935
  ...stats ? { stats } : {}
520
936
  }
521
937
  };
522
- await client.post("/timeline/events", body, { projectId });
938
+ await client.post("/timeline/events", body);
523
939
  return { ok: true };
524
940
  }
525
941
 
@@ -532,12 +948,23 @@ async function handleListProjects(client) {
532
948
  // src/tools/handlers/setProject.ts
533
949
  var import_v415 = require("zod/v4");
534
950
  var setProjectInputSchema = import_v415.z.object({
535
- projectId: import_v415.z.string().min(1)
951
+ projectKey: import_v415.z.string().min(1).optional(),
952
+ projectId: import_v415.z.string().min(1).optional()
536
953
  });
537
954
  async function handleSetProject(args) {
538
955
  const parsed2 = setProjectInputSchema.parse(args ?? {});
539
- setCurrentProjectId(parsed2.projectId);
540
- return { ok: true, projectId: parsed2.projectId };
956
+ const resolvedProjectKey = parsed2.projectKey ?? parsed2.projectId;
957
+ if (!resolvedProjectKey) {
958
+ throw new Error("projectKey is required");
959
+ }
960
+ setCurrentProjectId(resolvedProjectKey);
961
+ const repo = resolveRepoFingerprint(process.cwd());
962
+ await setProjectIdForFingerprint({
963
+ fingerprint: repo.fingerprint,
964
+ projectKey: resolvedProjectKey,
965
+ source: "manual"
966
+ });
967
+ return { ok: true, projectKey: resolvedProjectKey };
541
968
  }
542
969
 
543
970
  // src/index.ts
@@ -545,7 +972,10 @@ async function sendHeartbeat(client) {
545
972
  try {
546
973
  await client.post("/admin/api-keys/heartbeat", {}, { log: false });
547
974
  } catch (err) {
548
- console.error("[memoraone-mcp] heartbeat error (silent)", err);
975
+ process.stderr.write(
976
+ `[memoraone-mcp][info] heartbeat error (silent) ${String(err)}
977
+ `
978
+ );
549
979
  }
550
980
  }
551
981
  function redactSensitiveFields(obj) {
@@ -576,11 +1006,16 @@ function sanitizeArgsSummary(args) {
576
1006
  }
577
1007
  async function postWorklogEvent(client, message) {
578
1008
  try {
1009
+ const projectKey = getCurrentProjectId();
1010
+ if (!projectKey) {
1011
+ return;
1012
+ }
579
1013
  const body = {
580
1014
  kind: "note",
581
1015
  concept: "concept:worklog",
582
1016
  actor: { type: config.agentType, name: config.agentName },
583
1017
  message,
1018
+ projectKey,
584
1019
  metadata: {
585
1020
  source: config.source,
586
1021
  purpose: "worklog"
@@ -615,8 +1050,28 @@ function registerToolWithWorklog(server, client, toolName, description, schema,
615
1050
  });
616
1051
  }
617
1052
  async function main() {
618
- const client = new memoraClient_default(config);
1053
+ const repo = resolveRepoFingerprint(process.cwd());
1054
+ const envProjectKey = process.env.MEMORAONE_PROJECT_ID;
1055
+ const repoFilePath = readRepoProjectIdPath(repo.gitRoot);
1056
+ const repoFileProjectKey = await readRepoProjectId(repo.gitRoot);
1057
+ const workspaceResolved = await getProjectIdForFingerprint(repo.fingerprint);
1058
+ const binding = resolveProjectIdOrThrow({
1059
+ envProjectKey,
1060
+ repoFileProjectKey,
1061
+ workspaceProjectKey: workspaceResolved.projectKey,
1062
+ repoFilePath
1063
+ });
1064
+ const projectKey = binding.projectKey;
1065
+ const source = binding.source;
1066
+ setCurrentProjectId(projectKey);
619
1067
  const devMode = Boolean(config.devMode);
1068
+ if (devMode) {
1069
+ const remoteInfo = repo.remoteUrl ? ` remoteUrl=${repo.remoteUrl}` : "";
1070
+ console.error(
1071
+ `[memoraone-mcp][debug] repo fingerprint=${repo.fingerprint} projectKey=${projectKey} source=${source} gitRoot=${repo.gitRoot}${remoteInfo}`
1072
+ );
1073
+ }
1074
+ const client = new memoraClient_default(config, projectKey);
620
1075
  console.error(
621
1076
  `[memoraone-mcp] Agent identity: name="${config.agentName}", type="${config.agentType}", source="${config.source}"`
622
1077
  );
@@ -626,9 +1081,7 @@ async function main() {
626
1081
  });
627
1082
  const registeredToolNames = [];
628
1083
  registeredToolNames.push("memora_post_event");
629
- registerToolWithWorklog(
630
- server,
631
- client,
1084
+ server.tool(
632
1085
  "memora_post_event",
633
1086
  "Forward an event to MemoraOne timeline",
634
1087
  postEventShape,
@@ -664,7 +1117,7 @@ async function main() {
664
1117
  registeredToolNames.push("memora_set_project");
665
1118
  server.tool(
666
1119
  "memora_set_project",
667
- "Set the current project id for subsequent tool calls",
1120
+ "Set the current project key for subsequent tool calls",
668
1121
  setProjectShape,
669
1122
  async (args) => {
670
1123
  const result = await handleSetProject(args);