@memoraone/mcp 0.1.17 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/cli.cjs +213 -1203
- package/dist/daemon.cjs +1966 -0
- package/dist/index.cjs +996 -505
- 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
|
);
|
|
@@ -217,22 +226,29 @@ var MemoraClient = class {
|
|
|
217
226
|
);
|
|
218
227
|
}
|
|
219
228
|
if (!res.ok) {
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
229
|
+
const accepted = options?.acceptStatuses?.includes(res.status);
|
|
230
|
+
if (accepted) {
|
|
231
|
+
return res.text ? JSON.parse(res.text) : null;
|
|
232
|
+
}
|
|
233
|
+
const quiet = options?.quietHttpStatuses?.includes(res.status);
|
|
234
|
+
if (!quiet) {
|
|
235
|
+
const snippet = res.text.length > 200 ? `${res.text.slice(0, 200)}...` : res.text;
|
|
236
|
+
process.stderr.write(
|
|
237
|
+
`[memoraone-mcp][error] http error method=POST url=${url} status=${res.status} body=${snippet}
|
|
223
238
|
`
|
|
224
|
-
|
|
239
|
+
);
|
|
240
|
+
}
|
|
225
241
|
throw new MemoraOneHttpError(res.status, res.statusText, res.text);
|
|
226
242
|
}
|
|
227
243
|
console.error(
|
|
228
|
-
`[memoraone-mcp][info] MemoraClient.post EXIT path=${
|
|
244
|
+
`[memoraone-mcp][info] MemoraClient.post EXIT path=${path7}`
|
|
229
245
|
);
|
|
230
246
|
return res.text ? JSON.parse(res.text) : null;
|
|
231
247
|
}
|
|
232
|
-
async get(
|
|
233
|
-
const nonce =
|
|
234
|
-
const url = `${this.baseUrl}${
|
|
235
|
-
this.
|
|
248
|
+
async get(path7, options) {
|
|
249
|
+
const nonce = crypto.randomBytes(8).toString("hex");
|
|
250
|
+
const url = `${this.baseUrl}${path7.startsWith("/") ? path7 : `/${path7}`}`;
|
|
251
|
+
this.resolveProjectId();
|
|
236
252
|
console.error(
|
|
237
253
|
`[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_fetch method=GET url=${url}`
|
|
238
254
|
);
|
|
@@ -261,63 +277,57 @@ var MemoraClient = class {
|
|
|
261
277
|
};
|
|
262
278
|
var memoraClient_default = MemoraClient;
|
|
263
279
|
|
|
264
|
-
// src/
|
|
265
|
-
var fs2 = __toESM(require("fs"), 1);
|
|
280
|
+
// src/projectBinding.ts
|
|
281
|
+
var fs2 = __toESM(require("fs/promises"), 1);
|
|
266
282
|
var path2 = __toESM(require("path"), 1);
|
|
267
|
-
var
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
283
|
+
var uuidRegex2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
284
|
+
function parseAndValidateM1(content, markerPath) {
|
|
285
|
+
let parsed2;
|
|
286
|
+
try {
|
|
287
|
+
parsed2 = JSON.parse(content);
|
|
288
|
+
} catch {
|
|
289
|
+
throw new Error(`[memoraone-mcp] Invalid memoraone.m1 JSON at ${markerPath}`);
|
|
271
290
|
}
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
};
|
|
275
|
-
var debugEnabled2 = parseBooleanFlag3(process.env.MEMORAONE_DEV_MODE);
|
|
276
|
-
var debugLog = (message) => {
|
|
277
|
-
if (!debugEnabled2) {
|
|
278
|
-
return;
|
|
291
|
+
const projectId = parsed2?.projectId ?? parsed2?.project_id;
|
|
292
|
+
if (!projectId || typeof projectId !== "string") {
|
|
293
|
+
throw new Error(`[memoraone-mcp] memoraone.m1 missing projectId at ${markerPath}`);
|
|
279
294
|
}
|
|
280
|
-
|
|
281
|
-
`);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
};
|
|
294
|
-
var resolveGitDir = (gitPath) => {
|
|
295
|
+
if (!uuidRegex2.test(projectId.trim())) {
|
|
296
|
+
throw new Error(`[memoraone-mcp] memoraone.m1 projectId is not a UUID at ${markerPath}`);
|
|
297
|
+
}
|
|
298
|
+
const apiKeyRaw = parsed2?.MEMORAONE_API_KEY ?? parsed2?.api_key;
|
|
299
|
+
const apiKey = apiKeyRaw !== void 0 && apiKeyRaw !== null && typeof apiKeyRaw === "string" && apiKeyRaw.trim() !== "" ? apiKeyRaw.trim() : null;
|
|
300
|
+
return { projectId: projectId.trim(), apiKey };
|
|
301
|
+
}
|
|
302
|
+
async function resolveProjectIdFromExplicitM1Path() {
|
|
303
|
+
const raw = process.env.MEMORAONE_M1_PATH;
|
|
304
|
+
if (raw === void 0 || raw.trim() === "") {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
const markerPath = path2.resolve(raw);
|
|
295
308
|
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
|
-
}
|
|
309
|
+
const content = await fs2.readFile(markerPath, "utf8");
|
|
310
|
+
const { projectId, apiKey } = parseAndValidateM1(content, markerPath);
|
|
311
|
+
return { projectId, apiKey, foundAt: markerPath };
|
|
312
|
+
} catch (err) {
|
|
313
|
+
if (err?.code === "ENOENT") {
|
|
314
|
+
return null;
|
|
307
315
|
}
|
|
308
|
-
|
|
309
|
-
return null;
|
|
316
|
+
throw err;
|
|
310
317
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
let current = path2.resolve(start);
|
|
318
|
+
}
|
|
319
|
+
async function findM1WalkingUp(workspaceRoot) {
|
|
320
|
+
let current = path2.resolve(workspaceRoot);
|
|
315
321
|
while (true) {
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
322
|
+
const markerPath = path2.join(current, "memoraone.m1");
|
|
323
|
+
try {
|
|
324
|
+
const content = await fs2.readFile(markerPath, "utf8");
|
|
325
|
+
const { projectId, apiKey } = parseAndValidateM1(content, markerPath);
|
|
326
|
+
const repoRoot = path2.dirname(markerPath);
|
|
327
|
+
return { projectId, apiKey, repoRoot, markerPath };
|
|
328
|
+
} catch (err) {
|
|
329
|
+
if (err?.code !== "ENOENT") {
|
|
330
|
+
throw err;
|
|
321
331
|
}
|
|
322
332
|
}
|
|
323
333
|
const parent = path2.dirname(current);
|
|
@@ -327,275 +337,158 @@ var findGitRoot = (start) => {
|
|
|
327
337
|
current = parent;
|
|
328
338
|
}
|
|
329
339
|
return null;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
340
|
+
}
|
|
341
|
+
function normalizeWorkspaceSearchRoots(workspaceRoot) {
|
|
342
|
+
if (workspaceRoot === void 0) {
|
|
343
|
+
return [];
|
|
344
|
+
}
|
|
345
|
+
const list = Array.isArray(workspaceRoot) ? workspaceRoot : [workspaceRoot];
|
|
346
|
+
const seen = /* @__PURE__ */ new Set();
|
|
347
|
+
const out = [];
|
|
348
|
+
for (const raw of list) {
|
|
349
|
+
if (raw === void 0) {
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
const trimmed = String(raw).trim();
|
|
353
|
+
if (trimmed === "") {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
const resolved = path2.resolve(trimmed);
|
|
357
|
+
if (!seen.has(resolved)) {
|
|
358
|
+
seen.add(resolved);
|
|
359
|
+
out.push(resolved);
|
|
349
360
|
}
|
|
350
|
-
} catch {
|
|
351
|
-
return null;
|
|
352
361
|
}
|
|
353
|
-
return
|
|
354
|
-
}
|
|
355
|
-
function
|
|
356
|
-
const
|
|
357
|
-
if (
|
|
358
|
-
|
|
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
|
-
};
|
|
362
|
+
return out;
|
|
363
|
+
}
|
|
364
|
+
function resolveApiKeyWithSource(fileApiKey) {
|
|
365
|
+
const envApiKey = process.env.MEMORAONE_API_KEY?.trim();
|
|
366
|
+
if (envApiKey) {
|
|
367
|
+
return { apiKey: envApiKey, apiKeySource: "env" };
|
|
366
368
|
}
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
369
|
+
const aliasEnvApiKey = process.env.MEMORA_API_KEY?.trim();
|
|
370
|
+
if (aliasEnvApiKey) {
|
|
371
|
+
return { apiKey: aliasEnvApiKey, apiKeySource: "env" };
|
|
372
|
+
}
|
|
373
|
+
if (fileApiKey) {
|
|
374
|
+
return { apiKey: fileApiKey, apiKeySource: "memoraone.m1" };
|
|
375
|
+
}
|
|
376
|
+
return { apiKey: null, apiKeySource: "none" };
|
|
377
|
+
}
|
|
378
|
+
async function resolveAuthoritativeBinding(workspaceRoot) {
|
|
379
|
+
const explicitBinding = await resolveProjectIdFromExplicitM1Path();
|
|
380
|
+
if (explicitBinding) {
|
|
381
|
+
const resolved = resolveApiKeyWithSource(explicitBinding.apiKey);
|
|
373
382
|
return {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
383
|
+
projectId: explicitBinding.projectId,
|
|
384
|
+
workspaceRoot: path2.dirname(explicitBinding.foundAt),
|
|
385
|
+
m1Path: explicitBinding.foundAt,
|
|
386
|
+
apiKey: resolved.apiKey,
|
|
387
|
+
bindingSource: "explicit-m1-path",
|
|
388
|
+
apiKeySource: resolved.apiKeySource
|
|
378
389
|
};
|
|
379
390
|
}
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
391
|
+
const candidates = normalizeWorkspaceSearchRoots(workspaceRoot);
|
|
392
|
+
if (candidates.length === 0) {
|
|
393
|
+
throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
|
|
394
|
+
}
|
|
395
|
+
for (const root of candidates) {
|
|
396
|
+
const binding = await findM1WalkingUp(root);
|
|
397
|
+
if (binding) {
|
|
398
|
+
const resolved = resolveApiKeyWithSource(binding.apiKey);
|
|
399
|
+
return {
|
|
400
|
+
projectId: binding.projectId,
|
|
401
|
+
workspaceRoot: binding.repoRoot,
|
|
402
|
+
m1Path: binding.markerPath,
|
|
403
|
+
apiKey: resolved.apiKey,
|
|
404
|
+
bindingSource: "workspace-search",
|
|
405
|
+
apiKeySource: resolved.apiKeySource
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
|
|
387
410
|
}
|
|
388
411
|
|
|
389
|
-
// src/
|
|
390
|
-
var fs3 = __toESM(require("fs/promises"), 1);
|
|
412
|
+
// src/sourceRegistration.ts
|
|
391
413
|
var path3 = __toESM(require("path"), 1);
|
|
392
|
-
var
|
|
393
|
-
var
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
};
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
414
|
+
var import_node_url = require("url");
|
|
415
|
+
var LOG_PREFIX = "[memoraone-mcp][source-registration]";
|
|
416
|
+
function buildRepoSourcePayload(normalizedRepoPath, ideType) {
|
|
417
|
+
const body = {
|
|
418
|
+
kind: "repo",
|
|
419
|
+
label: path3.basename(normalizedRepoPath),
|
|
420
|
+
uri: (0, import_node_url.pathToFileURL)(normalizedRepoPath).href
|
|
421
|
+
};
|
|
422
|
+
if (ideType) {
|
|
423
|
+
body.metadata = {
|
|
424
|
+
ide_type: ideType
|
|
425
|
+
};
|
|
404
426
|
}
|
|
405
|
-
|
|
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");
|
|
427
|
+
return body;
|
|
411
428
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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}`
|
|
429
|
+
async function registerRepoSource(client, projectId, repoPath, ideType) {
|
|
430
|
+
try {
|
|
431
|
+
if (repoPath === void 0 || repoPath === null || String(repoPath).trim() === "") {
|
|
432
|
+
process.stderr.write(
|
|
433
|
+
`${LOG_PREFIX} skipped: empty repoPath (cannot register workspace)
|
|
434
|
+
`
|
|
426
435
|
);
|
|
436
|
+
return;
|
|
427
437
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
438
|
+
const normalizedRepoPath = path3.resolve(String(repoPath));
|
|
439
|
+
const body = buildRepoSourcePayload(normalizedRepoPath, ideType);
|
|
440
|
+
const primaryPath = `/v1/projects/${projectId}/sources`;
|
|
441
|
+
const alternatePath = `/v1/projects/${projectId}/sources/register`;
|
|
442
|
+
process.stderr.write(
|
|
443
|
+
`${LOG_PREFIX} registering projectId=${projectId} path=${normalizedRepoPath} ideType=${ideType ?? "(none)"}
|
|
444
|
+
`
|
|
445
|
+
);
|
|
446
|
+
try {
|
|
447
|
+
await client.post(primaryPath, body, {
|
|
448
|
+
acceptStatuses: [409],
|
|
449
|
+
log: false,
|
|
450
|
+
quietHttpStatuses: [404, 405, 501]
|
|
451
|
+
});
|
|
452
|
+
process.stderr.write(`${LOG_PREFIX} ok: POST ${primaryPath}
|
|
453
|
+
`);
|
|
454
|
+
return;
|
|
455
|
+
} catch (err) {
|
|
456
|
+
if (err instanceof MemoraOneHttpError && (err.status === 404 || err.status === 405 || err.status === 501)) {
|
|
457
|
+
process.stderr.write(
|
|
458
|
+
`${LOG_PREFIX} primary route returned ${err.status}; retrying POST ${alternatePath}
|
|
459
|
+
`
|
|
432
460
|
);
|
|
461
|
+
await client.post(alternatePath, body, { acceptStatuses: [409], log: false });
|
|
462
|
+
process.stderr.write(`${LOG_PREFIX} ok: POST ${alternatePath}
|
|
463
|
+
`);
|
|
464
|
+
return;
|
|
433
465
|
}
|
|
434
|
-
|
|
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
|
-
);
|
|
466
|
+
throw err;
|
|
452
467
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
468
|
+
} catch (err) {
|
|
469
|
+
const msg = String(err?.message ?? err);
|
|
470
|
+
process.stderr.write(`${LOG_PREFIX} failed: ${msg}
|
|
471
|
+
`);
|
|
472
|
+
if (err instanceof MemoraOneHttpError) {
|
|
473
|
+
const bodyStr = typeof err.body === "string" ? err.body : JSON.stringify(err.body ?? null);
|
|
474
|
+
process.stderr.write(
|
|
475
|
+
`${LOG_PREFIX} http status=${err.status} body=${bodyStr.length > 500 ? bodyStr.slice(0, 500) + "..." : bodyStr}
|
|
476
|
+
`
|
|
457
477
|
);
|
|
458
478
|
}
|
|
459
479
|
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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 } : {}
|
|
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
|
-
|
|
589
|
-
// src/tools/postEvent.ts
|
|
590
|
-
var import_v42 = require("zod/v4");
|
|
591
|
-
var postEventShape = {
|
|
592
|
-
kind: import_v42.z.string().min(1),
|
|
593
|
-
actor: import_v42.z.object({
|
|
594
|
-
identifier: import_v42.z.string().min(1),
|
|
595
|
-
id: import_v42.z.string().min(1).optional()
|
|
596
|
-
}),
|
|
597
|
-
content: import_v42.z.record(import_v42.z.string(), import_v42.z.any()),
|
|
598
|
-
metadata: import_v42.z.record(import_v42.z.string(), import_v42.z.any()).optional()
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/tools/postEvent.ts
|
|
483
|
+
var import_v42 = require("zod/v4");
|
|
484
|
+
var postEventShape = {
|
|
485
|
+
kind: import_v42.z.string().min(1),
|
|
486
|
+
actor: import_v42.z.object({
|
|
487
|
+
identifier: import_v42.z.string().min(1),
|
|
488
|
+
id: import_v42.z.string().min(1).optional()
|
|
489
|
+
}),
|
|
490
|
+
content: import_v42.z.record(import_v42.z.string(), import_v42.z.any()),
|
|
491
|
+
metadata: import_v42.z.record(import_v42.z.string(), import_v42.z.any()).optional()
|
|
599
492
|
};
|
|
600
493
|
|
|
601
494
|
// src/tools/askWithMemory.ts
|
|
@@ -672,7 +565,67 @@ var setProjectShape = {
|
|
|
672
565
|
|
|
673
566
|
// src/tools/handlers/postEvent.ts
|
|
674
567
|
var import_v49 = require("zod/v4");
|
|
675
|
-
var
|
|
568
|
+
var crypto3 = __toESM(require("crypto"), 1);
|
|
569
|
+
|
|
570
|
+
// src/runContext.ts
|
|
571
|
+
var import_node_async_hooks = require("async_hooks");
|
|
572
|
+
var crypto2 = __toESM(require("crypto"), 1);
|
|
573
|
+
var sessionContextStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
574
|
+
function createSessionRunContext(initial = {}) {
|
|
575
|
+
return {
|
|
576
|
+
currentRunId: null,
|
|
577
|
+
currentProjectId: null,
|
|
578
|
+
currentApiKey: null,
|
|
579
|
+
boundProjectId: null,
|
|
580
|
+
boundApiKey: null,
|
|
581
|
+
...initial
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function runWithSessionContext(context, fn) {
|
|
585
|
+
return sessionContextStorage.run(context, fn);
|
|
586
|
+
}
|
|
587
|
+
function getSessionContext() {
|
|
588
|
+
const context = sessionContextStorage.getStore();
|
|
589
|
+
if (!context) {
|
|
590
|
+
throw new Error("[memoraone-mcp] Session context not initialized");
|
|
591
|
+
}
|
|
592
|
+
return context;
|
|
593
|
+
}
|
|
594
|
+
function getBoundProjectId() {
|
|
595
|
+
return getSessionContext().boundProjectId;
|
|
596
|
+
}
|
|
597
|
+
function setBoundProjectId(id) {
|
|
598
|
+
getSessionContext().boundProjectId = id;
|
|
599
|
+
}
|
|
600
|
+
function setBoundApiKey(key) {
|
|
601
|
+
getSessionContext().boundApiKey = key;
|
|
602
|
+
}
|
|
603
|
+
function getCurrentRunId() {
|
|
604
|
+
return getSessionContext().currentRunId;
|
|
605
|
+
}
|
|
606
|
+
function setCurrentRunId(id) {
|
|
607
|
+
getSessionContext().currentRunId = id;
|
|
608
|
+
}
|
|
609
|
+
function getCurrentProjectId() {
|
|
610
|
+
return getSessionContext().currentProjectId;
|
|
611
|
+
}
|
|
612
|
+
function setCurrentProjectId(id) {
|
|
613
|
+
getSessionContext().currentProjectId = id;
|
|
614
|
+
}
|
|
615
|
+
function setCurrentApiKey(key) {
|
|
616
|
+
getSessionContext().currentApiKey = key;
|
|
617
|
+
}
|
|
618
|
+
function resolveRunId(passed) {
|
|
619
|
+
if (passed) {
|
|
620
|
+
return passed;
|
|
621
|
+
}
|
|
622
|
+
return getCurrentRunId();
|
|
623
|
+
}
|
|
624
|
+
function generateRunId() {
|
|
625
|
+
return crypto2.randomBytes(16).toString("hex");
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// src/tools/handlers/postEvent.ts
|
|
676
629
|
var postEventInputSchema = import_v49.z.object({
|
|
677
630
|
kind: import_v49.z.string().min(1),
|
|
678
631
|
actor: import_v49.z.object({
|
|
@@ -683,7 +636,7 @@ var postEventInputSchema = import_v49.z.object({
|
|
|
683
636
|
metadata: import_v49.z.record(import_v49.z.string(), import_v49.z.any()).optional()
|
|
684
637
|
});
|
|
685
638
|
async function handlePostEvent(client, args) {
|
|
686
|
-
const nonce =
|
|
639
|
+
const nonce = crypto3.randomBytes(8).toString("hex");
|
|
687
640
|
console.error(
|
|
688
641
|
`[memoraone-mcp][debug] tool=memora_post_event toolCallId=unknown nonce=${nonce} stage=before_post`
|
|
689
642
|
);
|
|
@@ -700,7 +653,7 @@ async function handlePostEvent(client, args) {
|
|
|
700
653
|
projectKey,
|
|
701
654
|
actor: {
|
|
702
655
|
type: "agent",
|
|
703
|
-
identifier:
|
|
656
|
+
identifier: config2.agentName,
|
|
704
657
|
...parsed2.actor.id ? { id: parsed2.actor.id } : {}
|
|
705
658
|
},
|
|
706
659
|
...parsed2.metadata ? { metadata: parsed2.metadata } : {}
|
|
@@ -789,13 +742,13 @@ async function handleLogIntent(client, args) {
|
|
|
789
742
|
setCurrentRunId(run_id);
|
|
790
743
|
const body = {
|
|
791
744
|
kind: "note",
|
|
792
|
-
actor: { type:
|
|
745
|
+
actor: { type: config2.agentType, name: config2.agentName },
|
|
793
746
|
concept,
|
|
794
747
|
// MUST be TOP-LEVEL so it populates timeline_events.concept
|
|
795
748
|
message,
|
|
796
749
|
projectKey,
|
|
797
750
|
metadata: {
|
|
798
|
-
source:
|
|
751
|
+
source: config2.source,
|
|
799
752
|
purpose,
|
|
800
753
|
// 'task' | 'decision'
|
|
801
754
|
intent_source: intent_source ?? "cursor_chat",
|
|
@@ -833,11 +786,11 @@ async function handleLogChangeSummary(client, args) {
|
|
|
833
786
|
const body = {
|
|
834
787
|
kind: "note",
|
|
835
788
|
concept: "concept:change_summary",
|
|
836
|
-
actor: { type:
|
|
789
|
+
actor: { type: config2.agentType, name: config2.agentName },
|
|
837
790
|
message,
|
|
838
791
|
projectKey,
|
|
839
792
|
metadata: {
|
|
840
|
-
source:
|
|
793
|
+
source: config2.source,
|
|
841
794
|
purpose: "change_summary",
|
|
842
795
|
tool: "memora_log_change_summary",
|
|
843
796
|
...scope ? { scope } : {},
|
|
@@ -876,11 +829,11 @@ async function handleLogToolResult(client, args) {
|
|
|
876
829
|
const body = {
|
|
877
830
|
kind: "note",
|
|
878
831
|
concept: "concept:tool_result",
|
|
879
|
-
actor: { type:
|
|
832
|
+
actor: { type: config2.agentType, name: config2.agentName },
|
|
880
833
|
message,
|
|
881
834
|
projectKey,
|
|
882
835
|
metadata: {
|
|
883
|
-
source:
|
|
836
|
+
source: config2.source,
|
|
884
837
|
purpose: "tool_result",
|
|
885
838
|
tool: "memora_log_tool_result",
|
|
886
839
|
tool_name: tool,
|
|
@@ -920,11 +873,11 @@ async function handleLogCommand(client, args) {
|
|
|
920
873
|
const body = {
|
|
921
874
|
kind: "note",
|
|
922
875
|
concept: "concept:command",
|
|
923
|
-
actor: { type:
|
|
876
|
+
actor: { type: config2.agentType, name: config2.agentName },
|
|
924
877
|
message,
|
|
925
878
|
projectKey,
|
|
926
879
|
metadata: {
|
|
927
|
-
source:
|
|
880
|
+
source: config2.source,
|
|
928
881
|
purpose: "command",
|
|
929
882
|
tool: "memora_log_command",
|
|
930
883
|
cmd,
|
|
@@ -941,12 +894,340 @@ async function handleLogCommand(client, args) {
|
|
|
941
894
|
|
|
942
895
|
// src/tools/handlers/listProjects.ts
|
|
943
896
|
async function handleListProjects(client) {
|
|
944
|
-
const res = await client.get("/
|
|
897
|
+
const res = await client.get("/v1/projects");
|
|
945
898
|
return res ?? { items: [] };
|
|
946
899
|
}
|
|
947
900
|
|
|
948
901
|
// src/tools/handlers/setProject.ts
|
|
949
902
|
var import_v415 = require("zod/v4");
|
|
903
|
+
|
|
904
|
+
// src/repoFingerprint.ts
|
|
905
|
+
var fs3 = __toESM(require("fs"), 1);
|
|
906
|
+
var path4 = __toESM(require("path"), 1);
|
|
907
|
+
var crypto4 = __toESM(require("crypto"), 1);
|
|
908
|
+
var parseBooleanFlag3 = (value) => {
|
|
909
|
+
if (!value) {
|
|
910
|
+
return false;
|
|
911
|
+
}
|
|
912
|
+
const normalized = value.trim().toLowerCase();
|
|
913
|
+
return ["1", "true", "yes", "on"].includes(normalized);
|
|
914
|
+
};
|
|
915
|
+
var debugEnabled2 = parseBooleanFlag3(process.env.MEMORAONE_DEV_MODE);
|
|
916
|
+
var debugLog = (message) => {
|
|
917
|
+
if (!debugEnabled2) {
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
process.stderr.write(`[memoraone-mcp][debug] ${message}
|
|
921
|
+
`);
|
|
922
|
+
};
|
|
923
|
+
var normalizeRemoteUrl = (remoteUrl) => {
|
|
924
|
+
let normalized = remoteUrl.trim();
|
|
925
|
+
normalized = normalized.replace(/^[a-z]+:\/\//i, "");
|
|
926
|
+
normalized = normalized.replace(/^git@([^:]+):/i, "$1/");
|
|
927
|
+
normalized = normalized.replace(/\.git$/i, "");
|
|
928
|
+
normalized = normalized.replace(/\/+$/, "");
|
|
929
|
+
return normalized.toLowerCase();
|
|
930
|
+
};
|
|
931
|
+
var sha256 = (value) => {
|
|
932
|
+
return crypto4.createHash("sha256").update(value).digest("hex");
|
|
933
|
+
};
|
|
934
|
+
var resolveGitDir = (gitPath) => {
|
|
935
|
+
try {
|
|
936
|
+
const stat2 = fs3.statSync(gitPath);
|
|
937
|
+
if (stat2.isDirectory()) {
|
|
938
|
+
return gitPath;
|
|
939
|
+
}
|
|
940
|
+
if (stat2.isFile()) {
|
|
941
|
+
const content = fs3.readFileSync(gitPath, "utf8");
|
|
942
|
+
const match = content.match(/^gitdir:\s*(.+)$/m);
|
|
943
|
+
if (match) {
|
|
944
|
+
const gitDir = match[1].trim();
|
|
945
|
+
return path4.resolve(path4.dirname(gitPath), gitDir);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
} catch {
|
|
949
|
+
return null;
|
|
950
|
+
}
|
|
951
|
+
return null;
|
|
952
|
+
};
|
|
953
|
+
var findGitRoot = (start) => {
|
|
954
|
+
let current = path4.resolve(start);
|
|
955
|
+
while (true) {
|
|
956
|
+
const gitPath = path4.join(current, ".git");
|
|
957
|
+
if (fs3.existsSync(gitPath)) {
|
|
958
|
+
const gitDir = resolveGitDir(gitPath);
|
|
959
|
+
if (gitDir) {
|
|
960
|
+
return { gitRoot: current, gitDir };
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
const parent = path4.dirname(current);
|
|
964
|
+
if (parent === current) {
|
|
965
|
+
break;
|
|
966
|
+
}
|
|
967
|
+
current = parent;
|
|
968
|
+
}
|
|
969
|
+
return null;
|
|
970
|
+
};
|
|
971
|
+
var readOriginRemote = (gitDir) => {
|
|
972
|
+
const configPath = path4.join(gitDir, "config");
|
|
973
|
+
try {
|
|
974
|
+
const content = fs3.readFileSync(configPath, "utf8");
|
|
975
|
+
const lines = content.split(/\r?\n/);
|
|
976
|
+
let inOrigin = false;
|
|
977
|
+
for (const line of lines) {
|
|
978
|
+
const sectionMatch = line.match(/^\s*\[(.+)]\s*$/);
|
|
979
|
+
if (sectionMatch) {
|
|
980
|
+
inOrigin = sectionMatch[1].trim() === 'remote "origin"';
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
if (inOrigin) {
|
|
984
|
+
const urlMatch = line.match(/^\s*url\s*=\s*(.+)\s*$/);
|
|
985
|
+
if (urlMatch) {
|
|
986
|
+
return urlMatch[1].trim();
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
} catch {
|
|
991
|
+
return null;
|
|
992
|
+
}
|
|
993
|
+
return null;
|
|
994
|
+
};
|
|
995
|
+
function resolveRepoFingerprint(cwd2) {
|
|
996
|
+
const found = findGitRoot(cwd2);
|
|
997
|
+
if (!found) {
|
|
998
|
+
const fallbackPath = path4.resolve(cwd2);
|
|
999
|
+
const fingerprint2 = sha256(fallbackPath);
|
|
1000
|
+
debugLog(`repo fingerprint=${fingerprint2} source=path-fallback`);
|
|
1001
|
+
return {
|
|
1002
|
+
fingerprint: fingerprint2,
|
|
1003
|
+
gitRoot: fallbackPath,
|
|
1004
|
+
source: "path-fallback"
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
const { gitRoot, gitDir } = found;
|
|
1008
|
+
const remoteUrl = readOriginRemote(gitDir);
|
|
1009
|
+
if (remoteUrl) {
|
|
1010
|
+
const normalized = normalizeRemoteUrl(remoteUrl);
|
|
1011
|
+
const fingerprint2 = sha256(normalized);
|
|
1012
|
+
debugLog(`repo fingerprint=${fingerprint2} source=git-remote`);
|
|
1013
|
+
return {
|
|
1014
|
+
fingerprint: fingerprint2,
|
|
1015
|
+
gitRoot,
|
|
1016
|
+
remoteUrl,
|
|
1017
|
+
source: "git-remote"
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
const fingerprint = sha256(path4.resolve(gitRoot));
|
|
1021
|
+
debugLog(`repo fingerprint=${fingerprint} source=path-fallback`);
|
|
1022
|
+
return {
|
|
1023
|
+
fingerprint,
|
|
1024
|
+
gitRoot,
|
|
1025
|
+
source: "path-fallback"
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// src/workspaceMap.ts
|
|
1030
|
+
var fs4 = __toESM(require("fs/promises"), 1);
|
|
1031
|
+
var path5 = __toESM(require("path"), 1);
|
|
1032
|
+
var import_node_os = __toESM(require("os"), 1);
|
|
1033
|
+
var parseBooleanFlag4 = (value) => {
|
|
1034
|
+
if (!value) {
|
|
1035
|
+
return false;
|
|
1036
|
+
}
|
|
1037
|
+
const normalized = value.trim().toLowerCase();
|
|
1038
|
+
return ["1", "true", "yes", "on"].includes(normalized);
|
|
1039
|
+
};
|
|
1040
|
+
var debugEnabled3 = parseBooleanFlag4(process.env.MEMORAONE_DEV_MODE);
|
|
1041
|
+
var debugLog2 = (message) => {
|
|
1042
|
+
if (!debugEnabled3) {
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
process.stderr.write(`[memoraone-mcp][debug] ${message}
|
|
1046
|
+
`);
|
|
1047
|
+
};
|
|
1048
|
+
var fingerprintRegex = /^[0-9a-f]{64}$/i;
|
|
1049
|
+
function getWorkspaceMapPath() {
|
|
1050
|
+
return path5.join(import_node_os.default.homedir(), ".memoraone", "workspaces.json");
|
|
1051
|
+
}
|
|
1052
|
+
var ensureWorkspaceDir = async () => {
|
|
1053
|
+
const dir = path5.dirname(getWorkspaceMapPath());
|
|
1054
|
+
await fs4.mkdir(dir, { recursive: true });
|
|
1055
|
+
};
|
|
1056
|
+
async function acquireWorkspaceMapLock() {
|
|
1057
|
+
const filePath = getWorkspaceMapPath();
|
|
1058
|
+
const lockPath = `${filePath}.lock`;
|
|
1059
|
+
const maxRetries = 10;
|
|
1060
|
+
const retryDelayMs = 50;
|
|
1061
|
+
const maxLockAgeMs = 5e3;
|
|
1062
|
+
await ensureWorkspaceDir();
|
|
1063
|
+
let lockAcquired = false;
|
|
1064
|
+
let retries = 0;
|
|
1065
|
+
while (!lockAcquired && retries < maxRetries) {
|
|
1066
|
+
try {
|
|
1067
|
+
try {
|
|
1068
|
+
const stat2 = await fs4.stat(lockPath);
|
|
1069
|
+
const ageMs = Date.now() - stat2.mtimeMs;
|
|
1070
|
+
if (ageMs > maxLockAgeMs) {
|
|
1071
|
+
await fs4.unlink(lockPath);
|
|
1072
|
+
debugLog2(`removed stale workspace map lock (age: ${ageMs}ms)`);
|
|
1073
|
+
}
|
|
1074
|
+
} catch (err) {
|
|
1075
|
+
if (err?.code !== "ENOENT") {
|
|
1076
|
+
throw err;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
const fd = await fs4.open(lockPath, "wx");
|
|
1080
|
+
await fd.close();
|
|
1081
|
+
lockAcquired = true;
|
|
1082
|
+
} catch (err) {
|
|
1083
|
+
if (err?.code === "EEXIST") {
|
|
1084
|
+
retries++;
|
|
1085
|
+
if (retries < maxRetries) {
|
|
1086
|
+
await new Promise((resolve6) => setTimeout(resolve6, retryDelayMs));
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
throw new Error(
|
|
1090
|
+
`[memoraone-mcp] Failed to acquire workspace map lock after ${maxRetries} retries`
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
throw err;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
return async () => {
|
|
1097
|
+
try {
|
|
1098
|
+
await fs4.unlink(lockPath);
|
|
1099
|
+
} catch (err) {
|
|
1100
|
+
if (err?.code !== "ENOENT") {
|
|
1101
|
+
debugLog2(`failed to release workspace map lock: ${String(err)}`);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
var validateWorkspaceMap = (map, filePath) => {
|
|
1107
|
+
if (!map || typeof map !== "object" || Array.isArray(map)) {
|
|
1108
|
+
throw new Error(
|
|
1109
|
+
`[memoraone-mcp] Invalid workspace map schema in ${filePath}`
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
for (const [fingerprint, entry] of Object.entries(map)) {
|
|
1113
|
+
if (!fingerprintRegex.test(fingerprint)) {
|
|
1114
|
+
throw new Error(
|
|
1115
|
+
`[memoraone-mcp] Invalid workspace fingerprint in ${filePath}`
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
if (typeof entry === "string") {
|
|
1119
|
+
if (!entry.trim()) {
|
|
1120
|
+
throw new Error(
|
|
1121
|
+
`[memoraone-mcp] Invalid workspace projectKey in ${filePath}`
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
1127
|
+
throw new Error(
|
|
1128
|
+
`[memoraone-mcp] Invalid workspace projectKey in ${filePath}`
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
const projectKey = entry.projectKey ?? entry.project_id;
|
|
1132
|
+
if (!projectKey || !projectKey.trim()) {
|
|
1133
|
+
throw new Error(
|
|
1134
|
+
`[memoraone-mcp] Invalid workspace projectKey in ${filePath}`
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
const source = entry.source;
|
|
1138
|
+
if (source !== void 0 && typeof source !== "string") {
|
|
1139
|
+
throw new Error(
|
|
1140
|
+
`[memoraone-mcp] Invalid workspace source in ${filePath}`
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
const linkedAt = entry.linked_at;
|
|
1144
|
+
if (linkedAt !== void 0 && typeof linkedAt !== "string") {
|
|
1145
|
+
throw new Error(
|
|
1146
|
+
`[memoraone-mcp] Invalid workspace linked_at in ${filePath}`
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
async function readWorkspaceMap() {
|
|
1152
|
+
const filePath = getWorkspaceMapPath();
|
|
1153
|
+
try {
|
|
1154
|
+
const content = await fs4.readFile(filePath, "utf8");
|
|
1155
|
+
const parsed2 = JSON.parse(content);
|
|
1156
|
+
validateWorkspaceMap(parsed2, filePath);
|
|
1157
|
+
const typed = parsed2;
|
|
1158
|
+
let migrated = false;
|
|
1159
|
+
const normalized = {};
|
|
1160
|
+
for (const [fingerprint, entry] of Object.entries(typed)) {
|
|
1161
|
+
if (typeof entry === "string") {
|
|
1162
|
+
normalized[fingerprint] = entry;
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
const projectKey = entry.projectKey ?? entry.project_id ?? "";
|
|
1166
|
+
if (entry.project_id && !entry.projectKey) {
|
|
1167
|
+
migrated = true;
|
|
1168
|
+
}
|
|
1169
|
+
normalized[fingerprint] = {
|
|
1170
|
+
...projectKey ? { projectKey } : {},
|
|
1171
|
+
...entry.source ? { source: entry.source } : {},
|
|
1172
|
+
...entry.linked_at ? { linked_at: entry.linked_at } : {}
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
debugLog2(
|
|
1176
|
+
`workspace map loaded path=${filePath} entries=${Object.keys(normalized).length}`
|
|
1177
|
+
);
|
|
1178
|
+
return { map: normalized, needsMigration: migrated };
|
|
1179
|
+
} catch (err) {
|
|
1180
|
+
if (err?.code === "ENOENT") {
|
|
1181
|
+
const emptyMap = {};
|
|
1182
|
+
debugLog2(`workspace map loaded path=${filePath} entries=0`);
|
|
1183
|
+
return { map: emptyMap, needsMigration: false };
|
|
1184
|
+
}
|
|
1185
|
+
if (err instanceof SyntaxError) {
|
|
1186
|
+
throw new Error(
|
|
1187
|
+
`[memoraone-mcp] Failed to parse workspace map at ${filePath}`
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
throw err;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
async function writeWorkspaceMap(map) {
|
|
1194
|
+
const filePath = getWorkspaceMapPath();
|
|
1195
|
+
validateWorkspaceMap(map, filePath);
|
|
1196
|
+
await ensureWorkspaceDir();
|
|
1197
|
+
const tempPath = `${filePath}.tmp`;
|
|
1198
|
+
const content = JSON.stringify(map, null, 2);
|
|
1199
|
+
await fs4.writeFile(tempPath, content, "utf8");
|
|
1200
|
+
await fs4.rename(tempPath, filePath);
|
|
1201
|
+
}
|
|
1202
|
+
async function setProjectIdForFingerprint(args) {
|
|
1203
|
+
const { fingerprint, projectKey, source, linked_at } = args;
|
|
1204
|
+
if (!fingerprintRegex.test(fingerprint)) {
|
|
1205
|
+
throw new Error("[memoraone-mcp] Invalid fingerprint");
|
|
1206
|
+
}
|
|
1207
|
+
if (!projectKey.trim()) {
|
|
1208
|
+
throw new Error("[memoraone-mcp] Invalid projectKey");
|
|
1209
|
+
}
|
|
1210
|
+
const releaseLock = await acquireWorkspaceMapLock();
|
|
1211
|
+
try {
|
|
1212
|
+
const { map, needsMigration } = await readWorkspaceMap();
|
|
1213
|
+
if (needsMigration) {
|
|
1214
|
+
await writeWorkspaceMap(map);
|
|
1215
|
+
}
|
|
1216
|
+
map[fingerprint] = {
|
|
1217
|
+
projectKey,
|
|
1218
|
+
...source ? { source } : {},
|
|
1219
|
+
linked_at: linked_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
1220
|
+
};
|
|
1221
|
+
await writeWorkspaceMap(map);
|
|
1222
|
+
debugLog2(
|
|
1223
|
+
`workspace map set fingerprint=${fingerprint} projectKey=${projectKey}`
|
|
1224
|
+
);
|
|
1225
|
+
} finally {
|
|
1226
|
+
await releaseLock();
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// src/tools/handlers/setProject.ts
|
|
950
1231
|
var setProjectInputSchema = import_v415.z.object({
|
|
951
1232
|
projectKey: import_v415.z.string().min(1).optional(),
|
|
952
1233
|
projectId: import_v415.z.string().min(1).optional()
|
|
@@ -957,6 +1238,13 @@ async function handleSetProject(args) {
|
|
|
957
1238
|
if (!resolvedProjectKey) {
|
|
958
1239
|
throw new Error("projectKey is required");
|
|
959
1240
|
}
|
|
1241
|
+
const requested = resolvedProjectKey.trim();
|
|
1242
|
+
const bound = getBoundProjectId();
|
|
1243
|
+
if (bound !== null && requested !== bound) {
|
|
1244
|
+
throw new Error(
|
|
1245
|
+
`Project switching is disabled (Option A). This MCP process is bound to ${bound}. Start a separate MCP instance/window for ${requested}.`
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
960
1248
|
setCurrentProjectId(resolvedProjectKey);
|
|
961
1249
|
const repo = resolveRepoFingerprint(process.cwd());
|
|
962
1250
|
await setProjectIdForFingerprint({
|
|
@@ -968,9 +1256,91 @@ async function handleSetProject(args) {
|
|
|
968
1256
|
}
|
|
969
1257
|
|
|
970
1258
|
// src/index.ts
|
|
971
|
-
|
|
1259
|
+
var notInitializedResult = {
|
|
1260
|
+
content: [
|
|
1261
|
+
{
|
|
1262
|
+
type: "text",
|
|
1263
|
+
text: "MemoraOne MCP not initialized (project binding missing)."
|
|
1264
|
+
}
|
|
1265
|
+
]
|
|
1266
|
+
};
|
|
1267
|
+
var initializeDiagDumped = false;
|
|
1268
|
+
var uriToPath = (uri) => {
|
|
1269
|
+
if (uri.startsWith("file://")) {
|
|
1270
|
+
return (0, import_node_url2.fileURLToPath)(uri);
|
|
1271
|
+
}
|
|
1272
|
+
return uri;
|
|
1273
|
+
};
|
|
1274
|
+
function getCursorWorkspaceRootFromEnv() {
|
|
1275
|
+
const raw = process.env.WORKSPACE_FOLDER_PATHS;
|
|
1276
|
+
if (raw === void 0 || raw.trim() === "") {
|
|
1277
|
+
return void 0;
|
|
1278
|
+
}
|
|
1279
|
+
const parts = raw.split(path6.delimiter).map((p) => p.trim()).filter(Boolean);
|
|
1280
|
+
const first = parts[0];
|
|
1281
|
+
return first ? path6.resolve(first) : void 0;
|
|
1282
|
+
}
|
|
1283
|
+
function isHeartbeatDebugEnabled() {
|
|
1284
|
+
const value = String(process.env.MEMORAONE_DEBUG_HEARTBEAT ?? "").trim().toLowerCase();
|
|
1285
|
+
return ["1", "true", "yes", "on"].includes(value);
|
|
1286
|
+
}
|
|
1287
|
+
function fingerprintApiKey(apiKey) {
|
|
1288
|
+
return crypto5.createHash("sha256").update(apiKey).digest("hex").slice(0, 12);
|
|
1289
|
+
}
|
|
1290
|
+
function inferIdeType(params) {
|
|
1291
|
+
if (config2.ideType) {
|
|
1292
|
+
return config2.ideType;
|
|
1293
|
+
}
|
|
1294
|
+
const clientInfoName = String(params?.clientInfo?.name ?? "").toLowerCase();
|
|
1295
|
+
const clientInfoVersion = String(params?.clientInfo?.version ?? "").toLowerCase();
|
|
1296
|
+
const termProgram = String(process.env.TERM_PROGRAM ?? "").toLowerCase();
|
|
1297
|
+
const argv = process.argv.join(" ").toLowerCase();
|
|
1298
|
+
const envKeys = Object.keys(process.env);
|
|
1299
|
+
const hasCursorSignals = envKeys.some((key) => key.startsWith("CURSOR_")) || termProgram === "cursor" || clientInfoName.includes("cursor") || clientInfoVersion.includes("cursor") || argv.includes("cursor");
|
|
1300
|
+
if (hasCursorSignals) {
|
|
1301
|
+
return "cursor";
|
|
1302
|
+
}
|
|
1303
|
+
const hasJetBrainsSignals = envKeys.some(
|
|
1304
|
+
(key) => [
|
|
1305
|
+
"JETBRAINS_IDE",
|
|
1306
|
+
"IDEA_INITIAL_DIRECTORY",
|
|
1307
|
+
"JETBRAINS_REMOTE_RUN",
|
|
1308
|
+
"INTELLIJ_ENVIRONMENT_READER"
|
|
1309
|
+
].includes(key)
|
|
1310
|
+
) || String(process.env.TERMINAL_EMULATOR ?? "").toLowerCase().includes("jetbrains") || /(jetbrains|intellij|pycharm|webstorm|goland|rubymine|clion|phpstorm|rider|datagrip)/.test(
|
|
1311
|
+
clientInfoName
|
|
1312
|
+
) || /(jetbrains|intellij|pycharm|webstorm|goland|rubymine|clion|phpstorm|rider|datagrip)/.test(
|
|
1313
|
+
argv
|
|
1314
|
+
);
|
|
1315
|
+
if (hasJetBrainsSignals) {
|
|
1316
|
+
return "jetbrains";
|
|
1317
|
+
}
|
|
1318
|
+
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);
|
|
1319
|
+
if (hasVsCodeSignals) {
|
|
1320
|
+
return "copilot-vscode";
|
|
1321
|
+
}
|
|
1322
|
+
return void 0;
|
|
1323
|
+
}
|
|
1324
|
+
async function sendHeartbeat(client, runtime) {
|
|
972
1325
|
try {
|
|
973
|
-
|
|
1326
|
+
const pid = runtime.projectId?.trim();
|
|
1327
|
+
if (isHeartbeatDebugEnabled()) {
|
|
1328
|
+
process.stderr.write(
|
|
1329
|
+
`[memoraone-mcp][diag] heartbeat projectId=${pid ?? "unknown"} apiKeySource=${runtime.apiKeySource ?? "unknown"} apiKeyFingerprint=${runtime.apiKeyFingerprint ?? "unknown"}
|
|
1330
|
+
`
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
if (!pid) {
|
|
1334
|
+
throw new Error("[memoraone-mcp] Cannot send heartbeat without an active project binding");
|
|
1335
|
+
}
|
|
1336
|
+
const body = {};
|
|
1337
|
+
if (runtime.ideType) body.ide_type = runtime.ideType;
|
|
1338
|
+
await client.post(`/v1/projects/${pid}/heartbeat`, body, {
|
|
1339
|
+
log: false,
|
|
1340
|
+
headers: {
|
|
1341
|
+
"x-project-id": pid
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
974
1344
|
} catch (err) {
|
|
975
1345
|
process.stderr.write(
|
|
976
1346
|
`[memoraone-mcp][info] heartbeat error (silent) ${String(err)}
|
|
@@ -1004,20 +1374,19 @@ function sanitizeArgsSummary(args) {
|
|
|
1004
1374
|
return "[unable to serialize args]";
|
|
1005
1375
|
}
|
|
1006
1376
|
}
|
|
1007
|
-
async function postWorklogEvent(client, message) {
|
|
1377
|
+
async function postWorklogEvent(client, projectId, message) {
|
|
1008
1378
|
try {
|
|
1009
|
-
|
|
1010
|
-
if (!projectKey) {
|
|
1379
|
+
if (!projectId) {
|
|
1011
1380
|
return;
|
|
1012
1381
|
}
|
|
1013
1382
|
const body = {
|
|
1014
1383
|
kind: "note",
|
|
1015
1384
|
concept: "concept:worklog",
|
|
1016
|
-
actor: { type:
|
|
1385
|
+
actor: { type: config2.agentType, name: config2.agentName },
|
|
1017
1386
|
message,
|
|
1018
|
-
projectKey,
|
|
1387
|
+
projectKey: projectId,
|
|
1019
1388
|
metadata: {
|
|
1020
|
-
source:
|
|
1389
|
+
source: config2.source,
|
|
1021
1390
|
purpose: "worklog"
|
|
1022
1391
|
}
|
|
1023
1392
|
};
|
|
@@ -1026,241 +1395,363 @@ async function postWorklogEvent(client, message) {
|
|
|
1026
1395
|
console.error("[memoraone-mcp] worklog error (silent)", err);
|
|
1027
1396
|
}
|
|
1028
1397
|
}
|
|
1029
|
-
function registerToolWithWorklog(server,
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1398
|
+
function registerToolWithWorklog(server, runtime, sessionContext, toolName, description, schema, handler) {
|
|
1399
|
+
const wrapped = async (args) => runWithSessionContext(sessionContext, async () => {
|
|
1400
|
+
if (!runtime.client || !runtime.projectId) return notInitializedResult;
|
|
1401
|
+
if (!config2.worklogEnabled) {
|
|
1402
|
+
return handler(args);
|
|
1403
|
+
}
|
|
1035
1404
|
const argsSummary = sanitizeArgsSummary(args);
|
|
1036
1405
|
const start = Date.now();
|
|
1037
|
-
await postWorklogEvent(client, `tool_start: ${toolName} ${argsSummary}`);
|
|
1406
|
+
await postWorklogEvent(runtime.client, runtime.projectId, `tool_start: ${toolName} ${argsSummary}`);
|
|
1038
1407
|
try {
|
|
1039
1408
|
const result = await handler(args);
|
|
1040
1409
|
const durationMs = Date.now() - start;
|
|
1041
|
-
await postWorklogEvent(client, `tool_end: ${toolName} ok (${durationMs}ms)`);
|
|
1410
|
+
await postWorklogEvent(runtime.client, runtime.projectId, `tool_end: ${toolName} ok (${durationMs}ms)`);
|
|
1042
1411
|
return result;
|
|
1043
1412
|
} catch (err) {
|
|
1044
1413
|
const durationMs = Date.now() - start;
|
|
1045
1414
|
const errorSummary = err?.message || String(err);
|
|
1046
1415
|
const shortError = errorSummary.length > 100 ? errorSummary.slice(0, 100) + "..." : errorSummary;
|
|
1047
|
-
await postWorklogEvent(
|
|
1416
|
+
await postWorklogEvent(
|
|
1417
|
+
runtime.client,
|
|
1418
|
+
runtime.projectId,
|
|
1419
|
+
`tool_end: ${toolName} error (${durationMs}ms): ${shortError}`
|
|
1420
|
+
);
|
|
1048
1421
|
throw err;
|
|
1049
1422
|
}
|
|
1050
1423
|
});
|
|
1424
|
+
server.tool(toolName, description, schema, wrapped);
|
|
1051
1425
|
}
|
|
1052
|
-
async function main() {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
const
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
const binding = resolveProjectIdOrThrow({
|
|
1059
|
-
envProjectKey,
|
|
1060
|
-
repoFileProjectKey,
|
|
1061
|
-
workspaceProjectKey: workspaceResolved.projectKey,
|
|
1062
|
-
repoFilePath
|
|
1426
|
+
async function main(opts = {}) {
|
|
1427
|
+
let bindingReadyResolve = null;
|
|
1428
|
+
let bindingReadyReject = null;
|
|
1429
|
+
const bindingReady = new Promise((resolve6, reject) => {
|
|
1430
|
+
bindingReadyResolve = resolve6;
|
|
1431
|
+
bindingReadyReject = reject;
|
|
1063
1432
|
});
|
|
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
|
-
);
|
|
1433
|
+
const devMode = Boolean(config2.devMode);
|
|
1434
|
+
const sessionLabel = opts.sessionLabel ?? "session";
|
|
1435
|
+
const sessionContext = createSessionRunContext();
|
|
1436
|
+
const runtime = {
|
|
1437
|
+
client: null,
|
|
1438
|
+
projectId: null,
|
|
1439
|
+
apiKeySource: null,
|
|
1440
|
+
apiKeyFingerprint: null,
|
|
1441
|
+
ideType: void 0
|
|
1442
|
+
};
|
|
1443
|
+
let workspaceRoot;
|
|
1078
1444
|
const server = new import_mcp.McpServer({
|
|
1079
1445
|
name: "memoraone-mcp",
|
|
1080
1446
|
version: "1.0.0"
|
|
1081
1447
|
});
|
|
1448
|
+
const resolveFallbackBindingFromInitialize = async (params) => {
|
|
1449
|
+
let fallbackWorkspaceRoot = workspaceRoot;
|
|
1450
|
+
try {
|
|
1451
|
+
const rootsResult = await server.server.listRoots({});
|
|
1452
|
+
const roots = Array.isArray(rootsResult?.roots) ? rootsResult.roots : [];
|
|
1453
|
+
console.error(`[memoraone-mcp] roots/list returned ${roots.length}: ${JSON.stringify(roots)}`);
|
|
1454
|
+
if (!fallbackWorkspaceRoot && roots.length > 0 && roots[0]?.uri) {
|
|
1455
|
+
fallbackWorkspaceRoot = uriToPath(roots[0].uri);
|
|
1456
|
+
console.error("[memoraone-mcp] Workspace resolution strategy: roots/list (first root)");
|
|
1457
|
+
}
|
|
1458
|
+
} catch (e) {
|
|
1459
|
+
console.error("[memoraone-mcp] roots/list failed:", String(e));
|
|
1460
|
+
}
|
|
1461
|
+
console.error(`[memoraone-mcp] process.cwd(): ${process.cwd()}`);
|
|
1462
|
+
console.error(
|
|
1463
|
+
`[memoraone-mcp] WORKSPACE_FOLDER_PATHS: ${process.env.WORKSPACE_FOLDER_PATHS ?? "(unset)"}`
|
|
1464
|
+
);
|
|
1465
|
+
if (!fallbackWorkspaceRoot) {
|
|
1466
|
+
if (params.workspaceFolders?.length) {
|
|
1467
|
+
fallbackWorkspaceRoot = uriToPath(params.workspaceFolders[0].uri);
|
|
1468
|
+
console.error("[memoraone-mcp] Workspace resolution strategy: initialize.workspaceFolders");
|
|
1469
|
+
} else if (params.rootUri) {
|
|
1470
|
+
fallbackWorkspaceRoot = uriToPath(params.rootUri);
|
|
1471
|
+
console.error("[memoraone-mcp] Workspace resolution strategy: initialize.rootUri");
|
|
1472
|
+
} else {
|
|
1473
|
+
const cursorRoot = getCursorWorkspaceRootFromEnv();
|
|
1474
|
+
if (cursorRoot !== void 0) {
|
|
1475
|
+
fallbackWorkspaceRoot = cursorRoot;
|
|
1476
|
+
console.error("[memoraone-mcp] Workspace resolution strategy: env.WORKSPACE_FOLDER_PATHS");
|
|
1477
|
+
} else {
|
|
1478
|
+
fallbackWorkspaceRoot = process.cwd();
|
|
1479
|
+
console.error("[memoraone-mcp] Workspace resolution strategy: process.cwd() fallback");
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
const binding = await resolveAuthoritativeBinding(fallbackWorkspaceRoot);
|
|
1484
|
+
workspaceRoot = binding.workspaceRoot;
|
|
1485
|
+
return binding;
|
|
1486
|
+
};
|
|
1082
1487
|
const registeredToolNames = [];
|
|
1083
|
-
registeredToolNames.push("memora_post_event");
|
|
1084
1488
|
server.tool(
|
|
1085
1489
|
"memora_post_event",
|
|
1086
1490
|
"Forward an event to MemoraOne timeline",
|
|
1087
1491
|
postEventShape,
|
|
1088
|
-
async (args) => {
|
|
1089
|
-
|
|
1492
|
+
async (args) => runWithSessionContext(sessionContext, async () => {
|
|
1493
|
+
if (!runtime.client || !runtime.projectId) return notInitializedResult;
|
|
1494
|
+
const result = await handlePostEvent(runtime.client, args);
|
|
1090
1495
|
return {
|
|
1091
|
-
content: [
|
|
1092
|
-
{
|
|
1093
|
-
type: "text",
|
|
1094
|
-
text: JSON.stringify(result)
|
|
1095
|
-
}
|
|
1096
|
-
]
|
|
1496
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1097
1497
|
};
|
|
1098
|
-
}
|
|
1498
|
+
})
|
|
1099
1499
|
);
|
|
1100
|
-
registeredToolNames.push("
|
|
1500
|
+
registeredToolNames.push("memora_post_event");
|
|
1101
1501
|
server.tool(
|
|
1102
1502
|
"memora_list_projects",
|
|
1103
1503
|
"List projects available to the current API key",
|
|
1104
1504
|
listProjectsShape,
|
|
1105
|
-
async () => {
|
|
1106
|
-
|
|
1505
|
+
async () => runWithSessionContext(sessionContext, async () => {
|
|
1506
|
+
if (!runtime.client || !runtime.projectId) return notInitializedResult;
|
|
1507
|
+
const result = await handleListProjects(runtime.client);
|
|
1107
1508
|
return {
|
|
1108
|
-
content: [
|
|
1109
|
-
{
|
|
1110
|
-
type: "text",
|
|
1111
|
-
text: JSON.stringify(result)
|
|
1112
|
-
}
|
|
1113
|
-
]
|
|
1509
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1114
1510
|
};
|
|
1115
|
-
}
|
|
1511
|
+
})
|
|
1116
1512
|
);
|
|
1117
|
-
registeredToolNames.push("
|
|
1513
|
+
registeredToolNames.push("memora_list_projects");
|
|
1118
1514
|
server.tool(
|
|
1119
1515
|
"memora_set_project",
|
|
1120
1516
|
"Set the current project key for subsequent tool calls",
|
|
1121
1517
|
setProjectShape,
|
|
1122
|
-
async (args) => {
|
|
1518
|
+
async (args) => runWithSessionContext(sessionContext, async () => {
|
|
1519
|
+
if (!runtime.client || !runtime.projectId) return notInitializedResult;
|
|
1123
1520
|
const result = await handleSetProject(args);
|
|
1124
1521
|
return {
|
|
1125
|
-
content: [
|
|
1126
|
-
{
|
|
1127
|
-
type: "text",
|
|
1128
|
-
text: JSON.stringify(result)
|
|
1129
|
-
}
|
|
1130
|
-
]
|
|
1522
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1131
1523
|
};
|
|
1132
|
-
}
|
|
1524
|
+
})
|
|
1133
1525
|
);
|
|
1134
|
-
registeredToolNames.push("
|
|
1526
|
+
registeredToolNames.push("memora_set_project");
|
|
1135
1527
|
registerToolWithWorklog(
|
|
1136
1528
|
server,
|
|
1137
|
-
|
|
1529
|
+
runtime,
|
|
1530
|
+
sessionContext,
|
|
1138
1531
|
"memora_ask_with_memory",
|
|
1139
1532
|
"Ask MemoraOne with project memory context",
|
|
1140
1533
|
askWithMemoryShape,
|
|
1141
1534
|
async (args) => {
|
|
1142
|
-
const result = await handleAskWithMemory(client, args);
|
|
1535
|
+
const result = await handleAskWithMemory(runtime.client, args);
|
|
1143
1536
|
return {
|
|
1144
|
-
content: [
|
|
1145
|
-
{
|
|
1146
|
-
type: "text",
|
|
1147
|
-
text: JSON.stringify(result)
|
|
1148
|
-
}
|
|
1149
|
-
]
|
|
1537
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1150
1538
|
};
|
|
1151
1539
|
}
|
|
1152
1540
|
);
|
|
1153
|
-
registeredToolNames.push("
|
|
1541
|
+
registeredToolNames.push("memora_ask_with_memory");
|
|
1154
1542
|
registerToolWithWorklog(
|
|
1155
1543
|
server,
|
|
1156
|
-
|
|
1544
|
+
runtime,
|
|
1545
|
+
sessionContext,
|
|
1157
1546
|
"memora_log_intent",
|
|
1158
1547
|
"Log a natural-language TASK or DECISION intent to the MemoraOne timeline",
|
|
1159
1548
|
logIntentShape,
|
|
1160
1549
|
async (args) => {
|
|
1161
|
-
const result = await handleLogIntent(client, args);
|
|
1550
|
+
const result = await handleLogIntent(runtime.client, args);
|
|
1162
1551
|
return {
|
|
1163
|
-
content: [
|
|
1164
|
-
{
|
|
1165
|
-
type: "text",
|
|
1166
|
-
text: JSON.stringify(result)
|
|
1167
|
-
}
|
|
1168
|
-
]
|
|
1552
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1169
1553
|
};
|
|
1170
1554
|
}
|
|
1171
1555
|
);
|
|
1172
|
-
registeredToolNames.push("
|
|
1556
|
+
registeredToolNames.push("memora_log_intent");
|
|
1173
1557
|
registerToolWithWorklog(
|
|
1174
1558
|
server,
|
|
1175
|
-
|
|
1559
|
+
runtime,
|
|
1560
|
+
sessionContext,
|
|
1176
1561
|
"memora_log_change_summary",
|
|
1177
1562
|
"Log a concise code change summary to the MemoraOne timeline",
|
|
1178
1563
|
logChangeSummaryShape,
|
|
1179
1564
|
async (args) => {
|
|
1180
|
-
const result = await handleLogChangeSummary(client, args);
|
|
1565
|
+
const result = await handleLogChangeSummary(runtime.client, args);
|
|
1181
1566
|
return {
|
|
1182
|
-
content: [
|
|
1183
|
-
{
|
|
1184
|
-
type: "text",
|
|
1185
|
-
text: JSON.stringify(result)
|
|
1186
|
-
}
|
|
1187
|
-
]
|
|
1567
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1188
1568
|
};
|
|
1189
1569
|
}
|
|
1190
1570
|
);
|
|
1191
|
-
registeredToolNames.push("
|
|
1571
|
+
registeredToolNames.push("memora_log_change_summary");
|
|
1192
1572
|
registerToolWithWorklog(
|
|
1193
1573
|
server,
|
|
1194
|
-
|
|
1574
|
+
runtime,
|
|
1575
|
+
sessionContext,
|
|
1195
1576
|
"memora_log_tool_result",
|
|
1196
1577
|
"Log a tool execution result to the MemoraOne timeline",
|
|
1197
1578
|
logToolResultShape,
|
|
1198
1579
|
async (args) => {
|
|
1199
|
-
const result = await handleLogToolResult(client, args);
|
|
1580
|
+
const result = await handleLogToolResult(runtime.client, args);
|
|
1200
1581
|
return {
|
|
1201
|
-
content: [
|
|
1202
|
-
{
|
|
1203
|
-
type: "text",
|
|
1204
|
-
text: JSON.stringify(result)
|
|
1205
|
-
}
|
|
1206
|
-
]
|
|
1582
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1207
1583
|
};
|
|
1208
1584
|
}
|
|
1209
1585
|
);
|
|
1210
|
-
registeredToolNames.push("
|
|
1586
|
+
registeredToolNames.push("memora_log_tool_result");
|
|
1211
1587
|
registerToolWithWorklog(
|
|
1212
1588
|
server,
|
|
1213
|
-
|
|
1589
|
+
runtime,
|
|
1590
|
+
sessionContext,
|
|
1214
1591
|
"memora_log_command",
|
|
1215
1592
|
"Log a command execution to the MemoraOne timeline",
|
|
1216
1593
|
logCommandShape,
|
|
1217
1594
|
async (args) => {
|
|
1218
|
-
const result = await handleLogCommand(client, args);
|
|
1595
|
+
const result = await handleLogCommand(runtime.client, args);
|
|
1219
1596
|
return {
|
|
1220
|
-
content: [
|
|
1221
|
-
{
|
|
1222
|
-
type: "text",
|
|
1223
|
-
text: JSON.stringify(result)
|
|
1224
|
-
}
|
|
1225
|
-
]
|
|
1597
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1226
1598
|
};
|
|
1227
1599
|
}
|
|
1228
1600
|
);
|
|
1601
|
+
registeredToolNames.push("memora_log_command");
|
|
1602
|
+
server.server.setRequestHandler(
|
|
1603
|
+
import_types.InitializeRequestSchema,
|
|
1604
|
+
async (request) => runWithSessionContext(sessionContext, async () => {
|
|
1605
|
+
try {
|
|
1606
|
+
const params = request.params;
|
|
1607
|
+
runtime.ideType = inferIdeType(params);
|
|
1608
|
+
if (!initializeDiagDumped) {
|
|
1609
|
+
initializeDiagDumped = true;
|
|
1610
|
+
const folders = Array.isArray(params.workspaceFolders) ? params.workspaceFolders.map((f) => ({ name: f?.name, uri: f?.uri })) : params.workspaceFolders;
|
|
1611
|
+
const capKeys = params.capabilities && typeof params.capabilities === "object" ? Object.keys(params.capabilities) : [];
|
|
1612
|
+
const cursorEnv = Object.keys(process.env).filter((k) => k.startsWith("CURSOR_")).reduce((acc, k) => {
|
|
1613
|
+
acc[k] = process.env[k] ?? "";
|
|
1614
|
+
return acc;
|
|
1615
|
+
}, {});
|
|
1616
|
+
const vscodeEnv = Object.keys(process.env).filter((k) => k.startsWith("VSCODE_")).reduce((acc, k) => {
|
|
1617
|
+
acc[k] = process.env[k] ?? "";
|
|
1618
|
+
return acc;
|
|
1619
|
+
}, {});
|
|
1620
|
+
console.error("[memoraone-mcp][diag] Initialize dump (once per process):");
|
|
1621
|
+
console.error(`[memoraone-mcp][diag] params.rootUri: ${JSON.stringify(params.rootUri)}`);
|
|
1622
|
+
console.error(`[memoraone-mcp][diag] params.workspaceFolders: ${JSON.stringify(folders)}`);
|
|
1623
|
+
console.error(`[memoraone-mcp][diag] params.clientInfo: ${JSON.stringify(params.clientInfo)}`);
|
|
1624
|
+
console.error(`[memoraone-mcp][diag] params.capabilities (keys): ${JSON.stringify(capKeys)}`);
|
|
1625
|
+
console.error(`[memoraone-mcp][diag] process.cwd(): ${process.cwd()}`);
|
|
1626
|
+
console.error(`[memoraone-mcp][diag] process.execPath: ${process.execPath}`);
|
|
1627
|
+
console.error(`[memoraone-mcp][diag] process.argv: ${JSON.stringify(process.argv)}`);
|
|
1628
|
+
console.error(`[memoraone-mcp][diag] process.env.WORKSPACE_FOLDER_PATHS: ${JSON.stringify(process.env.WORKSPACE_FOLDER_PATHS)}`);
|
|
1629
|
+
console.error(`[memoraone-mcp][diag] process.env.PWD: ${JSON.stringify(process.env.PWD)}`);
|
|
1630
|
+
console.error(`[memoraone-mcp][diag] process.env.HOME: ${JSON.stringify(process.env.HOME)}`);
|
|
1631
|
+
console.error(`[memoraone-mcp][diag] process.env.CURSOR_*: ${JSON.stringify(cursorEnv)}`);
|
|
1632
|
+
console.error(`[memoraone-mcp][diag] process.env.VSCODE_*: ${JSON.stringify(vscodeEnv)}`);
|
|
1633
|
+
console.error(`[memoraone-mcp][diag] process.env.TERM_PROGRAM: ${JSON.stringify(process.env.TERM_PROGRAM)}`);
|
|
1634
|
+
console.error(`[memoraone-mcp][diag] process.env.MEMORAONE_M1_PATH: ${JSON.stringify(process.env.MEMORAONE_M1_PATH)}`);
|
|
1635
|
+
}
|
|
1636
|
+
const debugAuth = ["1", "true", "yes", "on"].includes(
|
|
1637
|
+
String(process.env.MEMORAONE_DEBUG_AUTH ?? "").trim().toLowerCase()
|
|
1638
|
+
);
|
|
1639
|
+
const debugLog3 = config2.devMode || debugAuth;
|
|
1640
|
+
const binding = opts.authoritativeBinding ? opts.authoritativeBinding : await resolveFallbackBindingFromInitialize(params);
|
|
1641
|
+
const apiKeyToUse = binding.apiKey;
|
|
1642
|
+
if (!apiKeyToUse) {
|
|
1643
|
+
throw new Error(
|
|
1644
|
+
"[memoraone-mcp] No actor key. Set MEMORAONE_API_KEY or MEMORA_API_KEY, or add MEMORAONE_API_KEY/api_key to memoraone.m1"
|
|
1645
|
+
);
|
|
1646
|
+
}
|
|
1647
|
+
if (debugLog3) {
|
|
1648
|
+
console.error(
|
|
1649
|
+
"[memoraone-mcp][debug] Resolved actor key from " + (binding.apiKeySource === "env" ? "ENV" : "memoraone.m1")
|
|
1650
|
+
);
|
|
1651
|
+
}
|
|
1652
|
+
const projectId = binding.projectId;
|
|
1653
|
+
const existing = getBoundProjectId();
|
|
1654
|
+
if (existing !== null && existing !== projectId) {
|
|
1655
|
+
const requestedRoot = binding.workspaceRoot ?? workspaceRoot ?? process.cwd();
|
|
1656
|
+
const action = "Open this repo in a separate window or configure a separate MCP server instance per root.";
|
|
1657
|
+
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.`;
|
|
1658
|
+
console.error(
|
|
1659
|
+
`[memoraone-mcp][ERROR] Option A conflict: boundProjectId=${existing} requestedProjectId=${projectId} workspaceRoot=${requestedRoot}. ${action}`
|
|
1660
|
+
);
|
|
1661
|
+
bindingReadyReject?.(new Error(errMsg));
|
|
1662
|
+
setImmediate(() => process.exit(1));
|
|
1663
|
+
throw new Error(errMsg);
|
|
1664
|
+
}
|
|
1665
|
+
if (existing === null) {
|
|
1666
|
+
setBoundProjectId(projectId);
|
|
1667
|
+
setBoundApiKey(apiKeyToUse);
|
|
1668
|
+
console.error(
|
|
1669
|
+
`[memoraone-mcp] ${sessionLabel} bound to project ${projectId} (Option A: single-project binding)`
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
setCurrentProjectId(projectId);
|
|
1673
|
+
setCurrentApiKey(apiKeyToUse);
|
|
1674
|
+
runtime.projectId = projectId;
|
|
1675
|
+
runtime.apiKeySource = binding.apiKeySource;
|
|
1676
|
+
runtime.apiKeyFingerprint = fingerprintApiKey(apiKeyToUse);
|
|
1677
|
+
runtime.client = new memoraClient_default(config2, projectId, apiKeyToUse);
|
|
1678
|
+
workspaceRoot = binding.workspaceRoot;
|
|
1679
|
+
process.stderr.write(
|
|
1680
|
+
`[memoraone-mcp] registering workspace source bindingSource=${binding.bindingSource} workspaceRoot=${workspaceRoot ?? "(unset)"} m1Path=${binding.m1Path}
|
|
1681
|
+
`
|
|
1682
|
+
);
|
|
1683
|
+
await registerRepoSource(
|
|
1684
|
+
runtime.client,
|
|
1685
|
+
runtime.projectId,
|
|
1686
|
+
binding.workspaceRoot,
|
|
1687
|
+
runtime.ideType
|
|
1688
|
+
);
|
|
1689
|
+
if (debugAuth) {
|
|
1690
|
+
console.error("[memoraone-mcp][auth] repo root:", binding.workspaceRoot);
|
|
1691
|
+
console.error("[memoraone-mcp][auth] project_id:", projectId);
|
|
1692
|
+
console.error("[memoraone-mcp][auth] api_key source:", binding.apiKeySource);
|
|
1693
|
+
}
|
|
1694
|
+
console.error(
|
|
1695
|
+
`[memoraone-mcp] ${sessionLabel} authoritative binding: project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}`
|
|
1696
|
+
);
|
|
1697
|
+
bindingReadyResolve?.(runtime.client);
|
|
1698
|
+
return server.server._oninitialize(request);
|
|
1699
|
+
} catch (err) {
|
|
1700
|
+
bindingReadyReject?.(err);
|
|
1701
|
+
throw err;
|
|
1702
|
+
}
|
|
1703
|
+
})
|
|
1704
|
+
);
|
|
1229
1705
|
if (devMode) {
|
|
1230
1706
|
console.error("[memoraone-mcp] Starting MCP server with", registeredToolNames.length, "tools");
|
|
1231
1707
|
}
|
|
1232
|
-
const transport = new import_stdio.StdioServerTransport();
|
|
1708
|
+
const transport = opts.transport ?? new import_stdio.StdioServerTransport();
|
|
1233
1709
|
await server.connect(transport);
|
|
1710
|
+
const activeClient = await bindingReady;
|
|
1234
1711
|
let heartbeatInterval = null;
|
|
1235
|
-
if (
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1712
|
+
if (config2.heartbeatEnabled) {
|
|
1713
|
+
await sendHeartbeat(activeClient, runtime);
|
|
1714
|
+
const intervalMs = Number.isFinite(config2.heartbeatIntervalMs) ? Math.max(1e3, config2.heartbeatIntervalMs) : 3e4;
|
|
1715
|
+
console.error(
|
|
1716
|
+
`[memoraone-mcp] ${sessionLabel} owns heartbeat for project ${runtime.projectId} interval=${intervalMs}ms`
|
|
1717
|
+
);
|
|
1241
1718
|
heartbeatInterval = setInterval(() => {
|
|
1242
|
-
sendHeartbeat(
|
|
1719
|
+
sendHeartbeat(activeClient, runtime).catch(() => {
|
|
1243
1720
|
});
|
|
1244
1721
|
}, intervalMs);
|
|
1245
|
-
globalThis.__memoraHeartbeatInterval = heartbeatInterval;
|
|
1246
1722
|
}
|
|
1247
|
-
const
|
|
1723
|
+
const onSigInt = () => shutdown("SIGINT");
|
|
1724
|
+
const onSigTerm = () => shutdown("SIGTERM");
|
|
1725
|
+
const shutdown = (signal, exitProcess = true) => {
|
|
1726
|
+
process.off("SIGINT", onSigInt);
|
|
1727
|
+
process.off("SIGTERM", onSigTerm);
|
|
1248
1728
|
if (heartbeatInterval) clearInterval(heartbeatInterval);
|
|
1249
|
-
|
|
1250
|
-
|
|
1729
|
+
heartbeatInterval = null;
|
|
1730
|
+
if (runtime.projectId) {
|
|
1731
|
+
console.error(`[memoraone-mcp] ${sessionLabel} released heartbeat for project ${runtime.projectId}`);
|
|
1251
1732
|
}
|
|
1252
1733
|
if (devMode) {
|
|
1253
|
-
console.error(`[memoraone-mcp]
|
|
1734
|
+
console.error(`[memoraone-mcp] ${sessionLabel} received ${signal}, shutting down`);
|
|
1735
|
+
}
|
|
1736
|
+
if (exitProcess) {
|
|
1737
|
+
process.exit(0);
|
|
1254
1738
|
}
|
|
1255
|
-
process.exit(0);
|
|
1256
1739
|
};
|
|
1257
|
-
process.on("SIGINT",
|
|
1258
|
-
process.on("SIGTERM",
|
|
1740
|
+
process.on("SIGINT", onSigInt);
|
|
1741
|
+
process.on("SIGTERM", onSigTerm);
|
|
1259
1742
|
if (devMode) {
|
|
1260
1743
|
console.error("[memoraone-mcp] MCP server ready");
|
|
1261
1744
|
}
|
|
1745
|
+
if (opts.sessionSocket) {
|
|
1746
|
+
await new Promise((resolve6) => {
|
|
1747
|
+
opts.sessionSocket.once("close", () => {
|
|
1748
|
+
shutdown("session closed", false);
|
|
1749
|
+
resolve6();
|
|
1750
|
+
});
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1262
1753
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1754
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1755
|
+
0 && (module.exports = {
|
|
1756
|
+
main
|
|
1266
1757
|
});
|