@echomem/mcp 1.4.23 → 1.4.25

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, } 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,11 @@ 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
+ function memoryWebUrl(memoryId) {
22
+ return `${ECHO_MEMORY_WEB_URL}?memoryId=${encodeURIComponent(memoryId)}`;
23
+ }
20
24
  /** Thrown when no API token is present yet — the model gets a "run login" nudge, not a hard error. */
21
25
  class NoTokenError extends Error {
22
26
  }
@@ -495,6 +499,14 @@ function inputAnalyticsForTool(canonicalName, args) {
495
499
  memory_id_hash: memoryId ? hashText(memoryId) : undefined,
496
500
  };
497
501
  }
502
+ case canonicalToolNames.flagPublicationAttention: {
503
+ const memoryIds = Array.isArray(a.memoryIds) ? a.memoryIds.filter((id) => typeof id === "string") : [];
504
+ return {
505
+ memory_count: memoryIds.length,
506
+ has_label: !!readString(a, "label"),
507
+ confirmed: a.confirmed === true,
508
+ };
509
+ }
498
510
  case canonicalToolNames.publishBatchToGroup: {
499
511
  const memoryIds = Array.isArray(a.memoryIds) ? a.memoryIds.filter((id) => typeof id === "string") : [];
500
512
  return {
@@ -983,6 +995,16 @@ class EchoMemApiClient {
983
995
  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
996
  return response.data;
985
997
  }
998
+ async flagMemoriesForPublicationAttention(args) {
999
+ const parsed = flagPublicationAttentionSchema.parse(args ?? {});
1000
+ const response = await this.axios.post("/api/extension/memories/publication-attention", parsed);
1001
+ return response.data;
1002
+ }
1003
+ async updateGroupProfile(args) {
1004
+ const parsed = updateGroupProfileSchema.parse(args ?? {});
1005
+ const response = await this.axios.patch("/api/extension/social/groups/current/profile", parsed);
1006
+ return response.data;
1007
+ }
986
1008
  async completeGroupPublication(args) {
987
1009
  const parsed = completeGroupPublicationSchema.parse(args ?? {});
988
1010
  const enc = await this.encState();
@@ -993,7 +1015,9 @@ class EchoMemApiClient {
993
1015
  const parsed = publishToGroupSchema.parse(args ?? {});
994
1016
  const enc = await this.encState();
995
1017
  try {
996
- const response = await this.axios.post(`/api/extension/social/groups/current/memories/${encodeURIComponent(parsed.memoryId)}/publish`, {}, {
1018
+ const response = await this.axios.post(`/api/extension/social/groups/current/memories/${encodeURIComponent(parsed.memoryId)}/publish`, {
1019
+ acknowledgedFlaggedMemoryIds: parsed.acknowledgedFlaggedMemoryIds,
1020
+ }, {
997
1021
  headers: enc.enabled && enc.key ? { "X-Encryption-Key": enc.key } : undefined,
998
1022
  });
999
1023
  return response.data;
@@ -1009,6 +1033,8 @@ class EchoMemApiClient {
1009
1033
  const response = await this.axios.post("/api/extension/social/groups/current/memories/publish-batch", {
1010
1034
  memoryIds: parsed.memoryIds,
1011
1035
  contextId: parsed.contextId,
1036
+ selectionReason: parsed.selectionReason,
1037
+ acknowledgedFlaggedMemoryIds: parsed.acknowledgedFlaggedMemoryIds,
1012
1038
  }, {
1013
1039
  headers: enc.enabled && enc.key ? { "X-Encryption-Key": enc.key } : undefined,
1014
1040
  });
@@ -1205,6 +1231,10 @@ class EchoMemMCPServer {
1205
1231
  return await this.handleJoinGroup(request.params.arguments);
1206
1232
  case canonicalToolNames.prepareGroupPublication:
1207
1233
  return await this.handlePrepareGroupPublication(request.params.arguments);
1234
+ case canonicalToolNames.flagPublicationAttention:
1235
+ return await this.handleFlagPublicationAttention(request.params.arguments);
1236
+ case canonicalToolNames.updateGroupProfile:
1237
+ return await this.handleUpdateGroupProfile(request.params.arguments);
1208
1238
  case canonicalToolNames.completeGroupPublication:
1209
1239
  return await this.handleCompleteGroupPublication(request.params.arguments);
1210
1240
  case canonicalToolNames.publishToGroup:
@@ -1627,6 +1657,7 @@ Details: ${m.details || "N/A"}`)
1627
1657
  ? m.similarity_score
1628
1658
  : undefined;
1629
1659
  return `[${idx + 1}] Memory ID: ${m.id || "unknown"}
1660
+ Open memory: ${memoryWebUrl(String(m.id || "unknown"))}
1630
1661
  User ID: ${m.user_id || "unknown"}
1631
1662
  User Name: ${m.username || m.user_name || m.name || "Anonymous"}
1632
1663
  Time: ${m.time} | Location: ${m.location}
@@ -1752,6 +1783,7 @@ Details: ${m.details || "N/A"}`;
1752
1783
  }
1753
1784
  const text = [
1754
1785
  `Memory ID: ${memory.id || parsed.memoryId}`,
1786
+ `Open memory: ${memoryWebUrl(String(memory.id || parsed.memoryId))}`,
1755
1787
  `Owner User ID: ${memory.owner_user_id || memory.user_id || "Unknown"}`,
1756
1788
  memory.time ? `Time: ${memory.time}` : "",
1757
1789
  memory.location ? `Location: ${memory.location}` : "",
@@ -1776,6 +1808,9 @@ Details: ${m.details || "N/A"}`;
1776
1808
  const participants = Array.isArray(payload?.participants)
1777
1809
  ? payload.participants.filter(isRecord)
1778
1810
  : [];
1811
+ const currentParticipant = isRecord(payload?.currentParticipant)
1812
+ ? payload.currentParticipant
1813
+ : null;
1779
1814
  const coverage = isRecord(payload?.coverage) ? payload.coverage : {};
1780
1815
  if (!group) {
1781
1816
  return {
@@ -1803,6 +1838,10 @@ Details: ${m.details || "N/A"}`;
1803
1838
  participantText,
1804
1839
  "",
1805
1840
  "Declared titles and responsibilities are directory facts. Use search_others_memories for current work evidence, and label suggested contribution areas as inference that should be confirmed with the team.",
1841
+ currentParticipant
1842
+ && (!readString(currentParticipant, "title") || !readString(currentParticipant, "responsibilitySummary"))
1843
+ ? "Your group profile is incomplete. Use prepare_group_publication to review your memory evidence, propose the missing fields, and save them only after confirmation with update_group_profile."
1844
+ : "",
1806
1845
  ].filter(Boolean).join("\n");
1807
1846
  return { content: [{ type: "text", text }] };
1808
1847
  }
@@ -1854,8 +1893,8 @@ Details: ${m.details || "N/A"}`;
1854
1893
  content: [{
1855
1894
  type: "text",
1856
1895
  text: payload?.alreadyMember
1857
- ? `You are already a member of ${payload?.group?.name ?? "this group"}. No memories were published.`
1858
- : `Joined ${payload?.group?.name ?? "the company group"}. No memories were published. Use prepare_group_publication to review candidates.`,
1896
+ ? `You are already a member of ${payload?.group?.name ?? "this group"}. No memories were published. Use prepare_group_publication to review memories and propose any missing title or responsibility fields.`
1897
+ : `Joined ${payload?.group?.name ?? "the company group"}. No memories were published. Next use prepare_group_publication to review candidates, infer a proposed title and responsibility summary, and ask the user to confirm that profile together with the publication preview.`,
1859
1898
  }],
1860
1899
  };
1861
1900
  }
@@ -1863,15 +1902,29 @@ Details: ${m.details || "N/A"}`;
1863
1902
  const parsed = prepareGroupPublicationSchema.parse(args ?? {});
1864
1903
  const payload = await this.client.prepareGroupPublication(parsed);
1865
1904
  const candidates = Array.isArray(payload?.candidates) ? payload.candidates : [];
1866
- const formatted = candidates.map((candidate, index) => [
1867
- `[${index + 1}] Memory ID: ${readString(candidate, "memoryId") ?? "unknown"}`,
1868
- `Created: ${readString(candidate, "createdAt") ?? "unknown"}`,
1869
- `Category: ${readString(candidate, "category") ?? "unknown"}`,
1870
- `Description: ${readString(candidate, "description") ?? ""}`,
1871
- readString(candidate, "details") ? `Details: ${readString(candidate, "details")}` : "",
1872
- `Already published: ${candidate.alreadyPublished === true ? "yes" : "no"}`,
1873
- `Exact content duplicate: ${candidate.exactContentDuplicate === true ? "yes" : "no"}`,
1874
- ].filter(Boolean).join("\n")).join("\n\n");
1905
+ const participantProfile = isRecord(payload?.participantProfile)
1906
+ ? payload.participantProfile
1907
+ : {};
1908
+ const formatted = candidates.map((candidate, index) => {
1909
+ const attention = isRecord(candidate.publicationAttention)
1910
+ ? candidate.publicationAttention
1911
+ : null;
1912
+ return [
1913
+ `[${index + 1}] Memory ID: ${readString(candidate, "memoryId") ?? "unknown"}`,
1914
+ `Open memory: ${memoryWebUrl(readString(candidate, "memoryId") ?? "unknown")}`,
1915
+ `Created: ${readString(candidate, "createdAt") ?? "unknown"}`,
1916
+ `Category: ${readString(candidate, "category") ?? "unknown"}`,
1917
+ `Description: ${readString(candidate, "description") ?? ""}`,
1918
+ readString(candidate, "details") ? `Details: ${readString(candidate, "details")}` : "",
1919
+ `Already published: ${candidate.alreadyPublished === true ? "yes" : "no"}`,
1920
+ `Exact content duplicate: ${candidate.exactContentDuplicate === true ? "yes" : "no"}`,
1921
+ attention ? `PUBLICATION ATTENTION: ${readString(attention, "label") ?? "flagged"}` : "",
1922
+ ].filter(Boolean).join("\n");
1923
+ }).join("\n\n");
1924
+ const flaggedIds = candidates
1925
+ .filter((candidate) => isRecord(candidate.publicationAttention))
1926
+ .map((candidate) => readString(candidate, "memoryId"))
1927
+ .filter(Boolean);
1875
1928
  return {
1876
1929
  content: [{
1877
1930
  type: "text",
@@ -1879,10 +1932,53 @@ Details: ${m.details || "N/A"}`;
1879
1932
  `Prepared publication scan ${payload?.scanId} for ${payload?.group?.name ?? "your group"}.`,
1880
1933
  `Window: ${payload?.windowStart ?? "context start"} → ${payload?.windowEnd}`,
1881
1934
  `Candidates: ${candidates.length}. Nothing has been published.`,
1935
+ `Current title: ${readString(participantProfile, "title") ?? "Not declared"}`,
1936
+ `Current declared responsibility: ${readString(participantProfile, "responsibilitySummary") ?? "Not provided"}`,
1882
1937
  "",
1883
1938
  formatted,
1884
1939
  "",
1885
- "Select exact memory IDs matching the user's instruction, show the preview, and ask for confirmation before calling complete_group_publication.",
1940
+ flaggedIds.length
1941
+ ? `Extra attention required: ${flaggedIds.length} candidate(s) are flagged (${flaggedIds.join(", ")}). Show them in a separate warning and require a separate explicit acknowledgement before including their exact IDs in acknowledgedFlaggedMemoryIds.`
1942
+ : "",
1943
+ "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 explicit confirmation. After confirmation, call update_group_profile and complete_group_publication.",
1944
+ ].join("\n"),
1945
+ }],
1946
+ };
1947
+ }
1948
+ async handleFlagPublicationAttention(args) {
1949
+ const parsed = flagPublicationAttentionSchema.parse(args ?? {});
1950
+ const payload = await this.client.flagMemoriesForPublicationAttention(parsed);
1951
+ const alreadyPublished = typeof payload?.alreadyPublishedGroupCount === "number"
1952
+ ? payload.alreadyPublishedGroupCount
1953
+ : 0;
1954
+ return {
1955
+ content: [{
1956
+ type: "text",
1957
+ text: [
1958
+ `Flagged ${payload?.flaggedCount ?? parsed.memoryIds.length} memories for publication attention.`,
1959
+ `Label: ${parsed.label}`,
1960
+ `Memory IDs: ${parsed.memoryIds.join(", ")}`,
1961
+ "No memory was published, decrypted, or made globally public.",
1962
+ alreadyPublished > 0
1963
+ ? `Attention: ${alreadyPublished} flagged memories already have group snapshots. This flag does not retract those existing snapshots.`
1964
+ : "",
1965
+ ].filter(Boolean).join("\n"),
1966
+ }],
1967
+ };
1968
+ }
1969
+ async handleUpdateGroupProfile(args) {
1970
+ const parsed = updateGroupProfileSchema.parse(args ?? {});
1971
+ const payload = await this.client.updateGroupProfile(parsed);
1972
+ const participant = isRecord(payload?.participant) ? payload.participant : {};
1973
+ return {
1974
+ content: [{
1975
+ type: "text",
1976
+ text: [
1977
+ "Confirmed company-group profile updated.",
1978
+ `Display name: ${readString(participant, "displayName") ?? parsed.displayName ?? "unchanged"}`,
1979
+ `Title: ${readString(participant, "title") ?? parsed.title}`,
1980
+ `Declared responsibility: ${readString(participant, "responsibilitySummary") ?? parsed.responsibilitySummary}`,
1981
+ "These fields are now declared directory facts visible to group members.",
1886
1982
  ].join("\n"),
1887
1983
  }],
1888
1984
  };
@@ -28,8 +28,11 @@ export const MCP_SERVER_INSTRUCTIONS = [
28
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
29
  "Use echomem_update_status to check whether npm has a newer bridge; it is cached and non-blocking during normal tool listing.",
30
30
  "Do not auto-update on every MCP startup; this bridge is intentionally stable between explicit updates.",
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-write preview before complete_group_publication.",
32
- "Never complete a group publication without explicit confirmation of the prepared candidate set, and never store or log an echo_grp_ invite code.",
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
+ "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
+ "Each group-search result is one memory: preserve its Memory ID and direct echoknows.com link when citing it.",
34
+ "For sensitive-topic flags, search and preview exact owned memories before confirmed flag_memories_for_publication_attention. During publication, separate flagged candidates and require exact-ID acknowledgement.",
35
+ "Never save an inferred group profile or complete a group publication without explicit confirmation, and never store or log an echo_grp_ invite code.",
33
36
  ].join(" ");
34
37
  export function withMcpVersion(description) {
35
38
  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.`;
@@ -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;
@@ -396,10 +396,13 @@ function echomemGuidanceBlock() {
396
396
  "- Use `get_group_context` when the user asks who is in their company group, what teammates are responsible for, or what work is already covered. Treat declared participant fields as facts and published-memory conclusions as evidence or inference.",
397
397
  "- A group publication is separate from a globally public memory: publishing to a group creates a group-scoped snapshot and must not change the encrypted original or its global `is_public` setting.",
398
398
  "- If a user asks to create a group, call `create_memory_group`; if they ask for a code to share, call `create_group_invite` and return the secret invite code only to that user. Never save the invite code to memory or include it in logs, analytics, summaries, or unrelated output.",
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.",
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-write preview. For encrypted accounts, tell the user to run `echomem-mcp unlock` locally if the tool reports that the key is required.",
401
- "- After preparing, select only exact candidate memory IDs that match the user's stated scope, exclude already-published or exact-content duplicates, summarize what would be shared, and ask the user to confirm. Do not call `complete_group_publication` until the user explicitly confirms that preview.",
402
- "- On confirmation, call `complete_group_publication` with the exact `scanId`, selected memory IDs, and `confirmed: true`. An explicit request to join and upload still requires the publication preview and confirmation after joining.",
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
+ "- 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
+ "- 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
+ "- Each returned group-search result is one memory. When citing it in a bullet, preserve its Memory ID and include its `https://echoknows.com/company/memories?memoryId=...` link so the user can open the evidence directly.",
403
+ "- If the user asks to flag memories about a sensitive topic, search their own memories first, show the exact matches, 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. Show flagged candidates in a separate warning.",
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.",
403
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`.",
404
407
  "- If the user wants the HUD to come back after a computer restart, run the shell command `echomem-hud autostart on --client auto`.",
405
408
  AGENTS_MD_END,
@@ -15,6 +15,8 @@ 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",
19
+ updateGroupProfile: "update_group_profile",
18
20
  completeGroupPublication: "complete_group_publication",
19
21
  publishToGroup: "publish_memory_to_group",
20
22
  publishBatchToGroup: "publish_memories_to_group",
@@ -143,22 +145,38 @@ export const prepareGroupPublicationSchema = z.object({
143
145
  limit: z.number().int().min(1).max(50).optional(),
144
146
  instruction: z.string().max(500).optional(),
145
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
+ });
154
+ export const updateGroupProfileSchema = z.object({
155
+ ...triggerMetadataSchema,
156
+ displayName: z.string().min(1).max(120).optional(),
157
+ title: z.string().min(1).max(160),
158
+ responsibilitySummary: z.string().min(1).max(1000),
159
+ confirmed: z.literal(true),
160
+ });
146
161
  export const completeGroupPublicationSchema = z.object({
147
162
  ...triggerMetadataSchema,
148
163
  scanId: z.string().min(1),
149
164
  memoryIds: z.array(z.string().min(1)).max(50),
150
165
  confirmed: z.literal(true),
151
166
  selectionReason: z.string().max(500).optional(),
167
+ acknowledgedFlaggedMemoryIds: z.array(z.string().min(1)).max(50).optional(),
152
168
  });
153
169
  export const publishToGroupSchema = z.object({
154
170
  ...triggerMetadataSchema,
155
171
  memoryId: z.string().min(1),
172
+ acknowledgedFlaggedMemoryIds: z.array(z.string().min(1)).max(1).optional(),
156
173
  });
157
174
  export const publishBatchToGroupSchema = z.object({
158
175
  ...triggerMetadataSchema,
159
176
  memoryIds: z.array(z.string().min(1)).min(1).max(50),
160
177
  contextId: z.string().min(1).optional(),
161
178
  selectionReason: z.string().max(500).optional(),
179
+ acknowledgedFlaggedMemoryIds: z.array(z.string().min(1)).max(50).optional(),
162
180
  });
163
181
  export const deleteMemorySchema = z.object({
164
182
  memoryId: z.string().min(1),
@@ -460,7 +478,7 @@ export function listToolSpecs(opts = {}) {
460
478
  },
461
479
  {
462
480
  name: canonicalToolNames.joinGroup,
463
- description: "Join a company memory group using an invite code after the user explicitly asks to join. Joining never publishes memories; call prepare_group_publication separately.",
481
+ description: "Join a company memory group using an invite code after the user explicitly asks to join. Joining never publishes memories. Next call prepare_group_publication, infer a proposed title and responsibility summary from the user's own candidate memories, and ask the user to confirm the profile together with the publication preview.",
464
482
  inputSchema: {
465
483
  type: "object",
466
484
  properties: {
@@ -474,7 +492,7 @@ export function listToolSpecs(opts = {}) {
474
492
  },
475
493
  {
476
494
  name: canonicalToolNames.prepareGroupPublication,
477
- description: "Prepare a no-write 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 from the returned candidates and asks the user to confirm; 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, drafts a concise title and responsibility summary from memory evidence, and asks the user to confirm both the profile and publication selection together. Flagged memories are identified separately and require extra attention before publication. This tool never publishes.",
478
496
  inputSchema: {
479
497
  type: "object",
480
498
  properties: {
@@ -488,9 +506,49 @@ export function listToolSpecs(opts = {}) {
488
506
  },
489
507
  },
490
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 and summaries, 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
+ },
535
+ {
536
+ name: canonicalToolNames.updateGroupProfile,
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.",
538
+ inputSchema: {
539
+ type: "object",
540
+ properties: {
541
+ displayName: { type: "string" },
542
+ title: { type: "string" },
543
+ responsibilitySummary: { type: "string" },
544
+ confirmed: { type: "boolean", const: true },
545
+ },
546
+ required: ["title", "responsibilitySummary", "confirmed"],
547
+ },
548
+ },
491
549
  {
492
550
  name: canonicalToolNames.completeGroupPublication,
493
- 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 and their exact IDs supplied in acknowledgedFlaggedMemoryIds after extra user acknowledgement. Completing an empty selection safely advances the scan cursor without publishing.",
494
552
  inputSchema: {
495
553
  type: "object",
496
554
  properties: {
@@ -498,13 +556,19 @@ export function listToolSpecs(opts = {}) {
498
556
  memoryIds: { type: "array", maxItems: 50, items: { type: "string" } },
499
557
  confirmed: { type: "boolean", const: true },
500
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
+ },
501
565
  },
502
566
  required: ["scanId", "memoryIds", "confirmed"],
503
567
  },
504
568
  },
505
569
  {
506
570
  name: canonicalToolNames.publishToGroup,
507
- 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, call it out and pass its ID in acknowledgedFlaggedMemoryIds only after extra user acknowledgement. The operation is idempotent.",
508
572
  inputSchema: {
509
573
  type: "object",
510
574
  properties: {
@@ -517,13 +581,19 @@ export function listToolSpecs(opts = {}) {
517
581
  description: "Optional user message that explicitly requested publication.",
518
582
  },
519
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
+ },
520
590
  },
521
591
  required: ["memoryId"],
522
592
  },
523
593
  },
524
594
  {
525
595
  name: canonicalToolNames.publishBatchToGroup,
526
- 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 and acknowledged by exact ID.",
527
597
  inputSchema: {
528
598
  type: "object",
529
599
  properties: {
@@ -547,6 +617,12 @@ export function listToolSpecs(opts = {}) {
547
617
  description: "Optional user message that explicitly requested publication.",
548
618
  },
549
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
+ },
550
626
  },
551
627
  required: ["memoryIds"],
552
628
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@echomem/mcp",
3
- "version": "1.4.23",
3
+ "version": "1.4.25",
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",