@memoraone/mcp 0.1.17 → 0.1.18

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/index.cjs CHANGED
@@ -5,6 +5,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
8
12
  var __copyProps = (to, from, except, desc) => {
9
13
  if (from && typeof from === "object" || typeof from === "function") {
10
14
  for (let key of __getOwnPropNames(from))
@@ -21,26 +25,40 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
25
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
26
  mod
23
27
  ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
24
29
 
25
30
  // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ main: () => main
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+ var path6 = __toESM(require("path"), 1);
37
+ var crypto5 = __toESM(require("crypto"), 1);
38
+ var import_node_url2 = require("url");
26
39
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
40
+ var import_types = require("@modelcontextprotocol/sdk/types.js");
27
41
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
28
42
 
29
43
  // src/config.ts
30
44
  var process2 = __toESM(require("process"), 1);
31
45
  var fs = __toESM(require("fs"), 1);
32
46
  var path = __toESM(require("path"), 1);
33
- var import_dotenv = __toESM(require("dotenv"), 1);
47
+ var dotenv = __toESM(require("dotenv"), 1);
34
48
  var import_v4 = require("zod/v4");
35
49
 
36
50
  // src/configUtils.ts
37
- var DEFAULT_API_URL = "https://api.memoraone.com";
51
+ var DEFAULT_API_URL = "http://localhost:3001";
38
52
  var DEV_API_URL = "http://localhost:3001";
39
53
  function resolveApiUrl(env2) {
40
54
  const explicitUrl = env2.MEMORAONE_API_URL?.trim();
41
55
  if (explicitUrl) {
42
56
  return explicitUrl;
43
57
  }
58
+ const aliasUrl = env2.MEMORA_API_URL?.trim();
59
+ if (aliasUrl) {
60
+ return aliasUrl;
61
+ }
44
62
  if (env2.MEMORAONE_DEV_MODE === "1") {
45
63
  return DEV_API_URL;
46
64
  }
@@ -51,23 +69,24 @@ function resolveApiUrl(env2) {
51
69
  var dotenvPath = path.resolve(process2.cwd(), ".env");
52
70
  if (fs.existsSync(dotenvPath)) {
53
71
  try {
54
- import_dotenv.default.config({ path: dotenvPath });
72
+ dotenv.config({ path: dotenvPath });
55
73
  } catch (err) {
56
74
  process2.stderr.write("[memoraone-mcp] Failed to load .env: " + String(err) + "\n");
57
75
  }
58
76
  }
59
77
  var EnvSchema = import_v4.z.object({
60
78
  MEMORAONE_API_URL: import_v4.z.string().url().optional(),
61
- MEMORAONE_API_KEY: import_v4.z.string().min(1),
79
+ MEMORAONE_API_KEY: import_v4.z.string().min(1).optional(),
62
80
  MEMORAONE_DEV_MODE: import_v4.z.string().min(1).optional(),
63
81
  MEMORAONE_AGENT_NAME: import_v4.z.string().min(1).optional(),
64
82
  MEMORAONE_AGENT_TYPE: import_v4.z.string().min(1).optional(),
65
83
  MEMORAONE_SOURCE: import_v4.z.string().min(1).optional(),
84
+ MEMORAONE_IDE_TYPE: import_v4.z.enum(["cursor", "copilot-vscode", "jetbrains"]).optional(),
66
85
  MEMORAONE_WORKLOG: import_v4.z.string().min(1).optional(),
67
86
  MEMORAONE_HEARTBEAT: import_v4.z.string().min(1).optional(),
68
87
  MEMORAONE_HEARTBEAT_INTERVAL_MS: import_v4.z.string().min(1).optional()
69
88
  });
70
- var requiredEnvVars = ["MEMORAONE_API_KEY"];
89
+ var requiredEnvVars = [];
71
90
  var missingEnvVars = requiredEnvVars.filter((key) => {
72
91
  const value = process2.env[key];
73
92
  return value === void 0 || value.trim() === "";
@@ -101,12 +120,13 @@ var parseBooleanFlag = (value, defaultValue) => {
101
120
  }
102
121
  return defaultValue;
103
122
  };
104
- var config = {
123
+ var config2 = {
105
124
  apiUrl: resolvedApiUrl.replace(/\/+$/, ""),
106
125
  apiKey: parsed.data.MEMORAONE_API_KEY,
107
126
  agentName: parsed.data.MEMORAONE_AGENT_NAME ?? "cursor",
108
127
  agentType: parsed.data.MEMORAONE_AGENT_TYPE ?? "agent",
109
128
  source: parsed.data.MEMORAONE_SOURCE ?? "cursor",
129
+ ideType: parsed.data.MEMORAONE_IDE_TYPE,
110
130
  devMode: parseBooleanFlag(parsed.data.MEMORAONE_DEV_MODE, false),
111
131
  worklogEnabled: parseBooleanFlag(parsed.data.MEMORAONE_WORKLOG, true),
112
132
  heartbeatEnabled: parseBooleanFlag(parsed.data.MEMORAONE_HEARTBEAT, true),
@@ -114,32 +134,9 @@ var config = {
114
134
  };
115
135
 
116
136
  // src/client/memoraClient.ts
117
- var crypto2 = __toESM(require("crypto"), 1);
118
-
119
- // src/runContext.ts
120
137
  var crypto = __toESM(require("crypto"), 1);
121
- var currentRunId = null;
122
- var currentProjectId = null;
123
- function setCurrentRunId(id) {
124
- currentRunId = id;
125
- }
126
- function getCurrentProjectId() {
127
- return currentProjectId;
128
- }
129
- function setCurrentProjectId(id) {
130
- currentProjectId = id;
131
- }
132
- function resolveRunId(passed) {
133
- if (passed) {
134
- return passed;
135
- }
136
- return currentRunId;
137
- }
138
- function generateRunId() {
139
- return crypto.randomBytes(16).toString("hex");
140
- }
141
-
142
- // src/client/memoraClient.ts
138
+ var PROJECT_ID_HEADER = "x-project-id";
139
+ var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
143
140
  var parseBooleanFlag2 = (value) => {
144
141
  if (!value) {
145
142
  return false;
@@ -171,36 +168,48 @@ var MemoraOneHttpError = class extends Error {
171
168
  }
172
169
  };
173
170
  var MemoraClient = class {
174
- constructor(cfg, projectKey) {
175
- if (!projectKey?.trim()) {
176
- throw new Error("[memoraone-mcp] Invalid projectKey for MemoraClient");
171
+ constructor(cfg, projectId, apiKey) {
172
+ if (!uuidRegex.test(projectId)) {
173
+ throw new Error("[memoraone-mcp] Invalid project_id for MemoraClient");
177
174
  }
178
175
  this.baseUrl = cfg.apiUrl;
179
- this.apiKey = cfg.apiKey;
180
- this.projectKey = projectKey;
181
- }
182
- resolveProjectKey() {
183
- const selected = getCurrentProjectId();
184
- const projectKey = (selected ?? this.projectKey)?.trim();
185
- if (!projectKey) {
186
- throw new Error("Missing projectKey: select a project first");
176
+ this.apiKey = apiKey;
177
+ this.projectId = projectId;
178
+ }
179
+ resolveProjectId() {
180
+ const projectId = this.projectId?.trim();
181
+ if (!projectId) {
182
+ throw new Error(`Missing ${PROJECT_ID_HEADER}: select a project first`);
183
+ }
184
+ if (!uuidRegex.test(projectId)) {
185
+ throw new Error("[memoraone-mcp] Invalid project_id for request");
186
+ }
187
+ return projectId;
188
+ }
189
+ resolveApiKey() {
190
+ const key = this.apiKey?.trim();
191
+ if (!key) {
192
+ throw new Error("[memoraone-mcp] Missing api_key for request");
187
193
  }
188
- return projectKey;
194
+ return key;
189
195
  }
190
196
  buildHeaders(options) {
197
+ const projectId = this.resolveProjectId();
198
+ const apiKey = this.resolveApiKey();
191
199
  return {
192
200
  "content-type": "application/json",
193
- "x-api-key": this.apiKey,
201
+ "x-api-key": apiKey,
202
+ [PROJECT_ID_HEADER]: projectId,
194
203
  ...options?.headers ?? {}
195
204
  };
196
205
  }
197
- async post(path5, body, options) {
206
+ async post(path7, body, options) {
198
207
  console.error(
199
- `[memoraone-mcp][info] MemoraClient.post ENTER path=${path5}`
208
+ `[memoraone-mcp][info] MemoraClient.post ENTER path=${path7}`
200
209
  );
201
- const nonce = crypto2.randomBytes(8).toString("hex");
202
- const url = `${this.baseUrl}${path5.startsWith("/") ? path5 : `/${path5}`}`;
203
- this.resolveProjectKey();
210
+ const nonce = crypto.randomBytes(8).toString("hex");
211
+ const url = `${this.baseUrl}${path7.startsWith("/") ? path7 : `/${path7}`}`;
212
+ this.resolveProjectId();
204
213
  console.error(
205
214
  `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_fetch method=POST url=${url}`
206
215
  );
@@ -225,14 +234,14 @@ var MemoraClient = class {
225
234
  throw new MemoraOneHttpError(res.status, res.statusText, res.text);
226
235
  }
227
236
  console.error(
228
- `[memoraone-mcp][info] MemoraClient.post EXIT path=${path5}`
237
+ `[memoraone-mcp][info] MemoraClient.post EXIT path=${path7}`
229
238
  );
230
239
  return res.text ? JSON.parse(res.text) : null;
231
240
  }
232
- async get(path5, options) {
233
- const nonce = crypto2.randomBytes(8).toString("hex");
234
- const url = `${this.baseUrl}${path5.startsWith("/") ? path5 : `/${path5}`}`;
235
- this.resolveProjectKey();
241
+ async get(path7, options) {
242
+ const nonce = crypto.randomBytes(8).toString("hex");
243
+ const url = `${this.baseUrl}${path7.startsWith("/") ? path7 : `/${path7}`}`;
244
+ this.resolveProjectId();
236
245
  console.error(
237
246
  `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_fetch method=GET url=${url}`
238
247
  );
@@ -261,63 +270,57 @@ var MemoraClient = class {
261
270
  };
262
271
  var memoraClient_default = MemoraClient;
263
272
 
264
- // src/repoFingerprint.ts
265
- var fs2 = __toESM(require("fs"), 1);
273
+ // src/projectBinding.ts
274
+ var fs2 = __toESM(require("fs/promises"), 1);
266
275
  var path2 = __toESM(require("path"), 1);
267
- var crypto3 = __toESM(require("crypto"), 1);
268
- var parseBooleanFlag3 = (value) => {
269
- if (!value) {
270
- return false;
276
+ var uuidRegex2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
277
+ function parseAndValidateM1(content, markerPath) {
278
+ let parsed2;
279
+ try {
280
+ parsed2 = JSON.parse(content);
281
+ } catch {
282
+ throw new Error(`[memoraone-mcp] Invalid memoraone.m1 JSON at ${markerPath}`);
271
283
  }
272
- const normalized = value.trim().toLowerCase();
273
- return ["1", "true", "yes", "on"].includes(normalized);
274
- };
275
- var debugEnabled2 = parseBooleanFlag3(process.env.MEMORAONE_DEV_MODE);
276
- var debugLog = (message) => {
277
- if (!debugEnabled2) {
278
- return;
284
+ const projectId = parsed2?.projectId ?? parsed2?.project_id;
285
+ if (!projectId || typeof projectId !== "string") {
286
+ throw new Error(`[memoraone-mcp] memoraone.m1 missing projectId at ${markerPath}`);
279
287
  }
280
- process.stderr.write(`[memoraone-mcp][debug] ${message}
281
- `);
282
- };
283
- var normalizeRemoteUrl = (remoteUrl) => {
284
- let normalized = remoteUrl.trim();
285
- normalized = normalized.replace(/^[a-z]+:\/\//i, "");
286
- normalized = normalized.replace(/^git@([^:]+):/i, "$1/");
287
- normalized = normalized.replace(/\.git$/i, "");
288
- normalized = normalized.replace(/\/+$/, "");
289
- return normalized.toLowerCase();
290
- };
291
- var sha256 = (value) => {
292
- return crypto3.createHash("sha256").update(value).digest("hex");
293
- };
294
- var resolveGitDir = (gitPath) => {
288
+ if (!uuidRegex2.test(projectId.trim())) {
289
+ throw new Error(`[memoraone-mcp] memoraone.m1 projectId is not a UUID at ${markerPath}`);
290
+ }
291
+ const apiKeyRaw = parsed2?.MEMORAONE_API_KEY ?? parsed2?.api_key;
292
+ const apiKey = apiKeyRaw !== void 0 && apiKeyRaw !== null && typeof apiKeyRaw === "string" && apiKeyRaw.trim() !== "" ? apiKeyRaw.trim() : null;
293
+ return { projectId: projectId.trim(), apiKey };
294
+ }
295
+ async function resolveProjectIdFromExplicitM1Path() {
296
+ const raw = process.env.MEMORAONE_M1_PATH;
297
+ if (raw === void 0 || raw.trim() === "") {
298
+ return null;
299
+ }
300
+ const markerPath = path2.resolve(raw);
295
301
  try {
296
- const stat = fs2.statSync(gitPath);
297
- if (stat.isDirectory()) {
298
- return gitPath;
299
- }
300
- if (stat.isFile()) {
301
- const content = fs2.readFileSync(gitPath, "utf8");
302
- const match = content.match(/^gitdir:\s*(.+)$/m);
303
- if (match) {
304
- const gitDir = match[1].trim();
305
- return path2.resolve(path2.dirname(gitPath), gitDir);
306
- }
302
+ const content = await fs2.readFile(markerPath, "utf8");
303
+ const { projectId, apiKey } = parseAndValidateM1(content, markerPath);
304
+ return { projectId, apiKey, foundAt: markerPath };
305
+ } catch (err) {
306
+ if (err?.code === "ENOENT") {
307
+ return null;
307
308
  }
308
- } catch {
309
- return null;
309
+ throw err;
310
310
  }
311
- return null;
312
- };
313
- var findGitRoot = (start) => {
314
- let current = path2.resolve(start);
311
+ }
312
+ async function findM1WalkingUp(workspaceRoot) {
313
+ let current = path2.resolve(workspaceRoot);
315
314
  while (true) {
316
- const gitPath = path2.join(current, ".git");
317
- if (fs2.existsSync(gitPath)) {
318
- const gitDir = resolveGitDir(gitPath);
319
- if (gitDir) {
320
- return { gitRoot: current, gitDir };
315
+ const markerPath = path2.join(current, "memoraone.m1");
316
+ try {
317
+ const content = await fs2.readFile(markerPath, "utf8");
318
+ const { projectId, apiKey } = parseAndValidateM1(content, markerPath);
319
+ const repoRoot = path2.dirname(markerPath);
320
+ return { projectId, apiKey, repoRoot, markerPath };
321
+ } catch (err) {
322
+ if (err?.code !== "ENOENT") {
323
+ throw err;
321
324
  }
322
325
  }
323
326
  const parent = path2.dirname(current);
@@ -327,264 +330,118 @@ var findGitRoot = (start) => {
327
330
  current = parent;
328
331
  }
329
332
  return null;
330
- };
331
- var readOriginRemote = (gitDir) => {
332
- const configPath = path2.join(gitDir, "config");
333
- try {
334
- const content = fs2.readFileSync(configPath, "utf8");
335
- const lines = content.split(/\r?\n/);
336
- let inOrigin = false;
337
- for (const line of lines) {
338
- const sectionMatch = line.match(/^\s*\[(.+)]\s*$/);
339
- if (sectionMatch) {
340
- inOrigin = sectionMatch[1].trim() === 'remote "origin"';
341
- continue;
342
- }
343
- if (inOrigin) {
344
- const urlMatch = line.match(/^\s*url\s*=\s*(.+)\s*$/);
345
- if (urlMatch) {
346
- return urlMatch[1].trim();
347
- }
348
- }
349
- }
350
- } catch {
351
- return null;
352
- }
353
- return null;
354
- };
355
- function resolveRepoFingerprint(cwd2) {
356
- const found = findGitRoot(cwd2);
357
- if (!found) {
358
- const fallbackPath = path2.resolve(cwd2);
359
- const fingerprint2 = sha256(fallbackPath);
360
- debugLog(`repo fingerprint=${fingerprint2} source=path-fallback`);
361
- return {
362
- fingerprint: fingerprint2,
363
- gitRoot: fallbackPath,
364
- source: "path-fallback"
365
- };
333
+ }
334
+ function normalizeWorkspaceSearchRoots(workspaceRoot) {
335
+ if (workspaceRoot === void 0) {
336
+ return [];
366
337
  }
367
- const { gitRoot, gitDir } = found;
368
- const remoteUrl = readOriginRemote(gitDir);
369
- if (remoteUrl) {
370
- const normalized = normalizeRemoteUrl(remoteUrl);
371
- const fingerprint2 = sha256(normalized);
372
- debugLog(`repo fingerprint=${fingerprint2} source=git-remote`);
373
- return {
374
- fingerprint: fingerprint2,
375
- gitRoot,
376
- remoteUrl,
377
- source: "git-remote"
378
- };
338
+ const list = Array.isArray(workspaceRoot) ? workspaceRoot : [workspaceRoot];
339
+ const seen = /* @__PURE__ */ new Set();
340
+ const out = [];
341
+ for (const raw of list) {
342
+ if (raw === void 0) {
343
+ continue;
344
+ }
345
+ const trimmed = String(raw).trim();
346
+ if (trimmed === "") {
347
+ continue;
348
+ }
349
+ const resolved = path2.resolve(trimmed);
350
+ if (!seen.has(resolved)) {
351
+ seen.add(resolved);
352
+ out.push(resolved);
353
+ }
379
354
  }
380
- const fingerprint = sha256(path2.resolve(gitRoot));
381
- debugLog(`repo fingerprint=${fingerprint} source=path-fallback`);
382
- return {
383
- fingerprint,
384
- gitRoot,
385
- source: "path-fallback"
386
- };
355
+ return out;
387
356
  }
388
-
389
- // src/workspaceMap.ts
390
- var fs3 = __toESM(require("fs/promises"), 1);
391
- var path3 = __toESM(require("path"), 1);
392
- var import_node_os = __toESM(require("os"), 1);
393
- var parseBooleanFlag4 = (value) => {
394
- if (!value) {
395
- return false;
357
+ function resolveApiKeyWithSource(fileApiKey) {
358
+ const envApiKey = process.env.MEMORAONE_API_KEY?.trim();
359
+ if (envApiKey) {
360
+ return { apiKey: envApiKey, apiKeySource: "env" };
396
361
  }
397
- const normalized = value.trim().toLowerCase();
398
- return ["1", "true", "yes", "on"].includes(normalized);
399
- };
400
- var debugEnabled3 = parseBooleanFlag4(process.env.MEMORAONE_DEV_MODE);
401
- var debugLog2 = (message) => {
402
- if (!debugEnabled3) {
403
- return;
362
+ const aliasEnvApiKey = process.env.MEMORA_API_KEY?.trim();
363
+ if (aliasEnvApiKey) {
364
+ return { apiKey: aliasEnvApiKey, apiKeySource: "env" };
404
365
  }
405
- process.stderr.write(`[memoraone-mcp][debug] ${message}
406
- `);
407
- };
408
- var fingerprintRegex = /^[0-9a-f]{64}$/i;
409
- function getWorkspaceMapPath() {
410
- return path3.join(import_node_os.default.homedir(), ".memoraone", "workspaces.json");
366
+ if (fileApiKey) {
367
+ return { apiKey: fileApiKey, apiKeySource: "memoraone.m1" };
368
+ }
369
+ return { apiKey: null, apiKeySource: "none" };
411
370
  }
412
- var ensureWorkspaceDir = async () => {
413
- const dir = path3.dirname(getWorkspaceMapPath());
414
- await fs3.mkdir(dir, { recursive: true });
415
- };
416
- var validateWorkspaceMap = (map, filePath) => {
417
- if (!map || typeof map !== "object" || Array.isArray(map)) {
418
- throw new Error(
419
- `[memoraone-mcp] Invalid workspace map schema in ${filePath}`
420
- );
371
+ async function resolveAuthoritativeBinding(workspaceRoot) {
372
+ const explicitBinding = await resolveProjectIdFromExplicitM1Path();
373
+ if (explicitBinding) {
374
+ const resolved = resolveApiKeyWithSource(explicitBinding.apiKey);
375
+ return {
376
+ projectId: explicitBinding.projectId,
377
+ workspaceRoot: path2.dirname(explicitBinding.foundAt),
378
+ m1Path: explicitBinding.foundAt,
379
+ apiKey: resolved.apiKey,
380
+ bindingSource: "explicit-m1-path",
381
+ apiKeySource: resolved.apiKeySource
382
+ };
421
383
  }
422
- for (const [fingerprint, entry] of Object.entries(map)) {
423
- if (!fingerprintRegex.test(fingerprint)) {
424
- throw new Error(
425
- `[memoraone-mcp] Invalid workspace fingerprint in ${filePath}`
426
- );
427
- }
428
- if (typeof entry === "string") {
429
- if (!entry.trim()) {
430
- throw new Error(
431
- `[memoraone-mcp] Invalid workspace projectKey in ${filePath}`
432
- );
433
- }
434
- continue;
435
- }
436
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
437
- throw new Error(
438
- `[memoraone-mcp] Invalid workspace projectKey in ${filePath}`
439
- );
440
- }
441
- const projectKey = entry.projectKey ?? entry.project_id;
442
- if (!projectKey || !projectKey.trim()) {
443
- throw new Error(
444
- `[memoraone-mcp] Invalid workspace projectKey in ${filePath}`
445
- );
446
- }
447
- const source = entry.source;
448
- if (source !== void 0 && typeof source !== "string") {
449
- throw new Error(
450
- `[memoraone-mcp] Invalid workspace source in ${filePath}`
451
- );
452
- }
453
- const linkedAt = entry.linked_at;
454
- if (linkedAt !== void 0 && typeof linkedAt !== "string") {
455
- throw new Error(
456
- `[memoraone-mcp] Invalid workspace linked_at in ${filePath}`
457
- );
384
+ const candidates = normalizeWorkspaceSearchRoots(workspaceRoot);
385
+ if (candidates.length === 0) {
386
+ throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
387
+ }
388
+ for (const root of candidates) {
389
+ const binding = await findM1WalkingUp(root);
390
+ if (binding) {
391
+ const resolved = resolveApiKeyWithSource(binding.apiKey);
392
+ return {
393
+ projectId: binding.projectId,
394
+ workspaceRoot: binding.repoRoot,
395
+ m1Path: binding.markerPath,
396
+ apiKey: resolved.apiKey,
397
+ bindingSource: "workspace-search",
398
+ apiKeySource: resolved.apiKeySource
399
+ };
458
400
  }
459
401
  }
460
- };
461
- async function readWorkspaceMap() {
462
- const filePath = getWorkspaceMapPath();
402
+ throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
403
+ }
404
+
405
+ // src/sourceRegistration.ts
406
+ var path3 = __toESM(require("path"), 1);
407
+ var import_node_url = require("url");
408
+ async function registerRepoSource(client, projectId, repoPath, ideType) {
409
+ const normalizedRepoPath = path3.resolve(repoPath);
410
+ const endpointPath = `/v1/projects/${projectId}/sources`;
411
+ console.error(
412
+ `[memoraone-mcp DEBUG registerRepoSource] registerRepoSource() called projectId=${projectId} repoPath(raw)=${JSON.stringify(repoPath)} repoPath(normalized)=${JSON.stringify(normalizedRepoPath)} ideType=${ideType ?? "(none)"}`
413
+ );
463
414
  try {
464
- const content = await fs3.readFile(filePath, "utf8");
465
- const parsed2 = JSON.parse(content);
466
- validateWorkspaceMap(parsed2, filePath);
467
- const typed = parsed2;
468
- let migrated = false;
469
- const normalized = {};
470
- for (const [fingerprint, entry] of Object.entries(typed)) {
471
- if (typeof entry === "string") {
472
- normalized[fingerprint] = entry;
473
- continue;
474
- }
475
- const projectKey = entry.projectKey ?? entry.project_id ?? "";
476
- if (entry.project_id && !entry.projectKey) {
477
- migrated = true;
478
- }
479
- normalized[fingerprint] = {
480
- ...projectKey ? { projectKey } : {},
481
- ...entry.source ? { source: entry.source } : {},
482
- ...entry.linked_at ? { linked_at: entry.linked_at } : {}
415
+ const body = {
416
+ kind: "repo",
417
+ label: path3.basename(normalizedRepoPath),
418
+ uri: (0, import_node_url.pathToFileURL)(normalizedRepoPath).href
419
+ };
420
+ if (ideType) {
421
+ body.metadata = {
422
+ ide_type: ideType
483
423
  };
484
424
  }
485
- if (migrated) {
486
- await writeWorkspaceMap(normalized);
487
- }
488
- debugLog2(
489
- `workspace map loaded path=${filePath} entries=${Object.keys(normalized).length}`
425
+ console.error(
426
+ `[memoraone-mcp DEBUG registerRepoSource] POST endpoint=${endpointPath} body=${JSON.stringify(body)}`
427
+ );
428
+ const result = await client.post(endpointPath, body);
429
+ console.error(
430
+ `[memoraone-mcp DEBUG registerRepoSource] POST succeeded endpoint=${endpointPath} responseSummary=${JSON.stringify(result)}`
490
431
  );
491
- return normalized;
492
432
  } catch (err) {
493
- if (err?.code === "ENOENT") {
494
- const emptyMap = {};
495
- debugLog2(`workspace map loaded path=${filePath} entries=0`);
496
- return emptyMap;
497
- }
498
- if (err instanceof SyntaxError) {
499
- throw new Error(
500
- `[memoraone-mcp] Failed to parse workspace map at ${filePath}`
433
+ console.error(
434
+ `[memoraone-mcp DEBUG registerRepoSource] POST threw endpoint=${endpointPath} message=${String(err?.message ?? err)}`
435
+ );
436
+ if (err instanceof MemoraOneHttpError) {
437
+ const bodyStr = typeof err.body === "string" ? err.body : JSON.stringify(err.body ?? null);
438
+ console.error(
439
+ `[memoraone-mcp DEBUG registerRepoSource] MemoraOneHttpError status=${err.status} body=${bodyStr}`
501
440
  );
502
441
  }
503
- throw err;
442
+ console.warn("[memoraone-mcp] registerRepoSource failed:", err);
504
443
  }
505
444
  }
506
- async function writeWorkspaceMap(map) {
507
- const filePath = getWorkspaceMapPath();
508
- validateWorkspaceMap(map, filePath);
509
- await ensureWorkspaceDir();
510
- const tempPath = `${filePath}.tmp`;
511
- const content = JSON.stringify(map, null, 2);
512
- await fs3.writeFile(tempPath, content, "utf8");
513
- await fs3.rename(tempPath, filePath);
514
- }
515
- async function getProjectIdForFingerprint(fingerprint) {
516
- if (!fingerprintRegex.test(fingerprint)) {
517
- throw new Error("[memoraone-mcp] Invalid fingerprint");
518
- }
519
- const map = await readWorkspaceMap();
520
- const entry = map[fingerprint];
521
- if (!entry) {
522
- return { projectKey: null, source: "unknown" };
523
- }
524
- if (typeof entry === "string") {
525
- return { projectKey: entry, source: "unknown" };
526
- }
527
- return {
528
- projectKey: entry.projectKey ?? entry.project_id ?? null,
529
- source: entry.source ?? "unknown"
530
- };
531
- }
532
- async function setProjectIdForFingerprint(args) {
533
- const { fingerprint, projectKey, source, linked_at } = args;
534
- if (!fingerprintRegex.test(fingerprint)) {
535
- throw new Error("[memoraone-mcp] Invalid fingerprint");
536
- }
537
- if (!projectKey.trim()) {
538
- throw new Error("[memoraone-mcp] Invalid projectKey");
539
- }
540
- const map = await readWorkspaceMap();
541
- map[fingerprint] = {
542
- projectKey,
543
- ...source ? { source } : {},
544
- linked_at: linked_at ?? (/* @__PURE__ */ new Date()).toISOString()
545
- };
546
- await writeWorkspaceMap(map);
547
- debugLog2(
548
- `workspace map set fingerprint=${fingerprint} projectKey=${projectKey}`
549
- );
550
- }
551
-
552
- // src/projectBinding.ts
553
- var fs4 = __toESM(require("fs/promises"), 1);
554
- var path4 = __toESM(require("path"), 1);
555
- function readRepoProjectIdPath(gitRoot) {
556
- return path4.join(gitRoot, ".memoraone-project");
557
- }
558
- async function readRepoProjectId(gitRoot) {
559
- const filePath = readRepoProjectIdPath(gitRoot);
560
- try {
561
- const content = await fs4.readFile(filePath, "utf8");
562
- const value = content.trim();
563
- return value ? value : null;
564
- } catch (err) {
565
- if (err?.code === "ENOENT") {
566
- return null;
567
- }
568
- throw err;
569
- }
570
- }
571
- function resolveProjectIdOrThrow(args) {
572
- const envProjectKey = args.envProjectKey?.trim();
573
- if (envProjectKey) {
574
- return { projectKey: envProjectKey, source: "env" };
575
- }
576
- const repoFileProjectKey = args.repoFileProjectKey?.trim();
577
- if (repoFileProjectKey) {
578
- return { projectKey: repoFileProjectKey, source: "repo-file" };
579
- }
580
- const workspaceProjectKey = args.workspaceProjectKey?.trim();
581
- if (workspaceProjectKey) {
582
- return { projectKey: workspaceProjectKey, source: "workspace-map" };
583
- }
584
- throw new Error(
585
- `Repo not linked to a MemoraOne project. Set MEMORAONE_PROJECT_ID or create ${args.repoFilePath} containing a project key.`
586
- );
587
- }
588
445
 
589
446
  // src/tools/postEvent.ts
590
447
  var import_v42 = require("zod/v4");
@@ -672,7 +529,67 @@ var setProjectShape = {
672
529
 
673
530
  // src/tools/handlers/postEvent.ts
674
531
  var import_v49 = require("zod/v4");
675
- var crypto4 = __toESM(require("crypto"), 1);
532
+ var crypto3 = __toESM(require("crypto"), 1);
533
+
534
+ // src/runContext.ts
535
+ var import_node_async_hooks = require("async_hooks");
536
+ var crypto2 = __toESM(require("crypto"), 1);
537
+ var sessionContextStorage = new import_node_async_hooks.AsyncLocalStorage();
538
+ function createSessionRunContext(initial = {}) {
539
+ return {
540
+ currentRunId: null,
541
+ currentProjectId: null,
542
+ currentApiKey: null,
543
+ boundProjectId: null,
544
+ boundApiKey: null,
545
+ ...initial
546
+ };
547
+ }
548
+ function runWithSessionContext(context, fn) {
549
+ return sessionContextStorage.run(context, fn);
550
+ }
551
+ function getSessionContext() {
552
+ const context = sessionContextStorage.getStore();
553
+ if (!context) {
554
+ throw new Error("[memoraone-mcp] Session context not initialized");
555
+ }
556
+ return context;
557
+ }
558
+ function getBoundProjectId() {
559
+ return getSessionContext().boundProjectId;
560
+ }
561
+ function setBoundProjectId(id) {
562
+ getSessionContext().boundProjectId = id;
563
+ }
564
+ function setBoundApiKey(key) {
565
+ getSessionContext().boundApiKey = key;
566
+ }
567
+ function getCurrentRunId() {
568
+ return getSessionContext().currentRunId;
569
+ }
570
+ function setCurrentRunId(id) {
571
+ getSessionContext().currentRunId = id;
572
+ }
573
+ function getCurrentProjectId() {
574
+ return getSessionContext().currentProjectId;
575
+ }
576
+ function setCurrentProjectId(id) {
577
+ getSessionContext().currentProjectId = id;
578
+ }
579
+ function setCurrentApiKey(key) {
580
+ getSessionContext().currentApiKey = key;
581
+ }
582
+ function resolveRunId(passed) {
583
+ if (passed) {
584
+ return passed;
585
+ }
586
+ return getCurrentRunId();
587
+ }
588
+ function generateRunId() {
589
+ return crypto2.randomBytes(16).toString("hex");
590
+ }
591
+
592
+ // src/tools/handlers/postEvent.ts
676
593
  var postEventInputSchema = import_v49.z.object({
677
594
  kind: import_v49.z.string().min(1),
678
595
  actor: import_v49.z.object({
@@ -683,7 +600,7 @@ var postEventInputSchema = import_v49.z.object({
683
600
  metadata: import_v49.z.record(import_v49.z.string(), import_v49.z.any()).optional()
684
601
  });
685
602
  async function handlePostEvent(client, args) {
686
- const nonce = crypto4.randomBytes(8).toString("hex");
603
+ const nonce = crypto3.randomBytes(8).toString("hex");
687
604
  console.error(
688
605
  `[memoraone-mcp][debug] tool=memora_post_event toolCallId=unknown nonce=${nonce} stage=before_post`
689
606
  );
@@ -700,7 +617,7 @@ async function handlePostEvent(client, args) {
700
617
  projectKey,
701
618
  actor: {
702
619
  type: "agent",
703
- identifier: config.agentName,
620
+ identifier: config2.agentName,
704
621
  ...parsed2.actor.id ? { id: parsed2.actor.id } : {}
705
622
  },
706
623
  ...parsed2.metadata ? { metadata: parsed2.metadata } : {}
@@ -789,13 +706,13 @@ async function handleLogIntent(client, args) {
789
706
  setCurrentRunId(run_id);
790
707
  const body = {
791
708
  kind: "note",
792
- actor: { type: config.agentType, name: config.agentName },
709
+ actor: { type: config2.agentType, name: config2.agentName },
793
710
  concept,
794
711
  // MUST be TOP-LEVEL so it populates timeline_events.concept
795
712
  message,
796
713
  projectKey,
797
714
  metadata: {
798
- source: config.source,
715
+ source: config2.source,
799
716
  purpose,
800
717
  // 'task' | 'decision'
801
718
  intent_source: intent_source ?? "cursor_chat",
@@ -833,11 +750,11 @@ async function handleLogChangeSummary(client, args) {
833
750
  const body = {
834
751
  kind: "note",
835
752
  concept: "concept:change_summary",
836
- actor: { type: config.agentType, name: config.agentName },
753
+ actor: { type: config2.agentType, name: config2.agentName },
837
754
  message,
838
755
  projectKey,
839
756
  metadata: {
840
- source: config.source,
757
+ source: config2.source,
841
758
  purpose: "change_summary",
842
759
  tool: "memora_log_change_summary",
843
760
  ...scope ? { scope } : {},
@@ -876,11 +793,11 @@ async function handleLogToolResult(client, args) {
876
793
  const body = {
877
794
  kind: "note",
878
795
  concept: "concept:tool_result",
879
- actor: { type: config.agentType, name: config.agentName },
796
+ actor: { type: config2.agentType, name: config2.agentName },
880
797
  message,
881
798
  projectKey,
882
799
  metadata: {
883
- source: config.source,
800
+ source: config2.source,
884
801
  purpose: "tool_result",
885
802
  tool: "memora_log_tool_result",
886
803
  tool_name: tool,
@@ -920,11 +837,11 @@ async function handleLogCommand(client, args) {
920
837
  const body = {
921
838
  kind: "note",
922
839
  concept: "concept:command",
923
- actor: { type: config.agentType, name: config.agentName },
840
+ actor: { type: config2.agentType, name: config2.agentName },
924
841
  message,
925
842
  projectKey,
926
843
  metadata: {
927
- source: config.source,
844
+ source: config2.source,
928
845
  purpose: "command",
929
846
  tool: "memora_log_command",
930
847
  cmd,
@@ -941,12 +858,339 @@ async function handleLogCommand(client, args) {
941
858
 
942
859
  // src/tools/handlers/listProjects.ts
943
860
  async function handleListProjects(client) {
944
- const res = await client.get("/admin/projects");
861
+ const res = await client.get("/v1/projects");
945
862
  return res ?? { items: [] };
946
863
  }
947
864
 
948
865
  // src/tools/handlers/setProject.ts
949
866
  var import_v415 = require("zod/v4");
867
+
868
+ // src/repoFingerprint.ts
869
+ var fs3 = __toESM(require("fs"), 1);
870
+ var path4 = __toESM(require("path"), 1);
871
+ var crypto4 = __toESM(require("crypto"), 1);
872
+ var parseBooleanFlag3 = (value) => {
873
+ if (!value) {
874
+ return false;
875
+ }
876
+ const normalized = value.trim().toLowerCase();
877
+ return ["1", "true", "yes", "on"].includes(normalized);
878
+ };
879
+ var debugEnabled2 = parseBooleanFlag3(process.env.MEMORAONE_DEV_MODE);
880
+ var debugLog = (message) => {
881
+ if (!debugEnabled2) {
882
+ return;
883
+ }
884
+ process.stderr.write(`[memoraone-mcp][debug] ${message}
885
+ `);
886
+ };
887
+ var normalizeRemoteUrl = (remoteUrl) => {
888
+ let normalized = remoteUrl.trim();
889
+ normalized = normalized.replace(/^[a-z]+:\/\//i, "");
890
+ normalized = normalized.replace(/^git@([^:]+):/i, "$1/");
891
+ normalized = normalized.replace(/\.git$/i, "");
892
+ normalized = normalized.replace(/\/+$/, "");
893
+ return normalized.toLowerCase();
894
+ };
895
+ var sha256 = (value) => {
896
+ return crypto4.createHash("sha256").update(value).digest("hex");
897
+ };
898
+ var resolveGitDir = (gitPath) => {
899
+ try {
900
+ const stat2 = fs3.statSync(gitPath);
901
+ if (stat2.isDirectory()) {
902
+ return gitPath;
903
+ }
904
+ if (stat2.isFile()) {
905
+ const content = fs3.readFileSync(gitPath, "utf8");
906
+ const match = content.match(/^gitdir:\s*(.+)$/m);
907
+ if (match) {
908
+ const gitDir = match[1].trim();
909
+ return path4.resolve(path4.dirname(gitPath), gitDir);
910
+ }
911
+ }
912
+ } catch {
913
+ return null;
914
+ }
915
+ return null;
916
+ };
917
+ var findGitRoot = (start) => {
918
+ let current = path4.resolve(start);
919
+ while (true) {
920
+ const gitPath = path4.join(current, ".git");
921
+ if (fs3.existsSync(gitPath)) {
922
+ const gitDir = resolveGitDir(gitPath);
923
+ if (gitDir) {
924
+ return { gitRoot: current, gitDir };
925
+ }
926
+ }
927
+ const parent = path4.dirname(current);
928
+ if (parent === current) {
929
+ break;
930
+ }
931
+ current = parent;
932
+ }
933
+ return null;
934
+ };
935
+ var readOriginRemote = (gitDir) => {
936
+ const configPath = path4.join(gitDir, "config");
937
+ try {
938
+ const content = fs3.readFileSync(configPath, "utf8");
939
+ const lines = content.split(/\r?\n/);
940
+ let inOrigin = false;
941
+ for (const line of lines) {
942
+ const sectionMatch = line.match(/^\s*\[(.+)]\s*$/);
943
+ if (sectionMatch) {
944
+ inOrigin = sectionMatch[1].trim() === 'remote "origin"';
945
+ continue;
946
+ }
947
+ if (inOrigin) {
948
+ const urlMatch = line.match(/^\s*url\s*=\s*(.+)\s*$/);
949
+ if (urlMatch) {
950
+ return urlMatch[1].trim();
951
+ }
952
+ }
953
+ }
954
+ } catch {
955
+ return null;
956
+ }
957
+ return null;
958
+ };
959
+ function resolveRepoFingerprint(cwd2) {
960
+ const found = findGitRoot(cwd2);
961
+ if (!found) {
962
+ const fallbackPath = path4.resolve(cwd2);
963
+ const fingerprint2 = sha256(fallbackPath);
964
+ debugLog(`repo fingerprint=${fingerprint2} source=path-fallback`);
965
+ return {
966
+ fingerprint: fingerprint2,
967
+ gitRoot: fallbackPath,
968
+ source: "path-fallback"
969
+ };
970
+ }
971
+ const { gitRoot, gitDir } = found;
972
+ const remoteUrl = readOriginRemote(gitDir);
973
+ if (remoteUrl) {
974
+ const normalized = normalizeRemoteUrl(remoteUrl);
975
+ const fingerprint2 = sha256(normalized);
976
+ debugLog(`repo fingerprint=${fingerprint2} source=git-remote`);
977
+ return {
978
+ fingerprint: fingerprint2,
979
+ gitRoot,
980
+ remoteUrl,
981
+ source: "git-remote"
982
+ };
983
+ }
984
+ const fingerprint = sha256(path4.resolve(gitRoot));
985
+ debugLog(`repo fingerprint=${fingerprint} source=path-fallback`);
986
+ return {
987
+ fingerprint,
988
+ gitRoot,
989
+ source: "path-fallback"
990
+ };
991
+ }
992
+
993
+ // src/workspaceMap.ts
994
+ var fs4 = __toESM(require("fs/promises"), 1);
995
+ var path5 = __toESM(require("path"), 1);
996
+ var import_node_os = __toESM(require("os"), 1);
997
+ var parseBooleanFlag4 = (value) => {
998
+ if (!value) {
999
+ return false;
1000
+ }
1001
+ const normalized = value.trim().toLowerCase();
1002
+ return ["1", "true", "yes", "on"].includes(normalized);
1003
+ };
1004
+ var debugEnabled3 = parseBooleanFlag4(process.env.MEMORAONE_DEV_MODE);
1005
+ var debugLog2 = (message) => {
1006
+ if (!debugEnabled3) {
1007
+ return;
1008
+ }
1009
+ process.stderr.write(`[memoraone-mcp][debug] ${message}
1010
+ `);
1011
+ };
1012
+ var fingerprintRegex = /^[0-9a-f]{64}$/i;
1013
+ function getWorkspaceMapPath() {
1014
+ return path5.join(import_node_os.default.homedir(), ".memoraone", "workspaces.json");
1015
+ }
1016
+ var ensureWorkspaceDir = async () => {
1017
+ const dir = path5.dirname(getWorkspaceMapPath());
1018
+ await fs4.mkdir(dir, { recursive: true });
1019
+ };
1020
+ async function acquireWorkspaceMapLock() {
1021
+ const filePath = getWorkspaceMapPath();
1022
+ const lockPath = `${filePath}.lock`;
1023
+ const maxRetries = 10;
1024
+ const retryDelayMs = 50;
1025
+ const maxLockAgeMs = 5e3;
1026
+ let lockAcquired = false;
1027
+ let retries = 0;
1028
+ while (!lockAcquired && retries < maxRetries) {
1029
+ try {
1030
+ try {
1031
+ const stat2 = await fs4.stat(lockPath);
1032
+ const ageMs = Date.now() - stat2.mtimeMs;
1033
+ if (ageMs > maxLockAgeMs) {
1034
+ await fs4.unlink(lockPath);
1035
+ debugLog2(`removed stale workspace map lock (age: ${ageMs}ms)`);
1036
+ }
1037
+ } catch (err) {
1038
+ if (err?.code !== "ENOENT") {
1039
+ throw err;
1040
+ }
1041
+ }
1042
+ const fd = await fs4.open(lockPath, "wx");
1043
+ await fd.close();
1044
+ lockAcquired = true;
1045
+ } catch (err) {
1046
+ if (err?.code === "EEXIST") {
1047
+ retries++;
1048
+ if (retries < maxRetries) {
1049
+ await new Promise((resolve6) => setTimeout(resolve6, retryDelayMs));
1050
+ continue;
1051
+ }
1052
+ throw new Error(
1053
+ `[memoraone-mcp] Failed to acquire workspace map lock after ${maxRetries} retries`
1054
+ );
1055
+ }
1056
+ throw err;
1057
+ }
1058
+ }
1059
+ return async () => {
1060
+ try {
1061
+ await fs4.unlink(lockPath);
1062
+ } catch (err) {
1063
+ if (err?.code !== "ENOENT") {
1064
+ debugLog2(`failed to release workspace map lock: ${String(err)}`);
1065
+ }
1066
+ }
1067
+ };
1068
+ }
1069
+ var validateWorkspaceMap = (map, filePath) => {
1070
+ if (!map || typeof map !== "object" || Array.isArray(map)) {
1071
+ throw new Error(
1072
+ `[memoraone-mcp] Invalid workspace map schema in ${filePath}`
1073
+ );
1074
+ }
1075
+ for (const [fingerprint, entry] of Object.entries(map)) {
1076
+ if (!fingerprintRegex.test(fingerprint)) {
1077
+ throw new Error(
1078
+ `[memoraone-mcp] Invalid workspace fingerprint in ${filePath}`
1079
+ );
1080
+ }
1081
+ if (typeof entry === "string") {
1082
+ if (!entry.trim()) {
1083
+ throw new Error(
1084
+ `[memoraone-mcp] Invalid workspace projectKey in ${filePath}`
1085
+ );
1086
+ }
1087
+ continue;
1088
+ }
1089
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
1090
+ throw new Error(
1091
+ `[memoraone-mcp] Invalid workspace projectKey in ${filePath}`
1092
+ );
1093
+ }
1094
+ const projectKey = entry.projectKey ?? entry.project_id;
1095
+ if (!projectKey || !projectKey.trim()) {
1096
+ throw new Error(
1097
+ `[memoraone-mcp] Invalid workspace projectKey in ${filePath}`
1098
+ );
1099
+ }
1100
+ const source = entry.source;
1101
+ if (source !== void 0 && typeof source !== "string") {
1102
+ throw new Error(
1103
+ `[memoraone-mcp] Invalid workspace source in ${filePath}`
1104
+ );
1105
+ }
1106
+ const linkedAt = entry.linked_at;
1107
+ if (linkedAt !== void 0 && typeof linkedAt !== "string") {
1108
+ throw new Error(
1109
+ `[memoraone-mcp] Invalid workspace linked_at in ${filePath}`
1110
+ );
1111
+ }
1112
+ }
1113
+ };
1114
+ async function readWorkspaceMap() {
1115
+ const filePath = getWorkspaceMapPath();
1116
+ try {
1117
+ const content = await fs4.readFile(filePath, "utf8");
1118
+ const parsed2 = JSON.parse(content);
1119
+ validateWorkspaceMap(parsed2, filePath);
1120
+ const typed = parsed2;
1121
+ let migrated = false;
1122
+ const normalized = {};
1123
+ for (const [fingerprint, entry] of Object.entries(typed)) {
1124
+ if (typeof entry === "string") {
1125
+ normalized[fingerprint] = entry;
1126
+ continue;
1127
+ }
1128
+ const projectKey = entry.projectKey ?? entry.project_id ?? "";
1129
+ if (entry.project_id && !entry.projectKey) {
1130
+ migrated = true;
1131
+ }
1132
+ normalized[fingerprint] = {
1133
+ ...projectKey ? { projectKey } : {},
1134
+ ...entry.source ? { source: entry.source } : {},
1135
+ ...entry.linked_at ? { linked_at: entry.linked_at } : {}
1136
+ };
1137
+ }
1138
+ debugLog2(
1139
+ `workspace map loaded path=${filePath} entries=${Object.keys(normalized).length}`
1140
+ );
1141
+ return { map: normalized, needsMigration: migrated };
1142
+ } catch (err) {
1143
+ if (err?.code === "ENOENT") {
1144
+ const emptyMap = {};
1145
+ debugLog2(`workspace map loaded path=${filePath} entries=0`);
1146
+ return { map: emptyMap, needsMigration: false };
1147
+ }
1148
+ if (err instanceof SyntaxError) {
1149
+ throw new Error(
1150
+ `[memoraone-mcp] Failed to parse workspace map at ${filePath}`
1151
+ );
1152
+ }
1153
+ throw err;
1154
+ }
1155
+ }
1156
+ async function writeWorkspaceMap(map) {
1157
+ const filePath = getWorkspaceMapPath();
1158
+ validateWorkspaceMap(map, filePath);
1159
+ await ensureWorkspaceDir();
1160
+ const tempPath = `${filePath}.tmp`;
1161
+ const content = JSON.stringify(map, null, 2);
1162
+ await fs4.writeFile(tempPath, content, "utf8");
1163
+ await fs4.rename(tempPath, filePath);
1164
+ }
1165
+ async function setProjectIdForFingerprint(args) {
1166
+ const { fingerprint, projectKey, source, linked_at } = args;
1167
+ if (!fingerprintRegex.test(fingerprint)) {
1168
+ throw new Error("[memoraone-mcp] Invalid fingerprint");
1169
+ }
1170
+ if (!projectKey.trim()) {
1171
+ throw new Error("[memoraone-mcp] Invalid projectKey");
1172
+ }
1173
+ const releaseLock = await acquireWorkspaceMapLock();
1174
+ try {
1175
+ const { map, needsMigration } = await readWorkspaceMap();
1176
+ if (needsMigration) {
1177
+ await writeWorkspaceMap(map);
1178
+ }
1179
+ map[fingerprint] = {
1180
+ projectKey,
1181
+ ...source ? { source } : {},
1182
+ linked_at: linked_at ?? (/* @__PURE__ */ new Date()).toISOString()
1183
+ };
1184
+ await writeWorkspaceMap(map);
1185
+ debugLog2(
1186
+ `workspace map set fingerprint=${fingerprint} projectKey=${projectKey}`
1187
+ );
1188
+ } finally {
1189
+ await releaseLock();
1190
+ }
1191
+ }
1192
+
1193
+ // src/tools/handlers/setProject.ts
950
1194
  var setProjectInputSchema = import_v415.z.object({
951
1195
  projectKey: import_v415.z.string().min(1).optional(),
952
1196
  projectId: import_v415.z.string().min(1).optional()
@@ -957,6 +1201,13 @@ async function handleSetProject(args) {
957
1201
  if (!resolvedProjectKey) {
958
1202
  throw new Error("projectKey is required");
959
1203
  }
1204
+ const requested = resolvedProjectKey.trim();
1205
+ const bound = getBoundProjectId();
1206
+ if (bound !== null && requested !== bound) {
1207
+ throw new Error(
1208
+ `Project switching is disabled (Option A). This MCP process is bound to ${bound}. Start a separate MCP instance/window for ${requested}.`
1209
+ );
1210
+ }
960
1211
  setCurrentProjectId(resolvedProjectKey);
961
1212
  const repo = resolveRepoFingerprint(process.cwd());
962
1213
  await setProjectIdForFingerprint({
@@ -968,9 +1219,91 @@ async function handleSetProject(args) {
968
1219
  }
969
1220
 
970
1221
  // src/index.ts
971
- async function sendHeartbeat(client) {
1222
+ var notInitializedResult = {
1223
+ content: [
1224
+ {
1225
+ type: "text",
1226
+ text: "MemoraOne MCP not initialized (project binding missing)."
1227
+ }
1228
+ ]
1229
+ };
1230
+ var initializeDiagDumped = false;
1231
+ var uriToPath = (uri) => {
1232
+ if (uri.startsWith("file://")) {
1233
+ return (0, import_node_url2.fileURLToPath)(uri);
1234
+ }
1235
+ return uri;
1236
+ };
1237
+ function getCursorWorkspaceRootFromEnv() {
1238
+ const raw = process.env.WORKSPACE_FOLDER_PATHS;
1239
+ if (raw === void 0 || raw.trim() === "") {
1240
+ return void 0;
1241
+ }
1242
+ const parts = raw.split(path6.delimiter).map((p) => p.trim()).filter(Boolean);
1243
+ const first = parts[0];
1244
+ return first ? path6.resolve(first) : void 0;
1245
+ }
1246
+ function isHeartbeatDebugEnabled() {
1247
+ const value = String(process.env.MEMORAONE_DEBUG_HEARTBEAT ?? "").trim().toLowerCase();
1248
+ return ["1", "true", "yes", "on"].includes(value);
1249
+ }
1250
+ function fingerprintApiKey(apiKey) {
1251
+ return crypto5.createHash("sha256").update(apiKey).digest("hex").slice(0, 12);
1252
+ }
1253
+ function inferIdeType(params) {
1254
+ if (config2.ideType) {
1255
+ return config2.ideType;
1256
+ }
1257
+ const clientInfoName = String(params?.clientInfo?.name ?? "").toLowerCase();
1258
+ const clientInfoVersion = String(params?.clientInfo?.version ?? "").toLowerCase();
1259
+ const termProgram = String(process.env.TERM_PROGRAM ?? "").toLowerCase();
1260
+ const argv = process.argv.join(" ").toLowerCase();
1261
+ const envKeys = Object.keys(process.env);
1262
+ const hasCursorSignals = envKeys.some((key) => key.startsWith("CURSOR_")) || termProgram === "cursor" || clientInfoName.includes("cursor") || clientInfoVersion.includes("cursor") || argv.includes("cursor");
1263
+ if (hasCursorSignals) {
1264
+ return "cursor";
1265
+ }
1266
+ const hasJetBrainsSignals = envKeys.some(
1267
+ (key) => [
1268
+ "JETBRAINS_IDE",
1269
+ "IDEA_INITIAL_DIRECTORY",
1270
+ "JETBRAINS_REMOTE_RUN",
1271
+ "INTELLIJ_ENVIRONMENT_READER"
1272
+ ].includes(key)
1273
+ ) || String(process.env.TERMINAL_EMULATOR ?? "").toLowerCase().includes("jetbrains") || /(jetbrains|intellij|pycharm|webstorm|goland|rubymine|clion|phpstorm|rider|datagrip)/.test(
1274
+ clientInfoName
1275
+ ) || /(jetbrains|intellij|pycharm|webstorm|goland|rubymine|clion|phpstorm|rider|datagrip)/.test(
1276
+ argv
1277
+ );
1278
+ if (hasJetBrainsSignals) {
1279
+ return "jetbrains";
1280
+ }
1281
+ const hasVsCodeSignals = envKeys.some((key) => key.startsWith("VSCODE_")) || /(visual studio code|vscode|vs code|github copilot)/.test(clientInfoName) || /(visual studio code|vscode|vs code)/.test(argv);
1282
+ if (hasVsCodeSignals) {
1283
+ return "copilot-vscode";
1284
+ }
1285
+ return void 0;
1286
+ }
1287
+ async function sendHeartbeat(client, runtime) {
972
1288
  try {
973
- await client.post("/admin/api-keys/heartbeat", {}, { log: false });
1289
+ const pid = runtime.projectId?.trim();
1290
+ if (isHeartbeatDebugEnabled()) {
1291
+ process.stderr.write(
1292
+ `[memoraone-mcp][diag] heartbeat projectId=${pid ?? "unknown"} apiKeySource=${runtime.apiKeySource ?? "unknown"} apiKeyFingerprint=${runtime.apiKeyFingerprint ?? "unknown"}
1293
+ `
1294
+ );
1295
+ }
1296
+ if (!pid) {
1297
+ throw new Error("[memoraone-mcp] Cannot send heartbeat without an active project binding");
1298
+ }
1299
+ const body = {};
1300
+ if (runtime.ideType) body.ide_type = runtime.ideType;
1301
+ await client.post(`/v1/projects/${pid}/heartbeat`, body, {
1302
+ log: false,
1303
+ headers: {
1304
+ "x-project-id": pid
1305
+ }
1306
+ });
974
1307
  } catch (err) {
975
1308
  process.stderr.write(
976
1309
  `[memoraone-mcp][info] heartbeat error (silent) ${String(err)}
@@ -1004,20 +1337,19 @@ function sanitizeArgsSummary(args) {
1004
1337
  return "[unable to serialize args]";
1005
1338
  }
1006
1339
  }
1007
- async function postWorklogEvent(client, message) {
1340
+ async function postWorklogEvent(client, projectId, message) {
1008
1341
  try {
1009
- const projectKey = getCurrentProjectId();
1010
- if (!projectKey) {
1342
+ if (!projectId) {
1011
1343
  return;
1012
1344
  }
1013
1345
  const body = {
1014
1346
  kind: "note",
1015
1347
  concept: "concept:worklog",
1016
- actor: { type: config.agentType, name: config.agentName },
1348
+ actor: { type: config2.agentType, name: config2.agentName },
1017
1349
  message,
1018
- projectKey,
1350
+ projectKey: projectId,
1019
1351
  metadata: {
1020
- source: config.source,
1352
+ source: config2.source,
1021
1353
  purpose: "worklog"
1022
1354
  }
1023
1355
  };
@@ -1026,241 +1358,357 @@ async function postWorklogEvent(client, message) {
1026
1358
  console.error("[memoraone-mcp] worklog error (silent)", err);
1027
1359
  }
1028
1360
  }
1029
- function registerToolWithWorklog(server, client, toolName, description, schema, handler) {
1030
- if (!config.worklogEnabled) {
1031
- server.tool(toolName, description, schema, handler);
1032
- return;
1033
- }
1034
- server.tool(toolName, description, schema, async (args) => {
1361
+ function registerToolWithWorklog(server, runtime, sessionContext, toolName, description, schema, handler) {
1362
+ const wrapped = async (args) => runWithSessionContext(sessionContext, async () => {
1363
+ if (!runtime.client || !runtime.projectId) return notInitializedResult;
1364
+ if (!config2.worklogEnabled) {
1365
+ return handler(args);
1366
+ }
1035
1367
  const argsSummary = sanitizeArgsSummary(args);
1036
1368
  const start = Date.now();
1037
- await postWorklogEvent(client, `tool_start: ${toolName} ${argsSummary}`);
1369
+ await postWorklogEvent(runtime.client, runtime.projectId, `tool_start: ${toolName} ${argsSummary}`);
1038
1370
  try {
1039
1371
  const result = await handler(args);
1040
1372
  const durationMs = Date.now() - start;
1041
- await postWorklogEvent(client, `tool_end: ${toolName} ok (${durationMs}ms)`);
1373
+ await postWorklogEvent(runtime.client, runtime.projectId, `tool_end: ${toolName} ok (${durationMs}ms)`);
1042
1374
  return result;
1043
1375
  } catch (err) {
1044
1376
  const durationMs = Date.now() - start;
1045
1377
  const errorSummary = err?.message || String(err);
1046
1378
  const shortError = errorSummary.length > 100 ? errorSummary.slice(0, 100) + "..." : errorSummary;
1047
- await postWorklogEvent(client, `tool_end: ${toolName} error (${durationMs}ms): ${shortError}`);
1379
+ await postWorklogEvent(
1380
+ runtime.client,
1381
+ runtime.projectId,
1382
+ `tool_end: ${toolName} error (${durationMs}ms): ${shortError}`
1383
+ );
1048
1384
  throw err;
1049
1385
  }
1050
1386
  });
1387
+ server.tool(toolName, description, schema, wrapped);
1051
1388
  }
1052
- async function main() {
1053
- const repo = resolveRepoFingerprint(process.cwd());
1054
- const envProjectKey = process.env.MEMORAONE_PROJECT_ID;
1055
- const repoFilePath = readRepoProjectIdPath(repo.gitRoot);
1056
- const repoFileProjectKey = await readRepoProjectId(repo.gitRoot);
1057
- const workspaceResolved = await getProjectIdForFingerprint(repo.fingerprint);
1058
- const binding = resolveProjectIdOrThrow({
1059
- envProjectKey,
1060
- repoFileProjectKey,
1061
- workspaceProjectKey: workspaceResolved.projectKey,
1062
- repoFilePath
1389
+ async function main(opts = {}) {
1390
+ let bindingReadyResolve = null;
1391
+ let bindingReadyReject = null;
1392
+ const bindingReady = new Promise((resolve6, reject) => {
1393
+ bindingReadyResolve = resolve6;
1394
+ bindingReadyReject = reject;
1063
1395
  });
1064
- const projectKey = binding.projectKey;
1065
- const source = binding.source;
1066
- setCurrentProjectId(projectKey);
1067
- const devMode = Boolean(config.devMode);
1068
- if (devMode) {
1069
- const remoteInfo = repo.remoteUrl ? ` remoteUrl=${repo.remoteUrl}` : "";
1070
- console.error(
1071
- `[memoraone-mcp][debug] repo fingerprint=${repo.fingerprint} projectKey=${projectKey} source=${source} gitRoot=${repo.gitRoot}${remoteInfo}`
1072
- );
1073
- }
1074
- const client = new memoraClient_default(config, projectKey);
1075
- console.error(
1076
- `[memoraone-mcp] Agent identity: name="${config.agentName}", type="${config.agentType}", source="${config.source}"`
1077
- );
1396
+ const devMode = Boolean(config2.devMode);
1397
+ const sessionLabel = opts.sessionLabel ?? "session";
1398
+ const sessionContext = createSessionRunContext();
1399
+ const runtime = {
1400
+ client: null,
1401
+ projectId: null,
1402
+ apiKeySource: null,
1403
+ apiKeyFingerprint: null,
1404
+ ideType: void 0
1405
+ };
1406
+ let workspaceRoot;
1078
1407
  const server = new import_mcp.McpServer({
1079
1408
  name: "memoraone-mcp",
1080
1409
  version: "1.0.0"
1081
1410
  });
1411
+ const resolveFallbackBindingFromInitialize = async (params) => {
1412
+ let fallbackWorkspaceRoot = workspaceRoot;
1413
+ try {
1414
+ const rootsResult = await server.server.listRoots({});
1415
+ const roots = Array.isArray(rootsResult?.roots) ? rootsResult.roots : [];
1416
+ console.error(`[memoraone-mcp] roots/list returned ${roots.length}: ${JSON.stringify(roots)}`);
1417
+ if (!fallbackWorkspaceRoot && roots.length > 0 && roots[0]?.uri) {
1418
+ fallbackWorkspaceRoot = uriToPath(roots[0].uri);
1419
+ console.error("[memoraone-mcp] Workspace resolution strategy: roots/list (first root)");
1420
+ }
1421
+ } catch (e) {
1422
+ console.error("[memoraone-mcp] roots/list failed:", String(e));
1423
+ }
1424
+ console.error(`[memoraone-mcp] process.cwd(): ${process.cwd()}`);
1425
+ console.error(
1426
+ `[memoraone-mcp] WORKSPACE_FOLDER_PATHS: ${process.env.WORKSPACE_FOLDER_PATHS ?? "(unset)"}`
1427
+ );
1428
+ if (!fallbackWorkspaceRoot) {
1429
+ if (params.workspaceFolders?.length) {
1430
+ fallbackWorkspaceRoot = uriToPath(params.workspaceFolders[0].uri);
1431
+ console.error("[memoraone-mcp] Workspace resolution strategy: initialize.workspaceFolders");
1432
+ } else if (params.rootUri) {
1433
+ fallbackWorkspaceRoot = uriToPath(params.rootUri);
1434
+ console.error("[memoraone-mcp] Workspace resolution strategy: initialize.rootUri");
1435
+ } else {
1436
+ const cursorRoot = getCursorWorkspaceRootFromEnv();
1437
+ if (cursorRoot !== void 0) {
1438
+ fallbackWorkspaceRoot = cursorRoot;
1439
+ console.error("[memoraone-mcp] Workspace resolution strategy: env.WORKSPACE_FOLDER_PATHS");
1440
+ } else {
1441
+ fallbackWorkspaceRoot = process.cwd();
1442
+ console.error("[memoraone-mcp] Workspace resolution strategy: process.cwd() fallback");
1443
+ }
1444
+ }
1445
+ }
1446
+ const binding = await resolveAuthoritativeBinding(fallbackWorkspaceRoot);
1447
+ workspaceRoot = binding.workspaceRoot;
1448
+ return binding;
1449
+ };
1082
1450
  const registeredToolNames = [];
1083
- registeredToolNames.push("memora_post_event");
1084
1451
  server.tool(
1085
1452
  "memora_post_event",
1086
1453
  "Forward an event to MemoraOne timeline",
1087
1454
  postEventShape,
1088
- async (args) => {
1089
- const result = await handlePostEvent(client, args);
1455
+ async (args) => runWithSessionContext(sessionContext, async () => {
1456
+ if (!runtime.client || !runtime.projectId) return notInitializedResult;
1457
+ const result = await handlePostEvent(runtime.client, args);
1090
1458
  return {
1091
- content: [
1092
- {
1093
- type: "text",
1094
- text: JSON.stringify(result)
1095
- }
1096
- ]
1459
+ content: [{ type: "text", text: JSON.stringify(result) }]
1097
1460
  };
1098
- }
1461
+ })
1099
1462
  );
1100
- registeredToolNames.push("memora_list_projects");
1463
+ registeredToolNames.push("memora_post_event");
1101
1464
  server.tool(
1102
1465
  "memora_list_projects",
1103
1466
  "List projects available to the current API key",
1104
1467
  listProjectsShape,
1105
- async () => {
1106
- const result = await handleListProjects(client);
1468
+ async () => runWithSessionContext(sessionContext, async () => {
1469
+ if (!runtime.client || !runtime.projectId) return notInitializedResult;
1470
+ const result = await handleListProjects(runtime.client);
1107
1471
  return {
1108
- content: [
1109
- {
1110
- type: "text",
1111
- text: JSON.stringify(result)
1112
- }
1113
- ]
1472
+ content: [{ type: "text", text: JSON.stringify(result) }]
1114
1473
  };
1115
- }
1474
+ })
1116
1475
  );
1117
- registeredToolNames.push("memora_set_project");
1476
+ registeredToolNames.push("memora_list_projects");
1118
1477
  server.tool(
1119
1478
  "memora_set_project",
1120
1479
  "Set the current project key for subsequent tool calls",
1121
1480
  setProjectShape,
1122
- async (args) => {
1481
+ async (args) => runWithSessionContext(sessionContext, async () => {
1482
+ if (!runtime.client || !runtime.projectId) return notInitializedResult;
1123
1483
  const result = await handleSetProject(args);
1124
1484
  return {
1125
- content: [
1126
- {
1127
- type: "text",
1128
- text: JSON.stringify(result)
1129
- }
1130
- ]
1485
+ content: [{ type: "text", text: JSON.stringify(result) }]
1131
1486
  };
1132
- }
1487
+ })
1133
1488
  );
1134
- registeredToolNames.push("memora_ask_with_memory");
1489
+ registeredToolNames.push("memora_set_project");
1135
1490
  registerToolWithWorklog(
1136
1491
  server,
1137
- client,
1492
+ runtime,
1493
+ sessionContext,
1138
1494
  "memora_ask_with_memory",
1139
1495
  "Ask MemoraOne with project memory context",
1140
1496
  askWithMemoryShape,
1141
1497
  async (args) => {
1142
- const result = await handleAskWithMemory(client, args);
1498
+ const result = await handleAskWithMemory(runtime.client, args);
1143
1499
  return {
1144
- content: [
1145
- {
1146
- type: "text",
1147
- text: JSON.stringify(result)
1148
- }
1149
- ]
1500
+ content: [{ type: "text", text: JSON.stringify(result) }]
1150
1501
  };
1151
1502
  }
1152
1503
  );
1153
- registeredToolNames.push("memora_log_intent");
1504
+ registeredToolNames.push("memora_ask_with_memory");
1154
1505
  registerToolWithWorklog(
1155
1506
  server,
1156
- client,
1507
+ runtime,
1508
+ sessionContext,
1157
1509
  "memora_log_intent",
1158
1510
  "Log a natural-language TASK or DECISION intent to the MemoraOne timeline",
1159
1511
  logIntentShape,
1160
1512
  async (args) => {
1161
- const result = await handleLogIntent(client, args);
1513
+ const result = await handleLogIntent(runtime.client, args);
1162
1514
  return {
1163
- content: [
1164
- {
1165
- type: "text",
1166
- text: JSON.stringify(result)
1167
- }
1168
- ]
1515
+ content: [{ type: "text", text: JSON.stringify(result) }]
1169
1516
  };
1170
1517
  }
1171
1518
  );
1172
- registeredToolNames.push("memora_log_change_summary");
1519
+ registeredToolNames.push("memora_log_intent");
1173
1520
  registerToolWithWorklog(
1174
1521
  server,
1175
- client,
1522
+ runtime,
1523
+ sessionContext,
1176
1524
  "memora_log_change_summary",
1177
1525
  "Log a concise code change summary to the MemoraOne timeline",
1178
1526
  logChangeSummaryShape,
1179
1527
  async (args) => {
1180
- const result = await handleLogChangeSummary(client, args);
1528
+ const result = await handleLogChangeSummary(runtime.client, args);
1181
1529
  return {
1182
- content: [
1183
- {
1184
- type: "text",
1185
- text: JSON.stringify(result)
1186
- }
1187
- ]
1530
+ content: [{ type: "text", text: JSON.stringify(result) }]
1188
1531
  };
1189
1532
  }
1190
1533
  );
1191
- registeredToolNames.push("memora_log_tool_result");
1534
+ registeredToolNames.push("memora_log_change_summary");
1192
1535
  registerToolWithWorklog(
1193
1536
  server,
1194
- client,
1537
+ runtime,
1538
+ sessionContext,
1195
1539
  "memora_log_tool_result",
1196
1540
  "Log a tool execution result to the MemoraOne timeline",
1197
1541
  logToolResultShape,
1198
1542
  async (args) => {
1199
- const result = await handleLogToolResult(client, args);
1543
+ const result = await handleLogToolResult(runtime.client, args);
1200
1544
  return {
1201
- content: [
1202
- {
1203
- type: "text",
1204
- text: JSON.stringify(result)
1205
- }
1206
- ]
1545
+ content: [{ type: "text", text: JSON.stringify(result) }]
1207
1546
  };
1208
1547
  }
1209
1548
  );
1210
- registeredToolNames.push("memora_log_command");
1549
+ registeredToolNames.push("memora_log_tool_result");
1211
1550
  registerToolWithWorklog(
1212
1551
  server,
1213
- client,
1552
+ runtime,
1553
+ sessionContext,
1214
1554
  "memora_log_command",
1215
1555
  "Log a command execution to the MemoraOne timeline",
1216
1556
  logCommandShape,
1217
1557
  async (args) => {
1218
- const result = await handleLogCommand(client, args);
1558
+ const result = await handleLogCommand(runtime.client, args);
1219
1559
  return {
1220
- content: [
1221
- {
1222
- type: "text",
1223
- text: JSON.stringify(result)
1224
- }
1225
- ]
1560
+ content: [{ type: "text", text: JSON.stringify(result) }]
1226
1561
  };
1227
1562
  }
1228
1563
  );
1564
+ registeredToolNames.push("memora_log_command");
1565
+ server.server.setRequestHandler(
1566
+ import_types.InitializeRequestSchema,
1567
+ async (request) => runWithSessionContext(sessionContext, async () => {
1568
+ try {
1569
+ const params = request.params;
1570
+ runtime.ideType = inferIdeType(params);
1571
+ if (!initializeDiagDumped) {
1572
+ initializeDiagDumped = true;
1573
+ const folders = Array.isArray(params.workspaceFolders) ? params.workspaceFolders.map((f) => ({ name: f?.name, uri: f?.uri })) : params.workspaceFolders;
1574
+ const capKeys = params.capabilities && typeof params.capabilities === "object" ? Object.keys(params.capabilities) : [];
1575
+ const cursorEnv = Object.keys(process.env).filter((k) => k.startsWith("CURSOR_")).reduce((acc, k) => {
1576
+ acc[k] = process.env[k] ?? "";
1577
+ return acc;
1578
+ }, {});
1579
+ const vscodeEnv = Object.keys(process.env).filter((k) => k.startsWith("VSCODE_")).reduce((acc, k) => {
1580
+ acc[k] = process.env[k] ?? "";
1581
+ return acc;
1582
+ }, {});
1583
+ console.error("[memoraone-mcp][diag] Initialize dump (once per process):");
1584
+ console.error(`[memoraone-mcp][diag] params.rootUri: ${JSON.stringify(params.rootUri)}`);
1585
+ console.error(`[memoraone-mcp][diag] params.workspaceFolders: ${JSON.stringify(folders)}`);
1586
+ console.error(`[memoraone-mcp][diag] params.clientInfo: ${JSON.stringify(params.clientInfo)}`);
1587
+ console.error(`[memoraone-mcp][diag] params.capabilities (keys): ${JSON.stringify(capKeys)}`);
1588
+ console.error(`[memoraone-mcp][diag] process.cwd(): ${process.cwd()}`);
1589
+ console.error(`[memoraone-mcp][diag] process.execPath: ${process.execPath}`);
1590
+ console.error(`[memoraone-mcp][diag] process.argv: ${JSON.stringify(process.argv)}`);
1591
+ console.error(`[memoraone-mcp][diag] process.env.WORKSPACE_FOLDER_PATHS: ${JSON.stringify(process.env.WORKSPACE_FOLDER_PATHS)}`);
1592
+ console.error(`[memoraone-mcp][diag] process.env.PWD: ${JSON.stringify(process.env.PWD)}`);
1593
+ console.error(`[memoraone-mcp][diag] process.env.HOME: ${JSON.stringify(process.env.HOME)}`);
1594
+ console.error(`[memoraone-mcp][diag] process.env.CURSOR_*: ${JSON.stringify(cursorEnv)}`);
1595
+ console.error(`[memoraone-mcp][diag] process.env.VSCODE_*: ${JSON.stringify(vscodeEnv)}`);
1596
+ console.error(`[memoraone-mcp][diag] process.env.TERM_PROGRAM: ${JSON.stringify(process.env.TERM_PROGRAM)}`);
1597
+ console.error(`[memoraone-mcp][diag] process.env.MEMORAONE_M1_PATH: ${JSON.stringify(process.env.MEMORAONE_M1_PATH)}`);
1598
+ }
1599
+ const debugAuth = ["1", "true", "yes", "on"].includes(
1600
+ String(process.env.MEMORAONE_DEBUG_AUTH ?? "").trim().toLowerCase()
1601
+ );
1602
+ const debugLog3 = config2.devMode || debugAuth;
1603
+ const binding = opts.authoritativeBinding ? opts.authoritativeBinding : await resolveFallbackBindingFromInitialize(params);
1604
+ const apiKeyToUse = binding.apiKey;
1605
+ if (!apiKeyToUse) {
1606
+ throw new Error(
1607
+ "[memoraone-mcp] No actor key. Set MEMORAONE_API_KEY or MEMORA_API_KEY, or add MEMORAONE_API_KEY/api_key to memoraone.m1"
1608
+ );
1609
+ }
1610
+ if (debugLog3) {
1611
+ console.error(
1612
+ "[memoraone-mcp][debug] Resolved actor key from " + (binding.apiKeySource === "env" ? "ENV" : "memoraone.m1")
1613
+ );
1614
+ }
1615
+ const projectId = binding.projectId;
1616
+ const existing = getBoundProjectId();
1617
+ if (existing !== null && existing !== projectId) {
1618
+ const requestedRoot = binding.workspaceRoot ?? workspaceRoot ?? process.cwd();
1619
+ const action = "Open this repo in a separate window or configure a separate MCP server instance per root.";
1620
+ 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.`;
1621
+ console.error(
1622
+ `[memoraone-mcp][ERROR] Option A conflict: boundProjectId=${existing} requestedProjectId=${projectId} workspaceRoot=${requestedRoot}. ${action}`
1623
+ );
1624
+ bindingReadyReject?.(new Error(errMsg));
1625
+ setImmediate(() => process.exit(1));
1626
+ throw new Error(errMsg);
1627
+ }
1628
+ if (existing === null) {
1629
+ setBoundProjectId(projectId);
1630
+ setBoundApiKey(apiKeyToUse);
1631
+ console.error(
1632
+ `[memoraone-mcp] ${sessionLabel} bound to project ${projectId} (Option A: single-project binding)`
1633
+ );
1634
+ }
1635
+ setCurrentProjectId(projectId);
1636
+ setCurrentApiKey(apiKeyToUse);
1637
+ runtime.projectId = projectId;
1638
+ runtime.apiKeySource = binding.apiKeySource;
1639
+ runtime.apiKeyFingerprint = fingerprintApiKey(apiKeyToUse);
1640
+ runtime.client = new memoraClient_default(config2, projectId, apiKeyToUse);
1641
+ workspaceRoot = binding.workspaceRoot;
1642
+ console.error(
1643
+ `[memoraone-mcp DEBUG registerRepoSource] pre-register bindingSource=${binding.bindingSource} workspaceRoot=${JSON.stringify(binding.workspaceRoot)} m1Path=${JSON.stringify(binding.m1Path)} ideType=${runtime.ideType ?? "(none)"}`
1644
+ );
1645
+ await registerRepoSource(runtime.client, runtime.projectId, workspaceRoot, runtime.ideType);
1646
+ if (debugAuth) {
1647
+ console.error("[memoraone-mcp][auth] repo root:", binding.workspaceRoot);
1648
+ console.error("[memoraone-mcp][auth] project_id:", projectId);
1649
+ console.error("[memoraone-mcp][auth] api_key source:", binding.apiKeySource);
1650
+ }
1651
+ console.error(
1652
+ `[memoraone-mcp] ${sessionLabel} authoritative binding: project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}`
1653
+ );
1654
+ bindingReadyResolve?.(runtime.client);
1655
+ return server.server._oninitialize(request);
1656
+ } catch (err) {
1657
+ bindingReadyReject?.(err);
1658
+ throw err;
1659
+ }
1660
+ })
1661
+ );
1229
1662
  if (devMode) {
1230
1663
  console.error("[memoraone-mcp] Starting MCP server with", registeredToolNames.length, "tools");
1231
1664
  }
1232
- const transport = new import_stdio.StdioServerTransport();
1665
+ const transport = opts.transport ?? new import_stdio.StdioServerTransport();
1233
1666
  await server.connect(transport);
1667
+ const activeClient = await bindingReady;
1234
1668
  let heartbeatInterval = null;
1235
- if (config.heartbeatEnabled) {
1236
- if (globalThis.__memoraHeartbeatInterval) {
1237
- clearInterval(globalThis.__memoraHeartbeatInterval);
1238
- }
1239
- await sendHeartbeat(client);
1240
- const intervalMs = Number.isFinite(config.heartbeatIntervalMs) ? Math.max(1e3, config.heartbeatIntervalMs) : 3e4;
1669
+ if (config2.heartbeatEnabled) {
1670
+ await sendHeartbeat(activeClient, runtime);
1671
+ const intervalMs = Number.isFinite(config2.heartbeatIntervalMs) ? Math.max(1e3, config2.heartbeatIntervalMs) : 3e4;
1672
+ console.error(
1673
+ `[memoraone-mcp] ${sessionLabel} owns heartbeat for project ${runtime.projectId} interval=${intervalMs}ms`
1674
+ );
1241
1675
  heartbeatInterval = setInterval(() => {
1242
- sendHeartbeat(client).catch(() => {
1676
+ sendHeartbeat(activeClient, runtime).catch(() => {
1243
1677
  });
1244
1678
  }, intervalMs);
1245
- globalThis.__memoraHeartbeatInterval = heartbeatInterval;
1246
1679
  }
1247
- const shutdown = (signal) => {
1680
+ const onSigInt = () => shutdown("SIGINT");
1681
+ const onSigTerm = () => shutdown("SIGTERM");
1682
+ const shutdown = (signal, exitProcess = true) => {
1683
+ process.off("SIGINT", onSigInt);
1684
+ process.off("SIGTERM", onSigTerm);
1248
1685
  if (heartbeatInterval) clearInterval(heartbeatInterval);
1249
- if (globalThis.__memoraHeartbeatInterval) {
1250
- clearInterval(globalThis.__memoraHeartbeatInterval);
1686
+ heartbeatInterval = null;
1687
+ if (runtime.projectId) {
1688
+ console.error(`[memoraone-mcp] ${sessionLabel} released heartbeat for project ${runtime.projectId}`);
1251
1689
  }
1252
1690
  if (devMode) {
1253
- console.error(`[memoraone-mcp] Received ${signal}, shutting down`);
1691
+ console.error(`[memoraone-mcp] ${sessionLabel} received ${signal}, shutting down`);
1692
+ }
1693
+ if (exitProcess) {
1694
+ process.exit(0);
1254
1695
  }
1255
- process.exit(0);
1256
1696
  };
1257
- process.on("SIGINT", () => shutdown("SIGINT"));
1258
- process.on("SIGTERM", () => shutdown("SIGTERM"));
1697
+ process.on("SIGINT", onSigInt);
1698
+ process.on("SIGTERM", onSigTerm);
1259
1699
  if (devMode) {
1260
1700
  console.error("[memoraone-mcp] MCP server ready");
1261
1701
  }
1702
+ if (opts.sessionSocket) {
1703
+ await new Promise((resolve6) => {
1704
+ opts.sessionSocket.once("close", () => {
1705
+ shutdown("session closed", false);
1706
+ resolve6();
1707
+ });
1708
+ });
1709
+ }
1262
1710
  }
1263
- main().catch((err) => {
1264
- console.error("[memoraone-mcp] fatal error", err);
1265
- process.exit(1);
1711
+ // Annotate the CommonJS export names for ESM import in node:
1712
+ 0 && (module.exports = {
1713
+ main
1266
1714
  });