@memoraone/mcp 0.1.22 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +584 -57
- package/dist/daemon.cjs +204 -69
- package/dist/index.cjs +93 -66
- package/package.json +11 -10
package/dist/daemon.cjs
CHANGED
|
@@ -41,7 +41,28 @@ var os = __toESM(require("os"), 1);
|
|
|
41
41
|
var path = __toESM(require("path"), 1);
|
|
42
42
|
var fs = __toESM(require("fs"), 1);
|
|
43
43
|
var BASE_DIR = process.env.MEMORAONE_MCP_LOCK_DIR || path.join(os.homedir(), ".memoraone-mcp");
|
|
44
|
-
|
|
44
|
+
var IDE_TYPES = ["cursor", "copilot-vscode", "jetbrains"];
|
|
45
|
+
var IDE_TYPE_SET = new Set(IDE_TYPES);
|
|
46
|
+
function parseIdeType(value) {
|
|
47
|
+
if (value === void 0 || value.trim() === "" || !IDE_TYPE_SET.has(value)) {
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
function resolveIdeTypeFromEnv(env2 = process.env) {
|
|
53
|
+
return parseIdeType(env2.MEMORAONE_IDE_TYPE);
|
|
54
|
+
}
|
|
55
|
+
function parseIdeTypeFromArgv(args) {
|
|
56
|
+
const idx = args.indexOf("--ide");
|
|
57
|
+
if (idx === -1 || idx + 1 >= args.length) {
|
|
58
|
+
return void 0;
|
|
59
|
+
}
|
|
60
|
+
return parseIdeType(args[idx + 1]);
|
|
61
|
+
}
|
|
62
|
+
function getSocketPath(projectId, ideType) {
|
|
63
|
+
if (ideType) {
|
|
64
|
+
return path.join(BASE_DIR, `mcp-${projectId}-${ideType}.sock`);
|
|
65
|
+
}
|
|
45
66
|
return path.join(BASE_DIR, `mcp-${projectId}.sock`);
|
|
46
67
|
}
|
|
47
68
|
function ensureBaseDir() {
|
|
@@ -238,7 +259,6 @@ function decodeResolvedBinding(value) {
|
|
|
238
259
|
|
|
239
260
|
// src/index.ts
|
|
240
261
|
var path7 = __toESM(require("path"), 1);
|
|
241
|
-
var crypto5 = __toESM(require("crypto"), 1);
|
|
242
262
|
var import_node_url2 = require("url");
|
|
243
263
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
244
264
|
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
@@ -1536,47 +1556,123 @@ function handleBindingStatus(binding) {
|
|
|
1536
1556
|
return buildBindingStatus(binding);
|
|
1537
1557
|
}
|
|
1538
1558
|
|
|
1539
|
-
// src/
|
|
1540
|
-
var
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
type: "text",
|
|
1544
|
-
text: "MemoraOne MCP not initialized (project binding missing)."
|
|
1545
|
-
}
|
|
1546
|
-
]
|
|
1547
|
-
};
|
|
1548
|
-
var initializeDiagDumped = false;
|
|
1549
|
-
var uriToPath = (uri) => {
|
|
1550
|
-
if (uri.startsWith("file://")) {
|
|
1551
|
-
return (0, import_node_url2.fileURLToPath)(uri);
|
|
1552
|
-
}
|
|
1553
|
-
return uri;
|
|
1554
|
-
};
|
|
1555
|
-
function getCursorWorkspaceRootFromEnv() {
|
|
1556
|
-
const raw = process.env.WORKSPACE_FOLDER_PATHS;
|
|
1557
|
-
if (raw === void 0 || raw.trim() === "") {
|
|
1558
|
-
return void 0;
|
|
1559
|
-
}
|
|
1560
|
-
const parts = raw.split(path7.delimiter).map((p) => p.trim()).filter(Boolean);
|
|
1561
|
-
const first = parts[0];
|
|
1562
|
-
return first ? path7.resolve(first) : void 0;
|
|
1559
|
+
// src/heartbeat.ts
|
|
1560
|
+
var crypto5 = __toESM(require("crypto"), 1);
|
|
1561
|
+
function fingerprintApiKey(apiKey) {
|
|
1562
|
+
return crypto5.createHash("sha256").update(apiKey).digest("hex").slice(0, 12);
|
|
1563
1563
|
}
|
|
1564
1564
|
function isHeartbeatDebugEnabled() {
|
|
1565
1565
|
const value = String(process.env.MEMORAONE_DEBUG_HEARTBEAT ?? "").trim().toLowerCase();
|
|
1566
1566
|
return ["1", "true", "yes", "on"].includes(value);
|
|
1567
1567
|
}
|
|
1568
|
-
function
|
|
1569
|
-
return
|
|
1568
|
+
function resolveHeartbeatIntervalMs() {
|
|
1569
|
+
return Number.isFinite(config2.heartbeatIntervalMs) ? Math.max(1e3, config2.heartbeatIntervalMs) : 3e4;
|
|
1570
|
+
}
|
|
1571
|
+
function isDaemonIdleShutdownAllowed() {
|
|
1572
|
+
return !config2.heartbeatEnabled;
|
|
1570
1573
|
}
|
|
1571
|
-
function
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
+
async function sendProjectHeartbeat(client, ctx) {
|
|
1575
|
+
try {
|
|
1576
|
+
const pid = ctx.projectId?.trim();
|
|
1577
|
+
if (isHeartbeatDebugEnabled()) {
|
|
1578
|
+
process.stderr.write(
|
|
1579
|
+
`[memoraone-mcp][diag] heartbeat projectId=${pid ?? "unknown"} apiKeySource=${ctx.apiKeySource ?? "unknown"} apiKeyFingerprint=${ctx.apiKeyFingerprint ?? "unknown"} ideType=${ctx.ideType ?? "unknown"}
|
|
1580
|
+
`
|
|
1581
|
+
);
|
|
1582
|
+
}
|
|
1583
|
+
if (!pid) {
|
|
1584
|
+
throw new Error("[memoraone-mcp] Cannot send heartbeat without an active project binding");
|
|
1585
|
+
}
|
|
1586
|
+
const body = {};
|
|
1587
|
+
if (ctx.ideType) body.ide_type = ctx.ideType;
|
|
1588
|
+
await client.post(`/v1/projects/${pid}/heartbeat`, body, {
|
|
1589
|
+
log: false,
|
|
1590
|
+
headers: {
|
|
1591
|
+
"x-project-id": pid
|
|
1592
|
+
}
|
|
1593
|
+
});
|
|
1594
|
+
} catch (err) {
|
|
1595
|
+
process.stderr.write(
|
|
1596
|
+
`[memoraone-mcp][info] heartbeat error (silent) ${String(err)}
|
|
1597
|
+
`
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
function createDaemonHeartbeat(opts) {
|
|
1602
|
+
let interval = null;
|
|
1603
|
+
let client = null;
|
|
1604
|
+
const ctx = {
|
|
1605
|
+
projectId: opts.binding.projectId,
|
|
1606
|
+
ideType: opts.ideType,
|
|
1607
|
+
apiKeySource: opts.binding.apiKeySource,
|
|
1608
|
+
apiKeyFingerprint: opts.binding.apiKey ? fingerprintApiKey(opts.binding.apiKey) : null
|
|
1609
|
+
};
|
|
1610
|
+
const log2 = opts.onLog ?? ((msg) => {
|
|
1611
|
+
process.stderr.write(`[memoraone-mcp][daemon-heartbeat] ${msg}
|
|
1612
|
+
`);
|
|
1613
|
+
});
|
|
1614
|
+
const start = async () => {
|
|
1615
|
+
if (!config2.heartbeatEnabled) {
|
|
1616
|
+
log2("disabled by config");
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
if (interval) {
|
|
1620
|
+
log2("already running (skipped duplicate start)");
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
const apiKey = opts.binding.apiKey;
|
|
1624
|
+
if (!apiKey) {
|
|
1625
|
+
log2("cannot start: no api key in binding");
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
const projectId = opts.binding.projectId;
|
|
1629
|
+
client = new memoraClient_default(config2, projectId, apiKey);
|
|
1630
|
+
const intervalMs = resolveHeartbeatIntervalMs();
|
|
1631
|
+
log2(
|
|
1632
|
+
`daemon owns heartbeat for project=${projectId} ideType=${ctx.ideType ?? "unknown"} interval=${intervalMs}ms`
|
|
1633
|
+
);
|
|
1634
|
+
void sendProjectHeartbeat(client, ctx);
|
|
1635
|
+
interval = setInterval(() => {
|
|
1636
|
+
if (!client) return;
|
|
1637
|
+
sendProjectHeartbeat(client, ctx).catch(() => {
|
|
1638
|
+
});
|
|
1639
|
+
}, intervalMs);
|
|
1640
|
+
};
|
|
1641
|
+
const stop = () => {
|
|
1642
|
+
if (interval) {
|
|
1643
|
+
clearInterval(interval);
|
|
1644
|
+
interval = null;
|
|
1645
|
+
}
|
|
1646
|
+
client = null;
|
|
1647
|
+
log2(`daemon released heartbeat for project=${opts.binding.projectId}`);
|
|
1648
|
+
};
|
|
1649
|
+
const isRunning = () => interval !== null;
|
|
1650
|
+
const setIdeType = (ideType) => {
|
|
1651
|
+
if (ctx.ideType === ideType) {
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
ctx.ideType = ideType;
|
|
1655
|
+
log2(`daemon heartbeat ideType updated to ${ideType}`);
|
|
1656
|
+
if (client) {
|
|
1657
|
+
void sendProjectHeartbeat(client, ctx);
|
|
1658
|
+
}
|
|
1659
|
+
};
|
|
1660
|
+
const getIdeType = () => ctx.ideType;
|
|
1661
|
+
return { start, stop, isRunning, setIdeType, getIdeType };
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
// src/ideType.ts
|
|
1665
|
+
function inferIdeType(params, options = {}) {
|
|
1666
|
+
const env2 = options.env ?? process.env;
|
|
1667
|
+
const argv = (options.argv ?? process.argv).join(" ").toLowerCase();
|
|
1668
|
+
const configIdeType = options.configIdeType ?? config2.ideType;
|
|
1669
|
+
if (configIdeType) {
|
|
1670
|
+
return configIdeType;
|
|
1574
1671
|
}
|
|
1575
1672
|
const clientInfoName = String(params?.clientInfo?.name ?? "").toLowerCase();
|
|
1576
1673
|
const clientInfoVersion = String(params?.clientInfo?.version ?? "").toLowerCase();
|
|
1577
|
-
const termProgram = String(
|
|
1578
|
-
const
|
|
1579
|
-
const envKeys = Object.keys(process.env);
|
|
1674
|
+
const termProgram = String(env2.TERM_PROGRAM ?? "").toLowerCase();
|
|
1675
|
+
const envKeys = Object.keys(env2);
|
|
1580
1676
|
const hasCursorSignals = envKeys.some((key) => key.startsWith("CURSOR_")) || termProgram === "cursor" || clientInfoName.includes("cursor") || clientInfoVersion.includes("cursor") || argv.includes("cursor");
|
|
1581
1677
|
if (hasCursorSignals) {
|
|
1582
1678
|
return "cursor";
|
|
@@ -1588,7 +1684,7 @@ function inferIdeType(params) {
|
|
|
1588
1684
|
"JETBRAINS_REMOTE_RUN",
|
|
1589
1685
|
"INTELLIJ_ENVIRONMENT_READER"
|
|
1590
1686
|
].includes(key)
|
|
1591
|
-
) || String(
|
|
1687
|
+
) || String(env2.TERMINAL_EMULATOR ?? "").toLowerCase().includes("jetbrains") || /(jetbrains|intellij|pycharm|webstorm|goland|rubymine|clion|phpstorm|rider|datagrip)/.test(
|
|
1592
1688
|
clientInfoName
|
|
1593
1689
|
) || /(jetbrains|intellij|pycharm|webstorm|goland|rubymine|clion|phpstorm|rider|datagrip)/.test(
|
|
1594
1690
|
argv
|
|
@@ -1602,32 +1698,31 @@ function inferIdeType(params) {
|
|
|
1602
1698
|
}
|
|
1603
1699
|
return void 0;
|
|
1604
1700
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
);
|
|
1613
|
-
}
|
|
1614
|
-
if (!pid) {
|
|
1615
|
-
throw new Error("[memoraone-mcp] Cannot send heartbeat without an active project binding");
|
|
1701
|
+
|
|
1702
|
+
// src/index.ts
|
|
1703
|
+
var notInitializedResult = {
|
|
1704
|
+
content: [
|
|
1705
|
+
{
|
|
1706
|
+
type: "text",
|
|
1707
|
+
text: "MemoraOne MCP not initialized (project binding missing)."
|
|
1616
1708
|
}
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
}
|
|
1624
|
-
});
|
|
1625
|
-
} catch (err) {
|
|
1626
|
-
process.stderr.write(
|
|
1627
|
-
`[memoraone-mcp][info] heartbeat error (silent) ${String(err)}
|
|
1628
|
-
`
|
|
1629
|
-
);
|
|
1709
|
+
]
|
|
1710
|
+
};
|
|
1711
|
+
var initializeDiagDumped = false;
|
|
1712
|
+
var uriToPath = (uri) => {
|
|
1713
|
+
if (uri.startsWith("file://")) {
|
|
1714
|
+
return (0, import_node_url2.fileURLToPath)(uri);
|
|
1630
1715
|
}
|
|
1716
|
+
return uri;
|
|
1717
|
+
};
|
|
1718
|
+
function getCursorWorkspaceRootFromEnv() {
|
|
1719
|
+
const raw = process.env.WORKSPACE_FOLDER_PATHS;
|
|
1720
|
+
if (raw === void 0 || raw.trim() === "") {
|
|
1721
|
+
return void 0;
|
|
1722
|
+
}
|
|
1723
|
+
const parts = raw.split(path7.delimiter).map((p) => p.trim()).filter(Boolean);
|
|
1724
|
+
const first = parts[0];
|
|
1725
|
+
return first ? path7.resolve(first) : void 0;
|
|
1631
1726
|
}
|
|
1632
1727
|
function redactSensitiveFields(obj) {
|
|
1633
1728
|
if (obj === null || obj === void 0) return obj;
|
|
@@ -1939,6 +2034,9 @@ async function main(opts = {}) {
|
|
|
1939
2034
|
try {
|
|
1940
2035
|
const params = request.params;
|
|
1941
2036
|
runtime.ideType = inferIdeType(params);
|
|
2037
|
+
if (runtime.ideType && opts.onSessionIdeTypeKnown) {
|
|
2038
|
+
opts.onSessionIdeTypeKnown(runtime.ideType);
|
|
2039
|
+
}
|
|
1942
2040
|
if (!initializeDiagDumped) {
|
|
1943
2041
|
initializeDiagDumped = true;
|
|
1944
2042
|
const folders = Array.isArray(params.workspaceFolders) ? params.workspaceFolders.map((f) => ({ name: f?.name, uri: f?.uri })) : params.workspaceFolders;
|
|
@@ -2046,14 +2144,25 @@ async function main(opts = {}) {
|
|
|
2046
2144
|
await server.connect(transport);
|
|
2047
2145
|
const activeClient = await bindingReady;
|
|
2048
2146
|
let heartbeatInterval = null;
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2147
|
+
const daemonSession = Boolean(opts.sessionSocket);
|
|
2148
|
+
if (config2.heartbeatEnabled && daemonSession) {
|
|
2149
|
+
console.error(
|
|
2150
|
+
`[memoraone-mcp] ${sessionLabel} defers heartbeat to daemon for project ${runtime.projectId}`
|
|
2151
|
+
);
|
|
2152
|
+
} else if (config2.heartbeatEnabled) {
|
|
2153
|
+
const intervalMs = resolveHeartbeatIntervalMs();
|
|
2154
|
+
const heartbeatCtx = {
|
|
2155
|
+
projectId: runtime.projectId,
|
|
2156
|
+
ideType: runtime.ideType,
|
|
2157
|
+
apiKeySource: runtime.apiKeySource,
|
|
2158
|
+
apiKeyFingerprint: runtime.apiKeyFingerprint
|
|
2159
|
+
};
|
|
2052
2160
|
console.error(
|
|
2053
2161
|
`[memoraone-mcp] ${sessionLabel} owns heartbeat for project ${runtime.projectId} interval=${intervalMs}ms`
|
|
2054
2162
|
);
|
|
2163
|
+
await sendProjectHeartbeat(activeClient, heartbeatCtx);
|
|
2055
2164
|
heartbeatInterval = setInterval(() => {
|
|
2056
|
-
|
|
2165
|
+
sendProjectHeartbeat(activeClient, heartbeatCtx).catch(() => {
|
|
2057
2166
|
});
|
|
2058
2167
|
}, intervalMs);
|
|
2059
2168
|
}
|
|
@@ -2062,10 +2171,14 @@ async function main(opts = {}) {
|
|
|
2062
2171
|
const shutdown = (signal, exitProcess = true) => {
|
|
2063
2172
|
process.off("SIGINT", onSigInt);
|
|
2064
2173
|
process.off("SIGTERM", onSigTerm);
|
|
2065
|
-
if (heartbeatInterval)
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2174
|
+
if (heartbeatInterval) {
|
|
2175
|
+
clearInterval(heartbeatInterval);
|
|
2176
|
+
heartbeatInterval = null;
|
|
2177
|
+
if (runtime.projectId) {
|
|
2178
|
+
console.error(
|
|
2179
|
+
`[memoraone-mcp] ${sessionLabel} released session heartbeat for project ${runtime.projectId}`
|
|
2180
|
+
);
|
|
2181
|
+
}
|
|
2069
2182
|
}
|
|
2070
2183
|
if (devMode) {
|
|
2071
2184
|
console.error(`[memoraone-mcp] ${sessionLabel} received ${signal}, shutting down`);
|
|
@@ -2141,7 +2254,8 @@ async function ensureSocketClean(socketPath) {
|
|
|
2141
2254
|
async function runDaemon() {
|
|
2142
2255
|
const projectId = parseProjectIdFromArgv();
|
|
2143
2256
|
const binding = parseBindingFromEnv(projectId);
|
|
2144
|
-
const
|
|
2257
|
+
const ideType = parseIdeTypeFromArgv(process.argv.slice(2)) ?? config2.ideType ?? resolveIdeTypeFromEnv();
|
|
2258
|
+
const socketPath = getSocketPath(projectId, ideType);
|
|
2145
2259
|
let nextSessionId = 1;
|
|
2146
2260
|
let activeSessions = 0;
|
|
2147
2261
|
let idleTimer = null;
|
|
@@ -2152,6 +2266,14 @@ async function runDaemon() {
|
|
|
2152
2266
|
`authoritative binding project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}`
|
|
2153
2267
|
);
|
|
2154
2268
|
log("session policy: concurrent bridge sessions allowed per project daemon");
|
|
2269
|
+
if (!isDaemonIdleShutdownAllowed()) {
|
|
2270
|
+
log("idle shutdown disabled while daemon heartbeat is active");
|
|
2271
|
+
}
|
|
2272
|
+
const daemonHeartbeat = createDaemonHeartbeat({
|
|
2273
|
+
binding,
|
|
2274
|
+
ideType,
|
|
2275
|
+
onLog: (msg) => log(msg)
|
|
2276
|
+
});
|
|
2155
2277
|
await ensureSocketClean(socketPath);
|
|
2156
2278
|
const cleanupSocketFile = () => {
|
|
2157
2279
|
try {
|
|
@@ -2178,6 +2300,10 @@ async function runDaemon() {
|
|
|
2178
2300
|
activeSessions = Math.max(0, activeSessions - 1);
|
|
2179
2301
|
log(`session=${sessionId} closed activeSessions=${activeSessions}`);
|
|
2180
2302
|
if (activeSessions === 0 && !shuttingDown) {
|
|
2303
|
+
if (!isDaemonIdleShutdownAllowed()) {
|
|
2304
|
+
log("idle shutdown skipped (daemon heartbeat active)");
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2181
2307
|
idleTimer = setTimeout(() => {
|
|
2182
2308
|
if (activeSessions !== 0 || shuttingDown) return;
|
|
2183
2309
|
shuttingDown = true;
|
|
@@ -2200,7 +2326,10 @@ async function runDaemon() {
|
|
|
2200
2326
|
authoritativeBinding: binding,
|
|
2201
2327
|
transport,
|
|
2202
2328
|
sessionSocket: socket,
|
|
2203
|
-
sessionLabel: `daemon-session-${sessionId}
|
|
2329
|
+
sessionLabel: `daemon-session-${sessionId}`,
|
|
2330
|
+
onSessionIdeTypeKnown: (ideType2) => {
|
|
2331
|
+
daemonHeartbeat.setIdeType(ideType2);
|
|
2332
|
+
}
|
|
2204
2333
|
});
|
|
2205
2334
|
} catch (err) {
|
|
2206
2335
|
log(`session error: ${String(err)}`);
|
|
@@ -2221,6 +2350,9 @@ async function runDaemon() {
|
|
|
2221
2350
|
clearTimeout(idleTimer);
|
|
2222
2351
|
idleTimer = null;
|
|
2223
2352
|
}
|
|
2353
|
+
if (daemonHeartbeat.isRunning()) {
|
|
2354
|
+
daemonHeartbeat.stop();
|
|
2355
|
+
}
|
|
2224
2356
|
log(`daemon shutdown: ${reason}`);
|
|
2225
2357
|
server.close(() => {
|
|
2226
2358
|
cleanupSocketFile();
|
|
@@ -2233,6 +2365,9 @@ async function runDaemon() {
|
|
|
2233
2365
|
return new Promise((resolve6) => {
|
|
2234
2366
|
server.listen(socketPath, () => {
|
|
2235
2367
|
log(`daemon started, listening on ${socketPath}`);
|
|
2368
|
+
void daemonHeartbeat.start().catch((err) => {
|
|
2369
|
+
log(`daemon heartbeat start error: ${String(err)}`);
|
|
2370
|
+
});
|
|
2236
2371
|
resolve6();
|
|
2237
2372
|
});
|
|
2238
2373
|
});
|
package/dist/index.cjs
CHANGED
|
@@ -34,7 +34,6 @@ __export(index_exports, {
|
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(index_exports);
|
|
36
36
|
var path6 = __toESM(require("path"), 1);
|
|
37
|
-
var crypto5 = __toESM(require("crypto"), 1);
|
|
38
37
|
var import_node_url2 = require("url");
|
|
39
38
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
40
39
|
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
@@ -1474,47 +1473,58 @@ function handleBindingStatus(binding) {
|
|
|
1474
1473
|
return buildBindingStatus(binding);
|
|
1475
1474
|
}
|
|
1476
1475
|
|
|
1477
|
-
// src/
|
|
1478
|
-
var
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
type: "text",
|
|
1482
|
-
text: "MemoraOne MCP not initialized (project binding missing)."
|
|
1483
|
-
}
|
|
1484
|
-
]
|
|
1485
|
-
};
|
|
1486
|
-
var initializeDiagDumped = false;
|
|
1487
|
-
var uriToPath = (uri) => {
|
|
1488
|
-
if (uri.startsWith("file://")) {
|
|
1489
|
-
return (0, import_node_url2.fileURLToPath)(uri);
|
|
1490
|
-
}
|
|
1491
|
-
return uri;
|
|
1492
|
-
};
|
|
1493
|
-
function getCursorWorkspaceRootFromEnv() {
|
|
1494
|
-
const raw = process.env.WORKSPACE_FOLDER_PATHS;
|
|
1495
|
-
if (raw === void 0 || raw.trim() === "") {
|
|
1496
|
-
return void 0;
|
|
1497
|
-
}
|
|
1498
|
-
const parts = raw.split(path6.delimiter).map((p) => p.trim()).filter(Boolean);
|
|
1499
|
-
const first = parts[0];
|
|
1500
|
-
return first ? path6.resolve(first) : void 0;
|
|
1476
|
+
// src/heartbeat.ts
|
|
1477
|
+
var crypto5 = __toESM(require("crypto"), 1);
|
|
1478
|
+
function fingerprintApiKey(apiKey) {
|
|
1479
|
+
return crypto5.createHash("sha256").update(apiKey).digest("hex").slice(0, 12);
|
|
1501
1480
|
}
|
|
1502
1481
|
function isHeartbeatDebugEnabled() {
|
|
1503
1482
|
const value = String(process.env.MEMORAONE_DEBUG_HEARTBEAT ?? "").trim().toLowerCase();
|
|
1504
1483
|
return ["1", "true", "yes", "on"].includes(value);
|
|
1505
1484
|
}
|
|
1506
|
-
function
|
|
1507
|
-
return
|
|
1485
|
+
function resolveHeartbeatIntervalMs() {
|
|
1486
|
+
return Number.isFinite(config2.heartbeatIntervalMs) ? Math.max(1e3, config2.heartbeatIntervalMs) : 3e4;
|
|
1487
|
+
}
|
|
1488
|
+
async function sendProjectHeartbeat(client, ctx) {
|
|
1489
|
+
try {
|
|
1490
|
+
const pid = ctx.projectId?.trim();
|
|
1491
|
+
if (isHeartbeatDebugEnabled()) {
|
|
1492
|
+
process.stderr.write(
|
|
1493
|
+
`[memoraone-mcp][diag] heartbeat projectId=${pid ?? "unknown"} apiKeySource=${ctx.apiKeySource ?? "unknown"} apiKeyFingerprint=${ctx.apiKeyFingerprint ?? "unknown"} ideType=${ctx.ideType ?? "unknown"}
|
|
1494
|
+
`
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
if (!pid) {
|
|
1498
|
+
throw new Error("[memoraone-mcp] Cannot send heartbeat without an active project binding");
|
|
1499
|
+
}
|
|
1500
|
+
const body = {};
|
|
1501
|
+
if (ctx.ideType) body.ide_type = ctx.ideType;
|
|
1502
|
+
await client.post(`/v1/projects/${pid}/heartbeat`, body, {
|
|
1503
|
+
log: false,
|
|
1504
|
+
headers: {
|
|
1505
|
+
"x-project-id": pid
|
|
1506
|
+
}
|
|
1507
|
+
});
|
|
1508
|
+
} catch (err) {
|
|
1509
|
+
process.stderr.write(
|
|
1510
|
+
`[memoraone-mcp][info] heartbeat error (silent) ${String(err)}
|
|
1511
|
+
`
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1508
1514
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1515
|
+
|
|
1516
|
+
// src/ideType.ts
|
|
1517
|
+
function inferIdeType(params, options = {}) {
|
|
1518
|
+
const env2 = options.env ?? process.env;
|
|
1519
|
+
const argv = (options.argv ?? process.argv).join(" ").toLowerCase();
|
|
1520
|
+
const configIdeType = options.configIdeType ?? config2.ideType;
|
|
1521
|
+
if (configIdeType) {
|
|
1522
|
+
return configIdeType;
|
|
1512
1523
|
}
|
|
1513
1524
|
const clientInfoName = String(params?.clientInfo?.name ?? "").toLowerCase();
|
|
1514
1525
|
const clientInfoVersion = String(params?.clientInfo?.version ?? "").toLowerCase();
|
|
1515
|
-
const termProgram = String(
|
|
1516
|
-
const
|
|
1517
|
-
const envKeys = Object.keys(process.env);
|
|
1526
|
+
const termProgram = String(env2.TERM_PROGRAM ?? "").toLowerCase();
|
|
1527
|
+
const envKeys = Object.keys(env2);
|
|
1518
1528
|
const hasCursorSignals = envKeys.some((key) => key.startsWith("CURSOR_")) || termProgram === "cursor" || clientInfoName.includes("cursor") || clientInfoVersion.includes("cursor") || argv.includes("cursor");
|
|
1519
1529
|
if (hasCursorSignals) {
|
|
1520
1530
|
return "cursor";
|
|
@@ -1526,7 +1536,7 @@ function inferIdeType(params) {
|
|
|
1526
1536
|
"JETBRAINS_REMOTE_RUN",
|
|
1527
1537
|
"INTELLIJ_ENVIRONMENT_READER"
|
|
1528
1538
|
].includes(key)
|
|
1529
|
-
) || String(
|
|
1539
|
+
) || String(env2.TERMINAL_EMULATOR ?? "").toLowerCase().includes("jetbrains") || /(jetbrains|intellij|pycharm|webstorm|goland|rubymine|clion|phpstorm|rider|datagrip)/.test(
|
|
1530
1540
|
clientInfoName
|
|
1531
1541
|
) || /(jetbrains|intellij|pycharm|webstorm|goland|rubymine|clion|phpstorm|rider|datagrip)/.test(
|
|
1532
1542
|
argv
|
|
@@ -1540,32 +1550,31 @@ function inferIdeType(params) {
|
|
|
1540
1550
|
}
|
|
1541
1551
|
return void 0;
|
|
1542
1552
|
}
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
);
|
|
1551
|
-
}
|
|
1552
|
-
if (!pid) {
|
|
1553
|
-
throw new Error("[memoraone-mcp] Cannot send heartbeat without an active project binding");
|
|
1553
|
+
|
|
1554
|
+
// src/index.ts
|
|
1555
|
+
var notInitializedResult = {
|
|
1556
|
+
content: [
|
|
1557
|
+
{
|
|
1558
|
+
type: "text",
|
|
1559
|
+
text: "MemoraOne MCP not initialized (project binding missing)."
|
|
1554
1560
|
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1561
|
+
]
|
|
1562
|
+
};
|
|
1563
|
+
var initializeDiagDumped = false;
|
|
1564
|
+
var uriToPath = (uri) => {
|
|
1565
|
+
if (uri.startsWith("file://")) {
|
|
1566
|
+
return (0, import_node_url2.fileURLToPath)(uri);
|
|
1567
|
+
}
|
|
1568
|
+
return uri;
|
|
1569
|
+
};
|
|
1570
|
+
function getCursorWorkspaceRootFromEnv() {
|
|
1571
|
+
const raw = process.env.WORKSPACE_FOLDER_PATHS;
|
|
1572
|
+
if (raw === void 0 || raw.trim() === "") {
|
|
1573
|
+
return void 0;
|
|
1568
1574
|
}
|
|
1575
|
+
const parts = raw.split(path6.delimiter).map((p) => p.trim()).filter(Boolean);
|
|
1576
|
+
const first = parts[0];
|
|
1577
|
+
return first ? path6.resolve(first) : void 0;
|
|
1569
1578
|
}
|
|
1570
1579
|
function redactSensitiveFields(obj) {
|
|
1571
1580
|
if (obj === null || obj === void 0) return obj;
|
|
@@ -1877,6 +1886,9 @@ async function main(opts = {}) {
|
|
|
1877
1886
|
try {
|
|
1878
1887
|
const params = request.params;
|
|
1879
1888
|
runtime.ideType = inferIdeType(params);
|
|
1889
|
+
if (runtime.ideType && opts.onSessionIdeTypeKnown) {
|
|
1890
|
+
opts.onSessionIdeTypeKnown(runtime.ideType);
|
|
1891
|
+
}
|
|
1880
1892
|
if (!initializeDiagDumped) {
|
|
1881
1893
|
initializeDiagDumped = true;
|
|
1882
1894
|
const folders = Array.isArray(params.workspaceFolders) ? params.workspaceFolders.map((f) => ({ name: f?.name, uri: f?.uri })) : params.workspaceFolders;
|
|
@@ -1984,14 +1996,25 @@ async function main(opts = {}) {
|
|
|
1984
1996
|
await server.connect(transport);
|
|
1985
1997
|
const activeClient = await bindingReady;
|
|
1986
1998
|
let heartbeatInterval = null;
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1999
|
+
const daemonSession = Boolean(opts.sessionSocket);
|
|
2000
|
+
if (config2.heartbeatEnabled && daemonSession) {
|
|
2001
|
+
console.error(
|
|
2002
|
+
`[memoraone-mcp] ${sessionLabel} defers heartbeat to daemon for project ${runtime.projectId}`
|
|
2003
|
+
);
|
|
2004
|
+
} else if (config2.heartbeatEnabled) {
|
|
2005
|
+
const intervalMs = resolveHeartbeatIntervalMs();
|
|
2006
|
+
const heartbeatCtx = {
|
|
2007
|
+
projectId: runtime.projectId,
|
|
2008
|
+
ideType: runtime.ideType,
|
|
2009
|
+
apiKeySource: runtime.apiKeySource,
|
|
2010
|
+
apiKeyFingerprint: runtime.apiKeyFingerprint
|
|
2011
|
+
};
|
|
1990
2012
|
console.error(
|
|
1991
2013
|
`[memoraone-mcp] ${sessionLabel} owns heartbeat for project ${runtime.projectId} interval=${intervalMs}ms`
|
|
1992
2014
|
);
|
|
2015
|
+
await sendProjectHeartbeat(activeClient, heartbeatCtx);
|
|
1993
2016
|
heartbeatInterval = setInterval(() => {
|
|
1994
|
-
|
|
2017
|
+
sendProjectHeartbeat(activeClient, heartbeatCtx).catch(() => {
|
|
1995
2018
|
});
|
|
1996
2019
|
}, intervalMs);
|
|
1997
2020
|
}
|
|
@@ -2000,10 +2023,14 @@ async function main(opts = {}) {
|
|
|
2000
2023
|
const shutdown = (signal, exitProcess = true) => {
|
|
2001
2024
|
process.off("SIGINT", onSigInt);
|
|
2002
2025
|
process.off("SIGTERM", onSigTerm);
|
|
2003
|
-
if (heartbeatInterval)
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2026
|
+
if (heartbeatInterval) {
|
|
2027
|
+
clearInterval(heartbeatInterval);
|
|
2028
|
+
heartbeatInterval = null;
|
|
2029
|
+
if (runtime.projectId) {
|
|
2030
|
+
console.error(
|
|
2031
|
+
`[memoraone-mcp] ${sessionLabel} released session heartbeat for project ${runtime.projectId}`
|
|
2032
|
+
);
|
|
2033
|
+
}
|
|
2007
2034
|
}
|
|
2008
2035
|
if (devMode) {
|
|
2009
2036
|
console.error(`[memoraone-mcp] ${sessionLabel} received ${signal}, shutting down`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memoraone/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,15 @@
|
|
|
13
13
|
"publishConfig": {
|
|
14
14
|
"access": "public"
|
|
15
15
|
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup && node scripts/writeBinWrapper.cjs",
|
|
18
|
+
"prepublishOnly": "pnpm run build",
|
|
19
|
+
"dev": "tsx src/cli.ts",
|
|
20
|
+
"lint": "eslint .",
|
|
21
|
+
"lint:contracts": "node scripts/lint-contracts.cjs",
|
|
22
|
+
"test": "pnpm run lint:contracts && node --import=tsx --test test/*.test.js",
|
|
23
|
+
"validate:auth": "node --import=tsx --test test/memoraClient.test.js"
|
|
24
|
+
},
|
|
16
25
|
"dependencies": {
|
|
17
26
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
18
27
|
"dotenv": "^16.4.5",
|
|
@@ -22,13 +31,5 @@
|
|
|
22
31
|
"tsx": "^4.21.0",
|
|
23
32
|
"tsup": "^8.5.1",
|
|
24
33
|
"typescript": "^5.9.2"
|
|
25
|
-
},
|
|
26
|
-
"scripts": {
|
|
27
|
-
"build": "tsup && node scripts/writeBinWrapper.cjs",
|
|
28
|
-
"dev": "tsx src/cli.ts",
|
|
29
|
-
"lint": "eslint .",
|
|
30
|
-
"lint:contracts": "node scripts/lint-contracts.cjs",
|
|
31
|
-
"test": "pnpm run lint:contracts && node --import=tsx --test test/*.test.js",
|
|
32
|
-
"validate:auth": "node --import=tsx --test test/memoraClient.test.js"
|
|
33
34
|
}
|
|
34
|
-
}
|
|
35
|
+
}
|