@memoraone/mcp 0.1.16 → 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/README.md +18 -0
- package/dist/cli.cjs +254 -1196
- package/dist/daemon.cjs +1923 -0
- package/dist/index.cjs +939 -491
- package/package.json +11 -7
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
|
|
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 = "
|
|
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
|
-
|
|
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 = [
|
|
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
|
|
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
|
|
122
|
-
var
|
|
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,
|
|
175
|
-
if (!
|
|
176
|
-
throw new Error("[memoraone-mcp] Invalid
|
|
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 =
|
|
180
|
-
this.
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
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":
|
|
201
|
+
"x-api-key": apiKey,
|
|
202
|
+
[PROJECT_ID_HEADER]: projectId,
|
|
194
203
|
...options?.headers ?? {}
|
|
195
204
|
};
|
|
196
205
|
}
|
|
197
|
-
async post(
|
|
206
|
+
async post(path7, body, options) {
|
|
198
207
|
console.error(
|
|
199
|
-
`[memoraone-mcp][info] MemoraClient.post ENTER path=${
|
|
208
|
+
`[memoraone-mcp][info] MemoraClient.post ENTER path=${path7}`
|
|
200
209
|
);
|
|
201
|
-
const nonce =
|
|
202
|
-
const url = `${this.baseUrl}${
|
|
203
|
-
this.
|
|
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=${
|
|
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(
|
|
233
|
-
const nonce =
|
|
234
|
-
const url = `${this.baseUrl}${
|
|
235
|
-
this.
|
|
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/
|
|
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
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
|
273
|
-
|
|
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
|
-
|
|
281
|
-
`);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if (
|
|
301
|
-
|
|
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
|
-
|
|
309
|
-
return null;
|
|
309
|
+
throw err;
|
|
310
310
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
let current = path2.resolve(start);
|
|
311
|
+
}
|
|
312
|
+
async function findM1WalkingUp(workspaceRoot) {
|
|
313
|
+
let current = path2.resolve(workspaceRoot);
|
|
315
314
|
while (true) {
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
|
398
|
-
|
|
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
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
|
|
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
|
-
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
if (
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
|
|
462
|
-
|
|
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
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
753
|
+
actor: { type: config2.agentType, name: config2.agentName },
|
|
837
754
|
message,
|
|
838
755
|
projectKey,
|
|
839
756
|
metadata: {
|
|
840
|
-
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:
|
|
796
|
+
actor: { type: config2.agentType, name: config2.agentName },
|
|
880
797
|
message,
|
|
881
798
|
projectKey,
|
|
882
799
|
metadata: {
|
|
883
|
-
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:
|
|
840
|
+
actor: { type: config2.agentType, name: config2.agentName },
|
|
924
841
|
message,
|
|
925
842
|
projectKey,
|
|
926
843
|
metadata: {
|
|
927
|
-
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("/
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
1348
|
+
actor: { type: config2.agentType, name: config2.agentName },
|
|
1017
1349
|
message,
|
|
1018
|
-
projectKey,
|
|
1350
|
+
projectKey: projectId,
|
|
1019
1351
|
metadata: {
|
|
1020
|
-
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,
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
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(
|
|
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
|
-
|
|
1054
|
-
|
|
1055
|
-
const
|
|
1056
|
-
|
|
1057
|
-
|
|
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
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
const
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
}
|
|
1074
|
-
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
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("
|
|
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("
|
|
1489
|
+
registeredToolNames.push("memora_set_project");
|
|
1135
1490
|
registerToolWithWorklog(
|
|
1136
1491
|
server,
|
|
1137
|
-
|
|
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("
|
|
1504
|
+
registeredToolNames.push("memora_ask_with_memory");
|
|
1154
1505
|
registerToolWithWorklog(
|
|
1155
1506
|
server,
|
|
1156
|
-
|
|
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("
|
|
1519
|
+
registeredToolNames.push("memora_log_intent");
|
|
1173
1520
|
registerToolWithWorklog(
|
|
1174
1521
|
server,
|
|
1175
|
-
|
|
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("
|
|
1534
|
+
registeredToolNames.push("memora_log_change_summary");
|
|
1192
1535
|
registerToolWithWorklog(
|
|
1193
1536
|
server,
|
|
1194
|
-
|
|
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("
|
|
1549
|
+
registeredToolNames.push("memora_log_tool_result");
|
|
1211
1550
|
registerToolWithWorklog(
|
|
1212
1551
|
server,
|
|
1213
|
-
|
|
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 (
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
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(
|
|
1676
|
+
sendHeartbeat(activeClient, runtime).catch(() => {
|
|
1243
1677
|
});
|
|
1244
1678
|
}, intervalMs);
|
|
1245
|
-
globalThis.__memoraHeartbeatInterval = heartbeatInterval;
|
|
1246
1679
|
}
|
|
1247
|
-
const
|
|
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
|
-
|
|
1250
|
-
|
|
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]
|
|
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",
|
|
1258
|
-
process.on("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
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1711
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1712
|
+
0 && (module.exports = {
|
|
1713
|
+
main
|
|
1266
1714
|
});
|