@memoraone/mcp 0.1.54 → 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/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,14 +363,40 @@ 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
  }
274
- function getGatewayProtectedResourceMetadataUrl(env2 = process.env) {
275
- return `${getGatewayBaseUrl(env2)}${GATEWAY_OAUTH_PRM_PATH}`;
389
+ function getGatewayProtectedResourceMetadataUrl(env2 = process.env, workspaceRoot) {
390
+ const metadataUrl = `${getGatewayBaseUrl(env2)}${GATEWAY_OAUTH_PRM_PATH}`;
391
+ if (workspaceRoot === void 0) {
392
+ return metadataUrl;
393
+ }
394
+ const mcpUrl = buildGatewayMcpUrl(workspaceRoot, env2);
395
+ const queryIndex = mcpUrl.indexOf("?");
396
+ return queryIndex >= 0 ? `${metadataUrl}${mcpUrl.slice(queryIndex)}` : metadataUrl;
276
397
  }
277
- function buildGatewayMcpWwwAuthenticate(env2 = process.env) {
278
- const metadataUrl = getGatewayProtectedResourceMetadataUrl(env2);
398
+ function buildGatewayMcpWwwAuthenticate(env2 = process.env, workspaceRoot) {
399
+ const metadataUrl = getGatewayProtectedResourceMetadataUrl(env2, workspaceRoot);
279
400
  return `Bearer resource_metadata="${metadataUrl}"`;
280
401
  }
281
402
  function isAllowedOAuthRedirectUri(redirectUri) {
@@ -391,47 +512,47 @@ var GatewayOAuthCodeStore = class {
391
512
  };
392
513
 
393
514
  // src/gatewayWorkspace.ts
394
- var fs6 = __toESM(require("fs/promises"), 1);
395
- var path11 = __toESM(require("path"), 1);
515
+ var fs7 = __toESM(require("fs/promises"), 1);
516
+ var path12 = __toESM(require("path"), 1);
396
517
 
397
518
  // src/localState/resolveLocalBinding.ts
398
- var path10 = __toESM(require("path"), 1);
519
+ var path11 = __toESM(require("path"), 1);
399
520
 
400
521
  // src/localState/bindingStore.ts
401
- var fs4 = __toESM(require("fs/promises"), 1);
402
- var path6 = __toESM(require("path"), 1);
522
+ var fs5 = __toESM(require("fs/promises"), 1);
523
+ var path7 = __toESM(require("path"), 1);
403
524
 
404
525
  // src/localState/atomicFs.ts
405
- var fs2 = __toESM(require("fs/promises"), 1);
406
- var path4 = __toESM(require("path"), 1);
526
+ var fs3 = __toESM(require("fs/promises"), 1);
527
+ var path5 = __toESM(require("path"), 1);
407
528
  var import_node_crypto2 = require("crypto");
408
529
  var STATE_DIR_MODE = 448;
409
530
  var STATE_FILE_MODE = 384;
410
531
  async function ensurePrivateDir(dirPath) {
411
- await fs2.mkdir(dirPath, { recursive: true, mode: STATE_DIR_MODE });
532
+ await fs3.mkdir(dirPath, { recursive: true, mode: STATE_DIR_MODE });
412
533
  try {
413
- await fs2.chmod(dirPath, STATE_DIR_MODE);
534
+ await fs3.chmod(dirPath, STATE_DIR_MODE);
414
535
  } catch {
415
536
  }
416
537
  }
417
538
  async function writeFileAtomic(filePath, content, options = {}) {
418
539
  const mode = options.mode ?? STATE_FILE_MODE;
419
- const dir = path4.dirname(filePath);
540
+ const dir = path5.dirname(filePath);
420
541
  await ensurePrivateDir(dir);
421
- const tmpPath = path4.join(
542
+ const tmpPath = path5.join(
422
543
  dir,
423
- `.${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`
424
545
  );
425
546
  try {
426
- await fs2.writeFile(tmpPath, content, { encoding: "utf8", mode });
427
- await fs2.rename(tmpPath, filePath);
547
+ await fs3.writeFile(tmpPath, content, { encoding: "utf8", mode });
548
+ await fs3.rename(tmpPath, filePath);
428
549
  try {
429
- await fs2.chmod(filePath, mode);
550
+ await fs3.chmod(filePath, mode);
430
551
  } catch {
431
552
  }
432
553
  } catch (err) {
433
554
  try {
434
- await fs2.unlink(tmpPath);
555
+ await fs3.unlink(tmpPath);
435
556
  } catch {
436
557
  }
437
558
  throw err;
@@ -439,7 +560,7 @@ async function writeFileAtomic(filePath, content, options = {}) {
439
560
  }
440
561
  async function readJsonFile(filePath) {
441
562
  try {
442
- const raw = await fs2.readFile(filePath, "utf8");
563
+ const raw = await fs3.readFile(filePath, "utf8");
443
564
  return JSON.parse(raw);
444
565
  } catch (err) {
445
566
  if (err?.code === "ENOENT") {
@@ -566,32 +687,32 @@ function parseBindingRecord(raw) {
566
687
  }
567
688
 
568
689
  // src/localState/localLocks.ts
569
- var fs3 = __toESM(require("fs/promises"), 1);
690
+ var fs4 = __toESM(require("fs/promises"), 1);
570
691
 
571
692
  // src/localState/statePaths.ts
572
693
  var os2 = __toESM(require("os"), 1);
573
- var path5 = __toESM(require("path"), 1);
694
+ var path6 = __toESM(require("path"), 1);
574
695
  var MEMORAONE_STATE_DIRNAME = ".memoraone";
575
696
  function getMemoraoneStateDir(homeDir = os2.homedir()) {
576
- return path5.join(homeDir, MEMORAONE_STATE_DIRNAME);
697
+ return path6.join(homeDir, MEMORAONE_STATE_DIRNAME);
577
698
  }
578
699
  function getPathIndexPath(homeDir = os2.homedir()) {
579
- return path5.join(getMemoraoneStateDir(homeDir), "path-index.json");
700
+ return path6.join(getMemoraoneStateDir(homeDir), "path-index.json");
580
701
  }
581
702
  function getBindingsDir(homeDir = os2.homedir()) {
582
- return path5.join(getMemoraoneStateDir(homeDir), "bindings");
703
+ return path6.join(getMemoraoneStateDir(homeDir), "bindings");
583
704
  }
584
705
  function getBindingFilePath(repositoryBindingId, homeDir = os2.homedir()) {
585
- return path5.join(getBindingsDir(homeDir), `${repositoryBindingId}.json`);
706
+ return path6.join(getBindingsDir(homeDir), `${repositoryBindingId}.json`);
586
707
  }
587
708
  function getLocksDir(homeDir = os2.homedir()) {
588
- return path5.join(getMemoraoneStateDir(homeDir), "locks");
709
+ return path6.join(getMemoraoneStateDir(homeDir), "locks");
589
710
  }
590
711
  function getLockPath(lockName, homeDir = os2.homedir()) {
591
- return path5.join(getLocksDir(homeDir), `${lockName}.lock`);
712
+ return path6.join(getLocksDir(homeDir), `${lockName}.lock`);
592
713
  }
593
714
  function getGatewayTicketsPath(homeDir = os2.homedir()) {
594
- return path5.join(getMemoraoneStateDir(homeDir), "gateway-tickets.json");
715
+ return path6.join(getMemoraoneStateDir(homeDir), "gateway-tickets.json");
595
716
  }
596
717
 
597
718
  // src/localState/localLocks.ts
@@ -606,16 +727,16 @@ async function acquireLocalLock(lockName, options = {}) {
606
727
  while (retries <= maxRetries) {
607
728
  try {
608
729
  try {
609
- const stat5 = await fs3.stat(lockPath);
730
+ const stat5 = await fs4.stat(lockPath);
610
731
  if (Date.now() - stat5.mtimeMs > maxLockAgeMs) {
611
- await fs3.unlink(lockPath);
732
+ await fs4.unlink(lockPath);
612
733
  }
613
734
  } catch (err) {
614
735
  if (err?.code !== "ENOENT") {
615
736
  throw err;
616
737
  }
617
738
  }
618
- const fd = await fs3.open(lockPath, "wx");
739
+ const fd = await fs4.open(lockPath, "wx");
619
740
  await fd.writeFile(
620
741
  JSON.stringify({
621
742
  pid: process.pid,
@@ -626,7 +747,7 @@ async function acquireLocalLock(lockName, options = {}) {
626
747
  await fd.close();
627
748
  return async () => {
628
749
  try {
629
- await fs3.unlink(lockPath);
750
+ await fs4.unlink(lockPath);
630
751
  } catch (err) {
631
752
  if (err?.code !== "ENOENT") {
632
753
  }
@@ -640,7 +761,7 @@ async function acquireLocalLock(lockName, options = {}) {
640
761
  `[memoraone-mcp] Failed to acquire lock ${lockName} after ${maxRetries} retries`
641
762
  );
642
763
  }
643
- await new Promise((resolve46) => setTimeout(resolve46, retryDelayMs));
764
+ await new Promise((resolve47) => setTimeout(resolve47, retryDelayMs));
644
765
  continue;
645
766
  }
646
767
  throw err;
@@ -692,7 +813,7 @@ async function listBindingRecords(homeDir) {
692
813
  const dir = await ensureBindingsDir(homeDir);
693
814
  let names;
694
815
  try {
695
- names = await fs4.readdir(dir);
816
+ names = await fs5.readdir(dir);
696
817
  } catch (err) {
697
818
  if (err?.code === "ENOENT") return [];
698
819
  throw err;
@@ -710,9 +831,9 @@ async function listBindingRecords(homeDir) {
710
831
  return out;
711
832
  }
712
833
  async function findBindingRecordByWorkspaceRoot(workspaceRoot, homeDir) {
713
- const resolved = path6.resolve(workspaceRoot);
834
+ const resolved = path7.resolve(workspaceRoot);
714
835
  const records = await listBindingRecords(homeDir);
715
- return records.find((r) => path6.resolve(r.workspaceRoot) === resolved) ?? null;
836
+ return records.find((r) => path7.resolve(r.workspaceRoot) === resolved) ?? null;
716
837
  }
717
838
 
718
839
  // src/localState/installationCredentials.ts
@@ -857,6 +978,7 @@ function parsePayload(raw, repositoryBindingId) {
857
978
  optionalString("refreshTokenExpiresAt");
858
979
  optionalString("clientRedeemKey");
859
980
  optionalString("clientRefreshKey");
981
+ optionalString("supabaseUserId");
860
982
  return payload;
861
983
  }
862
984
  async function readInstallationCredentials(repositoryBindingId, options = {}) {
@@ -939,12 +1061,12 @@ function hasUsableAccessToken(payload) {
939
1061
  }
940
1062
 
941
1063
  // src/localState/pathIndex.ts
942
- var path8 = __toESM(require("path"), 1);
1064
+ var path9 = __toESM(require("path"), 1);
943
1065
 
944
1066
  // src/localState/rootFilesystemIdentity.ts
945
- var fs5 = __toESM(require("fs/promises"), 1);
1067
+ var fs6 = __toESM(require("fs/promises"), 1);
946
1068
  var os3 = __toESM(require("os"), 1);
947
- var path7 = __toESM(require("path"), 1);
1069
+ var path8 = __toESM(require("path"), 1);
948
1070
  var import_node_child_process = require("child_process");
949
1071
  var import_node_util = require("util");
950
1072
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
@@ -969,13 +1091,13 @@ async function readDarwinDeviceId() {
969
1091
  }
970
1092
  async function readLinuxDeviceId() {
971
1093
  try {
972
- const content = await fs5.readFile("/etc/machine-id", "utf8");
1094
+ const content = await fs6.readFile("/etc/machine-id", "utf8");
973
1095
  const id = content.trim();
974
1096
  if (id) return id;
975
1097
  } catch {
976
1098
  }
977
1099
  try {
978
- const content = await fs5.readFile("/var/lib/dbus/machine-id", "utf8");
1100
+ const content = await fs6.readFile("/var/lib/dbus/machine-id", "utf8");
979
1101
  const id = content.trim();
980
1102
  if (id) return id;
981
1103
  } catch {
@@ -1008,10 +1130,10 @@ async function resolveDeviceId(platform2 = os3.platform()) {
1008
1130
  throw new Error(`[memoraone-mcp] Unsupported platform for device ID: ${platform2}`);
1009
1131
  }
1010
1132
  async function captureRootFilesystemIdentity(rootPath, deps = {}) {
1011
- const resolved = path7.resolve(rootPath);
1133
+ const resolved = path8.resolve(rootPath);
1012
1134
  const platform2 = deps.platform ?? os3.platform();
1013
1135
  const statRoot = deps.statRoot ?? (async (p) => {
1014
- const st2 = await fs5.stat(p);
1136
+ const st2 = await fs6.stat(p);
1015
1137
  return {
1016
1138
  ino: st2.ino,
1017
1139
  dev: st2.dev,
@@ -1095,7 +1217,7 @@ async function savePathIndex(index, homeDir) {
1095
1217
  await writeJsonAtomic(getPathIndexPath(homeDir), next);
1096
1218
  }
1097
1219
  function lookupPathIndex(index, workspaceRoot, identity) {
1098
- const resolved = path8.resolve(workspaceRoot);
1220
+ const resolved = path9.resolve(workspaceRoot);
1099
1221
  const byPathId = index.byPath[resolved];
1100
1222
  if (byPathId) {
1101
1223
  return { kind: "path", repositoryBindingId: assertRepositoryBindingId(byPathId) };
@@ -1121,14 +1243,14 @@ function lookupPathIndex(index, workspaceRoot, identity) {
1121
1243
  }
1122
1244
  async function upsertPathIndexEntry(options) {
1123
1245
  const repositoryBindingId = assertRepositoryBindingId(options.repositoryBindingId);
1124
- const resolved = path8.resolve(options.workspaceRoot);
1246
+ const resolved = path9.resolve(options.workspaceRoot);
1125
1247
  const identityKey = filesystemIdentityKey(options.identity);
1126
1248
  return withLocalLock(
1127
1249
  "path-index",
1128
1250
  async () => {
1129
1251
  const index = await loadPathIndex(options.homeDir);
1130
- if (options.previousPath && path8.resolve(options.previousPath) !== resolved) {
1131
- delete index.byPath[path8.resolve(options.previousPath)];
1252
+ if (options.previousPath && path9.resolve(options.previousPath) !== resolved) {
1253
+ delete index.byPath[path9.resolve(options.previousPath)];
1132
1254
  }
1133
1255
  for (const [p, id] of Object.entries(index.byPath)) {
1134
1256
  if (id === repositoryBindingId && p !== resolved) {
@@ -1153,20 +1275,20 @@ function identityMatchesStored(stored, current) {
1153
1275
  }
1154
1276
 
1155
1277
  // src/localState/localConnectClient.ts
1156
- var path9 = __toESM(require("path"), 1);
1278
+ var path10 = __toESM(require("path"), 1);
1157
1279
 
1158
1280
  // src/client/memoraClient.ts
1159
1281
  var crypto2 = __toESM(require("crypto"), 1);
1160
1282
  var PROJECT_ID_HEADER = "x-project-id";
1161
1283
  var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1162
- var parseBooleanFlag = (value) => {
1284
+ var parseBooleanFlag2 = (value) => {
1163
1285
  if (!value) {
1164
1286
  return false;
1165
1287
  }
1166
1288
  const normalized = value.trim().toLowerCase();
1167
1289
  return ["1", "true", "yes", "on"].includes(normalized);
1168
1290
  };
1169
- var debugEnabled = parseBooleanFlag(process.env.MEMORAONE_DEV_MODE);
1291
+ var debugEnabled = parseBooleanFlag2(process.env.MEMORAONE_DEV_MODE);
1170
1292
  async function requestJson(url, method, headers, body) {
1171
1293
  const res = await fetch(url, {
1172
1294
  method,
@@ -1264,9 +1386,9 @@ var MemoraClient = class {
1264
1386
  ...options?.headers ?? {}
1265
1387
  };
1266
1388
  }
1267
- async perform(method, path52, body, options, retried = false) {
1389
+ async perform(method, path53, body, options, retried = false) {
1268
1390
  const nonce = crypto2.randomBytes(8).toString("hex");
1269
- const url = `${this.baseUrl}${path52.startsWith("/") ? path52 : `/${path52}`}`;
1391
+ const url = `${this.baseUrl}${path53.startsWith("/") ? path53 : `/${path53}`}`;
1270
1392
  this.resolveProjectId();
1271
1393
  console.error(
1272
1394
  `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_fetch method=${method} url=${url}`
@@ -1293,7 +1415,7 @@ var MemoraClient = class {
1293
1415
  if (res.status === 401 && !options?.skipAuthRefresh && !retried) {
1294
1416
  try {
1295
1417
  await this.refreshAccessToken();
1296
- return this.perform(method, path52, body, options, true);
1418
+ return this.perform(method, path53, body, options, true);
1297
1419
  } catch (err) {
1298
1420
  if (err instanceof ReconnectRequiredError) {
1299
1421
  await this.markReconnectRequired();
@@ -1313,22 +1435,22 @@ var MemoraClient = class {
1313
1435
  }
1314
1436
  return res.text ? JSON.parse(res.text) : null;
1315
1437
  }
1316
- async post(path52, body, options) {
1317
- console.error(`[memoraone-mcp][info] MemoraClient.post ENTER path=${path52}`);
1318
- const result = await this.perform("POST", path52, body, options);
1319
- 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}`);
1320
1442
  return result;
1321
1443
  }
1322
- async get(path52, options) {
1323
- return this.perform("GET", path52, void 0, options);
1444
+ async get(path53, options) {
1445
+ return this.perform("GET", path53, void 0, options);
1324
1446
  }
1325
1447
  };
1326
1448
  var memoraClient_default = MemoraClient;
1327
1449
 
1328
1450
  // src/localState/localConnectClient.ts
1329
- async function requestJson2(baseUrl, method, path52, options = {}) {
1451
+ async function requestJson2(baseUrl, method, path53, options = {}) {
1330
1452
  const fetchImpl = options.fetchImpl ?? fetch;
1331
- const url = `${baseUrl.replace(/\/+$/, "")}${path52.startsWith("/") ? path52 : `/${path52}`}`;
1453
+ const url = `${baseUrl.replace(/\/+$/, "")}${path53.startsWith("/") ? path53 : `/${path53}`}`;
1332
1454
  const res = await fetchImpl(url, {
1333
1455
  method,
1334
1456
  headers: {
@@ -1365,7 +1487,7 @@ function normalizePluginPairingSource(source) {
1365
1487
  return source;
1366
1488
  }
1367
1489
  function pairingRepositoryName(workspaceRoot) {
1368
- const repositoryName = path9.basename(path9.resolve(workspaceRoot)).trim();
1490
+ const repositoryName = path10.basename(path10.resolve(workspaceRoot)).trim();
1369
1491
  if (repositoryName.length === 0 || repositoryName.length > 256 || repositoryName.includes("/") || repositoryName.includes("\\")) {
1370
1492
  throw new Error("[memoraone-mcp] Workspace basename is not safe pairing metadata");
1371
1493
  }
@@ -1460,7 +1582,8 @@ async function redeemLocalConnectCode(apiUrl, body, options = {}) {
1460
1582
  const installationPublicId = data?.installation_public_id;
1461
1583
  const projectId = data?.project_id;
1462
1584
  const repositoryBindingId = data?.repository_binding_id;
1463
- 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) {
1464
1587
  throw new Error("[memoraone-mcp] Invalid redeem response");
1465
1588
  }
1466
1589
  let sourceId = null;
@@ -1478,7 +1601,8 @@ async function redeemLocalConnectCode(apiUrl, body, options = {}) {
1478
1601
  project_id: projectId,
1479
1602
  repository_binding_id: repositoryBindingId,
1480
1603
  source_id: sourceId,
1481
- recovered: Boolean(data.recovered)
1604
+ recovered: Boolean(data.recovered),
1605
+ supabase_user_id: supabaseUserId
1482
1606
  };
1483
1607
  }
1484
1608
  async function refreshLocalMcpToken(apiUrl, body, options = {}) {
@@ -1500,6 +1624,33 @@ async function refreshLocalMcpToken(apiUrl, body, options = {}) {
1500
1624
  repository_binding_id: typeof data.repository_binding_id === "string" ? data.repository_binding_id : void 0
1501
1625
  };
1502
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
+ }
1503
1654
 
1504
1655
  // src/localState/tokenRefreshCoordinator.ts
1505
1656
  var ReconnectRequiredError = class extends Error {
@@ -1564,14 +1715,14 @@ async function refreshInstallationAccessToken(options) {
1564
1715
 
1565
1716
  // src/localState/resolveLocalBinding.ts
1566
1717
  async function ensureRepositoryBindingForRoot(workspaceRoot, options = {}) {
1567
- const resolved = path10.resolve(workspaceRoot);
1718
+ const resolved = path11.resolve(workspaceRoot);
1568
1719
  const identity = await captureRootFilesystemIdentity(resolved, options.identityDeps);
1569
1720
  const index = await loadPathIndex(options.homeDir);
1570
1721
  const lookup = lookupPathIndex(index, resolved, identity);
1571
1722
  if (lookup.kind === "path") {
1572
1723
  const record = await readBindingRecord(lookup.repositoryBindingId, options.homeDir);
1573
1724
  if (record && identityMatchesStored(record.filesystemIdentity, identity)) {
1574
- if (path10.resolve(record.workspaceRoot) !== resolved) {
1725
+ if (path11.resolve(record.workspaceRoot) !== resolved) {
1575
1726
  const updated = {
1576
1727
  ...record,
1577
1728
  workspaceRoot: resolved,
@@ -1630,7 +1781,7 @@ async function ensureRepositoryBindingForRoot(workspaceRoot, options = {}) {
1630
1781
  };
1631
1782
  }
1632
1783
  async function lookupLocalBindingIdentity(workspaceRoot, options = {}) {
1633
- const resolved = path10.resolve(workspaceRoot);
1784
+ const resolved = path11.resolve(workspaceRoot);
1634
1785
  const identity = await captureRootFilesystemIdentity(resolved, options.identityDeps);
1635
1786
  const index = await loadPathIndex(options.homeDir);
1636
1787
  const lookup = lookupPathIndex(index, resolved, identity);
@@ -1641,7 +1792,7 @@ async function lookupLocalBindingIdentity(workspaceRoot, options = {}) {
1641
1792
  if (!record) {
1642
1793
  return { status: "unbound", workspaceRoot: resolved };
1643
1794
  }
1644
- if (path10.resolve(record.workspaceRoot) !== resolved) {
1795
+ if (path11.resolve(record.workspaceRoot) !== resolved) {
1645
1796
  return { status: "unbound", workspaceRoot: resolved };
1646
1797
  }
1647
1798
  if (!identityMatchesStored(record.filesystemIdentity, identity)) {
@@ -1654,7 +1805,7 @@ async function lookupLocalBindingIdentity(workspaceRoot, options = {}) {
1654
1805
  };
1655
1806
  }
1656
1807
  async function resolveLocalBinding(workspaceRoot, options = {}) {
1657
- const resolved = path10.resolve(workspaceRoot);
1808
+ const resolved = path11.resolve(workspaceRoot);
1658
1809
  const { repositoryBindingId } = await ensureRepositoryBindingForRoot(
1659
1810
  resolved,
1660
1811
  { ...options, createIfMissing: false }
@@ -1740,12 +1891,12 @@ async function parseGatewayWorkspaceRoot(wsRaw) {
1740
1891
  if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed)) {
1741
1892
  return fail("malformed_ws", "ws must be an absolute filesystem path");
1742
1893
  }
1743
- if (!path11.isAbsolute(trimmed)) {
1894
+ if (!path12.isAbsolute(trimmed)) {
1744
1895
  return fail("non_absolute", "ws must be an absolute workspace root");
1745
1896
  }
1746
- const workspaceRoot = path11.resolve(trimmed);
1897
+ const workspaceRoot = path12.resolve(trimmed);
1747
1898
  try {
1748
- const st = await fs6.stat(workspaceRoot);
1899
+ const st = await fs7.stat(workspaceRoot);
1749
1900
  if (!st.isDirectory()) {
1750
1901
  return fail("not_directory", "ws must point to an existing directory");
1751
1902
  }
@@ -1768,7 +1919,7 @@ async function parseGatewayWorkspaceUrl(input2) {
1768
1919
 
1769
1920
  // src/localState/gatewayTicketStore.ts
1770
1921
  var import_node_crypto5 = require("crypto");
1771
- var path12 = __toESM(require("path"), 1);
1922
+ var path13 = __toESM(require("path"), 1);
1772
1923
  var GATEWAY_TICKETS_VERSION = 1;
1773
1924
  var GATEWAY_TICKETS_LOCK_NAME = "gateway-tickets";
1774
1925
  var MGT_PREFIX = "mgt_";
@@ -1862,7 +2013,7 @@ async function resolveGatewayTicketScope(input2, options = {}) {
1862
2013
  if (!("workspaceRoot" in input2) || input2.workspaceRoot == null) {
1863
2014
  return fail2("invalid_workspace", "workspaceRoot or canonicalUrl is required");
1864
2015
  }
1865
- const parsed2 = await parseGatewayWorkspaceRoot(path12.resolve(input2.workspaceRoot));
2016
+ const parsed2 = await parseGatewayWorkspaceRoot(path13.resolve(input2.workspaceRoot));
1866
2017
  if (!parsed2.ok) {
1867
2018
  return fail2("invalid_workspace", parsed2.error);
1868
2019
  }
@@ -1959,101 +2110,6 @@ async function revokeGatewaySetupTicket(ticket, options = {}) {
1959
2110
  // src/pluginPairing.ts
1960
2111
  var path47 = __toESM(require("path"), 1);
1961
2112
 
1962
- // src/config.ts
1963
- var process2 = __toESM(require("process"), 1);
1964
- var fs7 = __toESM(require("fs"), 1);
1965
- var path13 = __toESM(require("path"), 1);
1966
- var dotenv = __toESM(require("dotenv"), 1);
1967
- var import_v4 = require("zod/v4");
1968
-
1969
- // src/configUtils.ts
1970
- var DEFAULT_API_URL = "https://api.memoraone.com";
1971
- var DEV_API_URL = "http://localhost:3001";
1972
- function resolveApiUrl(env2) {
1973
- const explicitUrl = env2.MEMORAONE_API_URL?.trim();
1974
- if (explicitUrl) {
1975
- return explicitUrl;
1976
- }
1977
- const aliasUrl = env2.MEMORA_API_URL?.trim();
1978
- if (aliasUrl) {
1979
- return aliasUrl;
1980
- }
1981
- if (env2.MEMORAONE_DEV_MODE === "1") {
1982
- return DEV_API_URL;
1983
- }
1984
- return DEFAULT_API_URL;
1985
- }
1986
-
1987
- // src/config.ts
1988
- var dotenvPath = path13.resolve(process2.cwd(), ".env");
1989
- if (fs7.existsSync(dotenvPath)) {
1990
- try {
1991
- dotenv.config({ path: dotenvPath });
1992
- } catch (err) {
1993
- process2.stderr.write("[memoraone-mcp] Failed to load .env: " + String(err) + "\n");
1994
- }
1995
- }
1996
- var EnvSchema = import_v4.z.object({
1997
- MEMORAONE_API_URL: import_v4.z.string().url().optional(),
1998
- MEMORAONE_API_KEY: import_v4.z.string().min(1).optional(),
1999
- MEMORAONE_DEV_MODE: import_v4.z.string().min(1).optional(),
2000
- MEMORAONE_AGENT_NAME: import_v4.z.string().min(1).optional(),
2001
- MEMORAONE_AGENT_TYPE: import_v4.z.string().min(1).optional(),
2002
- MEMORAONE_SOURCE: import_v4.z.string().min(1).optional(),
2003
- MEMORAONE_IDE_TYPE: import_v4.z.enum(IDE_TYPES).optional(),
2004
- MEMORAONE_WORKLOG: import_v4.z.string().min(1).optional(),
2005
- MEMORAONE_HEARTBEAT: import_v4.z.string().min(1).optional(),
2006
- MEMORAONE_HEARTBEAT_INTERVAL_MS: import_v4.z.string().min(1).optional()
2007
- });
2008
- var requiredEnvVars = [];
2009
- var missingEnvVars = requiredEnvVars.filter((key) => {
2010
- const value = process2.env[key];
2011
- return value === void 0 || value.trim() === "";
2012
- });
2013
- if (missingEnvVars.length > 0) {
2014
- for (const key of missingEnvVars) {
2015
- process2.stderr.write(`Missing ${key}
2016
- `);
2017
- }
2018
- process2.exit(1);
2019
- }
2020
- var parsed = EnvSchema.safeParse(process2.env);
2021
- var resolvedApiUrl = resolveApiUrl(process2.env);
2022
- if (!parsed.success) {
2023
- const formatted = parsed.error.format();
2024
- process2.stderr.write(
2025
- "[memoraone-mcp] Invalid environment variables " + JSON.stringify(formatted) + "\n"
2026
- );
2027
- throw new Error("Config validation failed");
2028
- }
2029
- var parseBooleanFlag2 = (value, defaultValue) => {
2030
- if (value === void 0) {
2031
- return defaultValue;
2032
- }
2033
- const normalized = value.trim().toLowerCase();
2034
- if (["1", "true", "yes", "on"].includes(normalized)) {
2035
- return true;
2036
- }
2037
- if (["0", "false", "no", "off"].includes(normalized)) {
2038
- return false;
2039
- }
2040
- return defaultValue;
2041
- };
2042
- var config2 = {
2043
- apiUrl: resolvedApiUrl.replace(/\/+$/, ""),
2044
- apiKey: parsed.data.MEMORAONE_API_KEY,
2045
- agentName: parsed.data.MEMORAONE_AGENT_NAME ?? "cursor",
2046
- agentType: parsed.data.MEMORAONE_AGENT_TYPE ?? "agent",
2047
- source: parsed.data.MEMORAONE_SOURCE ?? "cursor",
2048
- ideType: parsed.data.MEMORAONE_IDE_TYPE,
2049
- devMode: parseBooleanFlag2(parsed.data.MEMORAONE_DEV_MODE, false),
2050
- worklogEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_WORKLOG, true),
2051
- heartbeatEnabled: parseBooleanFlag2(parsed.data.MEMORAONE_HEARTBEAT, true),
2052
- // Cadence is owned by LOCAL_MCP_HEARTBEAT_INTERVAL_MS in heartbeat.ts (1_000).
2053
- // Env override is accepted for forward-compat but the timer path ignores it.
2054
- heartbeatIntervalMs: Number.parseInt(parsed.data.MEMORAONE_HEARTBEAT_INTERVAL_MS ?? "1000", 10)
2055
- };
2056
-
2057
2113
  // src/localState/connectCommand.ts
2058
2114
  var fs36 = __toESM(require("fs/promises"), 1);
2059
2115
  var path46 = __toESM(require("path"), 1);
@@ -4109,7 +4165,7 @@ function formatOptionalJetBrainsHandshakeUnavailableDetail(detail) {
4109
4165
  async function verifyJetBrainsMcpHandshake(options) {
4110
4166
  const timeoutMs = options.timeoutMs ?? 15e3;
4111
4167
  const { server } = options;
4112
- return new Promise((resolve46) => {
4168
+ return new Promise((resolve47) => {
4113
4169
  let settled = false;
4114
4170
  const finish = (ok, detail, optionalUnavailable) => {
4115
4171
  if (settled) return;
@@ -4119,7 +4175,7 @@ async function verifyJetBrainsMcpHandshake(options) {
4119
4175
  child.kill();
4120
4176
  } catch {
4121
4177
  }
4122
- resolve46({ ok, detail, optionalUnavailable });
4178
+ resolve47({ ok, detail, optionalUnavailable });
4123
4179
  };
4124
4180
  const child = (0, import_node_child_process4.spawn)(server.command, [...server.args], {
4125
4181
  env: { ...process.env, ...server.env },
@@ -10663,7 +10719,8 @@ async function runConnectCommand(options) {
10663
10719
  accessTokenExpiresAt: redeemed.access_token_expires_at ?? void 0,
10664
10720
  refreshTokenExpiresAt: redeemed.refresh_token_expires_at ?? void 0,
10665
10721
  installationPublicId: redeemed.installation_public_id,
10666
- projectId: redeemed.project_id
10722
+ projectId: redeemed.project_id,
10723
+ supabaseUserId: redeemed.supabase_user_id
10667
10724
  },
10668
10725
  { ...options.credentialOptions, clearKeys: ["clientRedeemKey"] }
10669
10726
  );
@@ -11108,7 +11165,7 @@ function html(res, status, body) {
11108
11165
  res.end(body);
11109
11166
  }
11110
11167
  function readUrlEncodedBody(req) {
11111
- return new Promise((resolve46, reject) => {
11168
+ return new Promise((resolve47, reject) => {
11112
11169
  const chunks = [];
11113
11170
  let size = 0;
11114
11171
  req.on("data", (chunk) => {
@@ -11121,13 +11178,13 @@ function readUrlEncodedBody(req) {
11121
11178
  chunks.push(chunk);
11122
11179
  });
11123
11180
  req.on("end", () => {
11124
- resolve46(new URLSearchParams(Buffer.concat(chunks).toString("utf8")));
11181
+ resolve47(new URLSearchParams(Buffer.concat(chunks).toString("utf8")));
11125
11182
  });
11126
11183
  req.on("error", reject);
11127
11184
  });
11128
11185
  }
11129
11186
  function readJsonBody(req) {
11130
- return new Promise((resolve46, reject) => {
11187
+ return new Promise((resolve47, reject) => {
11131
11188
  const chunks = [];
11132
11189
  let size = 0;
11133
11190
  req.on("data", (chunk) => {
@@ -11142,11 +11199,11 @@ function readJsonBody(req) {
11142
11199
  req.on("end", () => {
11143
11200
  const raw = Buffer.concat(chunks).toString("utf8");
11144
11201
  if (!raw.trim()) {
11145
- resolve46({});
11202
+ resolve47({});
11146
11203
  return;
11147
11204
  }
11148
11205
  try {
11149
- resolve46(JSON.parse(raw));
11206
+ resolve47(JSON.parse(raw));
11150
11207
  } catch {
11151
11208
  reject(new Error("invalid json"));
11152
11209
  }
@@ -11158,12 +11215,12 @@ function opaqueId(prefix) {
11158
11215
  return `${prefix}${(0, import_node_crypto28.randomBytes)(24).toString("base64url")}`;
11159
11216
  }
11160
11217
  function delay(ms) {
11161
- return new Promise((resolve46) => setTimeout(resolve46, ms));
11218
+ return new Promise((resolve47) => setTimeout(resolve47, ms));
11162
11219
  }
11163
- function buildProtectedResourceMetadata(env2 = process.env) {
11220
+ function buildProtectedResourceMetadata(env2 = process.env, workspaceRoot) {
11164
11221
  return {
11165
- resource: getGatewayResourceBaseUrl(env2),
11166
- authorization_servers: [getGatewayIssuerUrl(env2)]
11222
+ resource: workspaceRoot === void 0 ? getGatewayResourceBaseUrl(env2) : buildGatewayMcpUrl(workspaceRoot, env2),
11223
+ authorization_servers: [getGatewayAuthorizationServerIssuerUrl(env2)]
11167
11224
  };
11168
11225
  }
11169
11226
  function buildAuthorizationServerMetadata(env2 = process.env) {
@@ -11287,11 +11344,38 @@ var GatewayOAuthSurface = class {
11287
11344
  return;
11288
11345
  }
11289
11346
  }
11347
+ /**
11348
+ * RFC 9728 PRM. Generic probes (no `ws`) advertise the bare /mcp resource.
11349
+ * `?ws=` reuses gateway workspace identity; malformed/nonexistent fail closed.
11350
+ */
11351
+ async handleProtectedResourceMetadata(res, url) {
11352
+ const wsOrError = getGatewayWsQueryParam(url);
11353
+ if (wsOrError !== null && typeof wsOrError === "object" && wsOrError.ok === false) {
11354
+ json(res, 404, {
11355
+ error: "invalid_request",
11356
+ error_description: "resource workspace is invalid"
11357
+ });
11358
+ return;
11359
+ }
11360
+ if (wsOrError === null) {
11361
+ json(res, 200, buildProtectedResourceMetadata(this.env));
11362
+ return;
11363
+ }
11364
+ const parsed2 = await parseGatewayWorkspaceRoot(wsOrError);
11365
+ if (!parsed2.ok) {
11366
+ json(res, 404, {
11367
+ error: "invalid_request",
11368
+ error_description: "resource workspace is invalid"
11369
+ });
11370
+ return;
11371
+ }
11372
+ json(res, 200, buildProtectedResourceMetadata(this.env, parsed2.workspaceRoot));
11373
+ }
11290
11374
  async handleRequest(req, res, url) {
11291
11375
  const { pathname } = url;
11292
11376
  const method = req.method ?? "GET";
11293
11377
  if (method === "GET" && (pathname === GATEWAY_OAUTH_PRM_PATH || pathname === GATEWAY_OAUTH_PRM_ROOT_PATH)) {
11294
- json(res, 200, buildProtectedResourceMetadata(this.env));
11378
+ await this.handleProtectedResourceMetadata(res, url);
11295
11379
  return true;
11296
11380
  }
11297
11381
  if (method === "GET" && pathname === GATEWAY_OAUTH_AS_METADATA_PATH) {
@@ -11769,11 +11853,11 @@ async function isRetryablePluginStartupBindingError(err, options = {}) {
11769
11853
  return false;
11770
11854
  }
11771
11855
  function defaultSleep(ms) {
11772
- return new Promise((resolve46) => {
11773
- setTimeout(resolve46, ms);
11856
+ return new Promise((resolve47) => {
11857
+ setTimeout(resolve47, ms);
11774
11858
  });
11775
11859
  }
11776
- async function waitForPluginStartupBinding(resolve46, options = {}) {
11860
+ async function waitForPluginStartupBinding(resolve47, options = {}) {
11777
11861
  const now = options.now ?? Date.now;
11778
11862
  const sleep = options.sleep ?? defaultSleep;
11779
11863
  const timeoutMs = options.timeoutMs ?? PLUGIN_STARTUP_BINDING_WAIT_TIMEOUT_MS;
@@ -11783,7 +11867,7 @@ async function waitForPluginStartupBinding(resolve46, options = {}) {
11783
11867
  let lastError;
11784
11868
  while (true) {
11785
11869
  try {
11786
- const binding = await resolve46();
11870
+ const binding = await resolve47();
11787
11871
  if (waitingLogged) {
11788
11872
  options.log?.(
11789
11873
  `local MemoraOne binding available after Cursor plugin pairing (${now() - startedAt}ms)`
@@ -12263,9 +12347,9 @@ function buildPersonalContextPath(parsed2) {
12263
12347
  }
12264
12348
  async function handleGetPersonalContext(client, args) {
12265
12349
  const parsed2 = getPersonalContextInputSchema.parse(args ?? {});
12266
- const path52 = buildPersonalContextPath(parsed2);
12350
+ const path53 = buildPersonalContextPath(parsed2);
12267
12351
  try {
12268
- const result = await client.get(path52);
12352
+ const result = await client.get(path53);
12269
12353
  return { ok: true, result };
12270
12354
  } catch (err) {
12271
12355
  if (err instanceof MemoraOneHttpError) {
@@ -12737,8 +12821,8 @@ async function handleCreateConceptVersion(client, args) {
12737
12821
  if (input2.tags !== void 0) body.tags = input2.tags;
12738
12822
  if (input2.parent_id !== void 0) body.parent_id = input2.parent_id;
12739
12823
  try {
12740
- const path52 = `/concepts/${encodeURIComponent(input2.id)}/versions`;
12741
- return projectCreatedConceptVersionResponse(await client.post(path52, body));
12824
+ const path53 = `/concepts/${encodeURIComponent(input2.id)}/versions`;
12825
+ return projectCreatedConceptVersionResponse(await client.post(path53, body));
12742
12826
  } catch (error) {
12743
12827
  rethrowToolRequestError("memora_create_concept_version", error);
12744
12828
  }
@@ -12773,7 +12857,7 @@ function handleBindingStatus(binding, options = {}) {
12773
12857
  // src/tools/handlers/finishSetup.ts
12774
12858
  var DEFAULT_POLL_INTERVAL_MS = 1e3;
12775
12859
  function delay2(ms) {
12776
- return new Promise((resolve46) => setTimeout(resolve46, ms));
12860
+ return new Promise((resolve47) => setTimeout(resolve47, ms));
12777
12861
  }
12778
12862
  async function pollPairingSessionInBackground(session, pollIntervalMs, onReady) {
12779
12863
  for (; ; ) {
@@ -13066,8 +13150,8 @@ function registerToolWithWorklog(server, runtime, sessionContext, toolName, desc
13066
13150
  function createMemoraMcpServer(opts = {}) {
13067
13151
  let bindingReadyResolve = null;
13068
13152
  let bindingReadyReject = null;
13069
- const bindingReady = new Promise((resolve46, reject) => {
13070
- bindingReadyResolve = resolve46;
13153
+ const bindingReady = new Promise((resolve47, reject) => {
13154
+ bindingReadyResolve = resolve47;
13071
13155
  bindingReadyReject = reject;
13072
13156
  });
13073
13157
  const devMode = Boolean(config2.devMode);
@@ -13582,8 +13666,8 @@ function sendJsonRpcHttpError(res, status, message) {
13582
13666
  });
13583
13667
  res.end(body);
13584
13668
  }
13585
- function sendUnauthorizedChallenge(res, env2) {
13586
- const challenge = buildGatewayMcpWwwAuthenticate(env2);
13669
+ function sendUnauthorizedChallenge(res, env2, workspaceRoot) {
13670
+ const challenge = buildGatewayMcpWwwAuthenticate(env2, workspaceRoot);
13587
13671
  const body = "Unauthorized";
13588
13672
  res.writeHead(401, {
13589
13673
  "www-authenticate": challenge,
@@ -13737,7 +13821,7 @@ async function handleAuthenticatedGatewayMcpRequest(req, res, auth, options = {}
13737
13821
  const binding = await loadGatewayHttpBinding(workspaceRoot, options);
13738
13822
  if (!binding) {
13739
13823
  await closeGatewayMcpSessionsForWorkspace(workspaceRoot);
13740
- sendUnauthorizedChallenge(res, env2);
13824
+ sendUnauthorizedChallenge(res, env2, workspaceRoot);
13741
13825
  return;
13742
13826
  }
13743
13827
  const sessionHeader = req.headers["mcp-session-id"];
@@ -13753,7 +13837,7 @@ async function handleAuthenticatedGatewayMcpRequest(req, res, auth, options = {}
13753
13837
  return;
13754
13838
  }
13755
13839
  if (!await ensureBindingStillValid(existing, options)) {
13756
- sendUnauthorizedChallenge(res, env2);
13840
+ sendUnauthorizedChallenge(res, env2, workspaceRoot);
13757
13841
  return;
13758
13842
  }
13759
13843
  if (method === "POST") {
@@ -13810,8 +13894,8 @@ function extractBearerToken(authorizationHeader) {
13810
13894
  const token = match[1];
13811
13895
  return token || null;
13812
13896
  }
13813
- function sendUnauthorized(res, env2) {
13814
- const challenge = buildGatewayMcpWwwAuthenticate(env2);
13897
+ function sendUnauthorized(res, env2, workspaceRoot) {
13898
+ const challenge = buildGatewayMcpWwwAuthenticate(env2, workspaceRoot);
13815
13899
  const body = "Unauthorized";
13816
13900
  res.writeHead(401, {
13817
13901
  "www-authenticate": challenge,
@@ -13821,6 +13905,14 @@ function sendUnauthorized(res, env2) {
13821
13905
  });
13822
13906
  res.end(body);
13823
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
+ }
13824
13916
  async function handleGatewayMcpAuthRequest(req, res, url, options = {}) {
13825
13917
  if (url.pathname !== GATEWAY_MCP_PATH) return false;
13826
13918
  const env2 = options.env ?? process.env;
@@ -13830,6 +13922,15 @@ async function handleGatewayMcpAuthRequest(req, res, url, options = {}) {
13830
13922
  env: options.env ?? options.ticketOptions?.env ?? env2,
13831
13923
  ...options.ticketOptions
13832
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
+ };
13833
13934
  const parsed2 = await parseGatewayWorkspaceUrl(url);
13834
13935
  if (!parsed2.ok) {
13835
13936
  sendUnauthorized(res, env2);
@@ -13838,34 +13939,224 @@ async function handleGatewayMcpAuthRequest(req, res, url, options = {}) {
13838
13939
  const canonicalUrl = buildGatewayMcpUrl(parsed2.workspaceRoot, env2);
13839
13940
  const token = extractBearerToken(req.headers.authorization);
13840
13941
  if (!token) {
13841
- sendUnauthorized(res, env2);
13942
+ sendUnauthorized(res, env2, parsed2.workspaceRoot);
13842
13943
  return true;
13843
13944
  }
13844
- const verified = await verifyGatewaySetupTicket(
13845
- token,
13846
- { canonicalUrl },
13847
- ticketOptions
13848
- );
13849
- if (!verified.ok) {
13850
- sendUnauthorized(res, env2);
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
+ );
13851
13964
  return true;
13852
13965
  }
13853
- await handleAuthenticatedGatewayMcpRequest(
13854
- req,
13855
- res,
13856
- {
13857
- workspaceRoot: verified.workspaceRoot,
13858
- canonicalUrl: verified.canonicalUrl
13859
- },
13860
- {
13861
- env: env2,
13862
- homeDir: options.homeDir ?? ticketOptions.homeDir,
13863
- identityDeps: options.identityDeps ?? ticketOptions.identityDeps,
13864
- ticketOptions,
13865
- createMemoraClient: options.createMemoraClient
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;
13866
13971
  }
13867
- );
13868
- return true;
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 {
14028
+ sendUnauthorized(res, env2, parsed2.workspaceRoot);
14029
+ return true;
14030
+ }
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;
14104
+ }
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
+ };
13869
14160
  }
13870
14161
 
13871
14162
  // src/gateway.ts
@@ -13875,13 +14166,13 @@ var log3 = (msg) => {
13875
14166
  };
13876
14167
  function writePidFile(pid) {
13877
14168
  const dir = ensureBaseDir();
13878
- const pidPath = path51.join(dir, "gateway.pid");
14169
+ const pidPath = path52.join(dir, "gateway.pid");
13879
14170
  fs38.writeFileSync(pidPath, `${pid}
13880
14171
  `, { encoding: "utf8", mode: 384 });
13881
14172
  return pidPath;
13882
14173
  }
13883
14174
  function removePidFile() {
13884
- const pidPath = path51.join(getMcpBaseDir(), "gateway.pid");
14175
+ const pidPath = path52.join(getMcpBaseDir(), "gateway.pid");
13885
14176
  try {
13886
14177
  if (fs38.existsSync(pidPath)) {
13887
14178
  fs38.unlinkSync(pidPath);
@@ -13902,6 +14193,13 @@ async function probeExistingGatewayHealthy(env2 = process.env) {
13902
14193
  }
13903
14194
  function createGatewayRequestHandler(options = {}) {
13904
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
+ });
13905
14203
  return (req, res) => {
13906
14204
  void (async () => {
13907
14205
  try {
@@ -13926,7 +14224,10 @@ function createGatewayRequestHandler(options = {}) {
13926
14224
  homeDir: options.homeDir,
13927
14225
  identityDeps: options.identityDeps,
13928
14226
  ticketOptions: options.ticketOptions,
13929
- createMemoraClient: options.createMemoraClient
14227
+ createMemoraClient: options.createMemoraClient,
14228
+ apiUrl,
14229
+ fetchImpl: options.fetchImpl,
14230
+ unboundPairing
13930
14231
  });
13931
14232
  if (mcpHandled) return;
13932
14233
  res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
@@ -13964,7 +14265,7 @@ async function runGateway(env2 = process.env, options = {}) {
13964
14265
  process.on("SIGINT", () => shutdownNow("SIGINT"));
13965
14266
  process.on("SIGTERM", () => shutdownNow("SIGTERM"));
13966
14267
  process.on("exit", removePidFile);
13967
- await new Promise((resolve46, reject) => {
14268
+ await new Promise((resolve47, reject) => {
13968
14269
  server.once("error", async (err) => {
13969
14270
  if (err.code === "EADDRINUSE") {
13970
14271
  const healthy = await probeExistingGatewayHealthy(env2);
@@ -13991,7 +14292,7 @@ async function runGateway(env2 = process.env, options = {}) {
13991
14292
  }
13992
14293
  const pidPath = writePidFile(process.pid);
13993
14294
  log3(`gateway started pid=${process.pid} listening on http://${host}:${port} pidFile=${pidPath}`);
13994
- resolve46();
14295
+ resolve47();
13995
14296
  });
13996
14297
  });
13997
14298
  }