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