@memoraone/mcp 0.1.56 → 0.1.58
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 +1 -1
- package/dist/gateway.cjs +440 -31
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
package/dist/gateway.cjs
CHANGED
|
@@ -2233,6 +2233,21 @@ async function reconcileResolvedBindingWithDisk(cached, options = {}) {
|
|
|
2233
2233
|
}
|
|
2234
2234
|
return { binding: fresh, cacheRefreshed: true };
|
|
2235
2235
|
}
|
|
2236
|
+
async function checkRuntimeBindingValidity(cached, options = {}) {
|
|
2237
|
+
try {
|
|
2238
|
+
const { binding: current } = await reconcileResolvedBindingWithDisk(cached, options);
|
|
2239
|
+
if (current.status !== "connected") {
|
|
2240
|
+
return { valid: false, reason: `binding status is ${current.status}` };
|
|
2241
|
+
}
|
|
2242
|
+
if (!bindingRelevantValuesMatch(cached, current)) {
|
|
2243
|
+
return { valid: false, reason: "binding metadata changed" };
|
|
2244
|
+
}
|
|
2245
|
+
return { valid: true };
|
|
2246
|
+
} catch (err) {
|
|
2247
|
+
const reason = err instanceof Error && err.message.trim() ? err.message : String(err);
|
|
2248
|
+
return { valid: false, reason };
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2236
2251
|
function encodeResolvedBinding(binding) {
|
|
2237
2252
|
const payload = {
|
|
2238
2253
|
repositoryBindingId: binding.repositoryBindingId,
|
|
@@ -11218,8 +11233,9 @@ function delay(ms) {
|
|
|
11218
11233
|
return new Promise((resolve47) => setTimeout(resolve47, ms));
|
|
11219
11234
|
}
|
|
11220
11235
|
function buildProtectedResourceMetadata(env2 = process.env, workspaceRoot) {
|
|
11236
|
+
const externalIssuer = Boolean(env2.MEMORAONE_OAUTH_ISSUER_URL?.trim());
|
|
11221
11237
|
return {
|
|
11222
|
-
resource: workspaceRoot === void 0 ? getGatewayResourceBaseUrl(env2) : buildGatewayMcpUrl(workspaceRoot, env2),
|
|
11238
|
+
resource: workspaceRoot === void 0 || externalIssuer ? getGatewayResourceBaseUrl(env2) : buildGatewayMcpUrl(workspaceRoot, env2),
|
|
11223
11239
|
authorization_servers: [getGatewayAuthorizationServerIssuerUrl(env2)]
|
|
11224
11240
|
};
|
|
11225
11241
|
}
|
|
@@ -11350,17 +11366,17 @@ var GatewayOAuthSurface = class {
|
|
|
11350
11366
|
*/
|
|
11351
11367
|
async handleProtectedResourceMetadata(res, url) {
|
|
11352
11368
|
const wsOrError = getGatewayWsQueryParam(url);
|
|
11353
|
-
if (wsOrError
|
|
11369
|
+
if (wsOrError === null) {
|
|
11370
|
+
json(res, 200, buildProtectedResourceMetadata(this.env));
|
|
11371
|
+
return;
|
|
11372
|
+
}
|
|
11373
|
+
if (typeof wsOrError !== "string") {
|
|
11354
11374
|
json(res, 404, {
|
|
11355
11375
|
error: "invalid_request",
|
|
11356
11376
|
error_description: "resource workspace is invalid"
|
|
11357
11377
|
});
|
|
11358
11378
|
return;
|
|
11359
11379
|
}
|
|
11360
|
-
if (wsOrError === null) {
|
|
11361
|
-
json(res, 200, buildProtectedResourceMetadata(this.env));
|
|
11362
|
-
return;
|
|
11363
|
-
}
|
|
11364
11380
|
const parsed2 = await parseGatewayWorkspaceRoot(wsOrError);
|
|
11365
11381
|
if (!parsed2.ok) {
|
|
11366
11382
|
json(res, 404, {
|
|
@@ -11738,12 +11754,330 @@ var path50 = __toESM(require("path"), 1);
|
|
|
11738
11754
|
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
11739
11755
|
var import_types2 = require("@modelcontextprotocol/sdk/types.js");
|
|
11740
11756
|
|
|
11757
|
+
// src/heartbeat.ts
|
|
11758
|
+
var crypto4 = __toESM(require("crypto"), 1);
|
|
11759
|
+
var fs37 = __toESM(require("fs"), 1);
|
|
11760
|
+
var path48 = __toESM(require("path"), 1);
|
|
11761
|
+
|
|
11762
|
+
// src/localState/mcpSessionId.ts
|
|
11763
|
+
var import_node_crypto29 = require("crypto");
|
|
11764
|
+
var MCS_PREFIX = "mcs_";
|
|
11765
|
+
function generateMcpSessionId(random = () => (0, import_node_crypto29.randomBytes)(32)) {
|
|
11766
|
+
const bytes = random();
|
|
11767
|
+
if (bytes.length !== 32) {
|
|
11768
|
+
throw new Error("[memoraone-mcp] mcp session id requires exactly 32 random bytes");
|
|
11769
|
+
}
|
|
11770
|
+
return `${MCS_PREFIX}${bytes.toString("base64url")}`;
|
|
11771
|
+
}
|
|
11772
|
+
|
|
11773
|
+
// src/heartbeat.ts
|
|
11774
|
+
function fingerprintAccessToken(accessToken) {
|
|
11775
|
+
return crypto4.createHash("sha256").update(accessToken).digest("hex").slice(0, 12);
|
|
11776
|
+
}
|
|
11777
|
+
function isHeartbeatDebugEnabled() {
|
|
11778
|
+
const value = String(process.env.MEMORAONE_DEBUG_HEARTBEAT ?? "").trim().toLowerCase();
|
|
11779
|
+
return ["1", "true", "yes", "on"].includes(value);
|
|
11780
|
+
}
|
|
11781
|
+
var LOCAL_MCP_HEARTBEAT_INTERVAL_MS = 1e3;
|
|
11782
|
+
function resolveHeartbeatIntervalMs() {
|
|
11783
|
+
return LOCAL_MCP_HEARTBEAT_INTERVAL_MS;
|
|
11784
|
+
}
|
|
11785
|
+
function redactSensitiveText(text2) {
|
|
11786
|
+
return text2.replace(/mcs_[A-Za-z0-9_-]+/g, "mcs_[redacted]").replace(/mia_[A-Za-z0-9_-]+/g, "mia_[redacted]").replace(/mir_[A-Za-z0-9_-]+/g, "mir_[redacted]").replace(/mcc_[A-Za-z0-9_-]+/g, "mcc_[redacted]").replace(/Bearer\s+\S+/gi, "Bearer [redacted]");
|
|
11787
|
+
}
|
|
11788
|
+
function resolvePackageVersion() {
|
|
11789
|
+
const fromEnv = process.env.npm_package_version?.trim();
|
|
11790
|
+
if (fromEnv) return fromEnv;
|
|
11791
|
+
const moduleDir = typeof __dirname !== "undefined" ? __dirname : void 0;
|
|
11792
|
+
const candidates = [
|
|
11793
|
+
...moduleDir ? [path48.join(moduleDir, "..", "package.json"), path48.join(moduleDir, "package.json")] : [],
|
|
11794
|
+
path48.join(process.cwd(), "package.json"),
|
|
11795
|
+
path48.join(process.cwd(), "packages", "mcp", "package.json")
|
|
11796
|
+
];
|
|
11797
|
+
for (const candidate of candidates) {
|
|
11798
|
+
try {
|
|
11799
|
+
const pkg = JSON.parse(fs37.readFileSync(candidate, "utf8"));
|
|
11800
|
+
if (typeof pkg.version === "string" && pkg.version.trim()) {
|
|
11801
|
+
return pkg.version.trim();
|
|
11802
|
+
}
|
|
11803
|
+
} catch {
|
|
11804
|
+
}
|
|
11805
|
+
}
|
|
11806
|
+
return void 0;
|
|
11807
|
+
}
|
|
11808
|
+
function parseActiveFlag(payload) {
|
|
11809
|
+
if (!payload || typeof payload !== "object") return null;
|
|
11810
|
+
const active = payload.active;
|
|
11811
|
+
if (typeof active === "boolean") return active;
|
|
11812
|
+
return null;
|
|
11813
|
+
}
|
|
11814
|
+
async function announceIdeSession(client, ctx) {
|
|
11815
|
+
const body = {
|
|
11816
|
+
session_id: ctx.sessionId,
|
|
11817
|
+
ide_type: ctx.ideType
|
|
11818
|
+
};
|
|
11819
|
+
if (ctx.packageVersion) {
|
|
11820
|
+
body.package_version = ctx.packageVersion;
|
|
11821
|
+
}
|
|
11822
|
+
let lastErr;
|
|
11823
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
11824
|
+
try {
|
|
11825
|
+
const raw = await client.post("/v1/local-mcp/session/announce", body, { log: false });
|
|
11826
|
+
const data = raw ?? {};
|
|
11827
|
+
if (data.ok !== true || typeof data.active !== "boolean" || typeof data.ide_type !== "string") {
|
|
11828
|
+
throw new Error("[memoraone-mcp] Invalid session announce response");
|
|
11829
|
+
}
|
|
11830
|
+
return {
|
|
11831
|
+
ok: true,
|
|
11832
|
+
active: data.active,
|
|
11833
|
+
ideType: data.ide_type,
|
|
11834
|
+
announcedAt: typeof data.announced_at === "string" ? data.announced_at : ""
|
|
11835
|
+
};
|
|
11836
|
+
} catch (err) {
|
|
11837
|
+
lastErr = err;
|
|
11838
|
+
if (err instanceof MemoraOneHttpError && (err.status === 404 || err.status === 405)) {
|
|
11839
|
+
throw new Error(
|
|
11840
|
+
"[memoraone-mcp] Session announce endpoint is not supported by this API (POST /v1/local-mcp/session/announce)"
|
|
11841
|
+
);
|
|
11842
|
+
}
|
|
11843
|
+
const msg = String(err);
|
|
11844
|
+
const transient = /fetch failed|ECONNRESET|ETIMEDOUT|ENOTFOUND|network|socket/i.test(msg) && !(err instanceof MemoraOneHttpError && err.status >= 400 && err.status < 500);
|
|
11845
|
+
if (!transient || attempt === 1) {
|
|
11846
|
+
break;
|
|
11847
|
+
}
|
|
11848
|
+
}
|
|
11849
|
+
}
|
|
11850
|
+
const safe = redactSensitiveText(String(lastErr));
|
|
11851
|
+
throw new Error(`[memoraone-mcp] Session announce failed: ${safe}`);
|
|
11852
|
+
}
|
|
11853
|
+
async function sendProjectHeartbeat(client, ctx) {
|
|
11854
|
+
try {
|
|
11855
|
+
const pid = ctx.projectId?.trim();
|
|
11856
|
+
if (isHeartbeatDebugEnabled()) {
|
|
11857
|
+
process.stderr.write(
|
|
11858
|
+
`[memoraone-mcp][diag] heartbeat projectId=${pid ?? "unknown"} binding=${ctx.repositoryBindingId} credentialSource=${ctx.credentialSource ?? "keyring"} accessTokenFingerprint=${ctx.accessTokenFingerprint ?? "unknown"} ideType=${ctx.ideType ?? "unknown"}
|
|
11859
|
+
`
|
|
11860
|
+
);
|
|
11861
|
+
}
|
|
11862
|
+
if (!pid) {
|
|
11863
|
+
throw new Error("[memoraone-mcp] Cannot send heartbeat without an active project binding");
|
|
11864
|
+
}
|
|
11865
|
+
const body = {};
|
|
11866
|
+
if (ctx.ideType) body.ide_type = ctx.ideType;
|
|
11867
|
+
if (ctx.sessionId) body.session_id = ctx.sessionId;
|
|
11868
|
+
const raw = await client.post("/v1/local-mcp/heartbeat", body, { log: false });
|
|
11869
|
+
return { active: parseActiveFlag(raw) };
|
|
11870
|
+
} catch (err) {
|
|
11871
|
+
process.stderr.write(
|
|
11872
|
+
`[memoraone-mcp][info] heartbeat error (silent) ${redactSensitiveText(String(err))}
|
|
11873
|
+
`
|
|
11874
|
+
);
|
|
11875
|
+
return { active: null };
|
|
11876
|
+
}
|
|
11877
|
+
}
|
|
11878
|
+
function createDaemonHeartbeat(opts) {
|
|
11879
|
+
let interval = null;
|
|
11880
|
+
let runGeneration = 0;
|
|
11881
|
+
let client = null;
|
|
11882
|
+
let announced = false;
|
|
11883
|
+
let studioActive = null;
|
|
11884
|
+
let starting = false;
|
|
11885
|
+
let tickInFlight = false;
|
|
11886
|
+
let bindingAuthorityEnded = false;
|
|
11887
|
+
let waitingForIdeType = false;
|
|
11888
|
+
const sessionId = opts.sessionId ?? generateMcpSessionId();
|
|
11889
|
+
const packageVersion = opts.packageVersion ?? resolvePackageVersion();
|
|
11890
|
+
const ctx = {
|
|
11891
|
+
projectId: opts.binding.projectId,
|
|
11892
|
+
repositoryBindingId: opts.binding.repositoryBindingId,
|
|
11893
|
+
ideType: opts.ideType,
|
|
11894
|
+
sessionId,
|
|
11895
|
+
credentialSource: "keyring",
|
|
11896
|
+
accessTokenFingerprint: null
|
|
11897
|
+
};
|
|
11898
|
+
const log4 = opts.onLog ?? ((msg) => {
|
|
11899
|
+
process.stderr.write(`[memoraone-mcp][daemon-heartbeat] ${redactSensitiveText(msg)}
|
|
11900
|
+
`);
|
|
11901
|
+
});
|
|
11902
|
+
const validateBinding = opts.validateBinding ?? checkRuntimeBindingValidity;
|
|
11903
|
+
const endBindingAuthority = (reason) => {
|
|
11904
|
+
if (bindingAuthorityEnded) return;
|
|
11905
|
+
bindingAuthorityEnded = true;
|
|
11906
|
+
studioActive = false;
|
|
11907
|
+
runGeneration += 1;
|
|
11908
|
+
if (interval) {
|
|
11909
|
+
clearInterval(interval);
|
|
11910
|
+
interval = null;
|
|
11911
|
+
}
|
|
11912
|
+
log4(`heartbeat authority ended: ${redactSensitiveText(reason)}`);
|
|
11913
|
+
};
|
|
11914
|
+
const bindingIsValid = async () => {
|
|
11915
|
+
try {
|
|
11916
|
+
const result = await validateBinding(opts.binding);
|
|
11917
|
+
if (result.valid) return true;
|
|
11918
|
+
endBindingAuthority("reason" in result ? result.reason : "binding is no longer valid");
|
|
11919
|
+
return false;
|
|
11920
|
+
} catch (err) {
|
|
11921
|
+
endBindingAuthority(`binding validation failed: ${String(err)}`);
|
|
11922
|
+
return false;
|
|
11923
|
+
}
|
|
11924
|
+
};
|
|
11925
|
+
const ensureClient = async () => {
|
|
11926
|
+
if (client) return client;
|
|
11927
|
+
const creds = await readInstallationCredentials(opts.binding.repositoryBindingId);
|
|
11928
|
+
if (!creds?.accessToken) {
|
|
11929
|
+
log4("cannot start: no access token in keyring");
|
|
11930
|
+
return null;
|
|
11931
|
+
}
|
|
11932
|
+
ctx.accessTokenFingerprint = fingerprintAccessToken(creds.accessToken);
|
|
11933
|
+
client = opts.createClient?.(opts.binding) ?? new memoraClient_default(config2, {
|
|
11934
|
+
repositoryBindingId: opts.binding.repositoryBindingId,
|
|
11935
|
+
projectId: opts.binding.projectId
|
|
11936
|
+
});
|
|
11937
|
+
return client;
|
|
11938
|
+
};
|
|
11939
|
+
const ensureAnnounced = async (activeClient) => {
|
|
11940
|
+
if (announced) return true;
|
|
11941
|
+
if (!ctx.ideType) {
|
|
11942
|
+
waitingForIdeType = true;
|
|
11943
|
+
log4("waiting for ide type before session announce");
|
|
11944
|
+
return false;
|
|
11945
|
+
}
|
|
11946
|
+
waitingForIdeType = false;
|
|
11947
|
+
try {
|
|
11948
|
+
const result = await announceIdeSession(activeClient, {
|
|
11949
|
+
sessionId,
|
|
11950
|
+
ideType: ctx.ideType,
|
|
11951
|
+
packageVersion
|
|
11952
|
+
});
|
|
11953
|
+
announced = true;
|
|
11954
|
+
studioActive = result.active;
|
|
11955
|
+
log4(`session announced ideType=${ctx.ideType} active=${String(result.active)}`);
|
|
11956
|
+
return true;
|
|
11957
|
+
} catch (err) {
|
|
11958
|
+
studioActive = false;
|
|
11959
|
+
log4(`session announce failed: ${redactSensitiveText(String(err))}`);
|
|
11960
|
+
return false;
|
|
11961
|
+
}
|
|
11962
|
+
};
|
|
11963
|
+
const applyHeartbeatOutcome = (outcome) => {
|
|
11964
|
+
if (outcome.active === null) return;
|
|
11965
|
+
if (outcome.active === false && studioActive !== false) {
|
|
11966
|
+
studioActive = false;
|
|
11967
|
+
log4("session superseded (active=false); continuing to serve MCP without re-announce");
|
|
11968
|
+
return;
|
|
11969
|
+
}
|
|
11970
|
+
if (outcome.active === true) {
|
|
11971
|
+
studioActive = true;
|
|
11972
|
+
}
|
|
11973
|
+
};
|
|
11974
|
+
const tick = async (generation) => {
|
|
11975
|
+
if (!client || generation !== runGeneration || bindingAuthorityEnded || tickInFlight) return;
|
|
11976
|
+
tickInFlight = true;
|
|
11977
|
+
try {
|
|
11978
|
+
if (!await bindingIsValid()) return;
|
|
11979
|
+
if (generation !== runGeneration || !interval) return;
|
|
11980
|
+
const outcome = await sendProjectHeartbeat(client, ctx);
|
|
11981
|
+
if (generation !== runGeneration || !interval) return;
|
|
11982
|
+
applyHeartbeatOutcome(outcome);
|
|
11983
|
+
} finally {
|
|
11984
|
+
tickInFlight = false;
|
|
11985
|
+
}
|
|
11986
|
+
};
|
|
11987
|
+
const beginInterval = () => {
|
|
11988
|
+
if (interval) return;
|
|
11989
|
+
const intervalMs = resolveHeartbeatIntervalMs();
|
|
11990
|
+
const generation = runGeneration;
|
|
11991
|
+
log4(
|
|
11992
|
+
`daemon owns heartbeat for binding=${opts.binding.repositoryBindingId} project=${opts.binding.projectId} ideType=${ctx.ideType ?? "unknown"} interval=${intervalMs}ms`
|
|
11993
|
+
);
|
|
11994
|
+
interval = setInterval(() => {
|
|
11995
|
+
void tick(generation);
|
|
11996
|
+
}, intervalMs);
|
|
11997
|
+
interval.unref?.();
|
|
11998
|
+
void tick(generation);
|
|
11999
|
+
};
|
|
12000
|
+
const start = async () => {
|
|
12001
|
+
if (!config2.heartbeatEnabled) {
|
|
12002
|
+
log4("disabled by config");
|
|
12003
|
+
return;
|
|
12004
|
+
}
|
|
12005
|
+
if (interval) {
|
|
12006
|
+
log4("already running (skipped duplicate start)");
|
|
12007
|
+
return;
|
|
12008
|
+
}
|
|
12009
|
+
if (starting) {
|
|
12010
|
+
log4("already starting (skipped duplicate start)");
|
|
12011
|
+
return;
|
|
12012
|
+
}
|
|
12013
|
+
if (bindingAuthorityEnded) {
|
|
12014
|
+
log4("binding authority already ended; heartbeat will not restart");
|
|
12015
|
+
return;
|
|
12016
|
+
}
|
|
12017
|
+
starting = true;
|
|
12018
|
+
try {
|
|
12019
|
+
if (!await bindingIsValid()) return;
|
|
12020
|
+
const activeClient = await ensureClient();
|
|
12021
|
+
if (!activeClient) return;
|
|
12022
|
+
const ok = await ensureAnnounced(activeClient);
|
|
12023
|
+
if (!ok) {
|
|
12024
|
+
return;
|
|
12025
|
+
}
|
|
12026
|
+
beginInterval();
|
|
12027
|
+
} finally {
|
|
12028
|
+
starting = false;
|
|
12029
|
+
if (waitingForIdeType && !announced && !interval && ctx.ideType && config2.heartbeatEnabled) {
|
|
12030
|
+
waitingForIdeType = false;
|
|
12031
|
+
void start();
|
|
12032
|
+
}
|
|
12033
|
+
}
|
|
12034
|
+
};
|
|
12035
|
+
const stop = () => {
|
|
12036
|
+
runGeneration += 1;
|
|
12037
|
+
if (interval) {
|
|
12038
|
+
clearInterval(interval);
|
|
12039
|
+
interval = null;
|
|
12040
|
+
}
|
|
12041
|
+
log4(`daemon released heartbeat for binding=${opts.binding.repositoryBindingId}`);
|
|
12042
|
+
};
|
|
12043
|
+
const isRunning = () => interval !== null;
|
|
12044
|
+
const setIdeType = (ideType) => {
|
|
12045
|
+
const previous = ctx.ideType;
|
|
12046
|
+
if (previous === ideType) {
|
|
12047
|
+
return;
|
|
12048
|
+
}
|
|
12049
|
+
ctx.ideType = ideType;
|
|
12050
|
+
log4(`daemon heartbeat ideType updated to ${ideType}`);
|
|
12051
|
+
if (!announced) {
|
|
12052
|
+
void start();
|
|
12053
|
+
return;
|
|
12054
|
+
}
|
|
12055
|
+
if (client && interval) {
|
|
12056
|
+
void tick(runGeneration);
|
|
12057
|
+
}
|
|
12058
|
+
};
|
|
12059
|
+
const getIdeType = () => ctx.ideType;
|
|
12060
|
+
const getSessionId = () => sessionId;
|
|
12061
|
+
const isStudioActive = () => studioActive;
|
|
12062
|
+
const hasAnnounced = () => announced;
|
|
12063
|
+
return {
|
|
12064
|
+
start,
|
|
12065
|
+
stop,
|
|
12066
|
+
isRunning,
|
|
12067
|
+
setIdeType,
|
|
12068
|
+
getIdeType,
|
|
12069
|
+
getSessionId,
|
|
12070
|
+
isStudioActive,
|
|
12071
|
+
hasAnnounced
|
|
12072
|
+
};
|
|
12073
|
+
}
|
|
12074
|
+
|
|
11741
12075
|
// src/memoraMcpServer.ts
|
|
11742
12076
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
11743
12077
|
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
11744
12078
|
|
|
11745
12079
|
// src/pluginStartupBindingWait.ts
|
|
11746
|
-
var
|
|
12080
|
+
var path49 = __toESM(require("path"), 1);
|
|
11747
12081
|
var MEMORAONE_PLUGIN_ENV = "MEMORAONE_PLUGIN";
|
|
11748
12082
|
var MEMORAONE_CURSOR_PLUGIN = "cursor";
|
|
11749
12083
|
var PLUGIN_STARTUP_BINDING_WAIT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
@@ -11804,7 +12138,7 @@ function collectPluginStartupBindingRoots(options) {
|
|
|
11804
12138
|
if (raw === void 0 || raw === null || raw.trim() === "") {
|
|
11805
12139
|
return;
|
|
11806
12140
|
}
|
|
11807
|
-
const resolved =
|
|
12141
|
+
const resolved = path49.resolve(raw);
|
|
11808
12142
|
if (seen.has(resolved)) {
|
|
11809
12143
|
return;
|
|
11810
12144
|
}
|
|
@@ -12097,11 +12431,11 @@ var LOCAL_MCP_TOOL_NAMES = [
|
|
|
12097
12431
|
|
|
12098
12432
|
// src/tools/handlers/postEvent.ts
|
|
12099
12433
|
var import_v415 = require("zod/v4");
|
|
12100
|
-
var
|
|
12434
|
+
var crypto6 = __toESM(require("crypto"), 1);
|
|
12101
12435
|
|
|
12102
12436
|
// src/runContext.ts
|
|
12103
12437
|
var import_node_async_hooks = require("async_hooks");
|
|
12104
|
-
var
|
|
12438
|
+
var crypto5 = __toESM(require("crypto"), 1);
|
|
12105
12439
|
var sessionContextStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
12106
12440
|
function createSessionRunContext(initial = {}) {
|
|
12107
12441
|
return {
|
|
@@ -12148,7 +12482,7 @@ function resolveRunId(passed) {
|
|
|
12148
12482
|
return getCurrentRunId();
|
|
12149
12483
|
}
|
|
12150
12484
|
function generateRunId() {
|
|
12151
|
-
return
|
|
12485
|
+
return crypto5.randomBytes(16).toString("hex");
|
|
12152
12486
|
}
|
|
12153
12487
|
|
|
12154
12488
|
// src/tools/handlers/postEvent.ts
|
|
@@ -12188,7 +12522,7 @@ function buildPostEventContentFields(content) {
|
|
|
12188
12522
|
return { message: JSON.stringify(content) };
|
|
12189
12523
|
}
|
|
12190
12524
|
async function handlePostEvent(client, args) {
|
|
12191
|
-
const nonce =
|
|
12525
|
+
const nonce = crypto6.randomBytes(8).toString("hex");
|
|
12192
12526
|
console.error(
|
|
12193
12527
|
`[memoraone-mcp][debug] tool=memora_post_event toolCallId=unknown nonce=${nonce} stage=before_post`
|
|
12194
12528
|
);
|
|
@@ -12898,19 +13232,6 @@ async function handleFinishSetup(options) {
|
|
|
12898
13232
|
};
|
|
12899
13233
|
}
|
|
12900
13234
|
|
|
12901
|
-
// src/heartbeat.ts
|
|
12902
|
-
var crypto6 = __toESM(require("crypto"), 1);
|
|
12903
|
-
var fs37 = __toESM(require("fs"), 1);
|
|
12904
|
-
var path49 = __toESM(require("path"), 1);
|
|
12905
|
-
|
|
12906
|
-
// src/localState/mcpSessionId.ts
|
|
12907
|
-
var import_node_crypto29 = require("crypto");
|
|
12908
|
-
|
|
12909
|
-
// src/heartbeat.ts
|
|
12910
|
-
function fingerprintAccessToken(accessToken) {
|
|
12911
|
-
return crypto6.createHash("sha256").update(accessToken).digest("hex").slice(0, 12);
|
|
12912
|
-
}
|
|
12913
|
-
|
|
12914
13235
|
// src/ideType.ts
|
|
12915
13236
|
var RELIABLE_CLIENT_INFO_NAMES = /* @__PURE__ */ new Map([
|
|
12916
13237
|
// Existing
|
|
@@ -13652,6 +13973,66 @@ var log2 = (msg) => {
|
|
|
13652
13973
|
`);
|
|
13653
13974
|
};
|
|
13654
13975
|
var sessionsById = /* @__PURE__ */ new Map();
|
|
13976
|
+
function isGatewayHeartbeatEnabled(env2) {
|
|
13977
|
+
if (!config2.heartbeatEnabled) return false;
|
|
13978
|
+
const raw = env2?.MEMORAONE_HEARTBEAT;
|
|
13979
|
+
if (raw === void 0) return true;
|
|
13980
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
13981
|
+
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
13982
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
13983
|
+
return true;
|
|
13984
|
+
}
|
|
13985
|
+
function stopSessionHeartbeat(session) {
|
|
13986
|
+
if (!session.heartbeat) return;
|
|
13987
|
+
session.heartbeat.stop();
|
|
13988
|
+
session.heartbeat = null;
|
|
13989
|
+
}
|
|
13990
|
+
function releaseSession(session) {
|
|
13991
|
+
session.closed = true;
|
|
13992
|
+
stopSessionHeartbeat(session);
|
|
13993
|
+
}
|
|
13994
|
+
async function attachGatewayMcpSessionHeartbeat(session, handle, options = {}) {
|
|
13995
|
+
if (session.heartbeatAttach) {
|
|
13996
|
+
await session.heartbeatAttach;
|
|
13997
|
+
return;
|
|
13998
|
+
}
|
|
13999
|
+
session.heartbeatAttach = (async () => {
|
|
14000
|
+
let activeClient;
|
|
14001
|
+
try {
|
|
14002
|
+
activeClient = await handle.bindingReady;
|
|
14003
|
+
} catch {
|
|
14004
|
+
return;
|
|
14005
|
+
}
|
|
14006
|
+
if (session.closed) return;
|
|
14007
|
+
if (!activeClient) return;
|
|
14008
|
+
const boundClient = activeClient;
|
|
14009
|
+
if (!handle.runtime.authoritativeBinding) return;
|
|
14010
|
+
if (!isGatewayHeartbeatEnabled(options.env)) return;
|
|
14011
|
+
if (session.heartbeat) return;
|
|
14012
|
+
const heartbeat = createDaemonHeartbeat({
|
|
14013
|
+
binding: handle.runtime.authoritativeBinding,
|
|
14014
|
+
ideType: handle.runtime.ideType ?? config2.ideType,
|
|
14015
|
+
createClient: () => boundClient,
|
|
14016
|
+
validateBinding: (binding) => checkRuntimeBindingValidity(binding, { homeDir: options.homeDir }),
|
|
14017
|
+
onLog: (msg) => {
|
|
14018
|
+
log2(`[session-heartbeat] ${msg}`);
|
|
14019
|
+
}
|
|
14020
|
+
});
|
|
14021
|
+
session.heartbeat = heartbeat;
|
|
14022
|
+
if (session.closed) {
|
|
14023
|
+
stopSessionHeartbeat(session);
|
|
14024
|
+
return;
|
|
14025
|
+
}
|
|
14026
|
+
log2(
|
|
14027
|
+
`${handle.sessionLabel} owns heartbeat for project ${handle.runtime.projectId}`
|
|
14028
|
+
);
|
|
14029
|
+
await heartbeat.start();
|
|
14030
|
+
if (session.closed) {
|
|
14031
|
+
stopSessionHeartbeat(session);
|
|
14032
|
+
}
|
|
14033
|
+
})();
|
|
14034
|
+
await session.heartbeatAttach;
|
|
14035
|
+
}
|
|
13655
14036
|
function sendJsonRpcHttpError(res, status, message) {
|
|
13656
14037
|
if (res.headersSent) return;
|
|
13657
14038
|
const body = JSON.stringify({
|
|
@@ -13743,6 +14124,7 @@ async function removeSession(sessionId) {
|
|
|
13743
14124
|
const session = sessionsById.get(sessionId);
|
|
13744
14125
|
if (!session) return;
|
|
13745
14126
|
sessionsById.delete(sessionId);
|
|
14127
|
+
releaseSession(session);
|
|
13746
14128
|
try {
|
|
13747
14129
|
await session.transport.close();
|
|
13748
14130
|
} catch {
|
|
@@ -13778,7 +14160,11 @@ async function createSession(binding, canonicalUrl, options) {
|
|
|
13778
14160
|
sessionsById.set(sessionId, sessionRef);
|
|
13779
14161
|
},
|
|
13780
14162
|
onsessionclosed: async (sessionId) => {
|
|
13781
|
-
sessionsById.
|
|
14163
|
+
const existing = sessionsById.get(sessionId);
|
|
14164
|
+
if (existing) {
|
|
14165
|
+
sessionsById.delete(sessionId);
|
|
14166
|
+
releaseSession(existing);
|
|
14167
|
+
}
|
|
13782
14168
|
}
|
|
13783
14169
|
});
|
|
13784
14170
|
const handle = createMemoraMcpServer({
|
|
@@ -13788,15 +14174,24 @@ async function createSession(binding, canonicalUrl, options) {
|
|
|
13788
14174
|
bindingResolveOptions: {
|
|
13789
14175
|
homeDir: options.homeDir,
|
|
13790
14176
|
identityDeps: options.identityDeps
|
|
14177
|
+
},
|
|
14178
|
+
onSessionIdeTypeKnown: (ideType) => {
|
|
14179
|
+
sessionRef?.heartbeat?.setIdeType(ideType);
|
|
13791
14180
|
}
|
|
13792
14181
|
});
|
|
13793
|
-
void handle.bindingReady.catch(() => {
|
|
13794
|
-
});
|
|
13795
14182
|
await handle.server.connect(transport);
|
|
13796
14183
|
transport.onclose = () => {
|
|
13797
14184
|
const sid = transport.sessionId;
|
|
13798
14185
|
if (sid) {
|
|
13799
|
-
sessionsById.
|
|
14186
|
+
const existing = sessionsById.get(sid);
|
|
14187
|
+
if (existing) {
|
|
14188
|
+
sessionsById.delete(sid);
|
|
14189
|
+
releaseSession(existing);
|
|
14190
|
+
} else if (sessionRef && !sessionRef.closed) {
|
|
14191
|
+
releaseSession(sessionRef);
|
|
14192
|
+
}
|
|
14193
|
+
} else if (sessionRef) {
|
|
14194
|
+
releaseSession(sessionRef);
|
|
13800
14195
|
}
|
|
13801
14196
|
};
|
|
13802
14197
|
sessionRef = {
|
|
@@ -13805,12 +14200,26 @@ async function createSession(binding, canonicalUrl, options) {
|
|
|
13805
14200
|
canonicalUrl,
|
|
13806
14201
|
binding,
|
|
13807
14202
|
transport,
|
|
14203
|
+
heartbeat: null,
|
|
14204
|
+
heartbeatAttach: null,
|
|
14205
|
+
closed: false,
|
|
13808
14206
|
close: async () => {
|
|
13809
14207
|
const sid = transport.sessionId;
|
|
13810
|
-
if (sid
|
|
13811
|
-
|
|
14208
|
+
if (sid && sessionsById.get(sid) === sessionRef) {
|
|
14209
|
+
await removeSession(sid);
|
|
14210
|
+
return;
|
|
14211
|
+
}
|
|
14212
|
+
releaseSession(sessionRef);
|
|
14213
|
+
try {
|
|
14214
|
+
await transport.close();
|
|
14215
|
+
} catch {
|
|
14216
|
+
}
|
|
13812
14217
|
}
|
|
13813
14218
|
};
|
|
14219
|
+
void attachGatewayMcpSessionHeartbeat(sessionRef, handle, options).catch(
|
|
14220
|
+
() => {
|
|
14221
|
+
}
|
|
14222
|
+
);
|
|
13814
14223
|
return sessionRef;
|
|
13815
14224
|
}
|
|
13816
14225
|
async function handleAuthenticatedGatewayMcpRequest(req, res, auth, options = {}) {
|