@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/daemon.cjs CHANGED
@@ -54,7 +54,9 @@ function hashBindingIdentity(projectId, workspaceRoot, ideType) {
54
54
  return crypto.createHash("sha256").update(input).digest("hex").slice(0, BINDING_SOCKET_HASH_LENGTH);
55
55
  }
56
56
  function bindingsMatch(a, b) {
57
- return a.projectId.trim().toLowerCase() === b.projectId.trim().toLowerCase() && path.resolve(a.workspaceRoot) === path.resolve(b.workspaceRoot) && path.resolve(a.m1Path) === path.resolve(b.m1Path);
57
+ const envA = a.environment ?? void 0;
58
+ const envB = b.environment ?? void 0;
59
+ return a.projectId.trim().toLowerCase() === b.projectId.trim().toLowerCase() && path.resolve(a.workspaceRoot) === path.resolve(b.workspaceRoot) && path.resolve(a.m1Path) === path.resolve(b.m1Path) && (a.apiKey ?? null) === (b.apiKey ?? null) && envA === envB;
58
60
  }
59
61
  function formatMissingInitializeWorkspaceError(options) {
60
62
  const lines = [
@@ -125,6 +127,10 @@ function ensureBaseDir() {
125
127
  var fs2 = __toESM(require("fs/promises"), 1);
126
128
  var path3 = __toESM(require("path"), 1);
127
129
  var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
130
+ var CANONICAL_M1_FILENAME = "memoraone.m1";
131
+ function isCanonicalM1Path(m1Path) {
132
+ return path3.basename(m1Path) === CANONICAL_M1_FILENAME;
133
+ }
128
134
  function normalizeEnvironment(raw) {
129
135
  if (raw === void 0 || raw === null || typeof raw !== "string") {
130
136
  return void 0;
@@ -171,7 +177,7 @@ async function resolveProjectIdFromExplicitM1Path() {
171
177
  async function findM1WalkingUp(workspaceRoot) {
172
178
  let current = path3.resolve(workspaceRoot);
173
179
  while (true) {
174
- const markerPath = path3.join(current, "memoraone.m1");
180
+ const markerPath = path3.join(current, CANONICAL_M1_FILENAME);
175
181
  try {
176
182
  const content = await fs2.readFile(markerPath, "utf8");
177
183
  const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
@@ -278,6 +284,45 @@ async function resolveAuthoritativeBinding(workspaceRoot, options = {}) {
278
284
  }
279
285
  return bindings[0];
280
286
  }
287
+ function bindingRelevantValuesMatch(a, b) {
288
+ const envA = a.environment ?? void 0;
289
+ const envB = b.environment ?? void 0;
290
+ 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;
291
+ }
292
+ async function reconcileResolvedBindingWithDisk(cached) {
293
+ const m1Path = path3.resolve(cached.m1Path);
294
+ if (cached.bindingSource !== "explicit-m1-path" && !isCanonicalM1Path(m1Path)) {
295
+ throw new Error(
296
+ `[memoraone-mcp] Cached binding m1Path is not the canonical ${CANONICAL_M1_FILENAME}: ${m1Path}`
297
+ );
298
+ }
299
+ let content;
300
+ try {
301
+ content = await fs2.readFile(m1Path, "utf8");
302
+ } catch (err) {
303
+ if (err?.code === "ENOENT") {
304
+ throw new Error(
305
+ `[memoraone-mcp] Cached binding file missing at ${m1Path}. Open a folder containing ${CANONICAL_M1_FILENAME}.`
306
+ );
307
+ }
308
+ throw err;
309
+ }
310
+ const parsed2 = parseAndValidateM1(content, m1Path);
311
+ const resolved = resolveApiKeyWithSource(parsed2.apiKey);
312
+ const fresh = {
313
+ projectId: parsed2.projectId,
314
+ workspaceRoot: path3.resolve(path3.dirname(m1Path)),
315
+ m1Path,
316
+ apiKey: resolved.apiKey,
317
+ ...parsed2.environment !== void 0 ? { environment: parsed2.environment } : {},
318
+ bindingSource: cached.bindingSource,
319
+ apiKeySource: resolved.apiKeySource
320
+ };
321
+ if (bindingRelevantValuesMatch(cached, fresh)) {
322
+ return { binding: fresh, cacheRefreshed: false };
323
+ }
324
+ return { binding: fresh, cacheRefreshed: true };
325
+ }
281
326
  function encodeResolvedBinding(binding) {
282
327
  return Buffer.from(JSON.stringify(binding), "utf8").toString("base64");
283
328
  }
@@ -523,12 +568,12 @@ var MemoraClient = class {
523
568
  ...options?.headers ?? {}
524
569
  };
525
570
  }
526
- async post(path10, body, options) {
571
+ async post(path11, body, options) {
527
572
  console.error(
528
- `[memoraone-mcp][info] MemoraClient.post ENTER path=${path10}`
573
+ `[memoraone-mcp][info] MemoraClient.post ENTER path=${path11}`
529
574
  );
530
575
  const nonce = crypto2.randomBytes(8).toString("hex");
531
- const url = `${this.baseUrl}${path10.startsWith("/") ? path10 : `/${path10}`}`;
576
+ const url = `${this.baseUrl}${path11.startsWith("/") ? path11 : `/${path11}`}`;
532
577
  this.resolveProjectId();
533
578
  console.error(
534
579
  `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_fetch method=POST url=${url}`
@@ -561,13 +606,13 @@ var MemoraClient = class {
561
606
  throw new MemoraOneHttpError(res.status, res.statusText, res.text);
562
607
  }
563
608
  console.error(
564
- `[memoraone-mcp][info] MemoraClient.post EXIT path=${path10}`
609
+ `[memoraone-mcp][info] MemoraClient.post EXIT path=${path11}`
565
610
  );
566
611
  return res.text ? JSON.parse(res.text) : null;
567
612
  }
568
- async get(path10, options) {
613
+ async get(path11, options) {
569
614
  const nonce = crypto2.randomBytes(8).toString("hex");
570
- const url = `${this.baseUrl}${path10.startsWith("/") ? path10 : `/${path10}`}`;
615
+ const url = `${this.baseUrl}${path11.startsWith("/") ? path11 : `/${path11}`}`;
571
616
  this.resolveProjectId();
572
617
  console.error(
573
618
  `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_fetch method=GET url=${url}`
@@ -770,6 +815,9 @@ async function resolveBindingFromInitializeParams(params, options = {}) {
770
815
  });
771
816
  }
772
817
 
818
+ // src/index.ts
819
+ var path10 = __toESM(require("path"), 1);
820
+
773
821
  // src/bridgeClientRoots.ts
774
822
  var readline = __toESM(require("readline"), 1);
775
823
  var TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
@@ -1215,9 +1263,9 @@ function buildPersonalContextPath(parsed2) {
1215
1263
  }
1216
1264
  async function handleGetPersonalContext(client, args) {
1217
1265
  const parsed2 = getPersonalContextInputSchema.parse(args ?? {});
1218
- const path10 = buildPersonalContextPath(parsed2);
1266
+ const path11 = buildPersonalContextPath(parsed2);
1219
1267
  try {
1220
- const result = await client.get(path10);
1268
+ const result = await client.get(path11);
1221
1269
  return { ok: true, result };
1222
1270
  } catch (err) {
1223
1271
  if (err instanceof MemoraOneHttpError) {
@@ -1639,7 +1687,7 @@ async function acquireWorkspaceMapLock() {
1639
1687
  if (err?.code === "EEXIST") {
1640
1688
  retries++;
1641
1689
  if (retries < maxRetries) {
1642
- await new Promise((resolve7) => setTimeout(resolve7, retryDelayMs));
1690
+ await new Promise((resolve8) => setTimeout(resolve8, retryDelayMs));
1643
1691
  continue;
1644
1692
  }
1645
1693
  throw new Error(
@@ -1812,24 +1860,25 @@ async function handleSetProject(args) {
1812
1860
  }
1813
1861
 
1814
1862
  // src/tools/handlers/bindingStatus.ts
1815
- function buildBindingStatus(binding) {
1863
+ function buildBindingStatus(binding, options = {}) {
1816
1864
  const status = {
1817
1865
  projectId: binding.projectId,
1818
1866
  workspaceRoot: binding.workspaceRoot,
1819
1867
  m1Path: binding.m1Path,
1820
1868
  bindingSource: binding.bindingSource,
1821
- apiKeySource: binding.apiKeySource
1869
+ apiKeySource: binding.apiKeySource,
1870
+ cacheRefreshed: options.cacheRefreshed === true
1822
1871
  };
1823
1872
  if (binding.environment !== void 0) {
1824
1873
  status.environment = binding.environment;
1825
1874
  }
1826
1875
  return status;
1827
1876
  }
1828
- function handleBindingStatus(binding) {
1877
+ function handleBindingStatus(binding, options = {}) {
1829
1878
  if (!binding) {
1830
1879
  throw new Error("[memoraone-mcp] Binding status unavailable (not initialized)");
1831
1880
  }
1832
- return buildBindingStatus(binding);
1881
+ return buildBindingStatus(binding, options);
1833
1882
  }
1834
1883
 
1835
1884
  // src/heartbeat.ts
@@ -2063,8 +2112,8 @@ function registerToolWithWorklog(server, runtime, sessionContext, toolName, desc
2063
2112
  async function main(opts = {}) {
2064
2113
  let bindingReadyResolve = null;
2065
2114
  let bindingReadyReject = null;
2066
- const bindingReady = new Promise((resolve7, reject) => {
2067
- bindingReadyResolve = resolve7;
2115
+ const bindingReady = new Promise((resolve8, reject) => {
2116
+ bindingReadyResolve = resolve8;
2068
2117
  bindingReadyReject = reject;
2069
2118
  });
2070
2119
  const devMode = Boolean(config2.devMode);
@@ -2076,6 +2125,7 @@ async function main(opts = {}) {
2076
2125
  apiKeySource: null,
2077
2126
  apiKeyFingerprint: null,
2078
2127
  authoritativeBinding: null,
2128
+ bindingCacheRefreshed: false,
2079
2129
  ideType: void 0
2080
2130
  };
2081
2131
  let workspaceRoot;
@@ -2088,10 +2138,25 @@ async function main(opts = {}) {
2088
2138
  if (initializeRoots.length === 0 && opts.daemonBindingHint) {
2089
2139
  if (isInitializeDebugEnabled()) {
2090
2140
  console.error(
2091
- "[memoraone-mcp][init-debug] Workspace resolution strategy: daemonBindingHint (bridge pre-resolved)"
2141
+ "[memoraone-mcp][init-debug] Workspace resolution strategy: daemonBindingHint (bridge pre-resolved, reconciled from disk)"
2092
2142
  );
2093
2143
  }
2094
- return opts.daemonBindingHint;
2144
+ const reconciled = await reconcileResolvedBindingWithDisk(opts.daemonBindingHint);
2145
+ runtime.bindingCacheRefreshed = reconciled.cacheRefreshed;
2146
+ if (reconciled.cacheRefreshed) {
2147
+ console.error(
2148
+ `[memoraone-mcp] refreshed stale cached binding from ${reconciled.binding.m1Path}: project=${reconciled.binding.projectId}`
2149
+ );
2150
+ try {
2151
+ const socketPath = getBindingSocketPath(opts.daemonBindingHint);
2152
+ writeBindingSidecar(socketPath, reconciled.binding, runtime.ideType ?? "");
2153
+ } catch (err) {
2154
+ console.error(
2155
+ `[memoraone-mcp] warning: could not rewrite binding sidecar after refresh: ${String(err)}`
2156
+ );
2157
+ }
2158
+ }
2159
+ return reconciled.binding;
2095
2160
  }
2096
2161
  let rootsListUris;
2097
2162
  let rootsListAttempted = false;
@@ -2218,7 +2283,9 @@ async function main(opts = {}) {
2218
2283
  bindingStatusShape,
2219
2284
  async () => runWithSessionContext(sessionContext, async () => {
2220
2285
  if (!runtime.authoritativeBinding) return notInitializedResult;
2221
- const result = handleBindingStatus(runtime.authoritativeBinding);
2286
+ const result = handleBindingStatus(runtime.authoritativeBinding, {
2287
+ cacheRefreshed: runtime.bindingCacheRefreshed
2288
+ });
2222
2289
  return {
2223
2290
  content: [{ type: "text", text: JSON.stringify(result) }]
2224
2291
  };
@@ -2342,7 +2409,7 @@ async function main(opts = {}) {
2342
2409
  );
2343
2410
  const debugLog3 = config2.devMode || debugAuth;
2344
2411
  const binding = await resolveSessionBindingFromInitialize(params);
2345
- if (opts.daemonBindingHint && !bindingsMatch(opts.daemonBindingHint, binding)) {
2412
+ if (opts.daemonBindingHint && path10.resolve(opts.daemonBindingHint.m1Path) !== path10.resolve(binding.m1Path)) {
2346
2413
  const errMsg = formatBindingMismatchError(opts.daemonBindingHint, binding);
2347
2414
  console.error(`[memoraone-mcp][ERROR] ${errMsg}`);
2348
2415
  bindingReadyReject?.(new Error(errMsg));
@@ -2362,15 +2429,23 @@ async function main(opts = {}) {
2362
2429
  const projectId = binding.projectId;
2363
2430
  const existing = getBoundProjectId();
2364
2431
  if (existing !== null && existing !== projectId) {
2365
- const requestedRoot = binding.workspaceRoot ?? workspaceRoot ?? process.cwd();
2366
- const action = "Open this repo in a separate window or configure a separate MCP server instance per root.";
2367
- 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.`;
2368
- console.error(
2369
- `[memoraone-mcp][ERROR] Option A conflict: boundProjectId=${existing} requestedProjectId=${projectId} workspaceRoot=${requestedRoot}. ${action}`
2370
- );
2371
- bindingReadyReject?.(new Error(errMsg));
2372
- setImmediate(() => process.exit(1));
2373
- throw new Error(errMsg);
2432
+ if (runtime.bindingCacheRefreshed) {
2433
+ setBoundProjectId(projectId);
2434
+ setBoundApiKey(apiKeyToUse);
2435
+ console.error(
2436
+ `[memoraone-mcp] ${sessionLabel} rebound to project ${projectId} after stale memoraone.m1 cache refresh (was ${existing})`
2437
+ );
2438
+ } else {
2439
+ const requestedRoot = binding.workspaceRoot ?? workspaceRoot ?? process.cwd();
2440
+ const action = "Open this repo in a separate window or configure a separate MCP server instance per root.";
2441
+ 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.`;
2442
+ console.error(
2443
+ `[memoraone-mcp][ERROR] Option A conflict: boundProjectId=${existing} requestedProjectId=${projectId} workspaceRoot=${requestedRoot}. ${action}`
2444
+ );
2445
+ bindingReadyReject?.(new Error(errMsg));
2446
+ setImmediate(() => process.exit(1));
2447
+ throw new Error(errMsg);
2448
+ }
2374
2449
  }
2375
2450
  if (existing === null) {
2376
2451
  setBoundProjectId(projectId);
@@ -2471,10 +2546,10 @@ async function main(opts = {}) {
2471
2546
  console.error("[memoraone-mcp] MCP server ready");
2472
2547
  }
2473
2548
  if (opts.sessionSocket) {
2474
- await new Promise((resolve7) => {
2549
+ await new Promise((resolve8) => {
2475
2550
  opts.sessionSocket.once("close", () => {
2476
2551
  shutdown("session closed", false);
2477
- resolve7();
2552
+ resolve8();
2478
2553
  });
2479
2554
  });
2480
2555
  }
@@ -2513,7 +2588,7 @@ async function ensureSocketClean(socketPath) {
2513
2588
  } catch {
2514
2589
  return;
2515
2590
  }
2516
- return new Promise((resolve7) => {
2591
+ return new Promise((resolve8) => {
2517
2592
  const client = net.createConnection({ path: socketPath }, () => {
2518
2593
  client.destroy();
2519
2594
  log("daemon already running, exiting");
@@ -2525,7 +2600,7 @@ async function ensureSocketClean(socketPath) {
2525
2600
  log("stale socket removed");
2526
2601
  } catch {
2527
2602
  }
2528
- resolve7();
2603
+ resolve8();
2529
2604
  });
2530
2605
  });
2531
2606
  }
@@ -2641,14 +2716,14 @@ async function runDaemon() {
2641
2716
  process.on("SIGINT", () => shutdownNow("SIGINT"));
2642
2717
  process.on("SIGTERM", () => shutdownNow("SIGTERM"));
2643
2718
  process.on("exit", cleanupSocketFile);
2644
- return new Promise((resolve7) => {
2719
+ return new Promise((resolve8) => {
2645
2720
  server.listen(socketPath, () => {
2646
2721
  writeBindingSidecar(socketPath, binding, ideType ?? "");
2647
2722
  log(`daemon started, listening on ${socketPath}`);
2648
2723
  void daemonHeartbeat.start().catch((err) => {
2649
2724
  log(`daemon heartbeat start error: ${String(err)}`);
2650
2725
  });
2651
- resolve7();
2726
+ resolve8();
2652
2727
  });
2653
2728
  });
2654
2729
  }