@memoraone/mcp 0.1.17 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -30,7 +30,7 @@ var require_package = __commonJS({
30
30
  "package.json"(exports2, module2) {
31
31
  module2.exports = {
32
32
  name: "@memoraone/mcp",
33
- version: "0.1.17",
33
+ version: "0.1.19",
34
34
  type: "module",
35
35
  main: "dist/index.cjs",
36
36
  bin: {
@@ -46,8 +46,11 @@ var require_package = __commonJS({
46
46
  scripts: {
47
47
  build: "tsup && node scripts/writeBinWrapper.cjs",
48
48
  prepublishOnly: "pnpm run build",
49
- dev: "tsx src/index.ts",
50
- lint: "eslint ."
49
+ dev: "tsx src/cli.ts",
50
+ lint: "eslint .",
51
+ "lint:contracts": "node scripts/lint-contracts.cjs",
52
+ test: "pnpm run lint:contracts && node --import=tsx --test test/*.test.js",
53
+ "validate:auth": "node --import=tsx --test test/memoraClient.test.js"
51
54
  },
52
55
  dependencies: {
53
56
  "@modelcontextprotocol/sdk": "^1.25.1",
@@ -63,302 +66,75 @@ var require_package = __commonJS({
63
66
  }
64
67
  });
65
68
 
66
- // src/index.ts
67
- var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
68
- var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
69
+ // src/cli.ts
70
+ var path3 = __toESM(require("path"), 1);
71
+ var net = __toESM(require("net"), 1);
72
+ var import_node_child_process = require("child_process");
69
73
 
70
- // src/config.ts
71
- var process2 = __toESM(require("process"), 1);
72
- var fs = __toESM(require("fs"), 1);
74
+ // src/socketPaths.ts
75
+ var os = __toESM(require("os"), 1);
73
76
  var path = __toESM(require("path"), 1);
74
- var import_dotenv = __toESM(require("dotenv"), 1);
75
- var import_v4 = require("zod/v4");
76
-
77
- // src/configUtils.ts
78
- var DEFAULT_API_URL = "https://api.memoraone.com";
79
- var DEV_API_URL = "http://localhost:3001";
80
- function resolveApiUrl(env2) {
81
- const explicitUrl = env2.MEMORAONE_API_URL?.trim();
82
- if (explicitUrl) {
83
- return explicitUrl;
84
- }
85
- if (env2.MEMORAONE_DEV_MODE === "1") {
86
- return DEV_API_URL;
87
- }
88
- return DEFAULT_API_URL;
77
+ var fs = __toESM(require("fs"), 1);
78
+ var BASE_DIR = process.env.MEMORAONE_MCP_LOCK_DIR || path.join(os.homedir(), ".memoraone-mcp");
79
+ function getSocketPath(projectId) {
80
+ return path.join(BASE_DIR, `mcp-${projectId}.sock`);
81
+ }
82
+ function ensureBaseDir() {
83
+ fs.mkdirSync(BASE_DIR, { recursive: true });
84
+ return BASE_DIR;
89
85
  }
90
86
 
91
- // src/config.ts
92
- var dotenvPath = path.resolve(process2.cwd(), ".env");
93
- if (fs.existsSync(dotenvPath)) {
87
+ // src/projectBinding.ts
88
+ var fs2 = __toESM(require("fs/promises"), 1);
89
+ var path2 = __toESM(require("path"), 1);
90
+ var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
91
+ function parseAndValidateM1(content, markerPath) {
92
+ let parsed;
94
93
  try {
95
- import_dotenv.default.config({ path: dotenvPath });
96
- } catch (err) {
97
- process2.stderr.write("[memoraone-mcp] Failed to load .env: " + String(err) + "\n");
98
- }
99
- }
100
- var EnvSchema = import_v4.z.object({
101
- MEMORAONE_API_URL: import_v4.z.string().url().optional(),
102
- MEMORAONE_API_KEY: import_v4.z.string().min(1),
103
- MEMORAONE_DEV_MODE: import_v4.z.string().min(1).optional(),
104
- MEMORAONE_AGENT_NAME: import_v4.z.string().min(1).optional(),
105
- MEMORAONE_AGENT_TYPE: import_v4.z.string().min(1).optional(),
106
- MEMORAONE_SOURCE: import_v4.z.string().min(1).optional(),
107
- MEMORAONE_WORKLOG: import_v4.z.string().min(1).optional(),
108
- MEMORAONE_HEARTBEAT: import_v4.z.string().min(1).optional(),
109
- MEMORAONE_HEARTBEAT_INTERVAL_MS: import_v4.z.string().min(1).optional()
110
- });
111
- var requiredEnvVars = ["MEMORAONE_API_KEY"];
112
- var missingEnvVars = requiredEnvVars.filter((key) => {
113
- const value = process2.env[key];
114
- return value === void 0 || value.trim() === "";
115
- });
116
- if (missingEnvVars.length > 0) {
117
- for (const key of missingEnvVars) {
118
- process2.stderr.write(`Missing ${key}
119
- `);
120
- }
121
- process2.exit(1);
122
- }
123
- var parsed = EnvSchema.safeParse(process2.env);
124
- var resolvedApiUrl = resolveApiUrl(process2.env);
125
- if (!parsed.success) {
126
- const formatted = parsed.error.format();
127
- process2.stderr.write(
128
- "[memoraone-mcp] Invalid environment variables " + JSON.stringify(formatted) + "\n"
129
- );
130
- throw new Error("Config validation failed");
131
- }
132
- var parseBooleanFlag = (value, defaultValue) => {
133
- if (value === void 0) {
134
- return defaultValue;
135
- }
136
- const normalized = value.trim().toLowerCase();
137
- if (["1", "true", "yes", "on"].includes(normalized)) {
138
- return true;
139
- }
140
- if (["0", "false", "no", "off"].includes(normalized)) {
141
- return false;
94
+ parsed = JSON.parse(content);
95
+ } catch {
96
+ throw new Error(`[memoraone-mcp] Invalid memoraone.m1 JSON at ${markerPath}`);
142
97
  }
143
- return defaultValue;
144
- };
145
- var config = {
146
- apiUrl: resolvedApiUrl.replace(/\/+$/, ""),
147
- apiKey: parsed.data.MEMORAONE_API_KEY,
148
- agentName: parsed.data.MEMORAONE_AGENT_NAME ?? "cursor",
149
- agentType: parsed.data.MEMORAONE_AGENT_TYPE ?? "agent",
150
- source: parsed.data.MEMORAONE_SOURCE ?? "cursor",
151
- devMode: parseBooleanFlag(parsed.data.MEMORAONE_DEV_MODE, false),
152
- worklogEnabled: parseBooleanFlag(parsed.data.MEMORAONE_WORKLOG, true),
153
- heartbeatEnabled: parseBooleanFlag(parsed.data.MEMORAONE_HEARTBEAT, true),
154
- heartbeatIntervalMs: Number.parseInt(parsed.data.MEMORAONE_HEARTBEAT_INTERVAL_MS ?? "30000", 10)
155
- };
156
-
157
- // src/client/memoraClient.ts
158
- var crypto2 = __toESM(require("crypto"), 1);
159
-
160
- // src/runContext.ts
161
- var crypto = __toESM(require("crypto"), 1);
162
- var currentRunId = null;
163
- var currentProjectId = null;
164
- function setCurrentRunId(id) {
165
- currentRunId = id;
166
- }
167
- function getCurrentProjectId() {
168
- return currentProjectId;
169
- }
170
- function setCurrentProjectId(id) {
171
- currentProjectId = id;
172
- }
173
- function resolveRunId(passed) {
174
- if (passed) {
175
- return passed;
98
+ const projectId = parsed?.projectId ?? parsed?.project_id;
99
+ if (!projectId || typeof projectId !== "string") {
100
+ throw new Error(`[memoraone-mcp] memoraone.m1 missing projectId at ${markerPath}`);
176
101
  }
177
- return currentRunId;
178
- }
179
- function generateRunId() {
180
- return crypto.randomBytes(16).toString("hex");
181
- }
182
-
183
- // src/client/memoraClient.ts
184
- var parseBooleanFlag2 = (value) => {
185
- if (!value) {
186
- return false;
102
+ if (!uuidRegex.test(projectId.trim())) {
103
+ throw new Error(`[memoraone-mcp] memoraone.m1 projectId is not a UUID at ${markerPath}`);
187
104
  }
188
- const normalized = value.trim().toLowerCase();
189
- return ["1", "true", "yes", "on"].includes(normalized);
190
- };
191
- var debugEnabled = parseBooleanFlag2(process.env.MEMORAONE_DEV_MODE);
192
- async function requestJson(url, method, headers, body) {
193
- const res = await fetch(url, {
194
- method,
195
- headers,
196
- body: method === "GET" ? void 0 : JSON.stringify(body ?? {})
197
- });
198
- const text = await res.text();
199
- return {
200
- status: res.status,
201
- statusText: res.statusText,
202
- ok: res.ok,
203
- text
204
- };
105
+ const apiKeyRaw = parsed?.MEMORAONE_API_KEY ?? parsed?.api_key;
106
+ const apiKey = apiKeyRaw !== void 0 && apiKeyRaw !== null && typeof apiKeyRaw === "string" && apiKeyRaw.trim() !== "" ? apiKeyRaw.trim() : null;
107
+ return { projectId: projectId.trim(), apiKey };
205
108
  }
206
- var MemoraOneHttpError = class extends Error {
207
- constructor(status, statusText, body) {
208
- super(`MemoraOne request failed: ${status} ${statusText}`);
209
- this.name = "MemoraOneHttpError";
210
- this.status = status;
211
- this.body = body;
212
- }
213
- };
214
- var MemoraClient = class {
215
- constructor(cfg, projectKey) {
216
- if (!projectKey?.trim()) {
217
- throw new Error("[memoraone-mcp] Invalid projectKey for MemoraClient");
218
- }
219
- this.baseUrl = cfg.apiUrl;
220
- this.apiKey = cfg.apiKey;
221
- this.projectKey = projectKey;
222
- }
223
- resolveProjectKey() {
224
- const selected = getCurrentProjectId();
225
- const projectKey = (selected ?? this.projectKey)?.trim();
226
- if (!projectKey) {
227
- throw new Error("Missing projectKey: select a project first");
228
- }
229
- return projectKey;
230
- }
231
- buildHeaders(options) {
232
- return {
233
- "content-type": "application/json",
234
- "x-api-key": this.apiKey,
235
- ...options?.headers ?? {}
236
- };
237
- }
238
- async post(path5, body, options) {
239
- console.error(
240
- `[memoraone-mcp][info] MemoraClient.post ENTER path=${path5}`
241
- );
242
- const nonce = crypto2.randomBytes(8).toString("hex");
243
- const url = `${this.baseUrl}${path5.startsWith("/") ? path5 : `/${path5}`}`;
244
- this.resolveProjectKey();
245
- console.error(
246
- `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_fetch method=POST url=${url}`
247
- );
248
- const res = await requestJson(url, "POST", this.buildHeaders(options), body);
249
- if (debugEnabled && options?.log !== false) {
250
- const snippet = res.text.length > 200 ? `${res.text.slice(0, 200)}...` : res.text;
251
- console.error(
252
- `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_response_log`
253
- );
254
- const line = `[memoraone-mcp][info] http response method=POST url=${url} status=${res.status} body=${snippet}`;
255
- console.error(line);
256
- console.error(
257
- `[memoraone-mcp][info] requestJson nonce=${nonce} stage=after_response_log`
258
- );
259
- }
260
- if (!res.ok) {
261
- const snippet = res.text.length > 200 ? `${res.text.slice(0, 200)}...` : res.text;
262
- process.stderr.write(
263
- `[memoraone-mcp][error] http error method=POST url=${url} status=${res.status} body=${snippet}
264
- `
265
- );
266
- throw new MemoraOneHttpError(res.status, res.statusText, res.text);
267
- }
268
- console.error(
269
- `[memoraone-mcp][info] MemoraClient.post EXIT path=${path5}`
270
- );
271
- return res.text ? JSON.parse(res.text) : null;
272
- }
273
- async get(path5, options) {
274
- const nonce = crypto2.randomBytes(8).toString("hex");
275
- const url = `${this.baseUrl}${path5.startsWith("/") ? path5 : `/${path5}`}`;
276
- this.resolveProjectKey();
277
- console.error(
278
- `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_fetch method=GET url=${url}`
279
- );
280
- const res = await requestJson(url, "GET", this.buildHeaders(options));
281
- if (debugEnabled && options?.log !== false) {
282
- const snippet = res.text.length > 200 ? `${res.text.slice(0, 200)}...` : res.text;
283
- console.error(
284
- `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_response_log`
285
- );
286
- const line = `[memoraone-mcp][info] http response method=GET url=${url} status=${res.status} body=${snippet}`;
287
- console.error(line);
288
- console.error(
289
- `[memoraone-mcp][info] requestJson nonce=${nonce} stage=after_response_log`
290
- );
291
- }
292
- if (!res.ok) {
293
- const snippet = res.text.length > 200 ? `${res.text.slice(0, 200)}...` : res.text;
294
- process.stderr.write(
295
- `[memoraone-mcp][error] http error method=GET url=${url} status=${res.status} body=${snippet}
296
- `
297
- );
298
- throw new MemoraOneHttpError(res.status, res.statusText, res.text);
299
- }
300
- return res.text ? JSON.parse(res.text) : null;
301
- }
302
- };
303
- var memoraClient_default = MemoraClient;
304
-
305
- // src/repoFingerprint.ts
306
- var fs2 = __toESM(require("fs"), 1);
307
- var path2 = __toESM(require("path"), 1);
308
- var crypto3 = __toESM(require("crypto"), 1);
309
- var parseBooleanFlag3 = (value) => {
310
- if (!value) {
311
- return false;
312
- }
313
- const normalized = value.trim().toLowerCase();
314
- return ["1", "true", "yes", "on"].includes(normalized);
315
- };
316
- var debugEnabled2 = parseBooleanFlag3(process.env.MEMORAONE_DEV_MODE);
317
- var debugLog = (message) => {
318
- if (!debugEnabled2) {
319
- return;
109
+ async function resolveProjectIdFromExplicitM1Path() {
110
+ const raw = process.env.MEMORAONE_M1_PATH;
111
+ if (raw === void 0 || raw.trim() === "") {
112
+ return null;
320
113
  }
321
- process.stderr.write(`[memoraone-mcp][debug] ${message}
322
- `);
323
- };
324
- var normalizeRemoteUrl = (remoteUrl) => {
325
- let normalized = remoteUrl.trim();
326
- normalized = normalized.replace(/^[a-z]+:\/\//i, "");
327
- normalized = normalized.replace(/^git@([^:]+):/i, "$1/");
328
- normalized = normalized.replace(/\.git$/i, "");
329
- normalized = normalized.replace(/\/+$/, "");
330
- return normalized.toLowerCase();
331
- };
332
- var sha256 = (value) => {
333
- return crypto3.createHash("sha256").update(value).digest("hex");
334
- };
335
- var resolveGitDir = (gitPath) => {
114
+ const markerPath = path2.resolve(raw);
336
115
  try {
337
- const stat = fs2.statSync(gitPath);
338
- if (stat.isDirectory()) {
339
- return gitPath;
340
- }
341
- if (stat.isFile()) {
342
- const content = fs2.readFileSync(gitPath, "utf8");
343
- const match = content.match(/^gitdir:\s*(.+)$/m);
344
- if (match) {
345
- const gitDir = match[1].trim();
346
- return path2.resolve(path2.dirname(gitPath), gitDir);
347
- }
116
+ const content = await fs2.readFile(markerPath, "utf8");
117
+ const { projectId, apiKey } = parseAndValidateM1(content, markerPath);
118
+ return { projectId, apiKey, foundAt: markerPath };
119
+ } catch (err) {
120
+ if (err?.code === "ENOENT") {
121
+ return null;
348
122
  }
349
- } catch {
350
- return null;
123
+ throw err;
351
124
  }
352
- return null;
353
- };
354
- var findGitRoot = (start) => {
355
- let current = path2.resolve(start);
125
+ }
126
+ async function findM1WalkingUp(workspaceRoot) {
127
+ let current = path2.resolve(workspaceRoot);
356
128
  while (true) {
357
- const gitPath = path2.join(current, ".git");
358
- if (fs2.existsSync(gitPath)) {
359
- const gitDir = resolveGitDir(gitPath);
360
- if (gitDir) {
361
- return { gitRoot: current, gitDir };
129
+ const markerPath = path2.join(current, "memoraone.m1");
130
+ try {
131
+ const content = await fs2.readFile(markerPath, "utf8");
132
+ const { projectId, apiKey } = parseAndValidateM1(content, markerPath);
133
+ const repoRoot = path2.dirname(markerPath);
134
+ return { projectId, apiKey, repoRoot, markerPath };
135
+ } catch (err) {
136
+ if (err?.code !== "ENOENT") {
137
+ throw err;
362
138
  }
363
139
  }
364
140
  const parent = path2.dirname(current);
@@ -368,943 +144,80 @@ var findGitRoot = (start) => {
368
144
  current = parent;
369
145
  }
370
146
  return null;
371
- };
372
- var readOriginRemote = (gitDir) => {
373
- const configPath = path2.join(gitDir, "config");
374
- try {
375
- const content = fs2.readFileSync(configPath, "utf8");
376
- const lines = content.split(/\r?\n/);
377
- let inOrigin = false;
378
- for (const line of lines) {
379
- const sectionMatch = line.match(/^\s*\[(.+)]\s*$/);
380
- if (sectionMatch) {
381
- inOrigin = sectionMatch[1].trim() === 'remote "origin"';
382
- continue;
383
- }
384
- if (inOrigin) {
385
- const urlMatch = line.match(/^\s*url\s*=\s*(.+)\s*$/);
386
- if (urlMatch) {
387
- return urlMatch[1].trim();
388
- }
389
- }
390
- }
391
- } catch {
392
- return null;
393
- }
394
- return null;
395
- };
396
- function resolveRepoFingerprint(cwd2) {
397
- const found = findGitRoot(cwd2);
398
- if (!found) {
399
- const fallbackPath = path2.resolve(cwd2);
400
- const fingerprint2 = sha256(fallbackPath);
401
- debugLog(`repo fingerprint=${fingerprint2} source=path-fallback`);
402
- return {
403
- fingerprint: fingerprint2,
404
- gitRoot: fallbackPath,
405
- source: "path-fallback"
406
- };
407
- }
408
- const { gitRoot, gitDir } = found;
409
- const remoteUrl = readOriginRemote(gitDir);
410
- if (remoteUrl) {
411
- const normalized = normalizeRemoteUrl(remoteUrl);
412
- const fingerprint2 = sha256(normalized);
413
- debugLog(`repo fingerprint=${fingerprint2} source=git-remote`);
414
- return {
415
- fingerprint: fingerprint2,
416
- gitRoot,
417
- remoteUrl,
418
- source: "git-remote"
419
- };
420
- }
421
- const fingerprint = sha256(path2.resolve(gitRoot));
422
- debugLog(`repo fingerprint=${fingerprint} source=path-fallback`);
423
- return {
424
- fingerprint,
425
- gitRoot,
426
- source: "path-fallback"
427
- };
428
- }
429
-
430
- // src/workspaceMap.ts
431
- var fs3 = __toESM(require("fs/promises"), 1);
432
- var path3 = __toESM(require("path"), 1);
433
- var import_node_os = __toESM(require("os"), 1);
434
- var parseBooleanFlag4 = (value) => {
435
- if (!value) {
436
- return false;
437
- }
438
- const normalized = value.trim().toLowerCase();
439
- return ["1", "true", "yes", "on"].includes(normalized);
440
- };
441
- var debugEnabled3 = parseBooleanFlag4(process.env.MEMORAONE_DEV_MODE);
442
- var debugLog2 = (message) => {
443
- if (!debugEnabled3) {
444
- return;
445
- }
446
- process.stderr.write(`[memoraone-mcp][debug] ${message}
447
- `);
448
- };
449
- var fingerprintRegex = /^[0-9a-f]{64}$/i;
450
- function getWorkspaceMapPath() {
451
- return path3.join(import_node_os.default.homedir(), ".memoraone", "workspaces.json");
452
147
  }
453
- var ensureWorkspaceDir = async () => {
454
- const dir = path3.dirname(getWorkspaceMapPath());
455
- await fs3.mkdir(dir, { recursive: true });
456
- };
457
- var validateWorkspaceMap = (map, filePath) => {
458
- if (!map || typeof map !== "object" || Array.isArray(map)) {
459
- throw new Error(
460
- `[memoraone-mcp] Invalid workspace map schema in ${filePath}`
461
- );
148
+ function normalizeWorkspaceSearchRoots(workspaceRoot) {
149
+ if (workspaceRoot === void 0) {
150
+ return [];
462
151
  }
463
- for (const [fingerprint, entry] of Object.entries(map)) {
464
- if (!fingerprintRegex.test(fingerprint)) {
465
- throw new Error(
466
- `[memoraone-mcp] Invalid workspace fingerprint in ${filePath}`
467
- );
468
- }
469
- if (typeof entry === "string") {
470
- if (!entry.trim()) {
471
- throw new Error(
472
- `[memoraone-mcp] Invalid workspace projectKey in ${filePath}`
473
- );
474
- }
152
+ const list = Array.isArray(workspaceRoot) ? workspaceRoot : [workspaceRoot];
153
+ const seen = /* @__PURE__ */ new Set();
154
+ const out = [];
155
+ for (const raw of list) {
156
+ if (raw === void 0) {
475
157
  continue;
476
158
  }
477
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
478
- throw new Error(
479
- `[memoraone-mcp] Invalid workspace projectKey in ${filePath}`
480
- );
481
- }
482
- const projectKey = entry.projectKey ?? entry.project_id;
483
- if (!projectKey || !projectKey.trim()) {
484
- throw new Error(
485
- `[memoraone-mcp] Invalid workspace projectKey in ${filePath}`
486
- );
487
- }
488
- const source = entry.source;
489
- if (source !== void 0 && typeof source !== "string") {
490
- throw new Error(
491
- `[memoraone-mcp] Invalid workspace source in ${filePath}`
492
- );
493
- }
494
- const linkedAt = entry.linked_at;
495
- if (linkedAt !== void 0 && typeof linkedAt !== "string") {
496
- throw new Error(
497
- `[memoraone-mcp] Invalid workspace linked_at in ${filePath}`
498
- );
499
- }
500
- }
501
- };
502
- async function readWorkspaceMap() {
503
- const filePath = getWorkspaceMapPath();
504
- try {
505
- const content = await fs3.readFile(filePath, "utf8");
506
- const parsed2 = JSON.parse(content);
507
- validateWorkspaceMap(parsed2, filePath);
508
- const typed = parsed2;
509
- let migrated = false;
510
- const normalized = {};
511
- for (const [fingerprint, entry] of Object.entries(typed)) {
512
- if (typeof entry === "string") {
513
- normalized[fingerprint] = entry;
514
- continue;
515
- }
516
- const projectKey = entry.projectKey ?? entry.project_id ?? "";
517
- if (entry.project_id && !entry.projectKey) {
518
- migrated = true;
519
- }
520
- normalized[fingerprint] = {
521
- ...projectKey ? { projectKey } : {},
522
- ...entry.source ? { source: entry.source } : {},
523
- ...entry.linked_at ? { linked_at: entry.linked_at } : {}
524
- };
525
- }
526
- if (migrated) {
527
- await writeWorkspaceMap(normalized);
528
- }
529
- debugLog2(
530
- `workspace map loaded path=${filePath} entries=${Object.keys(normalized).length}`
531
- );
532
- return normalized;
533
- } catch (err) {
534
- if (err?.code === "ENOENT") {
535
- const emptyMap = {};
536
- debugLog2(`workspace map loaded path=${filePath} entries=0`);
537
- return emptyMap;
538
- }
539
- if (err instanceof SyntaxError) {
540
- throw new Error(
541
- `[memoraone-mcp] Failed to parse workspace map at ${filePath}`
542
- );
543
- }
544
- throw err;
545
- }
546
- }
547
- async function writeWorkspaceMap(map) {
548
- const filePath = getWorkspaceMapPath();
549
- validateWorkspaceMap(map, filePath);
550
- await ensureWorkspaceDir();
551
- const tempPath = `${filePath}.tmp`;
552
- const content = JSON.stringify(map, null, 2);
553
- await fs3.writeFile(tempPath, content, "utf8");
554
- await fs3.rename(tempPath, filePath);
555
- }
556
- async function getProjectIdForFingerprint(fingerprint) {
557
- if (!fingerprintRegex.test(fingerprint)) {
558
- throw new Error("[memoraone-mcp] Invalid fingerprint");
559
- }
560
- const map = await readWorkspaceMap();
561
- const entry = map[fingerprint];
562
- if (!entry) {
563
- return { projectKey: null, source: "unknown" };
564
- }
565
- if (typeof entry === "string") {
566
- return { projectKey: entry, source: "unknown" };
567
- }
568
- return {
569
- projectKey: entry.projectKey ?? entry.project_id ?? null,
570
- source: entry.source ?? "unknown"
571
- };
572
- }
573
- async function setProjectIdForFingerprint(args2) {
574
- const { fingerprint, projectKey, source, linked_at } = args2;
575
- if (!fingerprintRegex.test(fingerprint)) {
576
- throw new Error("[memoraone-mcp] Invalid fingerprint");
577
- }
578
- if (!projectKey.trim()) {
579
- throw new Error("[memoraone-mcp] Invalid projectKey");
580
- }
581
- const map = await readWorkspaceMap();
582
- map[fingerprint] = {
583
- projectKey,
584
- ...source ? { source } : {},
585
- linked_at: linked_at ?? (/* @__PURE__ */ new Date()).toISOString()
586
- };
587
- await writeWorkspaceMap(map);
588
- debugLog2(
589
- `workspace map set fingerprint=${fingerprint} projectKey=${projectKey}`
590
- );
591
- }
592
-
593
- // src/projectBinding.ts
594
- var fs4 = __toESM(require("fs/promises"), 1);
595
- var path4 = __toESM(require("path"), 1);
596
- function readRepoProjectIdPath(gitRoot) {
597
- return path4.join(gitRoot, ".memoraone-project");
598
- }
599
- async function readRepoProjectId(gitRoot) {
600
- const filePath = readRepoProjectIdPath(gitRoot);
601
- try {
602
- const content = await fs4.readFile(filePath, "utf8");
603
- const value = content.trim();
604
- return value ? value : null;
605
- } catch (err) {
606
- if (err?.code === "ENOENT") {
607
- return null;
608
- }
609
- throw err;
610
- }
611
- }
612
- function resolveProjectIdOrThrow(args2) {
613
- const envProjectKey = args2.envProjectKey?.trim();
614
- if (envProjectKey) {
615
- return { projectKey: envProjectKey, source: "env" };
616
- }
617
- const repoFileProjectKey = args2.repoFileProjectKey?.trim();
618
- if (repoFileProjectKey) {
619
- return { projectKey: repoFileProjectKey, source: "repo-file" };
620
- }
621
- const workspaceProjectKey = args2.workspaceProjectKey?.trim();
622
- if (workspaceProjectKey) {
623
- return { projectKey: workspaceProjectKey, source: "workspace-map" };
624
- }
625
- throw new Error(
626
- `Repo not linked to a MemoraOne project. Set MEMORAONE_PROJECT_ID or create ${args2.repoFilePath} containing a project key.`
627
- );
628
- }
629
-
630
- // src/tools/postEvent.ts
631
- var import_v42 = require("zod/v4");
632
- var postEventShape = {
633
- kind: import_v42.z.string().min(1),
634
- actor: import_v42.z.object({
635
- identifier: import_v42.z.string().min(1),
636
- id: import_v42.z.string().min(1).optional()
637
- }),
638
- content: import_v42.z.record(import_v42.z.string(), import_v42.z.any()),
639
- metadata: import_v42.z.record(import_v42.z.string(), import_v42.z.any()).optional()
640
- };
641
-
642
- // src/tools/askWithMemory.ts
643
- var import_v43 = require("zod/v4");
644
- var askWithMemoryShape = {
645
- question: import_v43.z.string().min(1),
646
- code_context: import_v43.z.object({
647
- file_path: import_v43.z.string().optional(),
648
- selected_text: import_v43.z.string().optional(),
649
- language: import_v43.z.string().optional()
650
- }).optional()
651
- };
652
-
653
- // src/tools/logIntent.ts
654
- var import_v44 = require("zod/v4");
655
- var logIntentShape = {
656
- intent: import_v44.z.enum(["task", "decision"]),
657
- message: import_v44.z.string().min(1),
658
- context: import_v44.z.record(import_v44.z.string(), import_v44.z.any()).optional(),
659
- intent_source: import_v44.z.string().optional().default("cursor_chat"),
660
- run_id: import_v44.z.string().min(1).optional()
661
- };
662
-
663
- // src/tools/logChangeSummary.ts
664
- var import_v45 = require("zod/v4");
665
- var logChangeSummaryShape = {
666
- summary: import_v45.z.string().min(1),
667
- scope: import_v45.z.string().min(1).optional(),
668
- files: import_v45.z.array(import_v45.z.string().min(1)).optional(),
669
- stats: import_v45.z.object({
670
- files: import_v45.z.number().int().nonnegative().optional(),
671
- add: import_v45.z.number().int().nonnegative().optional(),
672
- del: import_v45.z.number().int().nonnegative().optional()
673
- }).optional(),
674
- commit: import_v45.z.string().min(1).optional(),
675
- run_id: import_v45.z.string().min(1).optional()
676
- };
677
-
678
- // src/tools/logToolResult.ts
679
- var import_v46 = require("zod/v4");
680
- var logToolResultShape = {
681
- tool: import_v46.z.string().min(1),
682
- status: import_v46.z.enum(["ok", "error", "partial"]),
683
- summary: import_v46.z.string().min(1),
684
- run_id: import_v46.z.string().min(1).optional(),
685
- duration_ms: import_v46.z.number().int().nonnegative().optional(),
686
- error_code: import_v46.z.string().min(1).optional(),
687
- error_message: import_v46.z.string().min(1).optional(),
688
- error_kind: import_v46.z.enum(["infra", "logic", "auth", "rate_limit", "validation", "unknown"]).optional(),
689
- stats: import_v46.z.record(import_v46.z.string(), import_v46.z.any()).optional()
690
- };
691
-
692
- // src/tools/logCommand.ts
693
- var import_v47 = require("zod/v4");
694
- var logCommandShape = {
695
- cmd: import_v47.z.string().min(1),
696
- summary: import_v47.z.string().min(1),
697
- cwd: import_v47.z.string().min(1).optional(),
698
- exit_code: import_v47.z.number().int().optional(),
699
- duration_ms: import_v47.z.number().int().nonnegative().optional(),
700
- run_id: import_v47.z.string().min(1).optional(),
701
- stats: import_v47.z.record(import_v47.z.string(), import_v47.z.any()).optional()
702
- };
703
-
704
- // src/tools/listProjects.ts
705
- var listProjectsShape = {};
706
-
707
- // src/tools/setProject.ts
708
- var import_v48 = require("zod/v4");
709
- var setProjectShape = {
710
- projectKey: import_v48.z.string().min(1).optional(),
711
- projectId: import_v48.z.string().min(1).optional()
712
- };
713
-
714
- // src/tools/handlers/postEvent.ts
715
- var import_v49 = require("zod/v4");
716
- var crypto4 = __toESM(require("crypto"), 1);
717
- var postEventInputSchema = import_v49.z.object({
718
- kind: import_v49.z.string().min(1),
719
- actor: import_v49.z.object({
720
- identifier: import_v49.z.string().min(1),
721
- id: import_v49.z.string().min(1).optional()
722
- }),
723
- content: import_v49.z.record(import_v49.z.string(), import_v49.z.any()),
724
- metadata: import_v49.z.record(import_v49.z.string(), import_v49.z.any()).optional()
725
- });
726
- async function handlePostEvent(client, args2) {
727
- const nonce = crypto4.randomBytes(8).toString("hex");
728
- console.error(
729
- `[memoraone-mcp][debug] tool=memora_post_event toolCallId=unknown nonce=${nonce} stage=before_post`
730
- );
731
- const parsed2 = postEventInputSchema.parse(args2 ?? {});
732
- const projectKey = getCurrentProjectId();
733
- if (!projectKey) {
734
- throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
735
- }
736
- const content = parsed2.content ?? {};
737
- const message = typeof content.message === "string" ? content.message : typeof content.text === "string" ? content.text : JSON.stringify(content);
738
- const body = {
739
- kind: parsed2.kind,
740
- message,
741
- projectKey,
742
- actor: {
743
- type: "agent",
744
- identifier: config.agentName,
745
- ...parsed2.actor.id ? { id: parsed2.actor.id } : {}
746
- },
747
- ...parsed2.metadata ? { metadata: parsed2.metadata } : {}
748
- };
749
- try {
750
- await client.post("/timeline/events", body);
751
- console.error(
752
- `[memoraone-mcp][debug] tool=memora_post_event toolCallId=unknown nonce=${nonce} stage=after_post`
753
- );
754
- } catch (err) {
755
- if (err instanceof MemoraOneHttpError) {
756
- const bodyText = typeof err.body === "string" ? err.body : JSON.stringify(err.body ?? "");
757
- throw new Error(
758
- `memora_post_event failed: ${err.status} ${err.message} ${bodyText}`.trim()
759
- );
760
- }
761
- throw err;
762
- }
763
- return { ok: true, forwarded: true };
764
- }
765
-
766
- // src/tools/handlers/askWithMemory.ts
767
- var import_v410 = require("zod/v4");
768
- var askWithMemoryInputSchema = import_v410.z.object({
769
- question: import_v410.z.string().min(1),
770
- code_context: import_v410.z.object({
771
- file_path: import_v410.z.string().optional(),
772
- selected_text: import_v410.z.string().optional(),
773
- language: import_v410.z.string().optional()
774
- }).optional()
775
- });
776
- function isAskWithMemoryResponse(value) {
777
- return typeof value === "object" && value !== null && typeof value.answer === "string" && value.answer !== "";
778
- }
779
- async function handleAskWithMemory(client, args2) {
780
- const parsed2 = askWithMemoryInputSchema.parse(args2 ?? {});
781
- const projectKey = getCurrentProjectId();
782
- if (!projectKey) {
783
- throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
784
- }
785
- const payload = {
786
- question: parsed2.question,
787
- projectKey
788
- };
789
- if (parsed2.code_context) {
790
- payload.code_context = parsed2.code_context;
791
- }
792
- const res = await client.post("/agent/ask-with-memory", payload);
793
- if (!isAskWithMemoryResponse(res)) {
794
- const err = new Error("Unexpected response from MemoraOne");
795
- err.status = 502;
796
- err.body = res;
797
- throw err;
798
- }
799
- return {
800
- answer: res.answer,
801
- used: res.used ?? {}
802
- };
803
- }
804
-
805
- // src/tools/handlers/logIntent.ts
806
- var import_v411 = require("zod/v4");
807
- var logIntentInputSchema = import_v411.z.object({
808
- intent: import_v411.z.enum(["task", "decision"]),
809
- message: import_v411.z.string().min(1),
810
- context: import_v411.z.record(import_v411.z.string(), import_v411.z.any()).optional(),
811
- intent_source: import_v411.z.string().optional().default("cursor_chat"),
812
- run_id: import_v411.z.string().min(1).optional()
813
- });
814
- async function handleLogIntent(client, args2) {
815
- const parsed2 = logIntentInputSchema.parse(args2 ?? {});
816
- const projectKey = getCurrentProjectId();
817
- if (!projectKey) {
818
- throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
819
- }
820
- const intent = parsed2.intent;
821
- const message = parsed2.message.trim();
822
- if (!message) {
823
- throw new Error("message cannot be empty after trimming");
824
- }
825
- const intent_source = parsed2.intent_source ?? "cursor_chat";
826
- const context = parsed2.context;
827
- const purpose = intent;
828
- const concept = purpose === "task" ? "concept:task" : "concept:decision";
829
- const run_id = parsed2.run_id ?? generateRunId();
830
- setCurrentRunId(run_id);
831
- const body = {
832
- kind: "note",
833
- actor: { type: config.agentType, name: config.agentName },
834
- concept,
835
- // MUST be TOP-LEVEL so it populates timeline_events.concept
836
- message,
837
- projectKey,
838
- metadata: {
839
- source: config.source,
840
- purpose,
841
- // 'task' | 'decision'
842
- intent_source: intent_source ?? "cursor_chat",
843
- run_id,
844
- ...context ? { context } : {}
845
- }
846
- };
847
- await client.post("/timeline/events", body);
848
- return { ok: true, run_id };
849
- }
850
-
851
- // src/tools/handlers/logChangeSummary.ts
852
- var import_v412 = require("zod/v4");
853
- var logChangeSummaryInputSchema = import_v412.z.object({
854
- summary: import_v412.z.string().min(1),
855
- scope: import_v412.z.string().min(1).optional(),
856
- files: import_v412.z.array(import_v412.z.string().min(1)).optional(),
857
- stats: import_v412.z.object({
858
- files: import_v412.z.number().int().nonnegative().optional(),
859
- add: import_v412.z.number().int().nonnegative().optional(),
860
- del: import_v412.z.number().int().nonnegative().optional()
861
- }).optional(),
862
- commit: import_v412.z.string().min(1).optional(),
863
- run_id: import_v412.z.string().min(1).optional()
864
- });
865
- async function handleLogChangeSummary(client, args2) {
866
- const parsed2 = logChangeSummaryInputSchema.parse(args2 ?? {});
867
- const projectKey = getCurrentProjectId();
868
- if (!projectKey) {
869
- throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
870
- }
871
- const { summary, scope, files, stats, commit } = parsed2;
872
- const message = summary.startsWith("CHANGE:") ? summary : `CHANGE: ${scope ?? "code"} \u2014 ${summary}`;
873
- const run_id = resolveRunId(parsed2.run_id);
874
- const body = {
875
- kind: "note",
876
- concept: "concept:change_summary",
877
- actor: { type: config.agentType, name: config.agentName },
878
- message,
879
- projectKey,
880
- metadata: {
881
- source: config.source,
882
- purpose: "change_summary",
883
- tool: "memora_log_change_summary",
884
- ...scope ? { scope } : {},
885
- ...files ? { files } : {},
886
- ...stats ? { stats } : {},
887
- ...commit ? { commit } : {},
888
- ...run_id ? { run_id } : {}
889
- }
890
- };
891
- await client.post("/timeline/events", body);
892
- return { ok: true };
893
- }
894
-
895
- // src/tools/handlers/logToolResult.ts
896
- var import_v413 = require("zod/v4");
897
- var logToolResultInputSchema = import_v413.z.object({
898
- tool: import_v413.z.string().min(1),
899
- status: import_v413.z.enum(["ok", "error", "partial"]),
900
- summary: import_v413.z.string().min(1),
901
- run_id: import_v413.z.string().min(1).optional(),
902
- duration_ms: import_v413.z.number().int().nonnegative().optional(),
903
- error_code: import_v413.z.string().min(1).optional(),
904
- error_message: import_v413.z.string().min(1).optional(),
905
- error_kind: import_v413.z.enum(["infra", "logic", "auth", "rate_limit", "validation", "unknown"]).optional(),
906
- stats: import_v413.z.record(import_v413.z.string(), import_v413.z.any()).optional()
907
- });
908
- async function handleLogToolResult(client, args2) {
909
- const parsed2 = logToolResultInputSchema.parse(args2 ?? {});
910
- const projectKey = getCurrentProjectId();
911
- if (!projectKey) {
912
- throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
913
- }
914
- const { tool, status, summary, duration_ms, error_code, error_message, error_kind, stats } = parsed2;
915
- const message = summary.startsWith("RESULT:") ? summary : `RESULT: ${tool} \u2014 ${status} \u2014 ${summary}`;
916
- const run_id = resolveRunId(parsed2.run_id);
917
- const body = {
918
- kind: "note",
919
- concept: "concept:tool_result",
920
- actor: { type: config.agentType, name: config.agentName },
921
- message,
922
- projectKey,
923
- metadata: {
924
- source: config.source,
925
- purpose: "tool_result",
926
- tool: "memora_log_tool_result",
927
- tool_name: tool,
928
- status,
929
- ...run_id ? { run_id } : {},
930
- ...duration_ms ? { duration_ms } : {},
931
- ...error_code ? { error_code } : {},
932
- ...error_message ? { error_message } : {},
933
- ...error_kind ? { error_kind } : {},
934
- ...stats ? { stats } : {}
159
+ const trimmed = String(raw).trim();
160
+ if (trimmed === "") {
161
+ continue;
935
162
  }
936
- };
937
- await client.post("/timeline/events", body);
938
- return { ok: true };
939
- }
940
-
941
- // src/tools/handlers/logCommand.ts
942
- var import_v414 = require("zod/v4");
943
- var logCommandInputSchema = import_v414.z.object({
944
- cmd: import_v414.z.string().min(1),
945
- summary: import_v414.z.string().min(1),
946
- cwd: import_v414.z.string().min(1).optional(),
947
- exit_code: import_v414.z.number().int().optional(),
948
- duration_ms: import_v414.z.number().int().nonnegative().optional(),
949
- run_id: import_v414.z.string().min(1).optional(),
950
- stats: import_v414.z.record(import_v414.z.string(), import_v414.z.any()).optional()
951
- });
952
- async function handleLogCommand(client, args2) {
953
- const parsed2 = logCommandInputSchema.parse(args2 ?? {});
954
- const projectKey = getCurrentProjectId();
955
- if (!projectKey) {
956
- throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
957
- }
958
- const { cmd, summary, cwd: cwd2, exit_code, duration_ms, stats } = parsed2;
959
- const message = summary.startsWith("COMMAND:") ? summary : `COMMAND: ${cmd} \u2014 ${summary}`;
960
- const run_id = resolveRunId(parsed2.run_id);
961
- const body = {
962
- kind: "note",
963
- concept: "concept:command",
964
- actor: { type: config.agentType, name: config.agentName },
965
- message,
966
- projectKey,
967
- metadata: {
968
- source: config.source,
969
- purpose: "command",
970
- tool: "memora_log_command",
971
- cmd,
972
- ...cwd2 ? { cwd: cwd2 } : {},
973
- ...exit_code !== void 0 ? { exit_code } : {},
974
- ...duration_ms !== void 0 ? { duration_ms } : {},
975
- ...run_id ? { run_id } : {},
976
- ...stats ? { stats } : {}
163
+ const resolved = path2.resolve(trimmed);
164
+ if (!seen.has(resolved)) {
165
+ seen.add(resolved);
166
+ out.push(resolved);
977
167
  }
978
- };
979
- await client.post("/timeline/events", body);
980
- return { ok: true };
981
- }
982
-
983
- // src/tools/handlers/listProjects.ts
984
- async function handleListProjects(client) {
985
- const res = await client.get("/admin/projects");
986
- return res ?? { items: [] };
987
- }
988
-
989
- // src/tools/handlers/setProject.ts
990
- var import_v415 = require("zod/v4");
991
- var setProjectInputSchema = import_v415.z.object({
992
- projectKey: import_v415.z.string().min(1).optional(),
993
- projectId: import_v415.z.string().min(1).optional()
994
- });
995
- async function handleSetProject(args2) {
996
- const parsed2 = setProjectInputSchema.parse(args2 ?? {});
997
- const resolvedProjectKey = parsed2.projectKey ?? parsed2.projectId;
998
- if (!resolvedProjectKey) {
999
- throw new Error("projectKey is required");
1000
168
  }
1001
- setCurrentProjectId(resolvedProjectKey);
1002
- const repo = resolveRepoFingerprint(process.cwd());
1003
- await setProjectIdForFingerprint({
1004
- fingerprint: repo.fingerprint,
1005
- projectKey: resolvedProjectKey,
1006
- source: "manual"
1007
- });
1008
- return { ok: true, projectKey: resolvedProjectKey };
169
+ return out;
1009
170
  }
1010
-
1011
- // src/index.ts
1012
- async function sendHeartbeat(client) {
1013
- try {
1014
- await client.post("/admin/api-keys/heartbeat", {}, { log: false });
1015
- } catch (err) {
1016
- process.stderr.write(
1017
- `[memoraone-mcp][info] heartbeat error (silent) ${String(err)}
1018
- `
1019
- );
171
+ function resolveApiKeyWithSource(fileApiKey) {
172
+ const envApiKey = process.env.MEMORAONE_API_KEY?.trim();
173
+ if (envApiKey) {
174
+ return { apiKey: envApiKey, apiKeySource: "env" };
1020
175
  }
1021
- }
1022
- function redactSensitiveFields(obj) {
1023
- if (obj === null || obj === void 0) return obj;
1024
- if (typeof obj !== "object") return obj;
1025
- if (Array.isArray(obj)) return obj.map(redactSensitiveFields);
1026
- const redacted = {};
1027
- const sensitiveKeys = /^(headers?|api[_-]?key|token|cookie|authorization|auth|password|secret|credential|bearer)$/i;
1028
- for (const [key, value] of Object.entries(obj)) {
1029
- if (sensitiveKeys.test(key)) {
1030
- redacted[key] = "[REDACTED]";
1031
- } else if (typeof value === "object") {
1032
- redacted[key] = redactSensitiveFields(value);
1033
- } else {
1034
- redacted[key] = value;
1035
- }
176
+ const aliasEnvApiKey = process.env.MEMORA_API_KEY?.trim();
177
+ if (aliasEnvApiKey) {
178
+ return { apiKey: aliasEnvApiKey, apiKeySource: "env" };
1036
179
  }
1037
- return redacted;
1038
- }
1039
- function sanitizeArgsSummary(args2) {
1040
- try {
1041
- const redacted = redactSensitiveFields(args2);
1042
- const summary = JSON.stringify(redacted);
1043
- return summary.length > 200 ? summary.slice(0, 200) + "..." : summary;
1044
- } catch {
1045
- return "[unable to serialize args]";
180
+ if (fileApiKey) {
181
+ return { apiKey: fileApiKey, apiKeySource: "memoraone.m1" };
1046
182
  }
183
+ return { apiKey: null, apiKeySource: "none" };
1047
184
  }
1048
- async function postWorklogEvent(client, message) {
1049
- try {
1050
- const projectKey = getCurrentProjectId();
1051
- if (!projectKey) {
1052
- return;
1053
- }
1054
- const body = {
1055
- kind: "note",
1056
- concept: "concept:worklog",
1057
- actor: { type: config.agentType, name: config.agentName },
1058
- message,
1059
- projectKey,
1060
- metadata: {
1061
- source: config.source,
1062
- purpose: "worklog"
1063
- }
185
+ async function resolveAuthoritativeBinding(workspaceRoot) {
186
+ const explicitBinding = await resolveProjectIdFromExplicitM1Path();
187
+ if (explicitBinding) {
188
+ const resolved = resolveApiKeyWithSource(explicitBinding.apiKey);
189
+ return {
190
+ projectId: explicitBinding.projectId,
191
+ workspaceRoot: path2.dirname(explicitBinding.foundAt),
192
+ m1Path: explicitBinding.foundAt,
193
+ apiKey: resolved.apiKey,
194
+ bindingSource: "explicit-m1-path",
195
+ apiKeySource: resolved.apiKeySource
1064
196
  };
1065
- await client.post("/timeline/events", body);
1066
- } catch (err) {
1067
- console.error("[memoraone-mcp] worklog error (silent)", err);
1068
197
  }
1069
- }
1070
- function registerToolWithWorklog(server, client, toolName, description, schema, handler) {
1071
- if (!config.worklogEnabled) {
1072
- server.tool(toolName, description, schema, handler);
1073
- return;
198
+ const candidates = normalizeWorkspaceSearchRoots(workspaceRoot);
199
+ if (candidates.length === 0) {
200
+ throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
1074
201
  }
1075
- server.tool(toolName, description, schema, async (args2) => {
1076
- const argsSummary = sanitizeArgsSummary(args2);
1077
- const start = Date.now();
1078
- await postWorklogEvent(client, `tool_start: ${toolName} ${argsSummary}`);
1079
- try {
1080
- const result = await handler(args2);
1081
- const durationMs = Date.now() - start;
1082
- await postWorklogEvent(client, `tool_end: ${toolName} ok (${durationMs}ms)`);
1083
- return result;
1084
- } catch (err) {
1085
- const durationMs = Date.now() - start;
1086
- const errorSummary = err?.message || String(err);
1087
- const shortError = errorSummary.length > 100 ? errorSummary.slice(0, 100) + "..." : errorSummary;
1088
- await postWorklogEvent(client, `tool_end: ${toolName} error (${durationMs}ms): ${shortError}`);
1089
- throw err;
1090
- }
1091
- });
1092
- }
1093
- async function main() {
1094
- const repo = resolveRepoFingerprint(process.cwd());
1095
- const envProjectKey = process.env.MEMORAONE_PROJECT_ID;
1096
- const repoFilePath = readRepoProjectIdPath(repo.gitRoot);
1097
- const repoFileProjectKey = await readRepoProjectId(repo.gitRoot);
1098
- const workspaceResolved = await getProjectIdForFingerprint(repo.fingerprint);
1099
- const binding = resolveProjectIdOrThrow({
1100
- envProjectKey,
1101
- repoFileProjectKey,
1102
- workspaceProjectKey: workspaceResolved.projectKey,
1103
- repoFilePath
1104
- });
1105
- const projectKey = binding.projectKey;
1106
- const source = binding.source;
1107
- setCurrentProjectId(projectKey);
1108
- const devMode = Boolean(config.devMode);
1109
- if (devMode) {
1110
- const remoteInfo = repo.remoteUrl ? ` remoteUrl=${repo.remoteUrl}` : "";
1111
- console.error(
1112
- `[memoraone-mcp][debug] repo fingerprint=${repo.fingerprint} projectKey=${projectKey} source=${source} gitRoot=${repo.gitRoot}${remoteInfo}`
1113
- );
1114
- }
1115
- const client = new memoraClient_default(config, projectKey);
1116
- console.error(
1117
- `[memoraone-mcp] Agent identity: name="${config.agentName}", type="${config.agentType}", source="${config.source}"`
1118
- );
1119
- const server = new import_mcp.McpServer({
1120
- name: "memoraone-mcp",
1121
- version: "1.0.0"
1122
- });
1123
- const registeredToolNames = [];
1124
- registeredToolNames.push("memora_post_event");
1125
- server.tool(
1126
- "memora_post_event",
1127
- "Forward an event to MemoraOne timeline",
1128
- postEventShape,
1129
- async (args2) => {
1130
- const result = await handlePostEvent(client, args2);
1131
- return {
1132
- content: [
1133
- {
1134
- type: "text",
1135
- text: JSON.stringify(result)
1136
- }
1137
- ]
1138
- };
1139
- }
1140
- );
1141
- registeredToolNames.push("memora_list_projects");
1142
- server.tool(
1143
- "memora_list_projects",
1144
- "List projects available to the current API key",
1145
- listProjectsShape,
1146
- async () => {
1147
- const result = await handleListProjects(client);
1148
- return {
1149
- content: [
1150
- {
1151
- type: "text",
1152
- text: JSON.stringify(result)
1153
- }
1154
- ]
1155
- };
1156
- }
1157
- );
1158
- registeredToolNames.push("memora_set_project");
1159
- server.tool(
1160
- "memora_set_project",
1161
- "Set the current project key for subsequent tool calls",
1162
- setProjectShape,
1163
- async (args2) => {
1164
- const result = await handleSetProject(args2);
1165
- return {
1166
- content: [
1167
- {
1168
- type: "text",
1169
- text: JSON.stringify(result)
1170
- }
1171
- ]
1172
- };
1173
- }
1174
- );
1175
- registeredToolNames.push("memora_ask_with_memory");
1176
- registerToolWithWorklog(
1177
- server,
1178
- client,
1179
- "memora_ask_with_memory",
1180
- "Ask MemoraOne with project memory context",
1181
- askWithMemoryShape,
1182
- async (args2) => {
1183
- const result = await handleAskWithMemory(client, args2);
1184
- return {
1185
- content: [
1186
- {
1187
- type: "text",
1188
- text: JSON.stringify(result)
1189
- }
1190
- ]
1191
- };
1192
- }
1193
- );
1194
- registeredToolNames.push("memora_log_intent");
1195
- registerToolWithWorklog(
1196
- server,
1197
- client,
1198
- "memora_log_intent",
1199
- "Log a natural-language TASK or DECISION intent to the MemoraOne timeline",
1200
- logIntentShape,
1201
- async (args2) => {
1202
- const result = await handleLogIntent(client, args2);
1203
- return {
1204
- content: [
1205
- {
1206
- type: "text",
1207
- text: JSON.stringify(result)
1208
- }
1209
- ]
1210
- };
1211
- }
1212
- );
1213
- registeredToolNames.push("memora_log_change_summary");
1214
- registerToolWithWorklog(
1215
- server,
1216
- client,
1217
- "memora_log_change_summary",
1218
- "Log a concise code change summary to the MemoraOne timeline",
1219
- logChangeSummaryShape,
1220
- async (args2) => {
1221
- const result = await handleLogChangeSummary(client, args2);
1222
- return {
1223
- content: [
1224
- {
1225
- type: "text",
1226
- text: JSON.stringify(result)
1227
- }
1228
- ]
1229
- };
1230
- }
1231
- );
1232
- registeredToolNames.push("memora_log_tool_result");
1233
- registerToolWithWorklog(
1234
- server,
1235
- client,
1236
- "memora_log_tool_result",
1237
- "Log a tool execution result to the MemoraOne timeline",
1238
- logToolResultShape,
1239
- async (args2) => {
1240
- const result = await handleLogToolResult(client, args2);
202
+ for (const root of candidates) {
203
+ const binding = await findM1WalkingUp(root);
204
+ if (binding) {
205
+ const resolved = resolveApiKeyWithSource(binding.apiKey);
1241
206
  return {
1242
- content: [
1243
- {
1244
- type: "text",
1245
- text: JSON.stringify(result)
1246
- }
1247
- ]
207
+ projectId: binding.projectId,
208
+ workspaceRoot: binding.repoRoot,
209
+ m1Path: binding.markerPath,
210
+ apiKey: resolved.apiKey,
211
+ bindingSource: "workspace-search",
212
+ apiKeySource: resolved.apiKeySource
1248
213
  };
1249
214
  }
1250
- );
1251
- registeredToolNames.push("memora_log_command");
1252
- registerToolWithWorklog(
1253
- server,
1254
- client,
1255
- "memora_log_command",
1256
- "Log a command execution to the MemoraOne timeline",
1257
- logCommandShape,
1258
- async (args2) => {
1259
- const result = await handleLogCommand(client, args2);
1260
- return {
1261
- content: [
1262
- {
1263
- type: "text",
1264
- text: JSON.stringify(result)
1265
- }
1266
- ]
1267
- };
1268
- }
1269
- );
1270
- if (devMode) {
1271
- console.error("[memoraone-mcp] Starting MCP server with", registeredToolNames.length, "tools");
1272
- }
1273
- const transport = new import_stdio.StdioServerTransport();
1274
- await server.connect(transport);
1275
- let heartbeatInterval = null;
1276
- if (config.heartbeatEnabled) {
1277
- if (globalThis.__memoraHeartbeatInterval) {
1278
- clearInterval(globalThis.__memoraHeartbeatInterval);
1279
- }
1280
- await sendHeartbeat(client);
1281
- const intervalMs = Number.isFinite(config.heartbeatIntervalMs) ? Math.max(1e3, config.heartbeatIntervalMs) : 3e4;
1282
- heartbeatInterval = setInterval(() => {
1283
- sendHeartbeat(client).catch(() => {
1284
- });
1285
- }, intervalMs);
1286
- globalThis.__memoraHeartbeatInterval = heartbeatInterval;
1287
- }
1288
- const shutdown = (signal) => {
1289
- if (heartbeatInterval) clearInterval(heartbeatInterval);
1290
- if (globalThis.__memoraHeartbeatInterval) {
1291
- clearInterval(globalThis.__memoraHeartbeatInterval);
1292
- }
1293
- if (devMode) {
1294
- console.error(`[memoraone-mcp] Received ${signal}, shutting down`);
1295
- }
1296
- process.exit(0);
1297
- };
1298
- process.on("SIGINT", () => shutdown("SIGINT"));
1299
- process.on("SIGTERM", () => shutdown("SIGTERM"));
1300
- if (devMode) {
1301
- console.error("[memoraone-mcp] MCP server ready");
1302
215
  }
216
+ throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
217
+ }
218
+ function encodeResolvedBinding(binding) {
219
+ return Buffer.from(JSON.stringify(binding), "utf8").toString("base64");
1303
220
  }
1304
- main().catch((err) => {
1305
- console.error("[memoraone-mcp] fatal error", err);
1306
- process.exit(1);
1307
- });
1308
221
 
1309
222
  // src/cli.ts
1310
223
  var { version } = require_package();
@@ -1314,6 +227,103 @@ if (args.includes("--version") || args.includes("-v")) {
1314
227
  process.exit(0);
1315
228
  }
1316
229
  if (args.includes("--help") || args.includes("-h")) {
1317
- console.log("Usage: memoraone-mcp [--version] [--help]");
230
+ console.log("Usage: memoraone-mcp [--version] [--help] [--daemon --project-id <uuid>]");
1318
231
  process.exit(0);
1319
232
  }
233
+ if (args.includes("--daemon")) {
234
+ import("./daemon.cjs").then(({ runDaemon }) => runDaemon()).catch((err) => {
235
+ process.stderr.write(`[memoraone-mcp] daemon fatal: ${String(err)}
236
+ `);
237
+ process.exit(1);
238
+ });
239
+ } else {
240
+ let getWorkspaceRootCandidates = function() {
241
+ const raw = process.env.WORKSPACE_FOLDER_PATHS;
242
+ const parts = [];
243
+ if (raw !== void 0 && raw.trim() !== "") {
244
+ for (const p of raw.split(path3.delimiter).map((s) => s.trim()).filter(Boolean)) {
245
+ parts.push(path3.resolve(p));
246
+ }
247
+ }
248
+ parts.push(process.cwd());
249
+ const seen = /* @__PURE__ */ new Set();
250
+ const deduped = [];
251
+ for (const p of parts) {
252
+ if (!seen.has(p)) {
253
+ seen.add(p);
254
+ deduped.push(p);
255
+ }
256
+ }
257
+ return deduped;
258
+ }, connectWithRetry = function(socketPath) {
259
+ return new Promise((resolve3, reject) => {
260
+ const tryConnect = (attempt) => {
261
+ const socket = net.connect(socketPath, () => {
262
+ resolve3(socket);
263
+ });
264
+ socket.on("error", (err) => {
265
+ if (attempt >= MAX_RETRIES) {
266
+ reject(err);
267
+ return;
268
+ }
269
+ log(`connect attempt ${attempt + 1} failed, retrying in ${RETRY_DELAY_MS}ms: ${String(err)}`);
270
+ setTimeout(() => tryConnect(attempt + 1), RETRY_DELAY_MS);
271
+ });
272
+ };
273
+ tryConnect(0);
274
+ });
275
+ };
276
+ const log = (msg) => {
277
+ process.stderr.write(`[memoraone-mcp][bridge] ${msg}
278
+ `);
279
+ };
280
+ const MAX_RETRIES = 5;
281
+ const RETRY_DELAY_MS = 200;
282
+ async function resolveBinding() {
283
+ return resolveAuthoritativeBinding(getWorkspaceRootCandidates());
284
+ }
285
+ async function runBridge() {
286
+ ensureBaseDir();
287
+ const binding = await resolveBinding();
288
+ const socketPath = getSocketPath(binding.projectId);
289
+ log(
290
+ `authoritative binding project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}`
291
+ );
292
+ let socket;
293
+ try {
294
+ socket = await connectWithRetry(socketPath);
295
+ } catch {
296
+ log("daemon not running, spawning...");
297
+ const child = (0, import_node_child_process.spawn)(process.execPath, [process.argv[1], "--daemon", "--project-id", binding.projectId], {
298
+ detached: true,
299
+ stdio: "ignore",
300
+ env: {
301
+ ...process.env,
302
+ MEMORAONE_DAEMON_BINDING_B64: encodeResolvedBinding(binding)
303
+ }
304
+ });
305
+ child.unref();
306
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
307
+ socket = await connectWithRetry(socketPath);
308
+ }
309
+ log("bridge connected");
310
+ log("forwarding active");
311
+ process.stdin.on("data", (chunk) => {
312
+ const text = chunk.toString("utf8");
313
+ const hasToolsCall = text.includes('"tools/call"') || text.includes("tools/call");
314
+ const hasInitialize = text.includes('"initialize"');
315
+ log(`[stdin-diagnostic] bytes=${chunk.length} tools/call=${hasToolsCall} initialize=${hasInitialize}`);
316
+ });
317
+ process.stdin.pipe(socket);
318
+ socket.pipe(process.stdout);
319
+ socket.on("close", () => process.exit(0));
320
+ socket.on("error", (err) => {
321
+ log(`socket error: ${String(err)}`);
322
+ process.exit(1);
323
+ });
324
+ }
325
+ runBridge().catch((err) => {
326
+ log(`fatal: ${String(err)}`);
327
+ process.exit(1);
328
+ });
329
+ }