@echomem/mcp 1.4.24 → 1.4.26

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.
@@ -13,7 +13,7 @@ import { assembleCodex, assembleClaude } from "../migrate.js";
13
13
  import { readBillingAlert } from "../billing-alert.js";
14
14
  import { readSessionWorkingDirectory, } from "./session-launcher.js";
15
15
  const API_BASE = (process.env.ECHO_API_BASE_URL || "https://echo-mem-chrome.vercel.app").replace(/\/$/, "");
16
- const PRICING_URL = (process.env.ECHO_PRICING_URL || "https://echoknows.com/pricing").replace(/\/$/, "");
16
+ const PRICING_URL = (process.env.ECHO_PRICING_URL || "https://echoknows.com/account").replace(/\/$/, "");
17
17
  export async function createHudServer(opts = {}) {
18
18
  const mode = opts.mode || "auto";
19
19
  const port = opts.port ?? 17377;
package/dist/hud/web.js CHANGED
@@ -1729,7 +1729,7 @@ export const HUD_HTML = String.raw `<!doctype html>
1729
1729
  state: "ok",
1730
1730
  plan: "pro",
1731
1731
  paid: true,
1732
- pricingUrl: "https://yeahecho.com/pricing?source=hud_preview",
1732
+ pricingUrl: "https://echoknows.com/account?source=hud_preview",
1733
1733
  memoryProcessingQuota: { used: previewProcessingUsed, limit: 200000, remaining: 200000 - previewProcessingUsed, resetAt: new Date(Date.now() + 31 * 60 * 60 * 1000).toISOString() },
1734
1734
  memorySearchQuota: { used: previewSearchUsed, limit: 500, remaining: 500 - previewSearchUsed, resetAt: new Date(Date.now() + 31 * 60 * 60 * 1000).toISOString() },
1735
1735
  historicalConversationQuota: { used: 191, limit: 500, remaining: 309 },
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4
4
  import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js";
5
5
  import axios from "axios";
6
6
  import { ZodError } from "zod";
7
- import { canonicalToolNames, completeGroupPublicationSchema, createGroupInviteSchema, createGroupSchema, deleteMemorySchema, getByContextSchema, groupContextSchema, joinGroupSchema, keywordsSchema, listFriendsSchema, listToolSpecs, othersSchema, publishBatchToGroupSchema, publishToGroupSchema, prepareGroupPublicationSchema, publicMemorySchema, resolveCanonicalToolName, saveConversationSchema, searchMemoriesSchema, searchUsersSchema, sendFriendRequestSchema, timeRangeSchema, updateGroupProfileSchema, } from "./v1-contract.js";
7
+ import { canonicalToolNames, completeGroupPublicationSchema, createGroupInviteSchema, createGroupSchema, deleteMemorySchema, flagPublicationAttentionSchema, getByContextSchema, groupContextSchema, joinGroupSchema, keywordsSchema, listFriendsSchema, listToolSpecs, othersSchema, publishBatchToGroupSchema, publishToGroupSchema, prepareGroupPublicationSchema, publicMemorySchema, resolveCanonicalToolName, saveConversationSchema, searchMemoriesSchema, searchUsersSchema, sendFriendRequestSchema, timeRangeSchema, updateGroupProfileSchema, } from "./v1-contract.js";
8
8
  import { KeyStore } from "./keystore.js";
9
9
  import { EventLogger, hashText } from "./events.js";
10
10
  import { buildReportText } from "./report.js";
@@ -16,7 +16,15 @@ import { MCP_PACKAGE_VERSION, MCP_SERVER_INSTRUCTIONS } from "./package-metadata
16
16
  import { clearBillingAlert, writeBillingAlert } from "./billing-alert.js";
17
17
  import { checkLatestUpdateStatus, formatUpdateNotice, formatUpdateStatusText, startBackgroundUpdateCheck, } from "./update-check.js";
18
18
  const ECHO_API_BASE_URL = process.env.ECHO_API_BASE_URL || "https://echo-mem-chrome.vercel.app";
19
- const ECHO_PRICING_URL = process.env.ECHO_PRICING_URL || "https://echoknows.com/pricing";
19
+ const ECHO_PRICING_URL = process.env.ECHO_PRICING_URL || "https://echoknows.com/account";
20
+ const ECHO_MEMORY_WEB_URL = (process.env.ECHO_MEMORY_WEB_URL || "https://echoknows.com/company/memories").replace(/\/$/, "");
21
+ const ECHO_PERSONAL_MEMORY_WEB_URL = (process.env.ECHO_PERSONAL_MEMORY_WEB_URL || "https://echoknows.com/memories/timeline").replace(/\/$/, "");
22
+ function memoryWebUrl(memoryId) {
23
+ return `${ECHO_MEMORY_WEB_URL}?memoryId=${encodeURIComponent(memoryId)}`;
24
+ }
25
+ function personalMemoryWebUrl(memoryId) {
26
+ return `${ECHO_PERSONAL_MEMORY_WEB_URL}?memoryId=${encodeURIComponent(memoryId)}`;
27
+ }
20
28
  /** Thrown when no API token is present yet — the model gets a "run login" nudge, not a hard error. */
21
29
  class NoTokenError extends Error {
22
30
  }
@@ -495,6 +503,14 @@ function inputAnalyticsForTool(canonicalName, args) {
495
503
  memory_id_hash: memoryId ? hashText(memoryId) : undefined,
496
504
  };
497
505
  }
506
+ case canonicalToolNames.flagPublicationAttention: {
507
+ const memoryIds = Array.isArray(a.memoryIds) ? a.memoryIds.filter((id) => typeof id === "string") : [];
508
+ return {
509
+ memory_count: memoryIds.length,
510
+ has_label: !!readString(a, "label"),
511
+ confirmed: a.confirmed === true,
512
+ };
513
+ }
498
514
  case canonicalToolNames.publishBatchToGroup: {
499
515
  const memoryIds = Array.isArray(a.memoryIds) ? a.memoryIds.filter((id) => typeof id === "string") : [];
500
516
  return {
@@ -983,6 +999,11 @@ class EchoMemApiClient {
983
999
  const response = await this.axios.post("/api/extension/social/groups/current/publications/prepare", parsed, { headers: enc.enabled && enc.key ? { "X-Encryption-Key": enc.key } : undefined });
984
1000
  return response.data;
985
1001
  }
1002
+ async flagMemoriesForPublicationAttention(args) {
1003
+ const parsed = flagPublicationAttentionSchema.parse(args ?? {});
1004
+ const response = await this.axios.post("/api/extension/memories/publication-attention", parsed);
1005
+ return response.data;
1006
+ }
986
1007
  async updateGroupProfile(args) {
987
1008
  const parsed = updateGroupProfileSchema.parse(args ?? {});
988
1009
  const response = await this.axios.patch("/api/extension/social/groups/current/profile", parsed);
@@ -998,7 +1019,9 @@ class EchoMemApiClient {
998
1019
  const parsed = publishToGroupSchema.parse(args ?? {});
999
1020
  const enc = await this.encState();
1000
1021
  try {
1001
- const response = await this.axios.post(`/api/extension/social/groups/current/memories/${encodeURIComponent(parsed.memoryId)}/publish`, {}, {
1022
+ const response = await this.axios.post(`/api/extension/social/groups/current/memories/${encodeURIComponent(parsed.memoryId)}/publish`, {
1023
+ acknowledgedFlaggedMemoryIds: parsed.acknowledgedFlaggedMemoryIds,
1024
+ }, {
1002
1025
  headers: enc.enabled && enc.key ? { "X-Encryption-Key": enc.key } : undefined,
1003
1026
  });
1004
1027
  return response.data;
@@ -1014,6 +1037,8 @@ class EchoMemApiClient {
1014
1037
  const response = await this.axios.post("/api/extension/social/groups/current/memories/publish-batch", {
1015
1038
  memoryIds: parsed.memoryIds,
1016
1039
  contextId: parsed.contextId,
1040
+ selectionReason: parsed.selectionReason,
1041
+ acknowledgedFlaggedMemoryIds: parsed.acknowledgedFlaggedMemoryIds,
1017
1042
  }, {
1018
1043
  headers: enc.enabled && enc.key ? { "X-Encryption-Key": enc.key } : undefined,
1019
1044
  });
@@ -1210,6 +1235,8 @@ class EchoMemMCPServer {
1210
1235
  return await this.handleJoinGroup(request.params.arguments);
1211
1236
  case canonicalToolNames.prepareGroupPublication:
1212
1237
  return await this.handlePrepareGroupPublication(request.params.arguments);
1238
+ case canonicalToolNames.flagPublicationAttention:
1239
+ return await this.handleFlagPublicationAttention(request.params.arguments);
1213
1240
  case canonicalToolNames.updateGroupProfile:
1214
1241
  return await this.handleUpdateGroupProfile(request.params.arguments);
1215
1242
  case canonicalToolNames.completeGroupPublication:
@@ -1381,8 +1408,11 @@ class EchoMemMCPServer {
1381
1408
  : m.details == null
1382
1409
  ? ""
1383
1410
  : String(m.details).trim();
1411
+ const memoryId = readString(m, "id");
1384
1412
  return [
1385
1413
  `[${idx + 1}] ${key}${typeof score === "number" ? ` (score ${score.toFixed(3)})` : ""}`,
1414
+ memoryId ? `Memory ID: ${memoryId}` : "",
1415
+ memoryId ? `Open private memory: ${personalMemoryWebUrl(memoryId)}` : "",
1386
1416
  meta,
1387
1417
  `Description: ${description}`,
1388
1418
  details ? `Details: ${details}` : "",
@@ -1400,6 +1430,7 @@ class EchoMemMCPServer {
1400
1430
  }
1401
1431
  const formattedResults = memories
1402
1432
  .map((m, idx) => `[Result ${idx + 1}] Memory ID: ${m.id || "unknown"} (Similarity: ${m.similarity_score?.toFixed(3) || "N/A"})
1433
+ Open private memory: ${personalMemoryWebUrl(String(m.id || "unknown"))}
1403
1434
  Time: ${m.time} | Location: ${m.location}
1404
1435
  Category: ${m.category} | Object: ${m.object} | Emotion: ${m.emotion}
1405
1436
  Description: ${m.description}
@@ -1477,7 +1508,9 @@ Details: ${m.details || "N/A"}`)
1477
1508
  };
1478
1509
  }
1479
1510
  const formattedResults = memories
1480
- .map((m, idx) => `[${idx + 1}] Time: ${m.time} | Location: ${m.location}
1511
+ .map((m, idx) => `[${idx + 1}] Memory ID: ${m.id || "unknown"}
1512
+ Open private memory: ${personalMemoryWebUrl(String(m.id || "unknown"))}
1513
+ Time: ${m.time} | Location: ${m.location}
1481
1514
  Category: ${m.category} | Object: ${m.object} | Emotion: ${m.emotion}
1482
1515
  Description: ${m.description}
1483
1516
  Details: ${m.details || "N/A"}`)
@@ -1503,6 +1536,7 @@ Details: ${m.details || "N/A"}`)
1503
1536
  }
1504
1537
  const formattedResults = memories
1505
1538
  .map((m, idx) => `[${idx + 1}] ${m.keys || "Saved memory"}${m.id ? ` · id ${m.id}` : ""}
1539
+ ${m.id ? `Open private memory: ${personalMemoryWebUrl(String(m.id))}` : ""}
1506
1540
  Time: ${m.time} | Category: ${m.category} | Object: ${m.object} | Emotion: ${m.emotion}
1507
1541
  Description: ${m.description}
1508
1542
  Details: ${m.details || "N/A"}`)
@@ -1581,7 +1615,9 @@ Details: ${m.details || "N/A"}`)
1581
1615
  };
