@echomem/mcp 1.4.3 → 1.4.4
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 +12 -7
- package/dist/hud/capsule.js +68 -25
- package/dist/hud/metric.js +16 -3
- package/dist/hud/monitor.js +152 -14
- package/dist/hud/web.js +594 -430
- package/dist/index.js +14 -1
- package/dist/package-metadata.js +4 -2
- package/dist/setup.js +28 -33
- package/dist/update-check.js +154 -0
- package/dist/v1-contract.js +18 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
13
13
|
import { fetchEncryptionConfig, decryptMemoryFields } from "./encryption.js";
|
|
14
14
|
import { runCli } from "./setup.js";
|
|
15
15
|
import { MCP_PACKAGE_VERSION, MCP_SERVER_INSTRUCTIONS } from "./package-metadata.js";
|
|
16
|
+
import { checkLatestUpdateStatus, formatUpdateNotice, formatUpdateStatusText, startBackgroundUpdateCheck, } from "./update-check.js";
|
|
16
17
|
const ECHO_API_BASE_URL = process.env.ECHO_API_BASE_URL || "https://echo-mem-chrome.vercel.app";
|
|
17
18
|
const MEMORY_FEED_API_URL = process.env.MEMORY_FEED_API_URL || "https://memory-feed.vercel.app";
|
|
18
19
|
/** Thrown when no API token is present yet — the model gets a "run login" nudge, not a hard error. */
|
|
@@ -800,6 +801,7 @@ class EchoMemMCPServer {
|
|
|
800
801
|
mcpClientVersion;
|
|
801
802
|
/** Whether the most recent ListTools response carried the memory map (per-session recall signal). */
|
|
802
803
|
mapInjected = false;
|
|
804
|
+
updateStatus;
|
|
803
805
|
constructor(store) {
|
|
804
806
|
this.server = new Server({
|
|
805
807
|
name: "echomem-mcp",
|
|
@@ -812,6 +814,9 @@ class EchoMemMCPServer {
|
|
|
812
814
|
});
|
|
813
815
|
this.client = new EchoMemApiClient(store);
|
|
814
816
|
this.events = new EventLogger({ session_id: this.client.getSessionId(), app_version: SERVER_VERSION });
|
|
817
|
+
startBackgroundUpdateCheck((status) => {
|
|
818
|
+
this.updateStatus = status;
|
|
819
|
+
});
|
|
815
820
|
this.setupToolHandlers();
|
|
816
821
|
this.server.onerror = (error) => console.error("[MCP Error]", error);
|
|
817
822
|
process.on("SIGINT", async () => {
|
|
@@ -851,7 +856,8 @@ class EchoMemMCPServer {
|
|
|
851
856
|
? await Promise.race([this.mapCache, new Promise((r) => setTimeout(() => r(undefined), 2500))])
|
|
852
857
|
: undefined;
|
|
853
858
|
this.mapInjected = !!map;
|
|
854
|
-
|
|
859
|
+
const updateNotice = formatUpdateNotice(this.updateStatus);
|
|
860
|
+
return { tools: listToolSpecs({ map, updateNotice }) };
|
|
855
861
|
});
|
|
856
862
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
857
863
|
const canonicalName = resolveCanonicalToolName(request.params.name);
|
|
@@ -883,6 +889,12 @@ class EchoMemMCPServer {
|
|
|
883
889
|
if (canonicalName === canonicalToolNames.report) {
|
|
884
890
|
return { content: [{ type: "text", text: await buildReportText(false) }] };
|
|
885
891
|
}
|
|
892
|
+
if (canonicalName === canonicalToolNames.updateStatus) {
|
|
893
|
+
const force = isRecord(request.params.arguments) && request.params.arguments.force === true;
|
|
894
|
+
const status = await checkLatestUpdateStatus({ force });
|
|
895
|
+
this.updateStatus = status;
|
|
896
|
+
return { content: [{ type: "text", text: formatUpdateStatusText(status) }] };
|
|
897
|
+
}
|
|
886
898
|
if (canonicalName === canonicalToolNames.contextHealth) {
|
|
887
899
|
const client = isRecord(request.params.arguments) && typeof request.params.arguments.client === "string"
|
|
888
900
|
? request.params.arguments.client
|
|
@@ -994,6 +1006,7 @@ class EchoMemMCPServer {
|
|
|
994
1006
|
rec.latency_ms = Date.now() - t0;
|
|
995
1007
|
this.events.record(rec);
|
|
996
1008
|
if (canonicalName !== canonicalToolNames.report &&
|
|
1009
|
+
canonicalName !== canonicalToolNames.updateStatus &&
|
|
997
1010
|
canonicalName !== canonicalToolNames.contextHealth &&
|
|
998
1011
|
canonicalName !== canonicalToolNames.recompose &&
|
|
999
1012
|
canonicalName !== canonicalToolNames.save &&
|
package/dist/package-metadata.js
CHANGED
|
@@ -22,11 +22,13 @@ export const MCP_PACKAGE_VERSION = stringOrFallback(packageJson.version, FALLBAC
|
|
|
22
22
|
export const MCP_PACKAGE_DESCRIPTION = stringOrFallback(packageJson.description, FALLBACK_PACKAGE.description);
|
|
23
23
|
export const MCP_PACKAGE_LABEL = `${MCP_PACKAGE_NAME}@${MCP_PACKAGE_VERSION}`;
|
|
24
24
|
export const MCP_UPDATE_COMMAND = `npx -y ${MCP_PACKAGE_NAME}@latest update`;
|
|
25
|
+
export const MCP_UPDATE_ALL_COMMAND = `${MCP_UPDATE_COMMAND} --all`;
|
|
25
26
|
export const MCP_SERVER_INSTRUCTIONS = [
|
|
26
27
|
`${MCP_PACKAGE_DESCRIPTION} (${MCP_PACKAGE_LABEL}).`,
|
|
27
|
-
`If the user or local config expects a newer EchoMem MCP version than ${MCP_PACKAGE_VERSION}, update once with \`${
|
|
28
|
+
`If the user or local config expects a newer EchoMem MCP version than ${MCP_PACKAGE_VERSION}, update once with \`${MCP_UPDATE_ALL_COMMAND}\` and start a new MCP session.`,
|
|
29
|
+
"Use echomem_update_status to check whether npm has a newer bridge; it is cached and non-blocking during normal tool listing.",
|
|
28
30
|
"Do not auto-update on every MCP startup; this bridge is intentionally stable between explicit updates.",
|
|
29
31
|
].join(" ");
|
|
30
32
|
export function withMcpVersion(description) {
|
|
31
|
-
return `${description}\n\nEchoMem MCP bridge: ${MCP_PACKAGE_LABEL}. If this version is stale, update once with \`${
|
|
33
|
+
return `${description}\n\nEchoMem MCP bridge: ${MCP_PACKAGE_LABEL}. If this version is stale, update once with \`${MCP_UPDATE_ALL_COMMAND}\` (or add \`--client cursor|windsurf|claude-desktop|claude-code|codex\` for a single client), then start a new MCP session. Do not run updates repeatedly or on every startup.`;
|
|
32
34
|
}
|
package/dist/setup.js
CHANGED
|
@@ -30,7 +30,8 @@ import { syncCodexUsage } from "./codex-sync.js";
|
|
|
30
30
|
import { renderSetupPage } from "./setup-page.js";
|
|
31
31
|
import { repoLabel } from "./forensics.js";
|
|
32
32
|
import { installHooks } from "./hud/hooks.js";
|
|
33
|
-
import { MCP_PACKAGE_LABEL, MCP_PACKAGE_NAME, MCP_PACKAGE_VERSION, MCP_UPDATE_COMMAND } from "./package-metadata.js";
|
|
33
|
+
import { MCP_PACKAGE_LABEL, MCP_PACKAGE_NAME, MCP_PACKAGE_VERSION, MCP_UPDATE_ALL_COMMAND, MCP_UPDATE_COMMAND } from "./package-metadata.js";
|
|
34
|
+
import { checkLatestUpdateStatus, compareSemver, readCachedUpdateStatus } from "./update-check.js";
|
|
34
35
|
// The hosted connect-device page is now only the account-auth/token courier. The dashboard itself is
|
|
35
36
|
// served by this localhost bridge, where local logs and processed/unprocessed counts never leave the
|
|
36
37
|
// device unless the user explicitly starts migration. Override the hosted auth origin with ECHO_WEB_URL.
|
|
@@ -274,25 +275,14 @@ export function resolveServerEntryVersion(entry) {
|
|
|
274
275
|
}
|
|
275
276
|
const candidates = [...args, command].filter((value) => typeof value === "string");
|
|
276
277
|
for (const candidate of candidates) {
|
|
277
|
-
|
|
278
|
-
|
|
278
|
+
// Direct local/dev entries often look like ".../packages/mcp-server/dist/index.js";
|
|
279
|
+
// they do not contain the published package name, but walking upward still finds package.json.
|
|
279
280
|
const resolved = packageVersionFromPath(candidate);
|
|
280
281
|
if (resolved)
|
|
281
282
|
return resolved;
|
|
282
283
|
}
|
|
283
284
|
return {};
|
|
284
285
|
}
|
|
285
|
-
function compareSemver(a, b) {
|
|
286
|
-
const av = a.split(/[.-]/).map((part) => Number(part));
|
|
287
|
-
const bv = b.split(/[.-]/).map((part) => Number(part));
|
|
288
|
-
for (let i = 0; i < Math.max(av.length, bv.length, 3); i += 1) {
|
|
289
|
-
const ai = Number.isFinite(av[i]) ? av[i] : 0;
|
|
290
|
-
const bi = Number.isFinite(bv[i]) ? bv[i] : 0;
|
|
291
|
-
if (ai !== bi)
|
|
292
|
-
return ai > bi ? 1 : -1;
|
|
293
|
-
}
|
|
294
|
-
return 0;
|
|
295
|
-
}
|
|
296
286
|
function versionState(version, desiredVersion) {
|
|
297
287
|
if (!version || !desiredVersion)
|
|
298
288
|
return "unknown";
|
|
@@ -303,19 +293,6 @@ function versionState(version, desiredVersion) {
|
|
|
303
293
|
return "newer";
|
|
304
294
|
return "ok";
|
|
305
295
|
}
|
|
306
|
-
function readLatestPublishedVersion() {
|
|
307
|
-
try {
|
|
308
|
-
const raw = execFileSync("npm", ["view", MCP_PACKAGE_NAME, "version", "--silent"], {
|
|
309
|
-
encoding: "utf8",
|
|
310
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
311
|
-
timeout: 3500,
|
|
312
|
-
}).trim();
|
|
313
|
-
return /^\d+\.\d+\.\d+/.test(raw) ? raw : undefined;
|
|
314
|
-
}
|
|
315
|
-
catch {
|
|
316
|
-
return undefined;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
296
|
function inspectClientConfig(client, desiredVersion) {
|
|
320
297
|
if (client.kind === "snippet")
|
|
321
298
|
return inspectClaudeCodeConfig(client, desiredVersion);
|
|
@@ -1045,11 +1022,11 @@ function parseFlags(argv) {
|
|
|
1045
1022
|
async function cmdSetup(flags) {
|
|
1046
1023
|
const entry = buildServerEntry({ devEntryPath: typeof flags.dev === "string" ? flags.dev : undefined });
|
|
1047
1024
|
const requested = typeof flags.client === "string" ? flags.client : undefined;
|
|
1048
|
-
const targets = (requested
|
|
1025
|
+
const targets = selectSetupTargets(requested, Boolean(flags.all));
|
|
1049
1026
|
if (targets.length === 0) {
|
|
1050
1027
|
console.log("No client auto-detected. Add this MCP server entry manually:\n");
|
|
1051
1028
|
console.log(JSON.stringify({ echomem: entry }, null, 2));
|
|
1052
|
-
console.log("\n(Or re-run with --client cursor|windsurf|claude-desktop|codex.)");
|
|
1029
|
+
console.log("\n(Or re-run with --client cursor|windsurf|claude-desktop|claude-code|codex, or --all.)");
|
|
1053
1030
|
}
|
|
1054
1031
|
else {
|
|
1055
1032
|
for (const c of targets) {
|
|
@@ -1089,6 +1066,18 @@ async function cmdUpdate(flags) {
|
|
|
1089
1066
|
await cmdSetup({ ...flags, "skip-login": true });
|
|
1090
1067
|
console.log(`Update config complete. Start a new MCP session to load ${MCP_PACKAGE_LABEL}.`);
|
|
1091
1068
|
}
|
|
1069
|
+
function selectSetupTargets(requested, all) {
|
|
1070
|
+
if (all || requested === "all") {
|
|
1071
|
+
return knownClients().filter((client) => {
|
|
1072
|
+
if (client.kind === "json")
|
|
1073
|
+
return fs.existsSync(path.dirname(client.configPath));
|
|
1074
|
+
if (client.kind === "command")
|
|
1075
|
+
return fs.existsSync(client.detectDir);
|
|
1076
|
+
return client.id === "claude-code" && fs.existsSync(home(".claude"));
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
return requested ? knownClients().filter((client) => client.id === requested) : detectClients();
|
|
1080
|
+
}
|
|
1092
1081
|
async function cmdSetupHud(flags) {
|
|
1093
1082
|
const client = parseHudClient(flags["hud-client"] || "auto");
|
|
1094
1083
|
const hudCli = resolveHudCliPath();
|
|
@@ -1668,13 +1657,17 @@ async function cmdStatus(flags = {}) {
|
|
|
1668
1657
|
const store = new KeyStore();
|
|
1669
1658
|
const token = store.getToken();
|
|
1670
1659
|
console.log(`EchoMem MCP: ${MCP_PACKAGE_LABEL}`);
|
|
1671
|
-
const
|
|
1660
|
+
const updateStatus = flags["no-network"] ? readCachedUpdateStatus() : await checkLatestUpdateStatus({ force: true });
|
|
1661
|
+
const latest = updateStatus?.latestVersion;
|
|
1672
1662
|
if (latest) {
|
|
1673
1663
|
console.log(`Published latest: ${MCP_PACKAGE_NAME}@${latest}`);
|
|
1674
1664
|
if (compareSemver(MCP_PACKAGE_VERSION, latest) < 0) {
|
|
1675
|
-
console.log(`This status command is older than latest. Update client configs with: ${
|
|
1665
|
+
console.log(`This status command is older than latest. Update client configs with: ${MCP_UPDATE_ALL_COMMAND}`);
|
|
1676
1666
|
}
|
|
1677
1667
|
}
|
|
1668
|
+
else if (updateStatus?.error) {
|
|
1669
|
+
console.log(`Published latest: unavailable (${updateStatus.error})`);
|
|
1670
|
+
}
|
|
1678
1671
|
console.log(`Credentials file: ${store.path()}`);
|
|
1679
1672
|
console.log(`API token: ${token ? "present" : "MISSING — run `echomem-mcp login`"}`);
|
|
1680
1673
|
if (token) {
|
|
@@ -1723,7 +1716,8 @@ Usage:
|
|
|
1723
1716
|
echomem-mcp Run the MCP server (stdio; default — used by your editor)
|
|
1724
1717
|
echomem-mcp setup [--client X] Detect editor, write its MCP config, then log in
|
|
1725
1718
|
echomem-mcp setup --skip-login Write MCP config without opening login/browser
|
|
1726
|
-
echomem-mcp update
|
|
1719
|
+
echomem-mcp update --all Repoint detected clients to this installed bridge; no login/browser
|
|
1720
|
+
echomem-mcp update --client X Repoint one MCP client; no login/browser
|
|
1727
1721
|
echomem-mcp setup --with-hud Configure MCP, then launch the EchoMem context HUD
|
|
1728
1722
|
echomem-mcp login Approve this device in the browser (or --token/--passphrase)
|
|
1729
1723
|
echomem-mcp unlock Re-derive the encryption key after its TTL (or --passphrase)
|
|
@@ -1742,7 +1736,8 @@ Usage:
|
|
|
1742
1736
|
|
|
1743
1737
|
Manual / headless:
|
|
1744
1738
|
echomem-mcp login --token ec_xxx [--passphrase <vault pass> | --key <base64>]
|
|
1745
|
-
${
|
|
1739
|
+
${MCP_UPDATE_ALL_COMMAND} # one-shot latest update for detected clients, no browser login
|
|
1740
|
+
${MCP_UPDATE_COMMAND} --client codex # update one client only
|
|
1746
1741
|
echomem-mcp setup --dev /abs/path/dist/index.js # point clients at a local checkout
|
|
1747
1742
|
echomem-mcp setup --with-hud --install-hud-hooks --client codex [--hud-client auto]
|
|
1748
1743
|
echomem-mcp sync-usage --days 7 --limit 50 --dry-run
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { echoConfigDir } from "./keystore.js";
|
|
4
|
+
import { MCP_PACKAGE_NAME, MCP_PACKAGE_VERSION, MCP_UPDATE_ALL_COMMAND, } from "./package-metadata.js";
|
|
5
|
+
export const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
6
|
+
const UPDATE_CHECK_TIMEOUT_MS = 3500;
|
|
7
|
+
export function compareSemver(a, b) {
|
|
8
|
+
const av = a.split(/[.-]/).map((part) => Number(part));
|
|
9
|
+
const bv = b.split(/[.-]/).map((part) => Number(part));
|
|
10
|
+
for (let i = 0; i < Math.max(av.length, bv.length, 3); i += 1) {
|
|
11
|
+
const ai = Number.isFinite(av[i]) ? av[i] : 0;
|
|
12
|
+
const bi = Number.isFinite(bv[i]) ? bv[i] : 0;
|
|
13
|
+
if (ai !== bi)
|
|
14
|
+
return ai > bi ? 1 : -1;
|
|
15
|
+
}
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
export function updateCheckCachePath() {
|
|
19
|
+
return path.join(echoConfigDir(), "mcp-update-check.json");
|
|
20
|
+
}
|
|
21
|
+
export function readCachedUpdateStatus(nowMs = Date.now()) {
|
|
22
|
+
let raw;
|
|
23
|
+
try {
|
|
24
|
+
raw = JSON.parse(fs.readFileSync(updateCheckCachePath(), "utf8"));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
if (raw.packageName !== MCP_PACKAGE_NAME)
|
|
30
|
+
return undefined;
|
|
31
|
+
if (typeof raw.latestVersion !== "string" || typeof raw.checkedAt !== "string")
|
|
32
|
+
return undefined;
|
|
33
|
+
const checkedMs = Date.parse(raw.checkedAt);
|
|
34
|
+
if (!Number.isFinite(checkedMs))
|
|
35
|
+
return undefined;
|
|
36
|
+
return {
|
|
37
|
+
packageName: MCP_PACKAGE_NAME,
|
|
38
|
+
currentVersion: MCP_PACKAGE_VERSION,
|
|
39
|
+
latestVersion: raw.latestVersion,
|
|
40
|
+
checkedAt: raw.checkedAt,
|
|
41
|
+
updateAvailable: compareSemver(MCP_PACKAGE_VERSION, raw.latestVersion) < 0,
|
|
42
|
+
source: "cache",
|
|
43
|
+
command: MCP_UPDATE_ALL_COMMAND,
|
|
44
|
+
error: nowMs - checkedMs > UPDATE_CHECK_INTERVAL_MS ? "cached result is older than 24h" : undefined,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function isUpdateCheckFresh(status, nowMs = Date.now()) {
|
|
48
|
+
if (!status?.checkedAt)
|
|
49
|
+
return false;
|
|
50
|
+
const checkedMs = Date.parse(status.checkedAt);
|
|
51
|
+
return Number.isFinite(checkedMs) && nowMs - checkedMs <= UPDATE_CHECK_INTERVAL_MS;
|
|
52
|
+
}
|
|
53
|
+
export async function checkLatestUpdateStatus(opts = {}) {
|
|
54
|
+
const nowMs = opts.nowMs ?? Date.now();
|
|
55
|
+
const cached = readCachedUpdateStatus(nowMs);
|
|
56
|
+
if (process.env.ECHO_DISABLE_UPDATE_CHECK === "1") {
|
|
57
|
+
return cached ?? baseStatus("disabled");
|
|
58
|
+
}
|
|
59
|
+
if (!opts.force && cached && isUpdateCheckFresh(cached, nowMs))
|
|
60
|
+
return cached;
|
|
61
|
+
const registryUrl = opts.registryUrl
|
|
62
|
+
|| process.env.ECHO_MCP_UPDATE_REGISTRY_URL
|
|
63
|
+
|| `https://registry.npmjs.org/${encodeURIComponent(MCP_PACKAGE_NAME)}/latest`;
|
|
64
|
+
try {
|
|
65
|
+
const signal = AbortSignal.timeout(opts.timeoutMs ?? UPDATE_CHECK_TIMEOUT_MS);
|
|
66
|
+
const res = await fetch(registryUrl, {
|
|
67
|
+
headers: { accept: "application/json" },
|
|
68
|
+
signal,
|
|
69
|
+
});
|
|
70
|
+
if (!res.ok)
|
|
71
|
+
throw new Error(`npm registry HTTP ${res.status}`);
|
|
72
|
+
const data = await res.json();
|
|
73
|
+
const latestVersion = typeof data.version === "string" ? data.version.trim() : "";
|
|
74
|
+
if (!latestVersion)
|
|
75
|
+
throw new Error("npm registry response missing version");
|
|
76
|
+
const checkedAt = new Date(nowMs).toISOString();
|
|
77
|
+
const status = {
|
|
78
|
+
packageName: MCP_PACKAGE_NAME,
|
|
79
|
+
currentVersion: MCP_PACKAGE_VERSION,
|
|
80
|
+
latestVersion,
|
|
81
|
+
checkedAt,
|
|
82
|
+
updateAvailable: compareSemver(MCP_PACKAGE_VERSION, latestVersion) < 0,
|
|
83
|
+
source: "network",
|
|
84
|
+
command: MCP_UPDATE_ALL_COMMAND,
|
|
85
|
+
};
|
|
86
|
+
writeUpdateCache(status);
|
|
87
|
+
return status;
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
return {
|
|
91
|
+
...(cached ?? baseStatus("none")),
|
|
92
|
+
error: error instanceof Error ? error.message : String(error),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export function startBackgroundUpdateCheck(onStatus) {
|
|
97
|
+
const cached = readCachedUpdateStatus();
|
|
98
|
+
if (cached)
|
|
99
|
+
onStatus(cached);
|
|
100
|
+
if (process.env.ECHO_DISABLE_UPDATE_CHECK === "1" || isUpdateCheckFresh(cached))
|
|
101
|
+
return cached;
|
|
102
|
+
void checkLatestUpdateStatus({ force: true }).then(onStatus).catch(() => {
|
|
103
|
+
/* Update checks are advisory; never affect MCP startup. */
|
|
104
|
+
});
|
|
105
|
+
return cached;
|
|
106
|
+
}
|
|
107
|
+
export function formatUpdateNotice(status) {
|
|
108
|
+
if (!status?.updateAvailable || !status.latestVersion)
|
|
109
|
+
return undefined;
|
|
110
|
+
return [
|
|
111
|
+
`EchoMem update available: installed ${status.currentVersion}, latest ${status.latestVersion}.`,
|
|
112
|
+
`Offer to update, then run \`${status.command}\` if the user agrees.`,
|
|
113
|
+
"After updating, start a new agent/MCP session.",
|
|
114
|
+
].join(" ");
|
|
115
|
+
}
|
|
116
|
+
export function formatUpdateStatusText(status) {
|
|
117
|
+
const lines = [
|
|
118
|
+
`EchoMem MCP installed: ${status.packageName}@${status.currentVersion}`,
|
|
119
|
+
status.latestVersion ? `Latest published: ${status.packageName}@${status.latestVersion}` : "Latest published: unknown",
|
|
120
|
+
`Update available: ${status.updateAvailable ? "yes" : "no"}`,
|
|
121
|
+
];
|
|
122
|
+
if (status.updateAvailable) {
|
|
123
|
+
lines.push(`Update command: ${status.command}`);
|
|
124
|
+
lines.push("After updating, start a new agent/MCP session.");
|
|
125
|
+
}
|
|
126
|
+
if (status.checkedAt)
|
|
127
|
+
lines.push(`Checked at: ${status.checkedAt} (${status.source})`);
|
|
128
|
+
else
|
|
129
|
+
lines.push(`Checked at: not checked (${status.source})`);
|
|
130
|
+
if (status.error)
|
|
131
|
+
lines.push(`Check note: ${status.error}`);
|
|
132
|
+
return lines.join("\n");
|
|
133
|
+
}
|
|
134
|
+
function baseStatus(source) {
|
|
135
|
+
return {
|
|
136
|
+
packageName: MCP_PACKAGE_NAME,
|
|
137
|
+
currentVersion: MCP_PACKAGE_VERSION,
|
|
138
|
+
updateAvailable: false,
|
|
139
|
+
source,
|
|
140
|
+
command: MCP_UPDATE_ALL_COMMAND,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function writeUpdateCache(status) {
|
|
144
|
+
if (!status.latestVersion || !status.checkedAt)
|
|
145
|
+
return;
|
|
146
|
+
const dir = echoConfigDir();
|
|
147
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
148
|
+
fs.writeFileSync(updateCheckCachePath(), JSON.stringify({
|
|
149
|
+
packageName: status.packageName,
|
|
150
|
+
currentVersion: status.currentVersion,
|
|
151
|
+
latestVersion: status.latestVersion,
|
|
152
|
+
checkedAt: status.checkedAt,
|
|
153
|
+
}, null, 2), { mode: 0o600 });
|
|
154
|
+
}
|
package/dist/v1-contract.js
CHANGED
|
@@ -7,6 +7,7 @@ export const canonicalToolNames = {
|
|
|
7
7
|
keywords: "search_memories_by_keywords",
|
|
8
8
|
others: "search_others_memories",
|
|
9
9
|
report: "echomem_usage_report",
|
|
10
|
+
updateStatus: "echomem_update_status",
|
|
10
11
|
contextHealth: "echo_context_health",
|
|
11
12
|
recompose: "echo_recompose",
|
|
12
13
|
delete: "delete_memory",
|
|
@@ -75,13 +76,15 @@ export const getByContextSchema = z.object({
|
|
|
75
76
|
export function listToolSpecs(opts = {}) {
|
|
76
77
|
const currentTime = new Date().toISOString();
|
|
77
78
|
const map = opts.map?.trim();
|
|
79
|
+
const updateNotice = opts.updateNotice?.trim();
|
|
80
|
+
const updateSection = updateNotice ? `\n\nUPDATE NOTICE: ${updateNotice}` : "";
|
|
78
81
|
const mapSection = map
|
|
79
82
|
? `\n\nThis user's EchoMem currently covers these topics (a relevance guide — recall when the task relates to one of them):\n${map}\n`
|
|
80
83
|
: "";
|
|
81
84
|
return [
|
|
82
85
|
{
|
|
83
86
|
name: canonicalToolNames.search,
|
|
84
|
-
description: withMcpVersion(`Recall the user's prior decisions, preferences, constraints, and project context from EchoMem — their long-term memory across ALL their AI tools (Claude.ai, ChatGPT, other agents), not just this session.${mapSection}\nCall this when the task plausibly relates to that remembered context — a topic above, or when the user refers to past work ("what did we decide", "like before", "the usual") — so you don't re-derive or re-ask what they already settled. Skip it for self-contained tasks with no link to their history (e.g. a generic algorithm question). By default this returns only the ranked memories retrieved for recall and skips EchoMem answer generation; set includeAnswer=true only when you explicitly need EchoMem's legacy synthesized recall. Current time: ${currentTime}
|
|
87
|
+
description: withMcpVersion(`Recall the user's prior decisions, preferences, constraints, and project context from EchoMem — their long-term memory across ALL their AI tools (Claude.ai, ChatGPT, other agents), not just this session.${mapSection}\nCall this when the task plausibly relates to that remembered context — a topic above, or when the user refers to past work ("what did we decide", "like before", "the usual") — so you don't re-derive or re-ask what they already settled. Skip it for self-contained tasks with no link to their history (e.g. a generic algorithm question). By default this returns only the ranked memories retrieved for recall and skips EchoMem answer generation; set includeAnswer=true only when you explicitly need EchoMem's legacy synthesized recall. Current time: ${currentTime}.${updateSection}`),
|
|
85
88
|
inputSchema: {
|
|
86
89
|
type: "object",
|
|
87
90
|
properties: {
|
|
@@ -254,6 +257,20 @@ export function listToolSpecs(opts = {}) {
|
|
|
254
257
|
description: "Show the user a one-screen audit of THEIR OWN AI coding usage — computed locally from their Codex/Claude Code logs ($0, nothing uploaded): how many tokens their agents spent, how much was re-reading context, reads vs memory recalls, and what changes with EchoMem. Call this when the user asks about their usage, token spend, cost, how much they're wasting, or wants a summary of their agent activity — and you may offer it once right after EchoMem is first connected. Returns formatted text to show the user verbatim. Needs no login.",
|
|
255
258
|
inputSchema: { type: "object", properties: {} },
|
|
256
259
|
},
|
|
260
|
+
{
|
|
261
|
+
name: canonicalToolNames.updateStatus,
|
|
262
|
+
description: `Check whether this installed EchoMem MCP bridge is behind the latest published npm version. Works without login, uploads no user transcript, and normal background checks are cached so EchoMem does not hit npm on every startup. If it reports an update, tell the user and offer to run the returned update command; after updating, the user must start a new agent/MCP session.${updateSection}`,
|
|
263
|
+
inputSchema: {
|
|
264
|
+
type: "object",
|
|
265
|
+
properties: {
|
|
266
|
+
force: {
|
|
267
|
+
type: "boolean",
|
|
268
|
+
default: false,
|
|
269
|
+
description: "When true, refresh npm latest now instead of using the cached check.",
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
},
|
|
257
274
|
{
|
|
258
275
|
name: canonicalToolNames.contextHealth,
|
|
259
276
|
description: "Show the current local Codex/Claude context-health score as markdown: clean percentage, tracked lower-bound dead-weight, redundant reads, source client, and token-count source. Use when the user asks about the context HUD, dirty context, context pollution, whether cleanup is worth it, or wants an in-chat fallback to the passive HUD. This reads local agent logs only and needs no login.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@echomem/mcp",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.4",
|
|
4
4
|
"description": "EchoMem Cloud-First MCP Server",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"start": "node dist/index.js",
|
|
21
21
|
"dev": "tsx src/index.ts",
|
|
22
22
|
"smoke": "node smoke.mjs",
|
|
23
|
-
"test": "npm run build && node test/crypto.test.mjs && node test/integration.test.mjs && node test/no-restart.test.mjs && node test/report.test.mjs && node test/tools.test.mjs && node test/delete.test.mjs && node test/migrate.test.mjs && node test/hud.test.mjs",
|
|
23
|
+
"test": "npm run build && node test/crypto.test.mjs && node test/integration.test.mjs && node test/no-restart.test.mjs && node test/report.test.mjs && node test/tools.test.mjs && node test/update-check.test.mjs && node test/delete.test.mjs && node test/migrate.test.mjs && node test/hud.test.mjs",
|
|
24
24
|
"prepack": "npm run build && node scripts/bundle-city.mjs"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|