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