1582
1616
  }
1583
1617
  const formattedResults = memories
1584
- .map((m, idx) => `[${idx + 1}] Time: ${m.time} | Keys: ${m.keys || "N/A"}
1618
+ .map((m, idx) => `[${idx + 1}] Memory ID: ${m.id || "unknown"}
1619
+ Open private memory: ${personalMemoryWebUrl(String(m.id || "unknown"))}
1620
+ Time: ${m.time} | Keys: ${m.keys || "N/A"}
1585
1621
  Location: ${m.location} | Category: ${m.category} | Object: ${m.object}
1586
1622
  Description: ${m.description}
1587
1623
  Details: ${m.details || "N/A"}`)
@@ -1634,6 +1670,7 @@ Details: ${m.details || "N/A"}`)
1634
1670
  ? m.similarity_score
1635
1671
  : undefined;
1636
1672
  return `[${idx + 1}] Memory ID: ${m.id || "unknown"}
1673
+ Open memory: ${memoryWebUrl(String(m.id || "unknown"))}
1637
1674
  User ID: ${m.user_id || "unknown"}
1638
1675
  User Name: ${m.username || m.user_name || m.name || "Anonymous"}
1639
1676
  Time: ${m.time} | Location: ${m.location}
@@ -1759,6 +1796,7 @@ Details: ${m.details || "N/A"}`;
1759
1796
  }
