@memoraone/mcp 0.1.33 → 0.1.35

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 (4) hide show
  1. package/dist/cli.cjs +1479 -995
  2. package/dist/daemon.cjs +111 -36
  3. package/dist/index.cjs +221 -102
  4. package/package.json +11 -12
package/dist/index.cjs CHANGED
@@ -200,12 +200,12 @@ var MemoraClient = class {
200
200
  ...options?.headers ?? {}
201
201
  };
202
202
  }
203
- async post(path9, body, options) {
203
+ async post(path11, body, options) {
204
204
  console.error(
205
- `[memoraone-mcp][info] MemoraClient.post ENTER path=${path9}`
205
+ `[memoraone-mcp][info] MemoraClient.post ENTER path=${path11}`
206
206
  );
207
207
  const nonce = crypto.randomBytes(8).toString("hex");
208
- const url = `${this.baseUrl}${path9.startsWith("/") ? path9 : `/${path9}`}`;
208
+ const url = `${this.baseUrl}${path11.startsWith("/") ? path11 : `/${path11}`}`;
209
209
  this.resolveProjectId();
210
210
  console.error(
211
211
  `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_fetch method=POST url=${url}`
@@ -238,13 +238,13 @@ var MemoraClient = class {
238
238
  throw new MemoraOneHttpError(res.status, res.statusText, res.text);
239
239
  }
240
240
  console.error(
241
- `[memoraone-mcp][info] MemoraClient.post EXIT path=${path9}`
241
+ `[memoraone-mcp][info] MemoraClient.post EXIT path=${path11}`
242
242
  );
243
243
  return res.text ? JSON.parse(res.text) : null;
244
244
  }
245
- async get(path9, options) {
245
+ async get(path11, options) {
246
246
  const nonce = crypto.randomBytes(8).toString("hex");
247
- const url = `${this.baseUrl}${path9.startsWith("/") ? path9 : `/${path9}`}`;
247
+ const url = `${this.baseUrl}${path11.startsWith("/") ? path11 : `/${path11}`}`;
248
248
  this.resolveProjectId();
249
249
  console.error(
250
250
  `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_fetch method=GET url=${url}`
@@ -274,46 +274,14 @@ var MemoraClient = class {
274
274
  };
275
275
  var memoraClient_default = MemoraClient;
276
276
 
277
- // src/initializeBinding.ts
278
- var path5 = __toESM(require("path"), 1);
279
- var import_node_url = require("url");
280
-
281
- // src/bindingIdentity.ts
282
- var crypto2 = __toESM(require("crypto"), 1);
283
- var path2 = __toESM(require("path"), 1);
284
- var BINDING_SOCKET_HASH_LENGTH = 16;
285
- function bindingsMatch(a, b) {
286
- return a.projectId.trim().toLowerCase() === b.projectId.trim().toLowerCase() && path2.resolve(a.workspaceRoot) === path2.resolve(b.workspaceRoot) && path2.resolve(a.m1Path) === path2.resolve(b.m1Path);
287
- }
288
- function formatMissingInitializeWorkspaceError(options) {
289
- const lines = [
290
- "[memoraone-mcp] Could not resolve workspace from MCP initialize params."
291
- ];
292
- if (options?.rootsListAttempted) {
293
- lines.push(
294
- "Cursor initialize lacked workspaceFolders/rootUri; roots/list was attempted but returned no usable repo root."
295
- );
296
- if (options.rootsListUris && options.rootsListUris.length > 0) {
297
- lines.push(`roots/list URIs: ${options.rootsListUris.join(", ")}`);
298
- }
299
- lines.push(
300
- "Global Cursor MCP cannot safely bind per-window repos without a workspace signal from Cursor (initialize roots or roots/list)."
301
- );
302
- lines.push(
303
- "Reload MCP in this Cursor window, or ensure this repo has a managed .cursor/mcp.json from setup-ide-files --cursor."
304
- );
305
- return lines.join("\n");
306
- }
307
- lines.push(
308
- "Reload MCP in this Cursor window so initialize includes workspaceFolders, rootUri, or a usable roots/list response for this repo."
309
- );
310
- return lines.join("\n");
311
- }
312
-
313
277
  // src/projectBinding.ts
314
278
  var fs2 = __toESM(require("fs/promises"), 1);
315
- var path3 = __toESM(require("path"), 1);
279
+ var path2 = __toESM(require("path"), 1);
316
280
  var uuidRegex2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
281
+ var CANONICAL_M1_FILENAME = "memoraone.m1";
282
+ function isCanonicalM1Path(m1Path) {
283
+ return path2.basename(m1Path) === CANONICAL_M1_FILENAME;
284
+ }
317
285
  function normalizeEnvironment(raw) {
318
286
  if (raw === void 0 || raw === null || typeof raw !== "string") {
319
287
  return void 0;
@@ -345,7 +313,7 @@ async function resolveProjectIdFromExplicitM1Path() {
345
313
  if (raw === void 0 || raw.trim() === "") {
346
314
  return null;
347
315
  }
348
- const markerPath = path3.resolve(raw);
316
+ const markerPath = path2.resolve(raw);
349
317
  try {
350
318
  const content = await fs2.readFile(markerPath, "utf8");
351
319
  const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
@@ -358,20 +326,20 @@ async function resolveProjectIdFromExplicitM1Path() {
358
326
  }
359
327
  }
360
328
  async function findM1WalkingUp(workspaceRoot) {
361
- let current = path3.resolve(workspaceRoot);
329
+ let current = path2.resolve(workspaceRoot);
362
330
  while (true) {
363
- const markerPath = path3.join(current, "memoraone.m1");
331
+ const markerPath = path2.join(current, CANONICAL_M1_FILENAME);
364
332
  try {
365
333
  const content = await fs2.readFile(markerPath, "utf8");
366
334
  const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
367
- const repoRoot = path3.dirname(markerPath);
335
+ const repoRoot = path2.dirname(markerPath);
368
336
  return environment === void 0 ? { projectId, apiKey, repoRoot, markerPath } : { projectId, apiKey, environment, repoRoot, markerPath };
369
337
  } catch (err) {
370
338
  if (err?.code !== "ENOENT") {
371
339
  throw err;
372
340
  }
373
341
  }
374
- const parent = path3.dirname(current);
342
+ const parent = path2.dirname(current);
375
343
  if (parent === current) {
376
344
  break;
377
345
  }
@@ -394,7 +362,7 @@ function normalizeWorkspaceSearchRoots(workspaceRoot) {
394
362
  if (trimmed === "") {
395
363
  continue;
396
364
  }
397
- const resolved = path3.resolve(trimmed);
365
+ const resolved = path2.resolve(trimmed);
398
366
  if (!seen.has(resolved)) {
399
367
  seen.add(resolved);
400
368
  out.push(resolved);
@@ -424,7 +392,7 @@ async function resolveAuthoritativeBinding(workspaceRoot, options = {}) {
424
392
  const resolved = resolveApiKeyWithSource(explicitBinding.apiKey);
425
393
  return {
426
394
  projectId: explicitBinding.projectId,
427
- workspaceRoot: path3.dirname(explicitBinding.foundAt),
395
+ workspaceRoot: path2.dirname(explicitBinding.foundAt),
428
396
  m1Path: explicitBinding.foundAt,
429
397
  apiKey: resolved.apiKey,
430
398
  ...explicitBinding.environment !== void 0 ? { environment: explicitBinding.environment } : {},
@@ -467,6 +435,94 @@ async function resolveAuthoritativeBinding(workspaceRoot, options = {}) {
467
435
  }
468
436
  return bindings[0];
469
437
  }
438
+ function bindingRelevantValuesMatch(a, b) {
439
+ const envA = a.environment ?? void 0;
440
+ const envB = b.environment ?? void 0;
441
+ return a.projectId.trim().toLowerCase() === b.projectId.trim().toLowerCase() && path2.resolve(a.workspaceRoot) === path2.resolve(b.workspaceRoot) && path2.resolve(a.m1Path) === path2.resolve(b.m1Path) && (a.apiKey ?? null) === (b.apiKey ?? null) && envA === envB;
442
+ }
443
+ async function reconcileResolvedBindingWithDisk(cached) {
444
+ const m1Path = path2.resolve(cached.m1Path);
445
+ if (cached.bindingSource !== "explicit-m1-path" && !isCanonicalM1Path(m1Path)) {
446
+ throw new Error(
447
+ `[memoraone-mcp] Cached binding m1Path is not the canonical ${CANONICAL_M1_FILENAME}: ${m1Path}`
448
+ );
449
+ }
450
+ let content;
451
+ try {
452
+ content = await fs2.readFile(m1Path, "utf8");
453
+ } catch (err) {
454
+ if (err?.code === "ENOENT") {
455
+ throw new Error(
456
+ `[memoraone-mcp] Cached binding file missing at ${m1Path}. Open a folder containing ${CANONICAL_M1_FILENAME}.`
457
+ );
458
+ }
459
+ throw err;
460
+ }
461
+ const parsed2 = parseAndValidateM1(content, m1Path);
462
+ const resolved = resolveApiKeyWithSource(parsed2.apiKey);
463
+ const fresh = {
464
+ projectId: parsed2.projectId,
465
+ workspaceRoot: path2.resolve(path2.dirname(m1Path)),
466
+ m1Path,
467
+ apiKey: resolved.apiKey,
468
+ ...parsed2.environment !== void 0 ? { environment: parsed2.environment } : {},
469
+ bindingSource: cached.bindingSource,
470
+ apiKeySource: resolved.apiKeySource
471
+ };
472
+ if (bindingRelevantValuesMatch(cached, fresh)) {
473
+ return { binding: fresh, cacheRefreshed: false };
474
+ }
475
+ return { binding: fresh, cacheRefreshed: true };
476
+ }
477
+ function encodeResolvedBinding(binding) {
478
+ return Buffer.from(JSON.stringify(binding), "utf8").toString("base64");
479
+ }
480
+
481
+ // src/initializeBinding.ts
482
+ var path5 = __toESM(require("path"), 1);
483
+ var import_node_url = require("url");
484
+
485
+ // src/bindingIdentity.ts
486
+ var crypto2 = __toESM(require("crypto"), 1);
487
+ var path3 = __toESM(require("path"), 1);
488
+ var BINDING_SOCKET_HASH_LENGTH = 16;
489
+ function hashBindingIdentity(projectId, workspaceRoot, ideType) {
490
+ const input = [
491
+ projectId.trim().toLowerCase(),
492
+ path3.resolve(workspaceRoot),
493
+ ideType
494
+ ].join("|");
495
+ return crypto2.createHash("sha256").update(input).digest("hex").slice(0, BINDING_SOCKET_HASH_LENGTH);
496
+ }
497
+ function bindingsMatch(a, b) {
498
+ const envA = a.environment ?? void 0;
499
+ const envB = b.environment ?? void 0;
500
+ return a.projectId.trim().toLowerCase() === b.projectId.trim().toLowerCase() && path3.resolve(a.workspaceRoot) === path3.resolve(b.workspaceRoot) && path3.resolve(a.m1Path) === path3.resolve(b.m1Path) && (a.apiKey ?? null) === (b.apiKey ?? null) && envA === envB;
501
+ }
502
+ function formatMissingInitializeWorkspaceError(options) {
503
+ const lines = [
504
+ "[memoraone-mcp] Could not resolve workspace from MCP initialize params."
505
+ ];
506
+ if (options?.rootsListAttempted) {
507
+ lines.push(
508
+ "Cursor initialize lacked workspaceFolders/rootUri; roots/list was attempted but returned no usable repo root."
509
+ );
510
+ if (options.rootsListUris && options.rootsListUris.length > 0) {
511
+ lines.push(`roots/list URIs: ${options.rootsListUris.join(", ")}`);
512
+ }
513
+ lines.push(
514
+ "Global Cursor MCP cannot safely bind per-window repos without a workspace signal from Cursor (initialize roots or roots/list)."
515
+ );
516
+ lines.push(
517
+ "Reload MCP in this Cursor window, or ensure this repo has a managed .cursor/mcp.json from setup-ide-files --cursor."
518
+ );
519
+ return lines.join("\n");
520
+ }
521
+ lines.push(
522
+ "Reload MCP in this Cursor window so initialize includes workspaceFolders, rootUri, or a usable roots/list response for this repo."
523
+ );
524
+ return lines.join("\n");
525
+ }
470
526
 
471
527
  // src/socketPaths.ts
472
528
  var os = __toESM(require("os"), 1);
@@ -488,6 +544,17 @@ function parseIdeType(value) {
488
544
  function resolveIdeTypeFromEnv(env2 = process.env) {
489
545
  return parseIdeType(env2.MEMORAONE_IDE_TYPE);
490
546
  }
547
+ function resolveBindingIdeType(env2 = process.env) {
548
+ return resolveIdeTypeFromEnv(env2) ?? "";
549
+ }
550
+ function getBindingSocketFilename(binding, env2 = process.env) {
551
+ const ideType = resolveBindingIdeType(env2);
552
+ const hash = hashBindingIdentity(binding.projectId, binding.workspaceRoot, ideType);
553
+ return `mcp-${hash}.sock`;
554
+ }
555
+ function getBindingSocketPath(binding, env2 = process.env) {
556
+ return path4.join(BASE_DIR, getBindingSocketFilename(binding, env2));
557
+ }
491
558
 
492
559
  // src/initializeBinding.ts
493
560
  var MEMORAONE_WORKSPACE_ROOT_ENV = "MEMORAONE_WORKSPACE_ROOT";
@@ -660,6 +727,31 @@ async function resolveBindingFromInitializeParams(params, options = {}) {
660
727
  });
661
728
  }
662
729
 
730
+ // src/bindingSidecar.ts
731
+ var fs4 = __toESM(require("fs"), 1);
732
+ var path6 = __toESM(require("path"), 1);
733
+ function bindingSidecarPath(socketPath) {
734
+ if (socketPath.endsWith(".sock")) {
735
+ return `${socketPath.slice(0, -".sock".length)}.binding.json`;
736
+ }
737
+ return `${socketPath}.binding.json`;
738
+ }
739
+ function writeBindingSidecar(socketPath, binding, ideType = resolveBindingIdeType()) {
740
+ const payload = encodeResolvedBinding(binding);
741
+ const record = {
742
+ v: 2,
743
+ ...ideType ? { ideType } : {},
744
+ projectId: binding.projectId,
745
+ workspaceRoot: binding.workspaceRoot,
746
+ m1Path: binding.m1Path,
747
+ binding: payload
748
+ };
749
+ fs4.writeFileSync(bindingSidecarPath(socketPath), JSON.stringify(record), "utf8");
750
+ }
751
+
752
+ // src/index.ts
753
+ var path10 = __toESM(require("path"), 1);
754
+
663
755
  // src/bridgeClientRoots.ts
664
756
  var readline = __toESM(require("readline"), 1);
665
757
  var TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
@@ -668,13 +760,13 @@ function isInitializeDebugEnabled(env2 = process.env) {
668
760
  }
669
761
 
670
762
  // src/sourceRegistration.ts
671
- var path6 = __toESM(require("path"), 1);
763
+ var path7 = __toESM(require("path"), 1);
672
764
  var import_node_url2 = require("url");
673
765
  var LOG_PREFIX = "[memoraone-mcp][source-registration]";
674
766
  function buildRepoSourcePayload(normalizedRepoPath, ideType) {
675
767
  const body = {
676
768
  kind: "repo",
677
- label: path6.basename(normalizedRepoPath),
769
+ label: path7.basename(normalizedRepoPath),
678
770
  uri: (0, import_node_url2.pathToFileURL)(normalizedRepoPath).href
679
771
  };
680
772
  if (ideType) {
@@ -693,7 +785,7 @@ async function registerRepoSource(client, projectId, repoPath, ideType) {
693
785
  );
694
786
  return;
695
787
  }
696
- const normalizedRepoPath = path6.resolve(String(repoPath));
788
+ const normalizedRepoPath = path7.resolve(String(repoPath));
697
789
  const body = buildRepoSourcePayload(normalizedRepoPath, ideType);
698
790
  const primaryPath = `/v1/projects/${projectId}/sources`;
699
791
  const alternatePath = `/v1/projects/${projectId}/sources/register`;
@@ -1105,9 +1197,9 @@ function buildPersonalContextPath(parsed2) {
1105
1197
  }
1106
1198
  async function handleGetPersonalContext(client, args) {
1107
1199
  const parsed2 = getPersonalContextInputSchema.parse(args ?? {});
1108
- const path9 = buildPersonalContextPath(parsed2);
1200
+ const path11 = buildPersonalContextPath(parsed2);
1109
1201
  try {
1110
- const result = await client.get(path9);
1202
+ const result = await client.get(path11);
1111
1203
  return { ok: true, result };
1112
1204
  } catch (err) {
1113
1205
  if (err instanceof MemoraOneHttpError) {
@@ -1348,8 +1440,8 @@ async function handleListProjects(client) {
1348
1440
  var import_v421 = require("zod/v4");
1349
1441
 
1350
1442
  // src/repoFingerprint.ts
1351
- var fs4 = __toESM(require("fs"), 1);
1352
- var path7 = __toESM(require("path"), 1);
1443
+ var fs5 = __toESM(require("fs"), 1);
1444
+ var path8 = __toESM(require("path"), 1);
1353
1445
  var crypto5 = __toESM(require("crypto"), 1);
1354
1446
  var parseBooleanFlag3 = (value) => {
1355
1447
  if (!value) {
@@ -1379,16 +1471,16 @@ var sha256 = (value) => {
1379
1471
  };
1380
1472
  var resolveGitDir = (gitPath) => {
1381
1473
  try {
1382
- const stat2 = fs4.statSync(gitPath);
1474
+ const stat2 = fs5.statSync(gitPath);
1383
1475
  if (stat2.isDirectory()) {
1384
1476
  return gitPath;
1385
1477
  }
1386
1478
  if (stat2.isFile()) {
1387
- const content = fs4.readFileSync(gitPath, "utf8");
1479
+ const content = fs5.readFileSync(gitPath, "utf8");
1388
1480
  const match = content.match(/^gitdir:\s*(.+)$/m);
1389
1481
  if (match) {
1390
1482
  const gitDir = match[1].trim();
1391
- return path7.resolve(path7.dirname(gitPath), gitDir);
1483
+ return path8.resolve(path8.dirname(gitPath), gitDir);
1392
1484
  }
1393
1485
  }
1394
1486
  } catch {
@@ -1397,16 +1489,16 @@ var resolveGitDir = (gitPath) => {
1397
1489
  return null;
1398
1490
  };
1399
1491
  var findGitRoot = (start) => {
1400
- let current = path7.resolve(start);
1492
+ let current = path8.resolve(start);
1401
1493
  while (true) {
1402
- const gitPath = path7.join(current, ".git");
1403
- if (fs4.existsSync(gitPath)) {
1494
+ const gitPath = path8.join(current, ".git");
1495
+ if (fs5.existsSync(gitPath)) {
1404
1496
  const gitDir = resolveGitDir(gitPath);
1405
1497
  if (gitDir) {
1406
1498
  return { gitRoot: current, gitDir };
1407
1499
  }
1408
1500
  }
1409
- const parent = path7.dirname(current);
1501
+ const parent = path8.dirname(current);
1410
1502
  if (parent === current) {
1411
1503
  break;
1412
1504
  }
@@ -1415,9 +1507,9 @@ var findGitRoot = (start) => {
1415
1507
  return null;
1416
1508
  };
1417
1509
  var readOriginRemote = (gitDir) => {
1418
- const configPath = path7.join(gitDir, "config");
1510
+ const configPath = path8.join(gitDir, "config");
1419
1511
  try {
1420
- const content = fs4.readFileSync(configPath, "utf8");
1512
+ const content = fs5.readFileSync(configPath, "utf8");
1421
1513
  const lines = content.split(/\r?\n/);
1422
1514
  let inOrigin = false;
1423
1515
  for (const line of lines) {
@@ -1441,7 +1533,7 @@ var readOriginRemote = (gitDir) => {
1441
1533
  function resolveRepoFingerprint(cwd2) {
1442
1534
  const found = findGitRoot(cwd2);
1443
1535
  if (!found) {
1444
- const fallbackPath = path7.resolve(cwd2);
1536
+ const fallbackPath = path8.resolve(cwd2);
1445
1537
  const fingerprint2 = sha256(fallbackPath);
1446
1538
  debugLog(`repo fingerprint=${fingerprint2} source=path-fallback`);
1447
1539
  return {
@@ -1463,7 +1555,7 @@ function resolveRepoFingerprint(cwd2) {
1463
1555
  source: "git-remote"
1464
1556
  };
1465
1557
  }
1466
- const fingerprint = sha256(path7.resolve(gitRoot));
1558
+ const fingerprint = sha256(path8.resolve(gitRoot));
1467
1559
  debugLog(`repo fingerprint=${fingerprint} source=path-fallback`);
1468
1560
  return {
1469
1561
  fingerprint,
@@ -1473,8 +1565,8 @@ function resolveRepoFingerprint(cwd2) {
1473
1565
  }
1474
1566
 
1475
1567
  // src/workspaceMap.ts
1476
- var fs5 = __toESM(require("fs/promises"), 1);
1477
- var path8 = __toESM(require("path"), 1);
1568
+ var fs6 = __toESM(require("fs/promises"), 1);
1569
+ var path9 = __toESM(require("path"), 1);
1478
1570
  var import_node_os = __toESM(require("os"), 1);
1479
1571
  var parseBooleanFlag4 = (value) => {
1480
1572
  if (!value) {
@@ -1493,11 +1585,11 @@ var debugLog2 = (message) => {
1493
1585
  };
1494
1586
  var fingerprintRegex = /^[0-9a-f]{64}$/i;
1495
1587
  function getWorkspaceMapPath() {
1496
- return path8.join(import_node_os.default.homedir(), ".memoraone", "workspaces.json");
1588
+ return path9.join(import_node_os.default.homedir(), ".memoraone", "workspaces.json");
1497
1589
  }
1498
1590
  var ensureWorkspaceDir = async () => {
1499
- const dir = path8.dirname(getWorkspaceMapPath());
1500
- await fs5.mkdir(dir, { recursive: true });
1591
+ const dir = path9.dirname(getWorkspaceMapPath());
1592
+ await fs6.mkdir(dir, { recursive: true });
1501
1593
  };
1502
1594
  async function acquireWorkspaceMapLock() {
1503
1595
  const filePath = getWorkspaceMapPath();
@@ -1511,10 +1603,10 @@ async function acquireWorkspaceMapLock() {
1511
1603
  while (!lockAcquired && retries < maxRetries) {
1512
1604
  try {
1513
1605
  try {
1514
- const stat2 = await fs5.stat(lockPath);
1606
+ const stat2 = await fs6.stat(lockPath);
1515
1607
  const ageMs = Date.now() - stat2.mtimeMs;
1516
1608
  if (ageMs > maxLockAgeMs) {
1517
- await fs5.unlink(lockPath);
1609
+ await fs6.unlink(lockPath);
1518
1610
  debugLog2(`removed stale workspace map lock (age: ${ageMs}ms)`);
1519
1611
  }
1520
1612
  } catch (err) {
@@ -1522,14 +1614,14 @@ async function acquireWorkspaceMapLock() {
1522
1614
  throw err;
1523
1615
  }
1524
1616
  }
1525
- const fd = await fs5.open(lockPath, "wx");
1617
+ const fd = await fs6.open(lockPath, "wx");
1526
1618
  await fd.close();
1527
1619
  lockAcquired = true;
1528
1620
  } catch (err) {
1529
1621
  if (err?.code === "EEXIST") {
1530
1622
  retries++;
1531
1623
  if (retries < maxRetries) {
1532
- await new Promise((resolve7) => setTimeout(resolve7, retryDelayMs));
1624
+ await new Promise((resolve8) => setTimeout(resolve8, retryDelayMs));
1533
1625
  continue;
1534
1626
  }
1535
1627
  throw new Error(
@@ -1541,7 +1633,7 @@ async function acquireWorkspaceMapLock() {
1541
1633
  }
1542
1634
  return async () => {
1543
1635
  try {
1544
- await fs5.unlink(lockPath);
1636
+ await fs6.unlink(lockPath);
1545
1637
  } catch (err) {
1546
1638
  if (err?.code !== "ENOENT") {
1547
1639
  debugLog2(`failed to release workspace map lock: ${String(err)}`);
@@ -1597,7 +1689,7 @@ var validateWorkspaceMap = (map, filePath) => {
1597
1689
  async function readWorkspaceMap() {
1598
1690
  const filePath = getWorkspaceMapPath();
1599
1691
  try {
1600
- const content = await fs5.readFile(filePath, "utf8");
1692
+ const content = await fs6.readFile(filePath, "utf8");
1601
1693
  const parsed2 = JSON.parse(content);
1602
1694
  validateWorkspaceMap(parsed2, filePath);
1603
1695
  const typed = parsed2;
@@ -1642,8 +1734,8 @@ async function writeWorkspaceMap(map) {
1642
1734
  await ensureWorkspaceDir();
1643
1735
  const tempPath = `${filePath}.tmp`;
1644
1736
  const content = JSON.stringify(map, null, 2);
1645
- await fs5.writeFile(tempPath, content, "utf8");
1646
- await fs5.rename(tempPath, filePath);
1737
+ await fs6.writeFile(tempPath, content, "utf8");
1738
+ await fs6.rename(tempPath, filePath);
1647
1739
  }
1648
1740
  async function setProjectIdForFingerprint(args) {
1649
1741
  const { fingerprint, projectKey, source, linked_at } = args;
@@ -1702,24 +1794,25 @@ async function handleSetProject(args) {
1702
1794
  }
1703
1795
 
1704
1796
  // src/tools/handlers/bindingStatus.ts
1705
- function buildBindingStatus(binding) {
1797
+ function buildBindingStatus(binding, options = {}) {
1706
1798
  const status = {
1707
1799
  projectId: binding.projectId,
1708
1800
  workspaceRoot: binding.workspaceRoot,
1709
1801
  m1Path: binding.m1Path,
1710
1802
  bindingSource: binding.bindingSource,
1711
- apiKeySource: binding.apiKeySource
1803
+ apiKeySource: binding.apiKeySource,
1804
+ cacheRefreshed: options.cacheRefreshed === true
1712
1805
  };
1713
1806
  if (binding.environment !== void 0) {
1714
1807
  status.environment = binding.environment;
1715
1808
  }
1716
1809
  return status;
1717
1810
  }
1718
- function handleBindingStatus(binding) {
1811
+ function handleBindingStatus(binding, options = {}) {
1719
1812
  if (!binding) {
1720
1813
  throw new Error("[memoraone-mcp] Binding status unavailable (not initialized)");
1721
1814
  }
1722
- return buildBindingStatus(binding);
1815
+ return buildBindingStatus(binding, options);
1723
1816
  }
1724
1817
 
1725
1818
  // src/heartbeat.ts
@@ -1888,8 +1981,8 @@ function registerToolWithWorklog(server, runtime, sessionContext, toolName, desc
1888
1981
  async function main(opts = {}) {
1889
1982
  let bindingReadyResolve = null;
1890
1983
  let bindingReadyReject = null;
1891
- const bindingReady = new Promise((resolve7, reject) => {
1892
- bindingReadyResolve = resolve7;
1984
+ const bindingReady = new Promise((resolve8, reject) => {
1985
+ bindingReadyResolve = resolve8;
1893
1986
  bindingReadyReject = reject;
1894
1987
  });
1895
1988
  const devMode = Boolean(config2.devMode);
@@ -1901,6 +1994,7 @@ async function main(opts = {}) {
1901
1994
  apiKeySource: null,
1902
1995
  apiKeyFingerprint: null,
1903
1996
  authoritativeBinding: null,
1997
+ bindingCacheRefreshed: false,
1904
1998
  ideType: void 0
1905
1999
  };
1906
2000
  let workspaceRoot;
@@ -1913,10 +2007,25 @@ async function main(opts = {}) {
1913
2007
  if (initializeRoots.length === 0 && opts.daemonBindingHint) {
1914
2008
  if (isInitializeDebugEnabled()) {
1915
2009
  console.error(
1916
- "[memoraone-mcp][init-debug] Workspace resolution strategy: daemonBindingHint (bridge pre-resolved)"
2010
+ "[memoraone-mcp][init-debug] Workspace resolution strategy: daemonBindingHint (bridge pre-resolved, reconciled from disk)"
1917
2011
  );
1918
2012
  }
1919
- return opts.daemonBindingHint;
2013
+ const reconciled = await reconcileResolvedBindingWithDisk(opts.daemonBindingHint);
2014
+ runtime.bindingCacheRefreshed = reconciled.cacheRefreshed;
2015
+ if (reconciled.cacheRefreshed) {
2016
+ console.error(
2017
+ `[memoraone-mcp] refreshed stale cached binding from ${reconciled.binding.m1Path}: project=${reconciled.binding.projectId}`
2018
+ );
2019
+ try {
2020
+ const socketPath = getBindingSocketPath(opts.daemonBindingHint);
2021
+ writeBindingSidecar(socketPath, reconciled.binding, runtime.ideType ?? "");
2022
+ } catch (err) {
2023
+ console.error(
2024
+ `[memoraone-mcp] warning: could not rewrite binding sidecar after refresh: ${String(err)}`
2025
+ );
2026
+ }
2027
+ }
2028
+ return reconciled.binding;
1920
2029
  }
1921
2030
  let rootsListUris;
1922
2031
  let rootsListAttempted = false;
@@ -2043,7 +2152,9 @@ async function main(opts = {}) {
2043
2152
  bindingStatusShape,
2044
2153
  async () => runWithSessionContext(sessionContext, async () => {
2045
2154
  if (!runtime.authoritativeBinding) return notInitializedResult;
2046
- const result = handleBindingStatus(runtime.authoritativeBinding);
2155
+ const result = handleBindingStatus(runtime.authoritativeBinding, {
2156
+ cacheRefreshed: runtime.bindingCacheRefreshed
2157
+ });
2047
2158
  return {
2048
2159
  content: [{ type: "text", text: JSON.stringify(result) }]
2049
2160
  };
@@ -2167,7 +2278,7 @@ async function main(opts = {}) {
2167
2278
  );
2168
2279
  const debugLog3 = config2.devMode || debugAuth;
2169
2280
  const binding = await resolveSessionBindingFromInitialize(params);
2170
- if (opts.daemonBindingHint && !bindingsMatch(opts.daemonBindingHint, binding)) {
2281
+ if (opts.daemonBindingHint && path10.resolve(opts.daemonBindingHint.m1Path) !== path10.resolve(binding.m1Path)) {
2171
2282
  const errMsg = formatBindingMismatchError(opts.daemonBindingHint, binding);
2172
2283
  console.error(`[memoraone-mcp][ERROR] ${errMsg}`);
2173
2284
  bindingReadyReject?.(new Error(errMsg));
@@ -2187,15 +2298,23 @@ async function main(opts = {}) {
2187
2298
  const projectId = binding.projectId;
2188
2299
  const existing = getBoundProjectId();
2189
2300
  if (existing !== null && existing !== projectId) {
2190
- const requestedRoot = binding.workspaceRoot ?? workspaceRoot ?? process.cwd();
2191
- const action = "Open this repo in a separate window or configure a separate MCP server instance per root.";
2192
- const errMsg = `[memoraone-mcp] This MCP process is already bound to project ${existing}. Open a new IDE window or start a separate MCP instance for a different project.`;
2193
- console.error(
2194
- `[memoraone-mcp][ERROR] Option A conflict: boundProjectId=${existing} requestedProjectId=${projectId} workspaceRoot=${requestedRoot}. ${action}`
2195
- );
2196
- bindingReadyReject?.(new Error(errMsg));
2197
- setImmediate(() => process.exit(1));
2198
- throw new Error(errMsg);
2301
+ if (runtime.bindingCacheRefreshed) {
2302
+ setBoundProjectId(projectId);
2303
+ setBoundApiKey(apiKeyToUse);
2304
+ console.error(
2305
+ `[memoraone-mcp] ${sessionLabel} rebound to project ${projectId} after stale memoraone.m1 cache refresh (was ${existing})`
2306
+ );
2307
+ } else {
2308
+ const requestedRoot = binding.workspaceRoot ?? workspaceRoot ?? process.cwd();
2309
+ const action = "Open this repo in a separate window or configure a separate MCP server instance per root.";
2310
+ const errMsg = `[memoraone-mcp] This MCP process is already bound to project ${existing}. Open a new IDE window or start a separate MCP instance for a different project.`;
2311
+ console.error(
2312
+ `[memoraone-mcp][ERROR] Option A conflict: boundProjectId=${existing} requestedProjectId=${projectId} workspaceRoot=${requestedRoot}. ${action}`
2313
+ );
2314
+ bindingReadyReject?.(new Error(errMsg));
2315
+ setImmediate(() => process.exit(1));
2316
+ throw new Error(errMsg);
2317
+ }
2199
2318
  }
2200
2319
  if (existing === null) {
2201
2320
  setBoundProjectId(projectId);
@@ -2296,10 +2415,10 @@ async function main(opts = {}) {
2296
2415
  console.error("[memoraone-mcp] MCP server ready");
2297
2416
  }
2298
2417
  if (opts.sessionSocket) {
2299
- await new Promise((resolve7) => {
2418
+ await new Promise((resolve8) => {
2300
2419
  opts.sessionSocket.once("close", () => {
2301
2420
  shutdown("session closed", false);
2302
- resolve7();
2421
+ resolve8();
2303
2422
  });
2304
2423
  });
2305
2424
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memoraone/mcp",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {
@@ -13,23 +13,22 @@
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",
28
19
  "zod": "^4.0.0"
29
20
  },
30
21
  "devDependencies": {
31
- "tsx": "^4.21.0",
32
22
  "tsup": "^8.5.1",
23
+ "tsx": "^4.21.0",
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
+ }