@memoraone/mcp 0.1.14 → 0.1.16
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 +535 -77
- package/dist/index.cjs +530 -77
- package/package.json +8 -8
package/dist/cli.cjs
CHANGED
|
@@ -114,6 +114,40 @@ var config = {
|
|
|
114
114
|
};
|
|
115
115
|
|
|
116
116
|
// src/client/memoraClient.ts
|
|
117
|
+
var crypto2 = __toESM(require("crypto"), 1);
|
|
118
|
+
|
|
119
|
+
// src/runContext.ts
|
|
120
|
+
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
|
|
143
|
+
var parseBooleanFlag2 = (value) => {
|
|
144
|
+
if (!value) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
const normalized = value.trim().toLowerCase();
|
|
148
|
+
return ["1", "true", "yes", "on"].includes(normalized);
|
|
149
|
+
};
|
|
150
|
+
var debugEnabled = parseBooleanFlag2(process.env.MEMORAONE_DEV_MODE);
|
|
117
151
|
async function requestJson(url, method, headers, body) {
|
|
118
152
|
const res = await fetch(url, {
|
|
119
153
|
method,
|
|
@@ -137,9 +171,21 @@ var MemoraOneHttpError = class extends Error {
|
|
|
137
171
|
}
|
|
138
172
|
};
|
|
139
173
|
var MemoraClient = class {
|
|
140
|
-
constructor(cfg) {
|
|
174
|
+
constructor(cfg, projectKey) {
|
|
175
|
+
if (!projectKey?.trim()) {
|
|
176
|
+
throw new Error("[memoraone-mcp] Invalid projectKey for MemoraClient");
|
|
177
|
+
}
|
|
141
178
|
this.baseUrl = cfg.apiUrl;
|
|
142
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");
|
|
187
|
+
}
|
|
188
|
+
return projectKey;
|
|
143
189
|
}
|
|
144
190
|
buildHeaders(options) {
|
|
145
191
|
return {
|
|
@@ -148,30 +194,66 @@ var MemoraClient = class {
|
|
|
148
194
|
...options?.headers ?? {}
|
|
149
195
|
};
|
|
150
196
|
}
|
|
151
|
-
async post(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
`
|
|
197
|
+
async post(path5, body, options) {
|
|
198
|
+
console.error(
|
|
199
|
+
`[memoraone-mcp][info] MemoraClient.post ENTER path=${path5}`
|
|
200
|
+
);
|
|
201
|
+
const nonce = crypto2.randomBytes(8).toString("hex");
|
|
202
|
+
const url = `${this.baseUrl}${path5.startsWith("/") ? path5 : `/${path5}`}`;
|
|
203
|
+
this.resolveProjectKey();
|
|
204
|
+
console.error(
|
|
205
|
+
`[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_fetch method=POST url=${url}`
|
|
206
|
+
);
|
|
207
|
+
const res = await requestJson(url, "POST", this.buildHeaders(options), body);
|
|
208
|
+
if (debugEnabled && options?.log !== false) {
|
|
209
|
+
const snippet = res.text.length > 200 ? `${res.text.slice(0, 200)}...` : res.text;
|
|
210
|
+
console.error(
|
|
211
|
+
`[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_response_log`
|
|
212
|
+
);
|
|
213
|
+
const line = `[memoraone-mcp][info] http response method=POST url=${url} status=${res.status} body=${snippet}`;
|
|
214
|
+
console.error(line);
|
|
215
|
+
console.error(
|
|
216
|
+
`[memoraone-mcp][info] requestJson nonce=${nonce} stage=after_response_log`
|
|
157
217
|
);
|
|
158
218
|
}
|
|
159
|
-
const res = await requestJson(url, "POST", this.buildHeaders(options), body);
|
|
160
219
|
if (!res.ok) {
|
|
220
|
+
const snippet = res.text.length > 200 ? `${res.text.slice(0, 200)}...` : res.text;
|
|
221
|
+
process.stderr.write(
|
|
222
|
+
`[memoraone-mcp][error] http error method=POST url=${url} status=${res.status} body=${snippet}
|
|
223
|
+
`
|
|
224
|
+
);
|
|
161
225
|
throw new MemoraOneHttpError(res.status, res.statusText, res.text);
|
|
162
226
|
}
|
|
227
|
+
console.error(
|
|
228
|
+
`[memoraone-mcp][info] MemoraClient.post EXIT path=${path5}`
|
|
229
|
+
);
|
|
163
230
|
return res.text ? JSON.parse(res.text) : null;
|
|
164
231
|
}
|
|
165
|
-
async get(
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
`
|
|
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();
|
|
236
|
+
console.error(
|
|
237
|
+
`[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_fetch method=GET url=${url}`
|
|
238
|
+
);
|
|
239
|
+
const res = await requestJson(url, "GET", this.buildHeaders(options));
|
|
240
|
+
if (debugEnabled && options?.log !== false) {
|
|
241
|
+
const snippet = res.text.length > 200 ? `${res.text.slice(0, 200)}...` : res.text;
|
|
242
|
+
console.error(
|
|
243
|
+
`[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_response_log`
|
|
244
|
+
);
|
|
245
|
+
const line = `[memoraone-mcp][info] http response method=GET url=${url} status=${res.status} body=${snippet}`;
|
|
246
|
+
console.error(line);
|
|
247
|
+
console.error(
|
|
248
|
+
`[memoraone-mcp][info] requestJson nonce=${nonce} stage=after_response_log`
|
|
171
249
|
);
|
|
172
250
|
}
|
|
173
|
-
const res = await requestJson(url, "GET", this.buildHeaders(options));
|
|
174
251
|
if (!res.ok) {
|
|
252
|
+
const snippet = res.text.length > 200 ? `${res.text.slice(0, 200)}...` : res.text;
|
|
253
|
+
process.stderr.write(
|
|
254
|
+
`[memoraone-mcp][error] http error method=GET url=${url} status=${res.status} body=${snippet}
|
|
255
|
+
`
|
|
256
|
+
);
|
|
175
257
|
throw new MemoraOneHttpError(res.status, res.statusText, res.text);
|
|
176
258
|
}
|
|
177
259
|
return res.text ? JSON.parse(res.text) : null;
|
|
@@ -179,11 +261,339 @@ var MemoraClient = class {
|
|
|
179
261
|
};
|
|
180
262
|
var memoraClient_default = MemoraClient;
|
|
181
263
|
|
|
264
|
+
// src/repoFingerprint.ts
|
|
265
|
+
var fs2 = __toESM(require("fs"), 1);
|
|
266
|
+
var path2 = __toESM(require("path"), 1);
|
|
267
|
+
var crypto3 = __toESM(require("crypto"), 1);
|
|
268
|
+
var parseBooleanFlag3 = (value) => {
|
|
269
|
+
if (!value) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
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;
|
|
279
|
+
}
|
|
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) => {
|
|
295
|
+
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
|
+
}
|
|
307
|
+
}
|
|
308
|
+
} catch {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
return null;
|
|
312
|
+
};
|
|
313
|
+
var findGitRoot = (start) => {
|
|
314
|
+
let current = path2.resolve(start);
|
|
315
|
+
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 };
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const parent = path2.dirname(current);
|
|
324
|
+
if (parent === current) {
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
current = parent;
|
|
328
|
+
}
|
|
329
|
+
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
|
+
};
|
|
366
|
+
}
|
|
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
|
+
};
|
|
379
|
+
}
|
|
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
|
+
};
|
|
387
|
+
}
|
|
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;
|
|
396
|
+
}
|
|
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;
|
|
404
|
+
}
|
|
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");
|
|
411
|
+
}
|
|
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
|
+
);
|
|
421
|
+
}
|
|
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
|
+
);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
async function readWorkspaceMap() {
|
|
462
|
+
const filePath = getWorkspaceMapPath();
|
|
463
|
+
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 } : {}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
if (migrated) {
|
|
486
|
+
await writeWorkspaceMap(normalized);
|
|
487
|
+
}
|
|
488
|
+
debugLog2(
|
|
489
|
+
`workspace map loaded path=${filePath} entries=${Object.keys(normalized).length}`
|
|
490
|
+
);
|
|
491
|
+
return normalized;
|
|
492
|
+
} 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}`
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
throw err;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
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
|
+
|
|
182
589
|
// src/tools/postEvent.ts
|
|
183
590
|
var import_v42 = require("zod/v4");
|
|
184
591
|
var postEventShape = {
|
|
185
592
|
kind: import_v42.z.string().min(1),
|
|
186
|
-
actor: import_v42.z.object({
|
|
593
|
+
actor: import_v42.z.object({
|
|
594
|
+
identifier: import_v42.z.string().min(1),
|
|
595
|
+
id: import_v42.z.string().min(1).optional()
|
|
596
|
+
}),
|
|
187
597
|
content: import_v42.z.record(import_v42.z.string(), import_v42.z.any()),
|
|
188
598
|
metadata: import_v42.z.record(import_v42.z.string(), import_v42.z.any()).optional()
|
|
189
599
|
};
|
|
@@ -256,58 +666,59 @@ var listProjectsShape = {};
|
|
|
256
666
|
// src/tools/setProject.ts
|
|
257
667
|
var import_v48 = require("zod/v4");
|
|
258
668
|
var setProjectShape = {
|
|
259
|
-
|
|
669
|
+
projectKey: import_v48.z.string().min(1).optional(),
|
|
670
|
+
projectId: import_v48.z.string().min(1).optional()
|
|
260
671
|
};
|
|
261
672
|
|
|
262
673
|
// src/tools/handlers/postEvent.ts
|
|
263
674
|
var import_v49 = require("zod/v4");
|
|
264
|
-
|
|
265
|
-
// src/runContext.ts
|
|
266
|
-
var crypto = __toESM(require("crypto"), 1);
|
|
267
|
-
var currentRunId = null;
|
|
268
|
-
var currentProjectId = null;
|
|
269
|
-
function setCurrentRunId(id) {
|
|
270
|
-
currentRunId = id;
|
|
271
|
-
}
|
|
272
|
-
function getCurrentProjectId() {
|
|
273
|
-
return currentProjectId;
|
|
274
|
-
}
|
|
275
|
-
function setCurrentProjectId(id) {
|
|
276
|
-
currentProjectId = id;
|
|
277
|
-
}
|
|
278
|
-
function resolveRunId(passed) {
|
|
279
|
-
if (passed) {
|
|
280
|
-
return passed;
|
|
281
|
-
}
|
|
282
|
-
return currentRunId;
|
|
283
|
-
}
|
|
284
|
-
function generateRunId() {
|
|
285
|
-
return crypto.randomBytes(16).toString("hex");
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// src/tools/handlers/postEvent.ts
|
|
675
|
+
var crypto4 = __toESM(require("crypto"), 1);
|
|
289
676
|
var postEventInputSchema = import_v49.z.object({
|
|
290
677
|
kind: import_v49.z.string().min(1),
|
|
291
|
-
actor: import_v49.z.object({
|
|
678
|
+
actor: import_v49.z.object({
|
|
679
|
+
identifier: import_v49.z.string().min(1),
|
|
680
|
+
id: import_v49.z.string().min(1).optional()
|
|
681
|
+
}),
|
|
292
682
|
content: import_v49.z.record(import_v49.z.string(), import_v49.z.any()),
|
|
293
683
|
metadata: import_v49.z.record(import_v49.z.string(), import_v49.z.any()).optional()
|
|
294
684
|
});
|
|
295
685
|
async function handlePostEvent(client, args) {
|
|
686
|
+
const nonce = crypto4.randomBytes(8).toString("hex");
|
|
687
|
+
console.error(
|
|
688
|
+
`[memoraone-mcp][debug] tool=memora_post_event toolCallId=unknown nonce=${nonce} stage=before_post`
|
|
689
|
+
);
|
|
296
690
|
const parsed2 = postEventInputSchema.parse(args ?? {});
|
|
297
|
-
const
|
|
298
|
-
if (!
|
|
691
|
+
const projectKey = getCurrentProjectId();
|
|
692
|
+
if (!projectKey) {
|
|
299
693
|
throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
|
|
300
694
|
}
|
|
695
|
+
const content = parsed2.content ?? {};
|
|
696
|
+
const message = typeof content.message === "string" ? content.message : typeof content.text === "string" ? content.text : JSON.stringify(content);
|
|
301
697
|
const body = {
|
|
302
698
|
kind: parsed2.kind,
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
699
|
+
message,
|
|
700
|
+
projectKey,
|
|
701
|
+
actor: {
|
|
702
|
+
type: "agent",
|
|
703
|
+
identifier: config.agentName,
|
|
704
|
+
...parsed2.actor.id ? { id: parsed2.actor.id } : {}
|
|
705
|
+
},
|
|
706
|
+
...parsed2.metadata ? { metadata: parsed2.metadata } : {}
|
|
309
707
|
};
|
|
310
|
-
|
|
708
|
+
try {
|
|
709
|
+
await client.post("/timeline/events", body);
|
|
710
|
+
console.error(
|
|
711
|
+
`[memoraone-mcp][debug] tool=memora_post_event toolCallId=unknown nonce=${nonce} stage=after_post`
|
|
712
|
+
);
|
|
713
|
+
} catch (err) {
|
|
714
|
+
if (err instanceof MemoraOneHttpError) {
|
|
715
|
+
const bodyText = typeof err.body === "string" ? err.body : JSON.stringify(err.body ?? "");
|
|
716
|
+
throw new Error(
|
|
717
|
+
`memora_post_event failed: ${err.status} ${err.message} ${bodyText}`.trim()
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
throw err;
|
|
721
|
+
}
|
|
311
722
|
return { ok: true, forwarded: true };
|
|
312
723
|
}
|
|
313
724
|
|
|
@@ -326,17 +737,18 @@ function isAskWithMemoryResponse(value) {
|
|
|
326
737
|
}
|
|
327
738
|
async function handleAskWithMemory(client, args) {
|
|
328
739
|
const parsed2 = askWithMemoryInputSchema.parse(args ?? {});
|
|
329
|
-
const
|
|
330
|
-
if (!
|
|
740
|
+
const projectKey = getCurrentProjectId();
|
|
741
|
+
if (!projectKey) {
|
|
331
742
|
throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
|
|
332
743
|
}
|
|
333
744
|
const payload = {
|
|
334
|
-
question: parsed2.question
|
|
745
|
+
question: parsed2.question,
|
|
746
|
+
projectKey
|
|
335
747
|
};
|
|
336
748
|
if (parsed2.code_context) {
|
|
337
749
|
payload.code_context = parsed2.code_context;
|
|
338
750
|
}
|
|
339
|
-
const res = await client.post("/agent/ask-with-memory", payload
|
|
751
|
+
const res = await client.post("/agent/ask-with-memory", payload);
|
|
340
752
|
if (!isAskWithMemoryResponse(res)) {
|
|
341
753
|
const err = new Error("Unexpected response from MemoraOne");
|
|
342
754
|
err.status = 502;
|
|
@@ -360,8 +772,8 @@ var logIntentInputSchema = import_v411.z.object({
|
|
|
360
772
|
});
|
|
361
773
|
async function handleLogIntent(client, args) {
|
|
362
774
|
const parsed2 = logIntentInputSchema.parse(args ?? {});
|
|
363
|
-
const
|
|
364
|
-
if (!
|
|
775
|
+
const projectKey = getCurrentProjectId();
|
|
776
|
+
if (!projectKey) {
|
|
365
777
|
throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
|
|
366
778
|
}
|
|
367
779
|
const intent = parsed2.intent;
|
|
@@ -381,6 +793,7 @@ async function handleLogIntent(client, args) {
|
|
|
381
793
|
concept,
|
|
382
794
|
// MUST be TOP-LEVEL so it populates timeline_events.concept
|
|
383
795
|
message,
|
|
796
|
+
projectKey,
|
|
384
797
|
metadata: {
|
|
385
798
|
source: config.source,
|
|
386
799
|
purpose,
|
|
@@ -390,7 +803,7 @@ async function handleLogIntent(client, args) {
|
|
|
390
803
|
...context ? { context } : {}
|
|
391
804
|
}
|
|
392
805
|
};
|
|
393
|
-
await client.post("/timeline/events", body
|
|
806
|
+
await client.post("/timeline/events", body);
|
|
394
807
|
return { ok: true, run_id };
|
|
395
808
|
}
|
|
396
809
|
|
|
@@ -410,8 +823,8 @@ var logChangeSummaryInputSchema = import_v412.z.object({
|
|
|
410
823
|
});
|
|
411
824
|
async function handleLogChangeSummary(client, args) {
|
|
412
825
|
const parsed2 = logChangeSummaryInputSchema.parse(args ?? {});
|
|
413
|
-
const
|
|
414
|
-
if (!
|
|
826
|
+
const projectKey = getCurrentProjectId();
|
|
827
|
+
if (!projectKey) {
|
|
415
828
|
throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
|
|
416
829
|
}
|
|
417
830
|
const { summary, scope, files, stats, commit } = parsed2;
|
|
@@ -422,6 +835,7 @@ async function handleLogChangeSummary(client, args) {
|
|
|
422
835
|
concept: "concept:change_summary",
|
|
423
836
|
actor: { type: config.agentType, name: config.agentName },
|
|
424
837
|
message,
|
|
838
|
+
projectKey,
|
|
425
839
|
metadata: {
|
|
426
840
|
source: config.source,
|
|
427
841
|
purpose: "change_summary",
|
|
@@ -433,7 +847,7 @@ async function handleLogChangeSummary(client, args) {
|
|
|
433
847
|
...run_id ? { run_id } : {}
|
|
434
848
|
}
|
|
435
849
|
};
|
|
436
|
-
await client.post("/timeline/events", body
|
|
850
|
+
await client.post("/timeline/events", body);
|
|
437
851
|
return { ok: true };
|
|
438
852
|
}
|
|
439
853
|
|
|
@@ -452,8 +866,8 @@ var logToolResultInputSchema = import_v413.z.object({
|
|
|
452
866
|
});
|
|
453
867
|
async function handleLogToolResult(client, args) {
|
|
454
868
|
const parsed2 = logToolResultInputSchema.parse(args ?? {});
|
|
455
|
-
const
|
|
456
|
-
if (!
|
|
869
|
+
const projectKey = getCurrentProjectId();
|
|
870
|
+
if (!projectKey) {
|
|
457
871
|
throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
|
|
458
872
|
}
|
|
459
873
|
const { tool, status, summary, duration_ms, error_code, error_message, error_kind, stats } = parsed2;
|
|
@@ -464,6 +878,7 @@ async function handleLogToolResult(client, args) {
|
|
|
464
878
|
concept: "concept:tool_result",
|
|
465
879
|
actor: { type: config.agentType, name: config.agentName },
|
|
466
880
|
message,
|
|
881
|
+
projectKey,
|
|
467
882
|
metadata: {
|
|
468
883
|
source: config.source,
|
|
469
884
|
purpose: "tool_result",
|
|
@@ -478,7 +893,7 @@ async function handleLogToolResult(client, args) {
|
|
|
478
893
|
...stats ? { stats } : {}
|
|
479
894
|
}
|
|
480
895
|
};
|
|
481
|
-
await client.post("/timeline/events", body
|
|
896
|
+
await client.post("/timeline/events", body);
|
|
482
897
|
return { ok: true };
|
|
483
898
|
}
|
|
484
899
|
|
|
@@ -495,8 +910,8 @@ var logCommandInputSchema = import_v414.z.object({
|
|
|
495
910
|
});
|
|
496
911
|
async function handleLogCommand(client, args) {
|
|
497
912
|
const parsed2 = logCommandInputSchema.parse(args ?? {});
|
|
498
|
-
const
|
|
499
|
-
if (!
|
|
913
|
+
const projectKey = getCurrentProjectId();
|
|
914
|
+
if (!projectKey) {
|
|
500
915
|
throw new Error("No project selected. Use memora_list_projects and memora_set_project to select a project.");
|
|
501
916
|
}
|
|
502
917
|
const { cmd, summary, cwd: cwd2, exit_code, duration_ms, stats } = parsed2;
|
|
@@ -507,6 +922,7 @@ async function handleLogCommand(client, args) {
|
|
|
507
922
|
concept: "concept:command",
|
|
508
923
|
actor: { type: config.agentType, name: config.agentName },
|
|
509
924
|
message,
|
|
925
|
+
projectKey,
|
|
510
926
|
metadata: {
|
|
511
927
|
source: config.source,
|
|
512
928
|
purpose: "command",
|
|
@@ -519,7 +935,7 @@ async function handleLogCommand(client, args) {
|
|
|
519
935
|
...stats ? { stats } : {}
|
|
520
936
|
}
|
|
521
937
|
};
|
|
522
|
-
await client.post("/timeline/events", body
|
|
938
|
+
await client.post("/timeline/events", body);
|
|
523
939
|
return { ok: true };
|
|
524
940
|
}
|
|
525
941
|
|
|
@@ -532,12 +948,23 @@ async function handleListProjects(client) {
|
|
|
532
948
|
// src/tools/handlers/setProject.ts
|
|
533
949
|
var import_v415 = require("zod/v4");
|
|
534
950
|
var setProjectInputSchema = import_v415.z.object({
|
|
535
|
-
|
|
951
|
+
projectKey: import_v415.z.string().min(1).optional(),
|
|
952
|
+
projectId: import_v415.z.string().min(1).optional()
|
|
536
953
|
});
|
|
537
954
|
async function handleSetProject(args) {
|
|
538
955
|
const parsed2 = setProjectInputSchema.parse(args ?? {});
|
|
539
|
-
|
|
540
|
-
|
|
956
|
+
const resolvedProjectKey = parsed2.projectKey ?? parsed2.projectId;
|
|
957
|
+
if (!resolvedProjectKey) {
|
|
958
|
+
throw new Error("projectKey is required");
|
|
959
|
+
}
|
|
960
|
+
setCurrentProjectId(resolvedProjectKey);
|
|
961
|
+
const repo = resolveRepoFingerprint(process.cwd());
|
|
962
|
+
await setProjectIdForFingerprint({
|
|
963
|
+
fingerprint: repo.fingerprint,
|
|
964
|
+
projectKey: resolvedProjectKey,
|
|
965
|
+
source: "manual"
|
|
966
|
+
});
|
|
967
|
+
return { ok: true, projectKey: resolvedProjectKey };
|
|
541
968
|
}
|
|
542
969
|
|
|
543
970
|
// src/index.ts
|
|
@@ -545,7 +972,10 @@ async function sendHeartbeat(client) {
|
|
|
545
972
|
try {
|
|
546
973
|
await client.post("/admin/api-keys/heartbeat", {}, { log: false });
|
|
547
974
|
} catch (err) {
|
|
548
|
-
|
|
975
|
+
process.stderr.write(
|
|
976
|
+
`[memoraone-mcp][info] heartbeat error (silent) ${String(err)}
|
|
977
|
+
`
|
|
978
|
+
);
|
|
549
979
|
}
|
|
550
980
|
}
|
|
551
981
|
function redactSensitiveFields(obj) {
|
|
@@ -576,11 +1006,16 @@ function sanitizeArgsSummary(args) {
|
|
|
576
1006
|
}
|
|
577
1007
|
async function postWorklogEvent(client, message) {
|
|
578
1008
|
try {
|
|
1009
|
+
const projectKey = getCurrentProjectId();
|
|
1010
|
+
if (!projectKey) {
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
579
1013
|
const body = {
|
|
580
1014
|
kind: "note",
|
|
581
1015
|
concept: "concept:worklog",
|
|
582
1016
|
actor: { type: config.agentType, name: config.agentName },
|
|
583
1017
|
message,
|
|
1018
|
+
projectKey,
|
|
584
1019
|
metadata: {
|
|
585
1020
|
source: config.source,
|
|
586
1021
|
purpose: "worklog"
|
|
@@ -615,8 +1050,28 @@ function registerToolWithWorklog(server, client, toolName, description, schema,
|
|
|
615
1050
|
});
|
|
616
1051
|
}
|
|
617
1052
|
async function main() {
|
|
618
|
-
const
|
|
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
|
|
1063
|
+
});
|
|
1064
|
+
const projectKey = binding.projectKey;
|
|
1065
|
+
const source = binding.source;
|
|
1066
|
+
setCurrentProjectId(projectKey);
|
|
619
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);
|
|
620
1075
|
console.error(
|
|
621
1076
|
`[memoraone-mcp] Agent identity: name="${config.agentName}", type="${config.agentType}", source="${config.source}"`
|
|
622
1077
|
);
|
|
@@ -626,9 +1081,7 @@ async function main() {
|
|
|
626
1081
|
});
|
|
627
1082
|
const registeredToolNames = [];
|
|
628
1083
|
registeredToolNames.push("memora_post_event");
|
|
629
|
-
|
|
630
|
-
server,
|
|
631
|
-
client,
|
|
1084
|
+
server.tool(
|
|
632
1085
|
"memora_post_event",
|
|
633
1086
|
"Forward an event to MemoraOne timeline",
|
|
634
1087
|
postEventShape,
|
|
@@ -664,7 +1117,7 @@ async function main() {
|
|
|
664
1117
|
registeredToolNames.push("memora_set_project");
|
|
665
1118
|
server.tool(
|
|
666
1119
|
"memora_set_project",
|
|
667
|
-
"Set the current project
|
|
1120
|
+
"Set the current project key for subsequent tool calls",
|
|
668
1121
|
setProjectShape,
|
|
669
1122
|
async (args) => {
|
|
670
1123
|
const result = await handleSetProject(args);
|
|
@@ -811,3 +1264,8 @@ main().catch((err) => {
|
|
|
811
1264
|
console.error("[memoraone-mcp] fatal error", err);
|
|
812
1265
|
process.exit(1);
|
|
813
1266
|
});
|
|
1267
|
+
|
|
1268
|
+
// src/cli.ts
|
|
1269
|
+
console.error(
|
|
1270
|
+
`[memoraone-mcp][info] LOCAL BUILD ACTIVE version=0.1.14 pid=${process.pid} ts=${Date.now()}`
|
|
1271
|
+
);
|