1760
1797
  const text = [
1761
1798
  `Memory ID: ${memory.id || parsed.memoryId}`,
1799
+ `Open memory: ${memoryWebUrl(String(memory.id || parsed.memoryId))}`,
1762
1800
  `Owner User ID: ${memory.owner_user_id || memory.user_id || "Unknown"}`,
1763
1801
  memory.time ? `Time: ${memory.time}` : "",
1764
1802
  memory.location ? `Location: ${memory.location}` : "",
@@ -1880,15 +1918,26 @@ Details: ${m.details || "N/A"}`;
1880
1918
  const participantProfile = isRecord(payload?.participantProfile)
1881
1919
  ? payload.participantProfile
1882
1920
  : {};
1883
- const formatted = candidates.map((candidate, index) => [
1884
- `[${index + 1}] Memory ID: ${readString(candidate, "memoryId") ?? "unknown"}`,
1885
- `Created: ${readString(candidate, "createdAt") ?? "unknown"}`,
1886
- `Category: ${readString(candidate, "category") ?? "unknown"}`,
1887
- `Description: ${readString(candidate, "description") ?? ""}`,
1888
- readString(candidate, "details") ? `Details: ${readString(candidate, "details")}` : "",
1889
- `Already published: ${candidate.alreadyPublished === true ? "yes" : "no"}`,
1890
- `Exact content duplicate: ${candidate.exactContentDuplicate === true ? "yes" : "no"}`,
1891
- ].filter(Boolean).join("\n")).join("\n\n");
1921
+ const formatted = candidates.map((candidate, index) => {
1922
+ const attention = isRecord(candidate.publicationAttention)
1923
+ ? candidate.publicationAttention
1924
+ : null;
1925
+ return [
1926
+ `[${index + 1}] Memory ID: ${readString(candidate, "memoryId") ?? "unknown"}`,
1927
+ `Open private memory: ${personalMemoryWebUrl(readString(candidate, "memoryId") ?? "unknown")}`,
1928
+ `Created: ${readString(candidate, "createdAt") ?? "unknown"}`,
1929
+ `Category: ${readString(candidate, "category") ?? "unknown"}`,
1930
+ `Description: ${readString(candidate, "description") ?? ""}`,
1931
+ readString(candidate, "details") ? `Details: ${readString(candidate, "details")}` : "",
1932
+ `Already published: ${candidate.alreadyPublished === true ? "yes" : "no"}`,
1933
+ `Exact content duplicate: ${candidate.exactContentDuplicate === true ? "yes" : "no"}`,
1934
+ attention ? `PUBLICATION ATTENTION: ${readString(attention, "label") ?? "flagged"}` : "",
1935
+ ].filter(Boolean).join("\n");
1936
+ }).join("\n\n");
1937
+ const flaggedIds = candidates
1938
+ .filter((candidate) => isRecord(candidate.publicationAttention))
1939
+ .map((candidate) => readString(candidate, "memoryId"))
1940
+ .filter(Boolean);
1892
1941
  return {
1893
1942
  content: [{
1894
1943
  type: "text",
@@ -1901,11 +1950,36 @@ Details: ${m.details || "N/A"}`;
1901
1950
  "",
1902
1951
  formatted,
1903
1952
  "",
1904
- "Select exact memory IDs matching the user's instruction. From memory evidence, draft a concise title and responsibility summary. Show the profile proposal and memory preview together, then ask for one explicit confirmation. After confirmation, call update_group_profile and complete_group_publication.",
1953
+ flaggedIds.length
1954
+ ? `Extra attention required: ${flaggedIds.length} candidate(s) are flagged (${flaggedIds.join(", ")}). Nothing has been published. Show them in a separate warning and offer to exclude them, review them separately, or first search for and mark similar sensitive owned memories for publication attention. Never auto-flag based on inference. Require separate explicit acknowledgement before including their exact IDs in acknowledgedFlaggedMemoryIds.`
1955
+ : "",
1956
+ "Select exact memory IDs matching the user's instruction. From memory evidence, draft a concise title and responsibility summary. If an unflagged candidate appears sensitive, proactively ask whether the user wants to mark its exact ID for publication attention first. Explain that marking does not publish or change encryption; it means you will call it out and ask for detailed confirmation whenever a later publication includes it. Never auto-flag. Show the profile proposal and memory preview together, then ask for explicit confirmation. After confirmation, call update_group_profile and complete_group_publication.",
1905
1957
  ].join("\n"),
