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