@memoraone/mcp 0.1.55 → 0.1.56

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
@@ -231,7 +231,7 @@ var require_package = __commonJS({
231
231
  "package.json"(exports2, module2) {
232
232
  module2.exports = {
233
233
  name: "@memoraone/mcp",
234
- version: "0.1.55",
234
+ version: "0.1.56",
235
235
  type: "module",
236
236
  main: "dist/index.cjs",
237
237
  exports: {
@@ -769,6 +769,7 @@ function parsePayload(raw, repositoryBindingId) {
769
769
  optionalString("refreshTokenExpiresAt");
770
770
  optionalString("clientRedeemKey");
771
771
  optionalString("clientRefreshKey");
772
+ optionalString("supabaseUserId");
772
773
  return payload;
773
774
  }
774
775
  async function readInstallationCredentials(repositoryBindingId, options = {}) {
@@ -1226,7 +1227,8 @@ async function redeemLocalConnectCode(apiUrl, body, options = {}) {
1226
1227
  const installationPublicId = data?.installation_public_id;
1227
1228
  const projectId = data?.project_id;
1228
1229
  const repositoryBindingId = data?.repository_binding_id;
1229
- if (typeof accessToken !== "string" || typeof refreshToken !== "string" || typeof installationPublicId !== "string" || typeof projectId !== "string" || typeof repositoryBindingId !== "string") {
1230
+ const supabaseUserId = data?.supabase_user_id;
1231
+ if (typeof accessToken !== "string" || typeof refreshToken !== "string" || typeof installationPublicId !== "string" || typeof projectId !== "string" || typeof repositoryBindingId !== "string" || typeof supabaseUserId !== "string" || supabaseUserId.length === 0) {
1230
1232
  throw new Error("[memoraone-mcp] Invalid redeem response");
1231
1233
  }
1232
1234
  let sourceId = null;
@@ -1244,7 +1246,8 @@ async function redeemLocalConnectCode(apiUrl, body, options = {}) {
1244
1246
  project_id: projectId,
1245
1247
  repository_binding_id: repositoryBindingId,
1246
1248
  source_id: sourceId,
1247
- recovered: Boolean(data.recovered)
1249
+ recovered: Boolean(data.recovered),
1250
+ supabase_user_id: supabaseUserId
1248
1251
  };
1249
1252
  }
1250
1253
  var path8;
@@ -12339,7 +12342,8 @@ async function runConnectCommand(options) {
12339
12342
  accessTokenExpiresAt: redeemed.access_token_expires_at ?? void 0,
12340
12343
  refreshTokenExpiresAt: redeemed.refresh_token_expires_at ?? void 0,
12341
12344
  installationPublicId: redeemed.installation_public_id,
12342
- projectId: redeemed.project_id
12345
+ projectId: redeemed.project_id,
12346
+ supabaseUserId: redeemed.supabase_user_id
12343
12347
  },
12344
12348
  { ...options.credentialOptions, clearKeys: ["clientRedeemKey"] }
12345
12349
  );
package/dist/daemon.cjs CHANGED
@@ -675,6 +675,7 @@ function parsePayload(raw, repositoryBindingId) {
675
675
  optionalString("refreshTokenExpiresAt");
676
676
  optionalString("clientRedeemKey");
677
677
  optionalString("clientRefreshKey");
678
+ optionalString("supabaseUserId");
678
679
  return payload;
679
680
  }
680
681
  async function readInstallationCredentials(repositoryBindingId, options = {}) {
@@ -1278,7 +1279,8 @@ async function redeemLocalConnectCode(apiUrl, body, options = {}) {
1278
1279
  const installationPublicId = data?.installation_public_id;
1279
1280
  const projectId = data?.project_id;
1280
1281
  const repositoryBindingId = data?.repository_binding_id;
1281
- if (typeof accessToken !== "string" || typeof refreshToken !== "string" || typeof installationPublicId !== "string" || typeof projectId !== "string" || typeof repositoryBindingId !== "string") {
1282
+ const supabaseUserId = data?.supabase_user_id;
1283
+ if (typeof accessToken !== "string" || typeof refreshToken !== "string" || typeof installationPublicId !== "string" || typeof projectId !== "string" || typeof repositoryBindingId !== "string" || typeof supabaseUserId !== "string" || supabaseUserId.length === 0) {
1282
1284
  throw new Error("[memoraone-mcp] Invalid redeem response");
1283
1285
  }
1284
1286
  let sourceId = null;
@@ -1296,7 +1298,8 @@ async function redeemLocalConnectCode(apiUrl, body, options = {}) {
1296
1298
  project_id: projectId,
1297
1299
  repository_binding_id: repositoryBindingId,
1298
1300
  source_id: sourceId,
1299
- recovered: Boolean(data.recovered)
1301
+ recovered: Boolean(data.recovered),
1302
+ supabase_user_id: supabaseUserId
1300
1303
  };
1301
1304
  }
1302
1305
  async function refreshLocalMcpToken(apiUrl, body, options = {}) {
@@ -11366,7 +11369,8 @@ async function runConnectCommand(options) {
11366
11369
  accessTokenExpiresAt: redeemed.access_token_expires_at ?? void 0,
11367
11370
  refreshTokenExpiresAt: redeemed.refresh_token_expires_at ?? void 0,
11368
11371
  installationPublicId: redeemed.installation_public_id,
11369
- projectId: redeemed.project_id
11372
+ projectId: redeemed.project_id,
11373
+ supabaseUserId: redeemed.supabase_user_id
11370
11374
  },
11371
11375
  { ...options.credentialOptions, clearKeys: ["clientRedeemKey"] }
11372
11376
  );
package/dist/gateway.cjs CHANGED
@@ -35,7 +35,7 @@ __export(gateway_exports, {
35
35
  module.exports = __toCommonJS(gateway_exports);
36
36
  var http = __toESM(require("http"), 1);
37
37
  var fs38 = __toESM(require("fs"), 1);
38
- var path51 = __toESM(require("path"), 1);
38
+ var path52 = __toESM(require("path"), 1);
39
39
 
40
40
  // src/socketPaths.ts
41
41
  var os = __toESM(require("os"), 1);
@@ -245,6 +245,101 @@ function buildGatewayMcpUrl(workspaceRoot, env2 = process.env) {
245
245
  return `${getGatewayBaseUrl(env2)}${GATEWAY_MCP_PATH}?${GATEWAY_WS_QUERY}=${encodeURIComponent(root)}`;
246
246
  }
247
247
 
248
+ // src/config.ts
249
+ var process2 = __toESM(require("process"), 1);
250
+ var fs2 = __toESM(require("fs"), 1);
251
+ var path4 = __toESM(require("path"), 1);
252
+ var dotenv = __toESM(require("dotenv"), 1);
253
+ var import_v4 = require("zod/v4");
254
+
255
+ // src/configUtils.ts
256
+ var DEFAULT_API_URL = "https://api.memoraone.com";
257
+ var DEV_API_URL = "http://localhost:3001";
258
+ function resolveApiUrl(env2) {
259
+ const explicitUrl = env2.MEMORAONE_API_URL?.trim();
260
+ if (explicitUrl) {
261
+ return explicitUrl;
262
+ }
263
+ const aliasUrl = env2.MEMORA_API_URL?.trim();
264
+ if (aliasUrl) {
265
+ return aliasUrl;
266
+ }
267
+ if (env2.MEMORAONE_DEV_MODE === "1") {
268
+ return DEV_API_URL;
269
+ }
270
+ return DEFAULT_API_URL;
271
+ }
272
+
273
+ // src/config.ts
274
+ var dotenvPath = path4.resolve(process2.cwd(), ".env");
275
+ if (fs2.existsSync(dotenvPath)) {
276
+ try {
277
+ dotenv.config({ path: dotenvPath });
278
+ } catch (err) {
279
+ process2.stderr.write("[memoraone-mcp] Failed to load .env: " + String(err) + "\n");
280
+ }
281
+ }
282
+ var EnvSchema = import_v4.z.object({
283
+ MEMORAONE_API_URL: import_v4.z.string().url().optional(),
284
+ MEMORAONE_API_KEY: import_v4.z.string().min(1).optional(),
285
+ MEMORAONE_DEV_MODE: import_v4.z.string().min(1).optional(),
286
+ MEMORAONE_AGENT_NAME: import_v4.z.string().min(1).optional(),
287
+ MEMORAONE_AGENT_TYPE: import_v4.z.string().min(1).optional(),
288
+ MEMORAONE_SOURCE: import_v4.z.string().min(1).optional(),
289
+ MEMORAONE_IDE_TYPE: import_v4.z.enum(IDE_TYPES).optional(),
290
+ MEMORAONE_WORKLOG: import_v4.z.string().min(1).optional(),
291
+ MEMORAONE_HEARTBEAT: import_v4.z.string().min(1).optional(),
292
+ MEMORAONE_HEARTBEAT_INTERVAL_MS: import_v4.z.string().min(1).optional()
293
+ });
294
+ var requiredEnvVars = [];
295
+ var missingEnvVars = requiredEnvVars.filter((key) => {
296
+ const value = process2.env[key];
297
+ return value === void 0 || value.trim() === "";
298
+ });
299
+ if (missingEnvVars.length > 0) {
300
+ for (const key of missingEnvVars) {
301
+ process2.stderr.write(`Missing ${key}
302
+ `);
303
+ }
304
+ process2.exit(1);
305
+ }
306
+ var parsed = EnvSchema.safeParse(process2.env);
307
+ var resolvedApiUrl = resolveApiUrl(process2.env);
308
+ if (!parsed.success) {
309
+ const formatted = parsed.error.format();
310
+ process2.stderr.write(
311
+ "[memoraone-mcp] Invalid environment variables " + JSON.stringify(formatted) + "\n"
312
+ );
313
+ throw new Error("Config validation failed");
314
+ }
315
+ var parseBooleanFlag = (value, defaultValue) => {
316
+ if (value === void 0) {
317
+ return defaultValue;
318
+ }
319
+ const normalized = value.trim().toLowerCase();
320
+ if (["1", "true", "yes", "on"].includes(normalized)) {
321
+ return true;
322
+ }
323
+ if (["0", "false", "no", "off"].includes(normalized)) {
324
+ return false;
325
+ }
326
+ return defaultValue;
327
+ };
328
+ var config2 = {
329
+ apiUrl: resolvedApiUrl.replace(/\/+$/, ""),
330
+ apiKey: parsed.data.MEMORAONE_API_KEY,
331
+ agentName: parsed.data.MEMORAONE_AGENT_NAME ?? "cursor",
332
+ agentType: parsed.data.MEMORAONE_AGENT_TYPE ?? "agent",
333
+ source: parsed.data.MEMORAONE_SOURCE ?? "cursor",
334
+ ideType: parsed.data.MEMORAONE_IDE_TYPE,
335
+ devMode: parseBooleanFlag(parsed.data.MEMORAONE_DEV_MODE, false),
336
+ worklogEnabled: parseBooleanFlag(parsed.data.MEMORAONE_WORKLOG, true),
337
+ heartbeatEnabled: parseBooleanFlag(parsed.data.MEMORAONE_HEARTBEAT, true),
338
+ // Cadence is owned by LOCAL_MCP_HEARTBEAT_INTERVAL_MS in heartbeat.ts (1_000).
339
+ // Env override is accepted for forward-compat but the timer path ignores it.
340
+ heartbeatIntervalMs: Number.parseInt(parsed.data.MEMORAONE_HEARTBEAT_INTERVAL_MS ?? "1000", 10)
341
+ };
342
+
248
343
  // src/gatewayOAuth.ts
249
344
  var import_node_crypto28 = require("crypto");
250
345
 
@@ -268,6 +363,26 @@ var GATEWAY_OAUTH_ALLOWED_REDIRECT_URIS = [
268
363
  function getGatewayIssuerUrl(env2 = process.env) {
269
364
  return getGatewayBaseUrl(env2);
270
365
  }
366
+ function getGatewayAuthorizationServerIssuerUrl(env2 = process.env) {
367
+ const raw = env2.MEMORAONE_OAUTH_ISSUER_URL?.trim();
368
+ if (!raw) {
369
+ return getGatewayIssuerUrl(env2);
370
+ }
371
+ let parsed2;
372
+ try {
373
+ parsed2 = new URL(raw);
374
+ } catch {
375
+ throw new Error(
376
+ `[memoraone-mcp] Invalid MEMORAONE_OAUTH_ISSUER_URL=${JSON.stringify(raw)} (expected absolute http: or https: URL)`
377
+ );
378
+ }
379
+ if (parsed2.protocol !== "http:" && parsed2.protocol !== "https:") {
380
+ throw new Error(
381
+ `[memoraone-mcp] Invalid MEMORAONE_OAUTH_ISSUER_URL=${JSON.stringify(raw)} (expected absolute http: or https: URL)`
382
+ );
383
+ }
384
+ return raw.replace(/\/+$/, "");
385
+ }
271
386
  function getGatewayResourceBaseUrl(env2 = process.env) {
272
387
  return `${getGatewayBaseUrl(env2)}${GATEWAY_MCP_PATH}`;
273
388
  }
@@ -397,47 +512,47 @@ var GatewayOAuthCodeStore = class {
397
512
  };
398
513
 
399
514
  // src/gatewayWorkspace.ts
400
- var fs6 = __toESM(require("fs/promises"), 1);
401
- var path11 = __toESM(require("path"), 1);
515
+ var fs7 = __toESM(require("fs/promises"), 1);
516
+ var path12 = __toESM(require("path"), 1);
402
517
 
403
518
  // src/localState/resolveLocalBinding.ts
404
- var path10 = __toESM(require("path"), 1);
519
+ var path11 = __toESM(require("path"), 1);
405
520
 
406
521
  // src/localState/bindingStore.ts
407
- var fs4 = __toESM(require("fs/promises"), 1);
408
- var path6 = __toESM(require("path"), 1);
522
+ var fs5 = __toESM(require("fs/promises"), 1);
523
+ var path7 = __toESM(require("path"), 1);
409
524
 
410
525
  // src/localState/atomicFs.ts
411
- var fs2 = __toESM(require("fs/promises"), 1);
412
- var path4 = __toESM(require("path"), 1);
526
+ var fs3 = __toESM(require("fs/promises"), 1);
527
+ var path5 = __toESM(require("path"), 1);
413
528
  var import_node_crypto2 = require("crypto");
414
529
  var STATE_DIR_MODE = 448;
415
530
  var STATE_FILE_MODE = 384;
416
531
  async function ensurePrivateDir(dirPath) {
417
- await fs2.mkdir(dirPath, { recursive: true, mode: STATE_DIR_MODE });
532
+ await fs3.mkdir(dirPath, { recursive: true, mode: STATE_DIR_MODE });
418
533
  try {
419
- await fs2.chmod(dirPath, STATE_DIR_MODE);
534
+ await fs3.chmod(dirPath, STATE_DIR_MODE);
420
535
  } catch {
421
536
  }
422
537
  }
423
538
  async function writeFileAtomic(filePath, content, options = {}) {
424
539
  const mode = options.mode ?? STATE_FILE_MODE;
425
- const dir = path4.dirname(filePath);
540
+ const dir = path5.dirname(filePath);
426
541
  await ensurePrivateDir(dir);
427
- const tmpPath = path4.join(
542
+ const tmpPath = path5.join(
428
543
  dir,
429
- `.${path4.basename(filePath)}.${process.pid}.${(0, import_node_crypto2.randomBytes)(8).toString("hex")}.tmp`
544
+ `.${path5.basename(filePath)}.${process.pid}.${(0, import_node_crypto2.randomBytes)(8).toString("hex")}.tmp`
430
545
  );
431
546
  try {
432
- await fs2.writeFile(tmpPath, content, { encoding: "utf8", mode });
433
- await fs2.rename(tmpPath, filePath);
547
+ await fs3.writeFile(tmpPath, content, { encoding: "utf8", mode });
548
+ await fs3.rename(tmpPath, filePath);
434
549
  try {
435
- await fs2.chmod(filePath, mode);
550
+ await fs3.chmod(filePath, mode);
436
551
  } catch {
437
552
  }
438
553
  } catch (err) {
439
554
  try {
440
- await fs2.unlink(tmpPath);
555
+ await fs3.unlink(tmpPath);
441
556
  } catch {
442
557
  }
443
558
  throw err;
@@ -445,7 +560,7 @@ async function writeFileAtomic(filePath, content, options = {}) {
445
560
  }
446
561
  async function readJsonFile(filePath) {
447
562
  try {
448
- const raw = await fs2.readFile(filePath, "utf8");
563
+ const raw = await fs3.readFile(filePath, "utf8");
449
564
  return JSON.parse(raw);
450
565
  } catch (err) {
451
566
  if (err?.code === "ENOENT") {
@@ -572,32 +687,32 @@ function parseBindingRecord(raw) {
572
687
  }
573
688
 
574
689
  // src/localState/localLocks.ts
575
- var fs3 = __toESM(require("fs/promises"), 1);
690
+ var fs4 = __toESM(require("fs/promises"), 1);
576
691
 
577
692
  // src/localState/statePaths.ts
578
693
  var os2 = __toESM(require("os"), 1);
579
- var path5 = __toESM(require("path"), 1);
694
+ var path6 = __toESM(require("path"), 1);
580
695
  var MEMORAONE_STATE_DIRNAME = ".memoraone";
581
696
  function getMemoraoneStateDir(homeDir = os2.homedir()) {
582
- return path5.join(homeDir, MEMORAONE_STATE_DIRNAME);
697
+ return path6.join(homeDir, MEMORAONE_STATE_DIRNAME);
583
698
  }
584
699
  function getPathIndexPath(homeDir = os2.homedir()) {
585
- return path5.join(getMemoraoneStateDir(homeDir), "path-index.json");
700
+ return path6.join(getMemoraoneStateDir(homeDir), "path-index.json");
586
701
  }
587
702
  function getBindingsDir(homeDir = os2.homedir()) {
588
- return path5.join(getMemoraoneStateDir(homeDir), "bindings");
703
+ return path6.join(getMemoraoneStateDir(homeDir), "bindings");
589
704
  }
590
705
  function getBindingFilePath(repositoryBindingId, homeDir = os2.homedir()) {
591
- return path5.join(getBindingsDir(homeDir), `${repositoryBindingId}.json`);
706
+ return path6.join(getBindingsDir(homeDir), `${repositoryBindingId}.json`);
592
707
  }
593
708
  function getLocksDir(homeDir = os2.homedir()) {
594
- return path5.join(getMemoraoneStateDir(homeDir), "locks");
709
+ return path6.join(getMemoraoneStateDir(homeDir), "locks");
595
710
  }
596
711
  function getLockPath(lockName, homeDir = os2.homedir()) {
597
- return path5.join(getLocksDir(homeDir), `${lockName}.lock`);
712
+ return path6.join(getLocksDir(homeDir), `${lockName}.lock`);
598
713
  }
599
714
  function getGatewayTicketsPath(homeDir = os2.homedir()) {
600
- return path5.join(getMemoraoneStateDir(homeDir), "gateway-tickets.json");
715
+ return path6.join(getMemoraoneStateDir(homeDir), "gateway-tickets.json");
601
716
  }
602
717
 
603
718
  // src/localState/localLocks.ts
@@ -612,16 +727,16 @@ async function acquireLocalLock(lockName, options = {}) {
612
727
  while (retries <= maxRetries) {
613
728
  try {
614
729
  try {
615
- const stat5 = await fs3.stat(lockPath);
730
+ const stat5 = await fs4.stat(lockPath);
616
731
  if (Date.now() - stat5.mtimeMs > maxLockAgeMs) {
617
- await fs3.unlink(lockPath);
732
+ await fs4.unlink(lockPath);
618
733
  }
619
734
  } catch (err) {
620
735
  if (err?.code !== "ENOENT") {
621
736
  throw err;
622
737
  }
623
738
  }
624
- const fd = await fs3.open(lockPath, "wx");
739
+ const fd = await fs4.open(lockPath, "wx");
625
740
  await fd.writeFile(
626
741
  JSON.stringify({
627
742
  pid: process.pid,
@@ -632,7 +747,7 @@ async function acquireLocalLock(lockName, options = {}) {
632
747
  await fd.close();
633
748
  return async () => {
634
749
  try {
635
- await fs3.unlink(lockPath);
750
+ await fs4.unlink(lockPath);
636
751
  } catch (err) {
637
752
  if (err?.code !== "ENOENT") {
638
753
  }
@@ -646,7 +761,7 @@ async function acquireLocalLock(lockName, options = {}) {
646
761
  `[memoraone-mcp] Failed to acquire lock ${lockName} after ${maxRetries} retries`
647
762
  );
648
763
  }
649
- await new Promise((resolve46) => setTimeout(resolve46, retryDelayMs));
764
+ await new Promise((resolve47) => setTimeout(resolve47, retryDelayMs));
650
765
  continue;
651
766
  }
652
767
  throw err;
@@ -698,7 +813,7 @@ async function listBindingRecords(homeDir) {
698
813
  const dir = await ensureBindingsDir(homeDir);
699
814
  let names;
700
815
  try {
701
- names = await fs4.readdir(dir);
816
+ names = await fs5.readdir(dir);
702
817
  } catch (err) {
703
818
  if (err?.code === "ENOENT") return [];
704
819
  throw err;
@@ -716,9 +831,9 @@ async function listBindingRecords(homeDir) {
716
831
  return out;
717
832
  }
718
833
  async function findBindingRecordByWorkspaceRoot(workspaceRoot, homeDir) {
719
- const resolved = path6.resolve(workspaceRoot);
834
+ const resolved = path7.resolve(workspaceRoot);
720
835
  const records = await listBindingRecords(homeDir);
721
- return records.find((r) => path6.resolve(r.workspaceRoot) === resolved) ?? null;
836
+ return records.find((r) => path7.resolve(r.workspaceRoot) === resolved) ?? null;
722
837
  }
723
838
 
724
839
  // src/localState/installationCredentials.ts
@@ -863,6 +978,7 @@ function parsePayload(raw, repositoryBindingId) {
863
978
  optionalString("refreshTokenExpiresAt");
864
979
  optionalString("clientRedeemKey");
865
980
  optionalString("clientRefreshKey");
981
+ optionalString("supabaseUserId");
866
982
  return payload;
867
983
  }
868
984
  async function readInstallationCredentials(repositoryBindingId, options = {}) {
@@ -945,12 +1061,12 @@ function hasUsableAccessToken(payload) {
945
1061
  }
946
1062
 
947
1063
  // src/localState/pathIndex.ts
948
- var path8 = __toESM(require("path"), 1);
1064
+ var path9 = __toESM(require("path"), 1);
949
1065
 
950
1066
  // src/localState/rootFilesystemIdentity.ts
951
- var fs5 = __toESM(require("fs/promises"), 1);
1067
+ var fs6 = __toESM(require("fs/promises"), 1);
952
1068
  var os3 = __toESM(require("os"), 1);
953
- var path7 = __toESM(require("path"), 1);
1069
+ var path8 = __toESM(require("path"), 1);
954
1070
  var import_node_child_process = require("child_process");
955
1071
  var import_node_util = require("util");
956
1072
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
@@ -975,13 +1091,13 @@ async function readDarwinDeviceId() {
975
1091
  }
976
1092
  async function readLinuxDeviceId() {
977
1093
  try {
978
- const content = await fs5.readFile("/etc/machine-id", "utf8");
1094
+ const content = await fs6.readFile("/etc/machine-id", "utf8");
979
1095
  const id = content.trim();
980
1096
  if (id) return id;
981
1097
  } catch {
982
1098
  }
983
1099
  try {
984
- const content = await fs5.readFile("/var/lib/dbus/machine-id", "utf8");
1100
+ const content = await fs6.readFile("/var/lib/dbus/machine-id", "utf8");
985
1101
  const id = content.trim();
986
1102
  if (id) return id;
987
1103
  } catch {
@@ -1014,10 +1130,10 @@ async function resolveDeviceId(platform2 = os3.platform()) {
1014
1130
  throw new Error(`[memoraone-mcp] Unsupported platform for device ID: ${platform2}`);
1015
1131
  }
1016
1132
  async function captureRootFilesystemIdentity(rootPath, deps = {}) {
1017
- const resolved = path7.resolve(rootPath);
1133
+ const resolved = path8.resolve(rootPath);
1018
1134
  const platform2 = deps.platform ?? os3.platform();
1019
1135
  const statRoot = deps.statRoot ?? (async (p) => {
1020
- const st2 = await fs5.stat(p);
1136
+ const st2 = await fs6.stat(p);
1021
1137
  return {
1022
1138
  ino: st2.ino,
1023
1139
  dev: st2.dev,
@@ -1101,7 +1217,7 @@ async function savePathIndex(index, homeDir) {
1101
1217
  await writeJsonAtomic(getPathIndexPath(homeDir), next);
1102
1218
  }
1103
1219
  function lookupPathIndex(index, workspaceRoot, identity) {
1104
- const resolved = path8.resolve(workspaceRoot);
1220
+ const resolved = path9.resolve(workspaceRoot);
1105
1221
  const byPathId = index.byPath[resolved];
1106
1222
  if (byPathId) {
1107
1223
  return { kind: "path", repositoryBindingId: assertRepositoryBindingId(byPathId) };
@@ -1127,14 +1243,14 @@ function lookupPathIndex(index, workspaceRoot, identity) {
1127
1243
  }
1128
1244
  async function upsertPathIndexEntry(options) {
1129
1245
  const repositoryBindingId = assertRepositoryBindingId(options.repositoryBindingId);
1130
- const resolved = path8.resolve(options.workspaceRoot);
1246
+ const resolved = path9.resolve(options.workspaceRoot);
1131
1247
  const identityKey = filesystemIdentityKey(options.identity);
1132
1248
  return withLocalLock(
1133
1249
  "path-index",
1134
1250
  async () => {
1135
1251
  const index = await loadPathIndex(options.homeDir);
1136
- if (options.previousPath && path8.resolve(options.previousPath) !== resolved) {
1137
- delete index.byPath[path8.resolve(options.previousPath)];
1252
+ if (options.previousPath && path9.resolve(options.previousPath) !== resolved) {
1253
+ delete index.byPath[path9.resolve(options.previousPath)];
1138
1254
  }
1139
1255
  for (const [p, id] of Object.entries(index.byPath)) {
1140
1256
  if (id === repositoryBindingId && p !== resolved) {
@@ -1159,20 +1275,20 @@ function identityMatchesStored(stored, current) {
1159
1275
  }
1160
1276
 
1161
1277
  // src/localState/localConnectClient.ts
1162
- var path9 = __toESM(require("path"), 1);
1278
+ var path10 = __toESM(require("path"), 1);
1163
1279
 
1164
1280
  // src/client/memoraClient.ts
1165
1281
  var crypto2 = __toESM(require("crypto"), 1);
1166
1282
  var PROJECT_ID_HEADER = "x-project-id";
1167
1283
  var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1168
- var parseBooleanFlag = (value) => {
1284
+ var parseBooleanFlag2 = (value) => {
1169
1285
  if (!value) {
1170
1286
  return false;
1171
1287
  }
1172
1288
  const normalized = value.trim().toLowerCase();
1173
1289
  return ["1", "true", "yes", "on"].includes(normalized);
1174
1290
  };
1175
- var debugEnabled = parseBooleanFlag(process.env.MEMORAONE_DEV_MODE);
1291
+ var debugEnabled = parseBooleanFlag2(process.env.MEMORAONE_DEV_MODE);
1176
1292
  async function requestJson(url, method, headers, body) {
1177
1293
  const res = await fetch(url, {
1178
1294
  method,
@@ -1270,9 +1386,9 @@ var MemoraClient = class {
1270
1386
  ...options?.headers ?? {}
1271
1387
  };
1272
1388
  }
1273
- async perform(method, path52, body, options, retried = false) {
1389
+ async perform(method, path53, body, options, retried = false) {
1274
1390
  const nonce = crypto2.randomBytes(8).toString("hex");
1275
- const url = `${this.baseUrl}${path52.startsWith("/") ? path52 : `/${path52}`}`;
1391
+ const url = `${this.baseUrl}${path53.startsWith("/") ? path53 : `/${path53}`}`;
1276
1392
  this.resolveProjectId();
1277
1393
  console.error(
1278
1394
  `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_fetch method=${method} url=${url}`
@@ -1299,7 +1415,7 @@ var MemoraClient = class {
1299
1415
  if (res.status === 401 && !options?.skipAuthRefresh && !retried) {
1300
1416
  try {
1301
1417
  await this.refreshAccessToken();
1302
- return this.perform(method, path52, body, options, true);
1418
+ return this.perform(method, path53, body, options, true);
1303
1419
  } catch (err) {
1304
1420
  if (err instanceof ReconnectRequiredError) {
1305
1421
  await this.markReconnectRequired();
@@ -1319,22 +1435,22 @@ var MemoraClient = class {
1319
1435
  }
1320
1436
  return res.text ? JSON.parse(res.text) : null;
1321
1437
  }
1322
- async post(path52, body, options) {
1323
- console.error(`[memoraone-mcp][info] MemoraClient.post ENTER path=${path52}`);
1324
- const result = await this.perform("POST", path52, body, options);
1325
- console.error(`[memoraone-mcp][info] MemoraClient.post EXIT path=${path52}`);
1438
+ async post(path53, body, options) {
1439
+ console.error(`[memoraone-mcp][info] MemoraClient.post ENTER path=${path53}`);
1440
+ const result = await this.perform("POST", path53, body, options);
1441
+ console.error(`[memoraone-mcp][info] MemoraClient.post EXIT path=${path53}`);
1326
1442
  return result;
1327
1443
  }
1328
- async get(path52, options) {
1329
- return this.perform("GET", path52, void 0, options);
1444
+ async get(path53, options) {
1445
+ return this.perform("GET", path53, void 0, options);
1330
1446
  }
1331
1447
  };
1332
1448
  var memoraClient_default = MemoraClient;
1333
1449
 
1334
1450
  // src/localState/localConnectClient.ts
1335
- async function requestJson2(baseUrl, method, path52, options = {}) {
1451
+ async function requestJson2(baseUrl, method, path53, options = {}) {
1336
1452
  const fetchImpl = options.fetchImpl ?? fetch;
1337
- const url = `${baseUrl.replace(/\/+$/, "")}${path52.startsWith("/") ? path52 : `/${path52}`}`;
1453
+ const url = `${baseUrl.replace(/\/+$/, "")}${path53.startsWith("/") ? path53 : `/${path53}`}`;
1338
1454
  const res = await fetchImpl(url, {
1339
1455
  method,
1340
1456
  headers: {
@@ -1371,7 +1487,7 @@ function normalizePluginPairingSource(source) {
1371
1487
  return source;
1372
1488
  }
1373
1489
  function pairingRepositoryName(workspaceRoot) {
1374
- const repositoryName = path9.basename(path9.resolve(workspaceRoot)).trim();
1490
+ const repositoryName = path10.basename(path10.resolve(workspaceRoot)).trim();
1375
1491
  if (repositoryName.length === 0 || repositoryName.length > 256 || repositoryName.includes("/") || repositoryName.includes("\\")) {
1376
1492
  throw new Error("[memoraone-mcp] Workspace basename is not safe pairing metadata");
1377
1493
  }
@@ -1466,7 +1582,8 @@ async function redeemLocalConnectCode(apiUrl, body, options = {}) {
1466
1582
  const installationPublicId = data?.installation_public_id;
1467
1583
  const projectId = data?.project_id;
1468
1584
  const repositoryBindingId = data?.repository_binding_id;
1469
- if (typeof accessToken !== "string" || typeof refreshToken !== "string" || typeof installationPublicId !== "string" || typeof projectId !== "string" || typeof repositoryBindingId !== "string") {
1585
+ const supabaseUserId = data?.supabase_user_id;
1586
+ if (typeof accessToken !== "string" || typeof refreshToken !== "string" || typeof installationPublicId !== "string" || typeof projectId !== "string" || typeof repositoryBindingId !== "string" || typeof supabaseUserId !== "string" || supabaseUserId.length === 0) {
1470
1587
  throw new Error("[memoraone-mcp] Invalid redeem response");
1471
1588
  }
1472
1589
  let sourceId = null;
@@ -1484,7 +1601,8 @@ async function redeemLocalConnectCode(apiUrl, body, options = {}) {
1484
1601
  project_id: projectId,
1485
1602
  repository_binding_id: repositoryBindingId,
1486
1603
  source_id: sourceId,
1487
- recovered: Boolean(data.recovered)
1604
+ recovered: Boolean(data.recovered),
1605
+ supabase_user_id: supabaseUserId
1488
1606
  };
1489
1607
  }
1490
1608
  async function refreshLocalMcpToken(apiUrl, body, options = {}) {
@@ -1506,6 +1624,33 @@ async function refreshLocalMcpToken(apiUrl, body, options = {}) {
1506
1624
  repository_binding_id: typeof data.repository_binding_id === "string" ? data.repository_binding_id : void 0
1507
1625
  };
1508
1626
  }
1627
+ var AUTH_IDENTITY_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
1628
+ async function fetchAuthIdentity(apiUrl, bearerToken, options = {}) {
1629
+ const res = await requestJson2(apiUrl, "GET", "/auth/identity", {
1630
+ headers: { Authorization: `Bearer ${bearerToken}` },
1631
+ fetchImpl: options.fetchImpl
1632
+ });
1633
+ if (res.status !== 200) {
1634
+ throw new Error("[memoraone-mcp] Invalid auth identity response");
1635
+ }
1636
+ const data = res.json;
1637
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
1638
+ throw new Error("[memoraone-mcp] Invalid auth identity response");
1639
+ }
1640
+ const obj = data;
1641
+ const supabaseUserIdRaw = obj.supabase_user_id;
1642
+ if (obj.authenticated !== true || typeof supabaseUserIdRaw !== "string") {
1643
+ throw new Error("[memoraone-mcp] Invalid auth identity response");
1644
+ }
1645
+ const supabaseUserId = supabaseUserIdRaw.trim();
1646
+ if (!AUTH_IDENTITY_UUID_RE.test(supabaseUserId)) {
1647
+ throw new Error("[memoraone-mcp] Invalid auth identity response");
1648
+ }
1649
+ return {
1650
+ authenticated: true,
1651
+ supabase_user_id: supabaseUserId
1652
+ };
1653
+ }
1509
1654
 
1510
1655
  // src/localState/tokenRefreshCoordinator.ts
1511
1656
  var ReconnectRequiredError = class extends Error {
@@ -1570,14 +1715,14 @@ async function refreshInstallationAccessToken(options) {
1570
1715
 
1571
1716
  // src/localState/resolveLocalBinding.ts
1572
1717
  async function ensureRepositoryBindingForRoot(workspaceRoot, options = {}) {
1573
- const resolved = path10.resolve(workspaceRoot);
1718
+ const resolved = path11.resolve(workspaceRoot);
1574
1719
  const identity = await captureRootFilesystemIdentity(resolved, options.identityDeps);
1575
1720
  const index = await loadPathIndex(options.homeDir);
1576
1721
  const lookup = lookupPathIndex(index, resolved, identity);
1577
1722
  if (lookup.kind === "path") {
1578
1723
  const record = await readBindingRecord(lookup.repositoryBindingId, options.homeDir);
1579
1724
  if (record && identityMatchesStored(record.filesystemIdentity, identity)) {
1580
- if (path10.resolve(record.workspaceRoot) !== resolved) {
1725
+ if (path11.resolve(record.workspaceRoot) !== resolved) {
1581
1726
  const updated = {
1582
1727
  ...record,
1583
1728
  workspaceRoot: resolved,
@@ -1636,7 +1781,7 @@ async function ensureRepositoryBindingForRoot(workspaceRoot, options = {}) {
1636
1781
  };
1637
1782
  }
1638
1783
  async function lookupLocalBindingIdentity(workspaceRoot, options = {}) {
1639
- const resolved = path10.resolve(workspaceRoot);
1784
+ const resolved = path11.resolve(workspaceRoot);
1640
1785
  const identity = await captureRootFilesystemIdentity(resolved, options.identityDeps);
1641
1786
  const index = await loadPathIndex(options.homeDir);
1642
1787
  const lookup = lookupPathIndex(index, resolved, identity);
@@ -1647,7 +1792,7 @@ async function lookupLocalBindingIdentity(workspaceRoot, options = {}) {
1647
1792
  if (!record) {
1648
1793
  return { status: "unbound", workspaceRoot: resolved };
1649
1794
  }
1650
- if (path10.resolve(record.workspaceRoot) !== resolved) {
1795
+ if (path11.resolve(record.workspaceRoot) !== resolved) {
1651
1796
  return { status: "unbound", workspaceRoot: resolved };
1652
1797
  }
1653
1798
  if (!identityMatchesStored(record.filesystemIdentity, identity)) {
@@ -1660,7 +1805,7 @@ async function lookupLocalBindingIdentity(workspaceRoot, options = {}) {
1660
1805
  };
1661
1806
  }
1662
1807
  async function resolveLocalBinding(workspaceRoot, options = {}) {
1663
- const resolved = path10.resolve(workspaceRoot);
1808
+ const resolved = path11.resolve(workspaceRoot);
1664
1809
  const { repositoryBindingId } = await ensureRepositoryBindingForRoot(
1665
1810
  resolved,
1666
1811
  { ...options, createIfMissing: false }
@@ -1746,12 +1891,12 @@ async function parseGatewayWorkspaceRoot(wsRaw) {
1746
1891
  if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed)) {
1747
1892
  return fail("malformed_ws", "ws must be an absolute filesystem path");
1748
1893
  }
1749
- if (!path11.isAbsolute(trimmed)) {
1894
+ if (!path12.isAbsolute(trimmed)) {
1750
1895
  return fail("non_absolute", "ws must be an absolute workspace root");
1751
1896
  }
1752
- const workspaceRoot = path11.resolve(trimmed);
1897
+ const workspaceRoot = path12.resolve(trimmed);
1753
1898
  try {
1754
- const st = await fs6.stat(workspaceRoot);
1899
+ const st = await fs7.stat(workspaceRoot);
1755
1900
  if (!st.isDirectory()) {
1756
1901
  return fail("not_directory", "ws must point to an existing directory");
1757
1902
  }
@@ -1774,7 +1919,7 @@ async function parseGatewayWorkspaceUrl(input2) {
1774
1919
 
1775
1920
  // src/localState/gatewayTicketStore.ts
1776
1921
  var import_node_crypto5 = require("crypto");
1777
- var path12 = __toESM(require("path"), 1);
1922
+ var path13 = __toESM(require("path"), 1);
1778
1923
  var GATEWAY_TICKETS_VERSION = 1;
1779
1924
  var GATEWAY_TICKETS_LOCK_NAME = "gateway-tickets";
1780
1925
  var MGT_PREFIX = "mgt_";
@@ -1868,7 +2013,7 @@ async function resolveGatewayTicketScope(input2, options = {}) {
1868
2013
  if (!("workspaceRoot" in input2) || input2.workspaceRoot == null) {
1869
2014
  return fail2("invalid_workspace", "workspaceRoot or canonicalUrl is required");
1870
2015
  }
1871
- const parsed2 = await parseGatewayWorkspaceRoot(path12.resolve(input2.workspaceRoot));
2016
+ const parsed2 = await parseGatewayWorkspaceRoot(path13.resolve(input2.workspaceRoot));
1872
2017
  if (!parsed2.ok) {
1873
2018
  return fail2("invalid_workspace", parsed2.error);
1874
2019
  }
@@ -1965,101 +2110,6 @@ async function revokeGatewaySetupTicket(ticket, options = {}) {
1965
2110
  // src/pluginPairing.ts
1966
2111
  var path47 = __toESM(require("path"), 1);
1967
2112
 
1968
- // src/config.ts
1969
- var process2 = __toESM(require("process"), 1);
1970
- var fs7 = __toESM(require("fs"), 1);
1971
- var path13 = __toESM(require("path"), 1);
1972
- var dotenv = __toESM(require("dotenv"), 1);
1973
- var import_v4 = require("zod/v4");
1974
-
1975
- // src/configUtils.ts
1976
- var DEFAULT_API_URL = "https://api.memoraone.com";
1977
- var DEV_API_URL = "http://localhost:3001";
1978
- function resolveApiUrl(env2) {
1979
- const explicitUrl = env2.MEMORAONE_API_URL?.trim();
1980
- if (explicitUrl) {
1981
- return explicitUrl;
1982
- }
1983
- const aliasUrl = env2.MEMORA_API_URL?.trim();
1984
- if (aliasUrl) {
1985
- return aliasUrl;
1986
- }
1987
- if (env2.MEMORAONE_DEV_MODE === "1") {
1988
- return DEV_API_URL;
1989
- }
1990
- return DEFAULT_API_URL;
1991
- }
1992
-
1993
- // src/config.ts
1994
- var dotenvPath = path13.resolve(process2.cwd(), ".env");
1995
- if (fs7.existsSync(dotenvPath)) {
1996
- try {
1997
- dotenv.config({ path: dotenvPath });
1998
- } catch (err) {
1999
- process2.stderr.write("[memoraone-mcp] Failed to load .env: " + String(err) + "\n");
2000
- }
2001
- }
2002
- var EnvSchema = import_v4.z.object({
2003
- MEMORAONE_API_URL: import_v4.z.string().url().optional(),
2004
- MEMORAONE_API_KEY: import_v4.z.string().min(1).optional(),
2005
- MEMORAONE_DEV_MODE: import_v4.z.string().min(1).optional(),
2006
- MEMORAONE_AGENT_NAME: import_v4.z.string().min(1).optional(),
2007
- MEMORAONE_AGENT_TYPE: import_v4.z.string().min(1).optional(),
2008
- MEMORAONE_SOURCE: import_v4.z.string().min(1).optional(),
2009
- MEMORAONE_IDE_TYPE: import_v4.z.enum(IDE_TYPES).optional(),
2010
- MEMORAONE_WORKLOG: import_v4.z.string().min(1).optional(),
2011
- MEMORAONE_HEARTBEAT: import_v4.z.string().min(1).optional(),
2012
- MEMORAONE_HEARTBEAT_INTERVAL_MS: import_v4.z.string().min(1).optional()
2013
- });
2014
- var requiredEnvVars = [];
2015
- var missingEnvVars = requiredEnvVars.filter((key) => {
2016
- const value = process2.env[key];
2017
- return value === void 0 || value.trim() === "";
2018
- });
2019
- if (missingEnvVars.length > 0) {
2020
- for (const key of missingEnvVars) {
2021
- process2.stderr.write(`Missing ${key}
2022
- `);
2023
- }
2024
- process2.exit(1);
2025
- }
2026
- var parsed = EnvSchema.safeParse(process2.env);
2027
- var resolvedApiUrl = resolveApiUrl(process2.env);
2028
- if (!parsed.success) {
2029
- const formatted = parsed.error.format();
2030
- process2.stderr.write(
2031
- "[memoraone-mcp] Invalid environment variables " + JSON.stringify(formatted) + "\n"
2032
- );
2033
- throw new Error("Config validation failed");
2034
- }
2035
- var parseBooleanFlag2 = (value, defaultValue) => {
2036
- if (value === void 0) {
2037
- return defaultValue;
2038
- }
2039
- const normalized = value.trim().toLowerCase();
2040
- if (["1", "true", "yes", "on"].includes(normalized)) {
2041
- return true;
2042
- }
2043
- if (["0", "false", "no", "off"].includes(normalized)) {
2044
- return false;
2045
- }
2046
- return defaultValue;
2047
- };
2048
- var config2 = {
2049
- apiUrl: resolvedApiUrl.replace(/\/+$/, ""),
2050
- apiKey: parsed.data.MEMORAONE_API_KEY,
2051
- agentName: parsed.data.MEMORAONE_AGENT_NAME ?? "cursor",
2052
- agentType: parsed.data.MEMORAONE_AGENT_TYPE ?? "agent",
2053
- source: parsed.data.MEMORAONE_SOURCE ?? "cursor",
2054
- ideType: parsed.data.MEMORAONE_IDE_TYPE,
2055
- devMode: parseBooleanFlag2(parsed.data.MEMORAONE_DEV_MODE, false),
2056
- worklogEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_WORKLOG, true),
2057
- heartbeatEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_HEARTBEAT, true),
2058
- // Cadence is owned by LOCAL_MCP_HEARTBEAT_INTERVAL_MS in heartbeat.ts (1_000).
2059
- // Env override is accepted for forward-compat but the timer path ignores it.
2060
- heartbeatIntervalMs: Number.parseInt(parsed.data.MEMORAONE_HEARTBEAT_INTERVAL_MS ?? "1000", 10)
2061
- };
2062
-
2063
2113
  // src/localState/connectCommand.ts
2064
2114
  var fs36 = __toESM(require("fs/promises"), 1);
2065
2115
  var path46 = __toESM(require("path"), 1);
@@ -4115,7 +4165,7 @@ function formatOptionalJetBrainsHandshakeUnavailableDetail(detail) {
4115
4165
  async function verifyJetBrainsMcpHandshake(options) {
4116
4166
  const timeoutMs = options.timeoutMs ?? 15e3;
4117
4167
  const { server } = options;
4118
- return new Promise((resolve46) => {
4168
+ return new Promise((resolve47) => {
4119
4169
  let settled = false;
4120
4170
  const finish = (ok, detail, optionalUnavailable) => {
4121
4171
  if (settled) return;
@@ -4125,7 +4175,7 @@ async function verifyJetBrainsMcpHandshake(options) {
4125
4175
  child.kill();
4126
4176
  } catch {
4127
4177
  }
4128
- resolve46({ ok, detail, optionalUnavailable });
4178
+ resolve47({ ok, detail, optionalUnavailable });
4129
4179
  };
4130
4180
  const child = (0, import_node_child_process4.spawn)(server.command, [...server.args], {
4131
4181
  env: { ...process.env, ...server.env },
@@ -10669,7 +10719,8 @@ async function runConnectCommand(options) {
10669
10719
  accessTokenExpiresAt: redeemed.access_token_expires_at ?? void 0,
10670
10720
  refreshTokenExpiresAt: redeemed.refresh_token_expires_at ?? void 0,
10671
10721
  installationPublicId: redeemed.installation_public_id,
10672
- projectId: redeemed.project_id
10722
+ projectId: redeemed.project_id,
10723
+ supabaseUserId: redeemed.supabase_user_id
10673
10724
  },
10674
10725
  { ...options.credentialOptions, clearKeys: ["clientRedeemKey"] }
10675
10726
  );
@@ -11114,7 +11165,7 @@ function html(res, status, body) {
11114
11165
  res.end(body);
11115
11166
  }
11116
11167
  function readUrlEncodedBody(req) {
11117
- return new Promise((resolve46, reject) => {
11168
+ return new Promise((resolve47, reject) => {
11118
11169
  const chunks = [];
11119
11170
  let size = 0;
11120
11171
  req.on("data", (chunk) => {
@@ -11127,13 +11178,13 @@ function readUrlEncodedBody(req) {
11127
11178
  chunks.push(chunk);
11128
11179
  });
11129
11180
  req.on("end", () => {
11130
- resolve46(new URLSearchParams(Buffer.concat(chunks).toString("utf8")));
11181
+ resolve47(new URLSearchParams(Buffer.concat(chunks).toString("utf8")));
11131
11182
  });
11132
11183
  req.on("error", reject);
11133
11184
  });
11134
11185
  }
11135
11186
  function readJsonBody(req) {
11136
- return new Promise((resolve46, reject) => {
11187
+ return new Promise((resolve47, reject) => {
11137
11188
  const chunks = [];
11138
11189
  let size = 0;
11139
11190
  req.on("data", (chunk) => {
@@ -11148,11 +11199,11 @@ function readJsonBody(req) {
11148
11199
  req.on("end", () => {
11149
11200
  const raw = Buffer.concat(chunks).toString("utf8");
11150
11201
  if (!raw.trim()) {
11151
- resolve46({});
11202
+ resolve47({});
11152
11203
  return;
11153
11204
  }
11154
11205
  try {
11155
- resolve46(JSON.parse(raw));
11206
+ resolve47(JSON.parse(raw));
11156
11207
  } catch {
11157
11208
  reject(new Error("invalid json"));
11158
11209
  }
@@ -11164,12 +11215,12 @@ function opaqueId(prefix) {
11164
11215
  return `${prefix}${(0, import_node_crypto28.randomBytes)(24).toString("base64url")}`;
11165
11216
  }
11166
11217
  function delay(ms) {
11167
- return new Promise((resolve46) => setTimeout(resolve46, ms));
11218
+ return new Promise((resolve47) => setTimeout(resolve47, ms));
11168
11219
  }
11169
11220
  function buildProtectedResourceMetadata(env2 = process.env, workspaceRoot) {
11170
11221
  return {
11171
11222
  resource: workspaceRoot === void 0 ? getGatewayResourceBaseUrl(env2) : buildGatewayMcpUrl(workspaceRoot, env2),
11172
- authorization_servers: [getGatewayIssuerUrl(env2)]
11223
+ authorization_servers: [getGatewayAuthorizationServerIssuerUrl(env2)]
11173
11224
  };
11174
11225
  }
11175
11226
  function buildAuthorizationServerMetadata(env2 = process.env) {
@@ -11802,11 +11853,11 @@ async function isRetryablePluginStartupBindingError(err, options = {}) {
11802
11853
  return false;
11803
11854
  }
11804
11855
  function defaultSleep(ms) {
11805
- return new Promise((resolve46) => {
11806
- setTimeout(resolve46, ms);
11856
+ return new Promise((resolve47) => {
11857
+ setTimeout(resolve47, ms);
11807
11858
  });
11808
11859
  }
11809
- async function waitForPluginStartupBinding(resolve46, options = {}) {
11860
+ async function waitForPluginStartupBinding(resolve47, options = {}) {
11810
11861
  const now = options.now ?? Date.now;
11811
11862
  const sleep = options.sleep ?? defaultSleep;
11812
11863
  const timeoutMs = options.timeoutMs ?? PLUGIN_STARTUP_BINDING_WAIT_TIMEOUT_MS;
@@ -11816,7 +11867,7 @@ async function waitForPluginStartupBinding(resolve46, options = {}) {
11816
11867
  let lastError;
11817
11868
  while (true) {
11818
11869
  try {
11819
- const binding = await resolve46();
11870
+ const binding = await resolve47();
11820
11871
  if (waitingLogged) {
11821
11872
  options.log?.(
11822
11873
  `local MemoraOne binding available after Cursor plugin pairing (${now() - startedAt}ms)`
@@ -12296,9 +12347,9 @@ function buildPersonalContextPath(parsed2) {
12296
12347
  }
12297
12348
  async function handleGetPersonalContext(client, args) {
12298
12349
  const parsed2 = getPersonalContextInputSchema.parse(args ?? {});
12299
- const path52 = buildPersonalContextPath(parsed2);
12350
+ const path53 = buildPersonalContextPath(parsed2);
12300
12351
  try {
12301
- const result = await client.get(path52);
12352
+ const result = await client.get(path53);
12302
12353
  return { ok: true, result };
12303
12354
  } catch (err) {
12304
12355
  if (err instanceof MemoraOneHttpError) {
@@ -12770,8 +12821,8 @@ async function handleCreateConceptVersion(client, args) {
12770
12821
  if (input2.tags !== void 0) body.tags = input2.tags;
12771
12822
  if (input2.parent_id !== void 0) body.parent_id = input2.parent_id;
12772
12823
  try {
12773
- const path52 = `/concepts/${encodeURIComponent(input2.id)}/versions`;
12774
- return projectCreatedConceptVersionResponse(await client.post(path52, body));
12824
+ const path53 = `/concepts/${encodeURIComponent(input2.id)}/versions`;
12825
+ return projectCreatedConceptVersionResponse(await client.post(path53, body));
12775
12826
  } catch (error) {
12776
12827
  rethrowToolRequestError("memora_create_concept_version", error);
12777
12828
  }
@@ -12806,7 +12857,7 @@ function handleBindingStatus(binding, options = {}) {
12806
12857
  // src/tools/handlers/finishSetup.ts
12807
12858
  var DEFAULT_POLL_INTERVAL_MS = 1e3;
12808
12859
  function delay2(ms) {
12809
- return new Promise((resolve46) => setTimeout(resolve46, ms));
12860
+ return new Promise((resolve47) => setTimeout(resolve47, ms));
12810
12861
  }
12811
12862
  async function pollPairingSessionInBackground(session, pollIntervalMs, onReady) {
12812
12863
  for (; ; ) {
@@ -13099,8 +13150,8 @@ function registerToolWithWorklog(server, runtime, sessionContext, toolName, desc
13099
13150
  function createMemoraMcpServer(opts = {}) {
13100
13151
  let bindingReadyResolve = null;
13101
13152
  let bindingReadyReject = null;
13102
- const bindingReady = new Promise((resolve46, reject) => {
13103
- bindingReadyResolve = resolve46;
13153
+ const bindingReady = new Promise((resolve47, reject) => {
13154
+ bindingReadyResolve = resolve47;
13104
13155
  bindingReadyReject = reject;
13105
13156
  });
13106
13157
  const devMode = Boolean(config2.devMode);
@@ -13854,6 +13905,14 @@ function sendUnauthorized(res, env2, workspaceRoot) {
13854
13905
  });
13855
13906
  res.end(body);
13856
13907
  }
13908
+ function sendPairingGate(res, body) {
13909
+ res.writeHead(403, {
13910
+ "content-type": "text/plain; charset=utf-8",
13911
+ "cache-control": "no-store",
13912
+ "content-length": Buffer.byteLength(body)
13913
+ });
13914
+ res.end(body);
13915
+ }
13857
13916
  async function handleGatewayMcpAuthRequest(req, res, url, options = {}) {
13858
13917
  if (url.pathname !== GATEWAY_MCP_PATH) return false;
13859
13918
  const env2 = options.env ?? process.env;
@@ -13863,6 +13922,15 @@ async function handleGatewayMcpAuthRequest(req, res, url, options = {}) {
13863
13922
  env: options.env ?? options.ticketOptions?.env ?? env2,
13864
13923
  ...options.ticketOptions
13865
13924
  };
13925
+ const homeDir = options.homeDir ?? ticketOptions.homeDir;
13926
+ const identityDeps = options.identityDeps ?? ticketOptions.identityDeps;
13927
+ const transportOptions = {
13928
+ env: env2,
13929
+ homeDir,
13930
+ identityDeps,
13931
+ ticketOptions,
13932
+ createMemoraClient: options.createMemoraClient
13933
+ };
13866
13934
  const parsed2 = await parseGatewayWorkspaceUrl(url);
13867
13935
  if (!parsed2.ok) {
13868
13936
  sendUnauthorized(res, env2);
@@ -13874,31 +13942,221 @@ async function handleGatewayMcpAuthRequest(req, res, url, options = {}) {
13874
13942
  sendUnauthorized(res, env2, parsed2.workspaceRoot);
13875
13943
  return true;
13876
13944
  }
13877
- const verified = await verifyGatewaySetupTicket(
13878
- token,
13879
- { canonicalUrl },
13880
- ticketOptions
13881
- );
13882
- if (!verified.ok) {
13945
+ if (token.startsWith("mgt_")) {
13946
+ const verified = await verifyGatewaySetupTicket(
13947
+ token,
13948
+ { canonicalUrl },
13949
+ ticketOptions
13950
+ );
13951
+ if (!verified.ok) {
13952
+ sendUnauthorized(res, env2, parsed2.workspaceRoot);
13953
+ return true;
13954
+ }
13955
+ await handleAuthenticatedGatewayMcpRequest(
13956
+ req,
13957
+ res,
13958
+ {
13959
+ workspaceRoot: verified.workspaceRoot,
13960
+ canonicalUrl: verified.canonicalUrl
13961
+ },
13962
+ transportOptions
13963
+ );
13964
+ return true;
13965
+ }
13966
+ try {
13967
+ const rawApiUrl = options.apiUrl;
13968
+ if (typeof rawApiUrl !== "string" || rawApiUrl.trim().length === 0) {
13969
+ sendUnauthorized(res, env2, parsed2.workspaceRoot);
13970
+ return true;
13971
+ }
13972
+ const apiUrl = rawApiUrl.replace(/\/+$/, "");
13973
+ let binding = await lookupLocalBindingIdentity(parsed2.workspaceRoot, {
13974
+ homeDir,
13975
+ identityDeps
13976
+ });
13977
+ if (binding.status !== "resolved") {
13978
+ await fetchAuthIdentity(apiUrl, token, {
13979
+ fetchImpl: options.fetchImpl
13980
+ });
13981
+ const coordinator = options.unboundPairing;
13982
+ if (!coordinator) {
13983
+ sendUnauthorized(res, env2, parsed2.workspaceRoot);
13984
+ return true;
13985
+ }
13986
+ const pairing = await coordinator.ensure(parsed2.workspaceRoot);
13987
+ if (pairing.status === "pending") {
13988
+ sendPairingGate(res, "Pairing pending");
13989
+ return true;
13990
+ }
13991
+ if (pairing.status === "failed") {
13992
+ sendPairingGate(res, "Setup required");
13993
+ return true;
13994
+ }
13995
+ binding = await lookupLocalBindingIdentity(parsed2.workspaceRoot, {
13996
+ homeDir,
13997
+ identityDeps
13998
+ });
13999
+ if (binding.status !== "resolved") {
14000
+ sendPairingGate(res, "Setup required");
14001
+ return true;
14002
+ }
14003
+ }
14004
+ const creds = await readInstallationCredentials(binding.repositoryBindingId);
14005
+ const localOwnerId = creds?.supabaseUserId;
14006
+ if (typeof localOwnerId !== "string" || localOwnerId.length === 0) {
14007
+ sendUnauthorized(res, env2, parsed2.workspaceRoot);
14008
+ return true;
14009
+ }
14010
+ const identity = await fetchAuthIdentity(apiUrl, token, {
14011
+ fetchImpl: options.fetchImpl
14012
+ });
14013
+ if (identity.supabase_user_id !== localOwnerId) {
14014
+ sendUnauthorized(res, env2, parsed2.workspaceRoot);
14015
+ return true;
14016
+ }
14017
+ await handleAuthenticatedGatewayMcpRequest(
14018
+ req,
14019
+ res,
14020
+ {
14021
+ workspaceRoot: parsed2.workspaceRoot,
14022
+ canonicalUrl
14023
+ },
14024
+ transportOptions
14025
+ );
14026
+ return true;
14027
+ } catch {
13883
14028
  sendUnauthorized(res, env2, parsed2.workspaceRoot);
13884
14029
  return true;
13885
14030
  }
13886
- await handleAuthenticatedGatewayMcpRequest(
13887
- req,
13888
- res,
13889
- {
13890
- workspaceRoot: verified.workspaceRoot,
13891
- canonicalUrl: verified.canonicalUrl
13892
- },
13893
- {
13894
- env: env2,
13895
- homeDir: options.homeDir ?? ticketOptions.homeDir,
13896
- identityDeps: options.identityDeps ?? ticketOptions.identityDeps,
13897
- ticketOptions,
13898
- createMemoraClient: options.createMemoraClient
14031
+ }
14032
+
14033
+ // src/gatewayUnboundPairing.ts
14034
+ var path51 = __toESM(require("path"), 1);
14035
+
14036
+ // src/openUrlInBrowser.ts
14037
+ var import_node_child_process7 = require("child_process");
14038
+ var import_node_util6 = require("util");
14039
+ var execFileAsync6 = (0, import_node_util6.promisify)(import_node_child_process7.execFile);
14040
+ function assertHttpOrHttpsUrl(url) {
14041
+ let parsed2;
14042
+ try {
14043
+ parsed2 = new URL(url);
14044
+ } catch {
14045
+ throw new Error("URL must be an absolute http: or https: URL");
14046
+ }
14047
+ if (parsed2.protocol !== "http:" && parsed2.protocol !== "https:") {
14048
+ throw new Error("URL must be an absolute http: or https: URL");
14049
+ }
14050
+ }
14051
+ async function openUrlInBrowser(url, deps = {}) {
14052
+ assertHttpOrHttpsUrl(url);
14053
+ const platform2 = deps.platform ?? process.platform;
14054
+ const execFileImpl = deps.execFile ?? execFileAsync6;
14055
+ if (platform2 === "darwin") {
14056
+ await execFileImpl("open", [url]);
14057
+ return;
14058
+ }
14059
+ if (platform2 === "win32") {
14060
+ await execFileImpl("cmd", ["/c", "start", "", url]);
14061
+ return;
14062
+ }
14063
+ await execFileImpl("xdg-open", [url]);
14064
+ }
14065
+
14066
+ // src/gatewayUnboundPairing.ts
14067
+ var DEFAULT_POLL_INTERVAL_MS2 = 1e3;
14068
+ function delay3(ms) {
14069
+ return new Promise((resolve47) => {
14070
+ const timer = setTimeout(resolve47, ms);
14071
+ if (typeof timer.unref === "function") timer.unref();
14072
+ });
14073
+ }
14074
+ function createGatewayUnboundPairingCoordinator(options = {}) {
14075
+ const latches = /* @__PURE__ */ new Map();
14076
+ const beginPairing = options.beginPairing ?? beginPluginRepositoryPairing;
14077
+ const openUrl = options.openUrl ?? ((url) => openUrlInBrowser(url));
14078
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
14079
+ const apiUrl = (options.apiUrl ?? config2.apiUrl).replace(/\/+$/, "");
14080
+ async function pollInBackground(key, session, entry) {
14081
+ for (; ; ) {
14082
+ let result;
14083
+ try {
14084
+ result = await session.poll();
14085
+ } catch {
14086
+ entry.status = "failed";
14087
+ return;
14088
+ }
14089
+ if (result.status === "pending") {
14090
+ await delay3(pollIntervalMs);
14091
+ continue;
14092
+ }
14093
+ if (result.status === "ready") {
14094
+ entry.status = "ready";
14095
+ latches.delete(key);
14096
+ return;
14097
+ }
14098
+ if (result.status === "backend_failure" && result.operation === "poll") {
14099
+ await delay3(pollIntervalMs);
14100
+ continue;
14101
+ }
14102
+ entry.status = "failed";
14103
+ return;
13899
14104
  }
13900
- );
13901
- return true;
14105
+ }
14106
+ async function startPairing(key, entry) {
14107
+ try {
14108
+ const session = await beginPairing({
14109
+ workspaceRoot: key,
14110
+ source: "cursor",
14111
+ apiUrl,
14112
+ fetchImpl: options.fetchImpl,
14113
+ connectOptions: {
14114
+ homeDir: options.homeDir,
14115
+ identityDeps: options.identityDeps,
14116
+ fetchImpl: options.fetchImpl
14117
+ }
14118
+ });
14119
+ const authorizationUrl = session.authorizationUrl;
14120
+ if (typeof authorizationUrl === "string" && authorizationUrl.length > 0) {
14121
+ try {
14122
+ await openUrl(authorizationUrl);
14123
+ } catch {
14124
+ entry.status = "failed";
14125
+ return;
14126
+ }
14127
+ } else {
14128
+ entry.status = "failed";
14129
+ return;
14130
+ }
14131
+ setImmediate(() => {
14132
+ void pollInBackground(key, session, entry);
14133
+ });
14134
+ } catch {
14135
+ entry.status = "failed";
14136
+ }
14137
+ }
14138
+ return {
14139
+ async ensure(workspaceRoot) {
14140
+ const key = path51.resolve(workspaceRoot);
14141
+ const existing = latches.get(key);
14142
+ if (existing) {
14143
+ await existing.startPromise;
14144
+ return { status: existing.status };
14145
+ }
14146
+ let resolveStart;
14147
+ const startPromise = new Promise((resolve47) => {
14148
+ resolveStart = resolve47;
14149
+ });
14150
+ const entry = { status: "pending", startPromise };
14151
+ latches.set(key, entry);
14152
+ try {
14153
+ await startPairing(key, entry);
14154
+ } finally {
14155
+ resolveStart();
14156
+ }
14157
+ return { status: entry.status };
14158
+ }
14159
+ };
13902
14160
  }
13903
14161
 
13904
14162
  // src/gateway.ts
@@ -13908,13 +14166,13 @@ var log3 = (msg) => {
13908
14166
  };
13909
14167
  function writePidFile(pid) {
13910
14168
  const dir = ensureBaseDir();
13911
- const pidPath = path51.join(dir, "gateway.pid");
14169
+ const pidPath = path52.join(dir, "gateway.pid");
13912
14170
  fs38.writeFileSync(pidPath, `${pid}
13913
14171
  `, { encoding: "utf8", mode: 384 });
13914
14172
  return pidPath;
13915
14173
  }
13916
14174
  function removePidFile() {
13917
- const pidPath = path51.join(getMcpBaseDir(), "gateway.pid");
14175
+ const pidPath = path52.join(getMcpBaseDir(), "gateway.pid");
13918
14176
  try {
13919
14177
  if (fs38.existsSync(pidPath)) {
13920
14178
  fs38.unlinkSync(pidPath);
@@ -13935,6 +14193,13 @@ async function probeExistingGatewayHealthy(env2 = process.env) {
13935
14193
  }
13936
14194
  function createGatewayRequestHandler(options = {}) {
13937
14195
  const oauth = options.oauth ?? createGatewayOAuthSurface(options);
14196
+ const apiUrl = options.apiUrl ?? config2.apiUrl;
14197
+ const unboundPairing = options.unboundPairing ?? createGatewayUnboundPairingCoordinator({
14198
+ apiUrl,
14199
+ fetchImpl: options.fetchImpl,
14200
+ homeDir: options.homeDir,
14201
+ identityDeps: options.identityDeps
14202
+ });
13938
14203
  return (req, res) => {
13939
14204
  void (async () => {
13940
14205
  try {
@@ -13959,7 +14224,10 @@ function createGatewayRequestHandler(options = {}) {
13959
14224
  homeDir: options.homeDir,
13960
14225
  identityDeps: options.identityDeps,
13961
14226
  ticketOptions: options.ticketOptions,
13962
- createMemoraClient: options.createMemoraClient
14227
+ createMemoraClient: options.createMemoraClient,
14228
+ apiUrl,
14229
+ fetchImpl: options.fetchImpl,
14230
+ unboundPairing
13963
14231
  });
13964
14232
  if (mcpHandled) return;
13965
14233
  res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
@@ -13997,7 +14265,7 @@ async function runGateway(env2 = process.env, options = {}) {
13997
14265
  process.on("SIGINT", () => shutdownNow("SIGINT"));
13998
14266
  process.on("SIGTERM", () => shutdownNow("SIGTERM"));
13999
14267
  process.on("exit", removePidFile);
14000
- await new Promise((resolve46, reject) => {
14268
+ await new Promise((resolve47, reject) => {
14001
14269
  server.once("error", async (err) => {
14002
14270
  if (err.code === "EADDRINUSE") {
14003
14271
  const healthy = await probeExistingGatewayHealthy(env2);
@@ -14024,7 +14292,7 @@ async function runGateway(env2 = process.env, options = {}) {
14024
14292
  }
14025
14293
  const pidPath = writePidFile(process.pid);
14026
14294
  log3(`gateway started pid=${process.pid} listening on http://${host}:${port} pidFile=${pidPath}`);
14027
- resolve46();
14295
+ resolve47();
14028
14296
  });
14029
14297
  });
14030
14298
  }
package/dist/index.cjs CHANGED
@@ -466,6 +466,7 @@ function parsePayload(raw, repositoryBindingId) {
466
466
  optionalString("refreshTokenExpiresAt");
467
467
  optionalString("clientRedeemKey");
468
468
  optionalString("clientRefreshKey");
469
+ optionalString("supabaseUserId");
469
470
  return payload;
470
471
  }
471
472
  async function readInstallationCredentials(repositoryBindingId, options = {}) {
@@ -827,7 +828,8 @@ async function redeemLocalConnectCode(apiUrl, body, options = {}) {
827
828
  const installationPublicId = data?.installation_public_id;
828
829
  const projectId = data?.project_id;
829
830
  const repositoryBindingId = data?.repository_binding_id;
830
- if (typeof accessToken !== "string" || typeof refreshToken !== "string" || typeof installationPublicId !== "string" || typeof projectId !== "string" || typeof repositoryBindingId !== "string") {
831
+ const supabaseUserId = data?.supabase_user_id;
832
+ if (typeof accessToken !== "string" || typeof refreshToken !== "string" || typeof installationPublicId !== "string" || typeof projectId !== "string" || typeof repositoryBindingId !== "string" || typeof supabaseUserId !== "string" || supabaseUserId.length === 0) {
831
833
  throw new Error("[memoraone-mcp] Invalid redeem response");
832
834
  }
833
835
  let sourceId = null;
@@ -845,7 +847,8 @@ async function redeemLocalConnectCode(apiUrl, body, options = {}) {
845
847
  project_id: projectId,
846
848
  repository_binding_id: repositoryBindingId,
847
849
  source_id: sourceId,
848
- recovered: Boolean(data.recovered)
850
+ recovered: Boolean(data.recovered),
851
+ supabase_user_id: supabaseUserId
849
852
  };
850
853
  }
851
854
  async function refreshLocalMcpToken(apiUrl, body, options = {}) {
@@ -11346,7 +11349,8 @@ async function runConnectCommand(options) {
11346
11349
  accessTokenExpiresAt: redeemed.access_token_expires_at ?? void 0,
11347
11350
  refreshTokenExpiresAt: redeemed.refresh_token_expires_at ?? void 0,
11348
11351
  installationPublicId: redeemed.installation_public_id,
11349
- projectId: redeemed.project_id
11352
+ projectId: redeemed.project_id,
11353
+ supabaseUserId: redeemed.supabase_user_id
11350
11354
  },
11351
11355
  { ...options.credentialOptions, clearKeys: ["clientRedeemKey"] }
11352
11356
  );
@@ -199,6 +199,7 @@ function parsePayload(raw, repositoryBindingId) {
199
199
  optionalString("refreshTokenExpiresAt");
200
200
  optionalString("clientRedeemKey");
201
201
  optionalString("clientRefreshKey");
202
+ optionalString("supabaseUserId");
202
203
  return payload;
203
204
  }
204
205
  async function readInstallationCredentials(repositoryBindingId, options = {}) {
@@ -539,7 +540,8 @@ async function redeemLocalConnectCode(apiUrl, body, options = {}) {
539
540
  const installationPublicId = data?.installation_public_id;
540
541
  const projectId = data?.project_id;
541
542
  const repositoryBindingId = data?.repository_binding_id;
542
- if (typeof accessToken !== "string" || typeof refreshToken !== "string" || typeof installationPublicId !== "string" || typeof projectId !== "string" || typeof repositoryBindingId !== "string") {
543
+ const supabaseUserId = data?.supabase_user_id;
544
+ if (typeof accessToken !== "string" || typeof refreshToken !== "string" || typeof installationPublicId !== "string" || typeof projectId !== "string" || typeof repositoryBindingId !== "string" || typeof supabaseUserId !== "string" || supabaseUserId.length === 0) {
543
545
  throw new Error("[memoraone-mcp] Invalid redeem response");
544
546
  }
545
547
  let sourceId = null;
@@ -557,7 +559,8 @@ async function redeemLocalConnectCode(apiUrl, body, options = {}) {
557
559
  project_id: projectId,
558
560
  repository_binding_id: repositoryBindingId,
559
561
  source_id: sourceId,
560
- recovered: Boolean(data.recovered)
562
+ recovered: Boolean(data.recovered),
563
+ supabase_user_id: supabaseUserId
561
564
  };
562
565
  }
563
566
 
@@ -9641,7 +9644,8 @@ async function runConnectCommand(options) {
9641
9644
  accessTokenExpiresAt: redeemed.access_token_expires_at ?? void 0,
9642
9645
  refreshTokenExpiresAt: redeemed.refresh_token_expires_at ?? void 0,
9643
9646
  installationPublicId: redeemed.installation_public_id,
9644
- projectId: redeemed.project_id
9647
+ projectId: redeemed.project_id,
9648
+ supabaseUserId: redeemed.supabase_user_id
9645
9649
  },
9646
9650
  { ...options.credentialOptions, clearKeys: ["clientRedeemKey"] }
9647
9651
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memoraone/mcp",
3
- "version": "0.1.55",
3
+ "version": "0.1.56",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "exports": {