1906
1958
  }],
1907
1959
  };
1908
1960
  }
1961
+ async handleFlagPublicationAttention(args) {
1962
+ const parsed = flagPublicationAttentionSchema.parse(args ?? {});
1963
+ const payload = await this.client.flagMemoriesForPublicationAttention(parsed);
1964
+ const alreadyPublished = typeof payload?.alreadyPublishedGroupCount === "number"
1965
+ ? payload.alreadyPublishedGroupCount
1966
+ : 0;
1967
+ return {
1968
+ content: [{
1969
+ type: "text",
1970
+ text: [
1971
+ `Flagged ${payload?.flaggedCount ?? parsed.memoryIds.length} memories for publication attention.`,
1972
+ `Label: ${parsed.label}`,
1973
+ `Memory IDs: ${parsed.memoryIds.join(", ")}`,
1974
+ `Private review links:\n${parsed.memoryIds.map((memoryId) => `- ${memoryId}: ${personalMemoryWebUrl(memoryId)}`).join("\n")}`,
1975
+ "No memory was published, decrypted, or made globally public.",
1976
+ alreadyPublished > 0
1977
+ ? `Attention: ${alreadyPublished} flagged memories already have group snapshots. This flag does not retract those existing snapshots.`
1978
+ : "",
1979
+ ].filter(Boolean).join("\n"),
1980
+ }],
1981
+ };
1982
+ }
1909
1983
  async handleUpdateGroupProfile(args) {
1910
1984
  const parsed = updateGroupProfileSchema.parse(args ?? {});
1911
1985
  const payload = await this.client.updateGroupProfile(parsed);
@@ -30,6 +30,9 @@ export const MCP_SERVER_INSTRUCTIONS = [
30
30
  "Do not auto-update on every MCP startup; this bridge is intentionally stable between explicit updates.",
31
31
  "For company-group sharing, use get_group_context for orientation; create_memory_group/create_group_invite/join_memory_group for membership; and prepare_group_publication as a no-publication preview.",
32
32
  "After joining or when profile fields are missing, use candidate memory evidence to propose a title and responsibility summary. Ask the user to confirm that proposal together with the publication preview, then call update_group_profile and complete_group_publication.",
33
+ "Use owner-only /memories/timeline?memoryId=... links for the user's private search, flag, and publication-preview evidence. Use /company/memories?memoryId=... only for already-published group evidence.",
34
+ "Each group-search result is one memory: preserve its Memory ID and direct echoknows.com link when citing it.",
35
+ "During a publication preview, if an unflagged candidate appears sensitive, proactively ask whether the user wants to mark its exact ID for publication attention first. Explain that marking does not publish or change encryption; it means the agent will call it out and ask for detailed confirmation whenever a later publication includes it. Never auto-flag inferred sensitivity. For sensitive-topic flags, search and preview exact owned memories before confirmed flag_memories_for_publication_attention. Separate already-flagged candidates, state that nothing has been published yet, and offer to exclude them, review them separately, or first search for and mark similar sensitive owned memories.",
33
36
  "Never save an inferred group profile or complete a group publication without explicit confirmation, and never store or log an echo_grp_ invite code.",
34
37
  ].join(" ");
35
38
  export function withMcpVersion(description) {
@@ -900,7 +900,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
900
900
  var paidPlan = plan === "power" ? "power" : "pro";
901
901
  return (billingStatus && billingStatus.pricingUrl)
902
902
  ? billingStatus.pricingUrl + (startTrial ? "&start=" + paidPlan + "&trial=" + (billingStatus.trialAvailable === false ? "0" : "1") : "")
903
- : "https://echoknows.com/pricing?source=mcp_onboarding" + (startTrial ? "&start=" + paidPlan + "&trial=1" : "");
903
+ : "https://echoknows.com/account?source=mcp_onboarding" + (startTrial ? "&start=" + paidPlan + "&trial=1" : "");
904
904
  }
905
905
  function setupPlanConfirmed() {
906
906
  return setupPlanChoice === "free" || paidRecallPlan(billingStatus && billingStatus.plan);
@@ -234,7 +234,7 @@ function extractionPreviewBootstrap(options) {
234
234
  trialUsed: options.trialUsed,
235
235
  subscriptionStatus: options.subscriptionStatus ?? null,
236
236
  trialEndedAt: options.trialEndedAt ?? null,
237
- pricingUrl: "https://echoknows.com/pricing?source=mcp_onboarding",
237
+ pricingUrl: "https://echoknows.com/account?source=mcp_onboarding",
238
238
  account: {
239
239
  displayName: "Preview User",
240
240
  email: "preview@example.com",
package/dist/setup.js CHANGED
@@ -37,7 +37,7 @@ import { checkLatestUpdateStatus, compareSemver, readCachedUpdateStatus } from "
37
37
  // The setup dashboard, account login, and encryption passphrase entry are all served by this
38
38
  // localhost bridge. The hosted API only sends OTP email, verifies the code, and mints a device token.
39
39
  const API_BASE_URL = (process.env.ECHO_API_BASE_URL || "https://echo-mem-chrome.vercel.app").replace(/\/$/, "");
40
- const PRICING_URL = (process.env.ECHO_PRICING_URL || "https://echoknows.com/pricing").replace(/\/$/, "");
40
+ const PRICING_URL = (process.env.ECHO_PRICING_URL || "https://echoknows.com/account").replace(/\/$/, "");
41
41
  function hostedBillingEndpoint(pathname) {
42
42
  const url = new URL(PRICING_URL);
43
43
  url.pathname = pathname;
@@ -399,8 +399,10 @@ function echomemGuidanceBlock() {
399
399
  "- If a user supplies an `echo_grp_...` code and explicitly asks to join, call `join_memory_group`. Joining never authorizes publishing by itself and must not move a user out of another group. After joining, continue into the profile-and-publication preview instead of leaving title or responsibility blank.",
400
400
  "- For requests such as “prepare my recent work memories,” “upload work from this ticket,” or “publish work since my last sync,” call `prepare_group_publication` first. This is a no-publication preview. For encrypted accounts, tell the user to run `echomem-mcp unlock` locally if the tool reports that the key is required.",
401
401
  "- After preparing, select only exact candidate memory IDs that match the user's stated scope and exclude already-published or exact-content duplicates. Use the candidate evidence to draft a concise title and responsibility summary for the current member, but label both as proposals rather than facts.",
402
- "- Present the proposed title/responsibility and the memory publication preview together and ask for one explicit confirmation. Never save an inferred profile or publish memories before confirmation.",
403
- "- On confirmation, call `update_group_profile` with the confirmed title, responsibility summary, and `confirmed: true`, then call `complete_group_publication` with the exact `scanId`, selected memory IDs, and `confirmed: true`. If the user edits either proposal, use their wording. An explicit request to join and upload still requires this combined preview and confirmation.",
402
+ "- Use two distinct evidence links. For the user's own source memories during search, flag review, or publication preview, preserve the Memory ID and use `https://echoknows.com/memories/timeline?memoryId=...`; this is an owner-only personal link and may require Vault unlock. For already-published results from `search_others_memories`, use `https://echoknows.com/company/memories?memoryId=...`, which requires current group access.",
403
+ "- If the user asks to flag memories about a sensitive topic, search their own memories first, show the exact matches with owner-only personal links, and ask them to confirm. Only then call `flag_memories_for_publication_attention`; flagging does not publish, decrypt, change visibility, or retract an existing group snapshot.",
404
+ "- Present the proposed title/responsibility and the memory publication preview together and ask for explicit confirmation. Never save an inferred profile or publish memories before confirmation. If an unflagged candidate appears sensitive, proactively ask whether the user wants to mark its exact ID for publication attention first. Explain naturally: marking does not publish or change encryption; it means you will call it out and ask for detailed confirmation whenever a later publication includes it. Never auto-flag based on agent inference. Show already-flagged candidates in a separate warning, state that nothing has been published yet, and offer three choices: exclude them, review them separately, or first search for and mark similar sensitive owned memories for publication attention.",
405
+ "- On confirmation, call `update_group_profile` with the confirmed title, responsibility summary, and `confirmed: true`, then call `complete_group_publication` with the exact `scanId`, selected memory IDs, and `confirmed: true`. If a flagged memory is selected, require separate explicit acknowledgement and pass its exact ID in `acknowledgedFlaggedMemoryIds`. If the user edits either proposal, use their wording. An explicit request to join and upload still requires this preview and confirmation.",
404
406
  "- If the user asks to show, reopen, restart, or bring back the EchoMem HUD (the context-health overlay), run the shell command `echomem-hud app --client auto`.",
405
407
  "- If the user wants the HUD to come back after a computer restart, run the shell command `echomem-hud autostart on --client auto`.",
406
408
  AGENTS_MD_END,
@@ -15,6 +15,7 @@ export const canonicalToolNames = {
15
15
  createGroupInvite: "create_group_invite",
16
16
  joinGroup: "join_memory_group",
17
17
  prepareGroupPublication: "prepare_group_publication",
18
+ flagPublicationAttention: "flag_memories_for_publication_attention",
18
19
  updateGroupProfile: "update_group_profile",
19
20
  completeGroupPublication: "complete_group_publication",
20
21
  publishToGroup: "publish_memory_to_group",
@@ -144,6 +145,12 @@ export const prepareGroupPublicationSchema = z.object({
144
145
  limit: z.number().int().min(1).max(50).optional(),
145
146
  instruction: z.string().max(500).optional(),
146
147
  });
148
+ export const flagPublicationAttentionSchema = z.object({
149
+ ...triggerMetadataSchema,
150
+ memoryIds: z.array(z.string().min(1)).min(1).max(50),
151
+ label: z.string().min(1).max(120),
152
+ confirmed: z.literal(true),
153
+ });
147
154
  export const updateGroupProfileSchema = z.object({
148
155
  ...triggerMetadataSchema,
149
156
  displayName: z.string().min(1).max(120).optional(),
@@ -157,16 +164,19 @@ export const completeGroupPublicationSchema = z.object({
157
164
  memoryIds: z.array(z.string().min(1)).max(50),
158
165
  confirmed: z.literal(true),
159
166
  selectionReason: z.string().max(500).optional(),
167
+ acknowledgedFlaggedMemoryIds: z.array(z.string().min(1)).max(50).optional(),
160
168
  });
161
169
  export const publishToGroupSchema = z.object({
162
170
  ...triggerMetadataSchema,
163
171
  memoryId: z.string().min(1),
172
+ acknowledgedFlaggedMemoryIds: z.array(z.string().min(1)).max(1).optional(),
164
173
  });
165
174
  export const publishBatchToGroupSchema = z.object({
166
175
  ...triggerMetadataSchema,
167
176
  memoryIds: z.array(z.string().min(1)).min(1).max(50),
168
177
  contextId: z.string().min(1).optional(),
169
178
  selectionReason: z.string().max(500).optional(),
179
+ acknowledgedFlaggedMemoryIds: z.array(z.string().min(1)).max(50).optional(),
170
180
  });
171
181
  export const deleteMemorySchema = z.object({
172
182
  memoryId: z.string().min(1),
@@ -482,7 +492,7 @@ export function listToolSpecs(opts = {}) {
482
492
  },
483
493
  {
484
494
  name: canonicalToolNames.prepareGroupPublication,
485
- description: "Prepare a no-publication preview of up to 50 owned memories for group publication. For encrypted accounts the local bridge must be unlocked. The host agent selects exact work-related memory ids, drafts a concise title and responsibility summary from memory evidence, and asks the user to confirm both the profile and publication selection together. This tool never publishes.",
495
+ description: "Prepare a no-publication preview of up to 50 owned memories for group publication. For encrypted accounts the local bridge must be unlocked. The host agent selects exact work-related memory ids, gives each candidate an owner-only https://echoknows.com/memories/timeline?memoryId=... review link, drafts a concise title and responsibility summary from memory evidence, and asks the user to confirm both the profile and publication selection together. If an unflagged candidate appears sensitive, proactively ask whether the user wants to mark its exact ID for publication attention first. Explain that marking does not publish or change encryption; it means the agent will call it out and ask for detailed confirmation whenever a later publication includes it. Never auto-flag inferred sensitivity. Already-flagged memories are identified separately and require extra attention before publication. This tool never publishes.",
486
496
  inputSchema: {
487
497
  type: "object",
488
498
  properties: {
@@ -496,6 +506,32 @@ export function listToolSpecs(opts = {}) {
496
506
  },
497
507
  },
498
508
  },
509
+ {
510
+ name: canonicalToolNames.flagPublicationAttention,
511
+ description: "Mark exact owned memories for extra attention during future group publication. First search the user's own memories, show the matching memory IDs, summaries, and owner-only https://echoknows.com/memories/timeline?memoryId=... review links, and obtain explicit confirmation. This only records a safety flag; it does not publish, decrypt, change visibility, or retract an existing group snapshot.",
512
+ inputSchema: {
513
+ type: "object",
514
+ properties: {
515
+ memoryIds: {
516
+ type: "array",
517
+ minItems: 1,
518
+ maxItems: 50,
519
+ items: { type: "string" },
520
+ description: "Exact owned memory IDs the user reviewed and confirmed.",
521
+ },
522
+ label: {
523
+ type: "string",
524
+ description: "A short user-supplied attention label, such as investment or customer-confidential.",
525
+ },
526
+ confirmed: {
527
+ type: "boolean",
528
+ const: true,
529
+ description: "Set true only after the user confirms the exact memories.",
530
+ },
531
+ },
532
+ required: ["memoryIds", "label", "confirmed"],
533
+ },
534
+ },
499
535
  {
500
536
  name: canonicalToolNames.updateGroupProfile,
501
537
  description: "Save the current member's declared group title and responsibility summary only after the user explicitly confirms the agent's proposal. These become directory facts visible to group members; never save an unconfirmed inference.",
@@ -512,7 +548,7 @@ export function listToolSpecs(opts = {}) {
512
548
  },
513
549
  {
514
550
  name: canonicalToolNames.completeGroupPublication,
515
- description: "Publish only the exact memory ids from a prepared scan after the user explicitly confirms the preview. confirmed must be true. Completing an empty selection safely advances the scan cursor without publishing.",
551
+ description: "Publish only the exact memory ids from a prepared scan after the user explicitly confirms the preview. confirmed must be true. Any flagged memories must be called out separately; state that nothing has been published yet and offer to exclude them, review them separately, or first search for and mark similar sensitive owned memories. Never auto-flag inferred sensitivity. Publish flagged memories only after extra user acknowledgement with their exact IDs in acknowledgedFlaggedMemoryIds. Completing an empty selection safely advances the scan cursor without publishing.",
516
552
  inputSchema: {
517
553
  type: "object",
518
554
  properties: {
@@ -520,13 +556,19 @@ export function listToolSpecs(opts = {}) {
520
556
  memoryIds: { type: "array", maxItems: 50, items: { type: "string" } },
521
557
  confirmed: { type: "boolean", const: true },
522
558
  selectionReason: { type: "string" },
559
+ acknowledgedFlaggedMemoryIds: {
560
+ type: "array",
561
+ maxItems: 50,
562
+ items: { type: "string" },
563
+ description: "Exact flagged IDs the user separately acknowledged for this publication.",
564
+ },
523
565
  },
524
566
  required: ["scanId", "memoryIds", "confirmed"],
525
567
  },
526
568
  },
527
569
  {
528
570
  name: canonicalToolNames.publishToGroup,
529
- description: "Publish one exact memory snapshot to your current company group without changing the memory's global is_public or encryption state. Call only after the user explicitly asks to publish or push that memory. The operation is idempotent.",
571
+ description: "Publish one exact memory snapshot to your current company group without changing the memory's global is_public or encryption state. Call only after the user explicitly asks to publish or push that memory. If it is flagged, state that nothing has been published yet and offer to exclude it, review it separately, or first search for and mark similar sensitive owned memories. Never auto-flag inferred sensitivity. Pass its ID in acknowledgedFlaggedMemoryIds only after extra user acknowledgement. The operation is idempotent.",
530
572
  inputSchema: {
531
573
  type: "object",
532
574
  properties: {
@@ -539,13 +581,19 @@ export function listToolSpecs(opts = {}) {
539
581
  description: "Optional user message that explicitly requested publication.",
540
582
  },
541
583
  triggerMessageRole: { type: "string", default: "user" },
584
+ acknowledgedFlaggedMemoryIds: {
585
+ type: "array",
586
+ maxItems: 1,
587
+ items: { type: "string" },
588
+ description: "Include the memory ID only after separate acknowledgement if the memory is flagged.",
589
+ },
542
590
  },
543
591
  required: ["memoryId"],
544
592
  },
545
593
  },
546
594
  {
547
595
  name: canonicalToolNames.publishBatchToGroup,
548
- description: "Publish up to 50 exact memory snapshots to your current company group without changing global is_public or the encrypted originals. For a session request, first call get_memories_by_context, select the exact ids that match the user's instruction, then pass those ids with the same contextId. Call only after explicit group-publication intent; report what was selected when the user delegates work-related filtering.",
596
+ description: "Publish up to 50 exact memory snapshots to your current company group without changing global is_public or the encrypted originals. For a session request, first call get_memories_by_context, select the exact ids that match the user's instruction, then pass those ids with the same contextId. Call only after explicit group-publication intent; report what was selected when the user delegates work-related filtering. Flagged memories must be called out separately; state that nothing has been published yet and offer exclusion, separate review, or a confirmed search-and-flag pass for similar sensitive owned memories. Never auto-flag inferred sensitivity. Publish flagged memories only after exact-ID acknowledgement.",
549
597
  inputSchema: {
550
598
  type: "object",
551
599
  properties: {
@@ -569,6 +617,12 @@ export function listToolSpecs(opts = {}) {
569
617
  description: "Optional user message that explicitly requested publication.",
570
618
  },
571
619
  triggerMessageRole: { type: "string", default: "user" },
620
+ acknowledgedFlaggedMemoryIds: {
621
+ type: "array",
622
+ maxItems: 50,
623
+ items: { type: "string" },
624
+ description: "Exact flagged IDs the user separately acknowledged for this publication.",
625
+ },
572
626
  },
573
627
  required: ["memoryIds"],
574
628
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@echomem/mcp",
3
- "version": "1.4.24",
3
+ "version": "1.4.26",
4
4
  "description": "EchoMem MCP bridge: cloud-first memory tools, local context HUD, and the Agent Doctor workspace forensics report (cost ledger + 3D repo city)",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",