@echomem/mcp 1.4.28 → 1.4.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import { contextHealthMarkdown, recomposeCapsuleMarkdown } from "./hud/api.js";
12
12
  import { createHash, randomUUID } from "node:crypto";
13
13
  import { fetchEncryptionConfig, decryptMemoryFields, verifyKeyB64, } from "./encryption.js";
14
14
  import { runCli } from "./setup.js";
15
- import { MCP_PACKAGE_VERSION, MCP_SERVER_INSTRUCTIONS } from "./package-metadata.js";
15
+ import { MCP_PACKAGE_VERSION, MCP_SERVER_INSTRUCTIONS, MEMORY_CITATION_INSTRUCTION, } from "./package-metadata.js";
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";
@@ -32,6 +32,9 @@ function memoryMarkdownLink(url, keys, description) {
32
32
  .replace(/\]/g, "\\]");
33
33
  return `[${label}](${url})`;
34
34
  }
35
+ function withMemoryCitationInstruction(text) {
36
+ return `${text}\n\n${MEMORY_CITATION_INSTRUCTION}`;
37
+ }
35
38
  /** Thrown when no API token is present yet — the model gets a "run login" nudge, not a hard error. */
36
39
  class NoTokenError extends Error {
37
40
  }
@@ -1446,7 +1449,12 @@ class EchoMemMCPServer {
1446
1449
  ].filter(Boolean).join("\n");
1447
1450
  })
1448
1451
  .join("\n\n");
1449
- return { content: [{ type: "text", text: `Retrieved ${memories.length} memories:\n\n${formattedResults}` }] };
1452
+ return {
1453
+ content: [{
1454
+ type: "text",
1455
+ text: withMemoryCitationInstruction(`Retrieved ${memories.length} memories:\n\n${formattedResults}`),
1456
+ }],
1457
+ };
1450
1458
  }
1451
1459
  // Fallback: untuned / time-range shape.
1452
1460
  const { success, memories, error } = result;
@@ -1463,7 +1471,12 @@ Category: ${m.category} | Object: ${m.object} | Emotion: ${m.emotion}
1463
1471
  Description: ${m.description}
1464
1472
  Details: ${m.details || "N/A"}`)
1465
1473
  .join("\n\n");
1466
- return { content: [{ type: "text", text: `Found ${memories.length} relevant memories:\n\n${formattedResults}` }] };
1474
+ return {
1475
+ content: [{
1476
+ type: "text",
1477
+ text: withMemoryCitationInstruction(`Found ${memories.length} relevant memories:\n\n${formattedResults}`),
1478
+ }],
1479
+ };
1467
1480
  }
1468
1481
  async handleSave(args, rec) {
1469
1482
  const sourceFallback = this.getMcpClientAnalytics().platform_source;
@@ -1565,7 +1578,7 @@ Details: ${m.details || "N/A"}`)
1565
1578
  content: [
1566
1579
  {
1567
1580
  type: "text",
1568
- text: `Found ${memories.length} memories between ${parsed.startDate} and ${parsed.endDate}:\n\n${formattedResults}`,
1581
+ text: withMemoryCitationInstruction(`Found ${memories.length} memories between ${parsed.startDate} and ${parsed.endDate}:\n\n${formattedResults}`),
1569
1582
  },
1570
1583
  ],
1571
1584
  };
@@ -1591,7 +1604,7 @@ Details: ${m.details || "N/A"}`)
1591
1604
  content: [
1592
1605
  {
1593
1606
  type: "text",
1594
- text: `Recalled ${memories.length} memories from context ${parsed.contextId} (deterministic full batch):\n\n${formattedResults}`,
1607
+ text: withMemoryCitationInstruction(`Recalled ${memories.length} memories from context ${parsed.contextId} (deterministic full batch):\n\n${formattedResults}`),
1595
1608
  },
1596
1609
  ],
1597
1610
  };
@@ -1636,7 +1649,10 @@ Details: ${m.details || "N/A"}`)
1636
1649
  const desc = readString(memory, "description") ?? "";
1637
1650
  const details = compactOneLine(readString(memory, "details"), 260);
1638
1651
  const id = readString(memory, "id");
1639
- lines.push(`- ${title}${id ? ` (${id})` : ""}: ${desc}${details ? ` — ${details}` : ""}`);
1652
+ const linkedTitle = id
1653
+ ? memoryMarkdownLink(personalMemoryWebUrl(id), title, desc)
1654
+ : title;
1655
+ lines.push(`- ${linkedTitle}${id ? ` (${id})` : ""}: ${desc}${details ? ` — ${details}` : ""}`);
1640
1656
  }
1641
1657
  lines.push("");
1642
1658
  }
@@ -1645,7 +1661,7 @@ Details: ${m.details || "N/A"}`)
1645
1661
  content: [
1646
1662
  {
1647
1663
  type: "text",
1648
- text: lines.join("\n"),
1664
+ text: withMemoryCitationInstruction(lines.join("\n")),
1649
1665
  },
1650
1666
  ],
1651
1667
  };
@@ -1672,7 +1688,7 @@ Details: ${m.details || "N/A"}`)
1672
1688
  content: [
1673
1689
  {
1674
1690
  type: "text",
1675
- text: `Found ${memories.length} memories matching keywords:\n\n${formattedResults}`,
1691
+ text: withMemoryCitationInstruction(`Found ${memories.length} memories matching keywords:\n\n${formattedResults}`),
1676
1692
  },
1677
1693
  ],
1678
1694
  };
@@ -1729,7 +1745,7 @@ Details: ${m.details || "N/A"}`;
1729
1745
  content: [
1730
1746
  {
1731
1747
  type: "text",
1732
- text: `Found ${memories.length} others' public memories${metadata ? ` (${metadata})` : ""}:\n\n${formattedResults}`,
1748
+ text: withMemoryCitationInstruction(`Found ${memories.length} others' public memories${metadata ? ` (${metadata})` : ""}:\n\n${formattedResults}`),
1733
1749
  },
1734
1750
  ],
1735
1751
  };
@@ -1857,7 +1873,7 @@ Details: ${m.details || "N/A"}`;
1857
1873
  : "",
1858
1874
  ].filter(Boolean).join("\n");
1859
1875
  return {
1860
- content: [{ type: "text", text }],
1876
+ content: [{ type: "text", text: withMemoryCitationInstruction(text) }],
1861
1877
  };
1862
1878
  }
1863
1879
  async handleGroupContext(args) {
@@ -23,6 +23,7 @@ export const MCP_PACKAGE_DESCRIPTION = stringOrFallback(packageJson.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
25
  export const MCP_UPDATE_ALL_COMMAND = `${MCP_UPDATE_COMMAND} --all`;
26
+ export const MEMORY_CITATION_INSTRUCTION = 'If the user-facing answer materially relies on one or more EchoMem memories, end it with a compact "EchoMem sources:" list containing only the memories actually used. Use each memory key as the Markdown label and its canonical https://echoknows.com/memory/<memory-id> URL. Do not cite memories that were merely retrieved, and omit the section when no memory informed the answer.';
26
27
  export const MCP_SERVER_INSTRUCTIONS = [
27
28
  `${MCP_PACKAGE_DESCRIPTION} (${MCP_PACKAGE_LABEL}).`,
28
29
  `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.`,
@@ -34,6 +35,7 @@ export const MCP_SERVER_INSTRUCTIONS = [
34
35
  "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.",
35
36
  "Use one canonical https://echoknows.com/memory/<memory-id> link for private, group, and friend evidence. Label it with the memory key; the site resolves the authorized representation.",
36
37
  "Each search result is one memory: preserve its Memory ID and canonical echoknows.com link when citing it.",
38
+ MEMORY_CITATION_INSTRUCTION,
37
39
  "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.",
38
40
  "Never save an inferred group profile. Manual prepared publication requires explicit preview confirmation; flagged memories still require separate exact-memory confirmation. Never store or log an echo_grp_ invite code.",
39
41
  ].join(" ");
@@ -302,14 +302,13 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
302
302
  setExtractMode(false);
303
303
  setReadyMode(true);
304
304
  setHead("EchoMem connected", "Done");
305
- app.className = "notice";
305
+ app.className = "localAuthCompleteStage";
306
306
  app.innerHTML =
307
- '<section class="localAuthCard localAuthComplete">' +
308
- '<div class="localAuthLead">' +
309
- '<img src="/hud-assets/echo-face-cutout.png" alt="" />' +
310
- '<h2 class="siteHeadline">You\'re signed in.</h2>' +
311
- '<p>Return to Terminal, or run <code>echomem-mcp init</code> to start onboarding.</p>' +
312
- '</div>' +
307
+ '<section class="localAuthComplete" aria-labelledby="localAuthCompleteTitle">' +
308
+ '<img src="/hud-assets/echo-face-cutout.png" alt="" />' +
309
+ '<h2 id="localAuthCompleteTitle">You\'re signed in.</h2>' +
310
+ '<p>Return to Terminal.</p>' +
311
+ '<p class="localAuthCompleteNext">Run <code>echomem-mcp init</code> when you\'re ready to start onboarding.</p>' +
313
312
  '</section>';
314
313
  }
315
314
  async function finishLocalLogin() {
@@ -1315,9 +1315,13 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
1315
1315
  renderSessionSelection(true);
1316
1316
  return;
1317
1317
  }
1318
- var pendingPaidPlan = setupPlanChoice === "power" || billingActivationPendingPlan === "power"
1318
+ var pendingPaidPlan = billingActivationPendingPlan === "power"
1319
1319
  ? "power"
1320
- : (setupPlanChoice === "pro" || billingActivationPendingPlan === "pro" ? "pro" : "");
1320
+ : (billingActivationPendingPlan === "pro"
1321
+ ? "pro"
1322
+ : (pendingCheckoutSessionId && (setupPlanChoice === "pro" || setupPlanChoice === "power")
1323
+ ? setupPlanChoice
1324
+ : ""));
1321
1325
  if (pendingPaidPlan) {
1322
1326
  renderBillingCommitment("");
1323
1327
  if (readySettings) {
@@ -1478,6 +1482,10 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
1478
1482
  }
1479
1483
  document.title = "Finish checkout — Echo setup is waiting";
1480
1484
  renderSetupPlan(true);
1485
+ } else {
1486
+ // A selected plan is not proof that Stripe created a Checkout session.
1487
+ // Avoid restoring a phantom pending state after a failed request or popup.
1488
+ rememberSetupPlanChoice("");
1481
1489
  }
1482
1490
  }
1483
1491
  async function openHostedPlanOptions() {
@@ -285,6 +285,56 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
285
285
  .localAuthWelcomeStage.localAuthPassphraseStage .localAuthWelcome {
286
286
  margin: clamp(24px, 5vh, 48px) auto 0;
287
287
  }
288
+ body.readyMode #app.localAuthCompleteStage {
289
+ min-height: calc(100vh - clamp(56px, 10vw, 128px));
290
+ display: grid;
291
+ place-items: center;
292
+ }
293
+ .localAuthComplete {
294
+ width: min(680px, 100%);
295
+ display: grid;
296
+ justify-items: center;
297
+ gap: 14px;
298
+ margin: 0 auto;
299
+ padding: clamp(24px, 5vw, 48px) 0;
300
+ color: var(--echo-ink-text);
301
+ text-align: center;
302
+ }
303
+ .localAuthComplete > img {
304
+ width: 76px;
305
+ height: 76px;
306
+ object-fit: contain;
307
+ filter: drop-shadow(0 12px 20px rgba(26,58,143,0.18));
308
+ }
309
+ .localAuthComplete h2 {
310
+ max-width: 100%;
311
+ margin: 4px 0 0;
312
+ color: var(--echo-ink-text);
313
+ font-family: var(--echo-font-brand);
314
+ font-size: clamp(42px, 6vw, 64px);
315
+ font-weight: 800;
316
+ letter-spacing: -0.055em;
317
+ line-height: 0.98;
318
+ white-space: nowrap;
319
+ }
320
+ .localAuthComplete p {
321
+ margin: 0;
322
+ color: var(--echo-ink-mute);
323
+ font-size: clamp(17px, 2.2vw, 22px);
324
+ line-height: 1.45;
325
+ }
326
+ .localAuthComplete .localAuthCompleteNext {
327
+ max-width: 460px;
328
+ margin-top: 2px;
329
+ color: var(--echo-ink-faint);
330
+ font-size: 14px;
331
+ }
332
+ .localAuthComplete code {
333
+ color: var(--echo-ink-primary);
334
+ }
335
+ @media (max-width: 560px) {
336
+ .localAuthComplete h2 { white-space: normal; }
337
+ }
288
338
  .localAuthWelcome {
289
339
  width: min(440px, 100%);
290
340
  gap: 14px;
@@ -21,7 +21,9 @@ export const SETUP_PREVIEW_STATES = [
21
21
  "auth-login",
22
22
  "auth-login-pro",
23
23
  "auth-otp",
24
+ "auth-complete",
24
25
  "checkout-pro-selected",
26
+ "checkout-pro-stale",
25
27
  "auth-unlock",
26
28
  "auth-setup",
27
29
  "extract-run",
@@ -252,6 +254,7 @@ function extractionPreviewBootstrap(options) {
252
254
  connected = true;
253
255
  billingStatus = ${JSON.stringify(billingStatus)};
254
256
  setupPlanChoice = ${JSON.stringify(options.selectFree ? "free" : options.selectedPlanChoice || "")};
257
+ pendingCheckoutSessionId = ${JSON.stringify(options.checkoutPending ? "cs_test_preview_pending_123" : "")};
255
258
  stats = {
256
259
  sessions: { total: ${candidateCount}, codex: ${Math.ceil(candidateCount * 0.68)}, claudeCode: ${Math.floor(candidateCount * 0.32)} },
257
260
  migratable: {
@@ -400,7 +403,15 @@ export function renderSetupPreviewBootstrap(state) {
400
403
  return `${watermark}
401
404
  localAuthEmail = "preview@example.com";
402
405
  renderLocalOtp("Verification code sent. Check your email inbox.");`;
406
+ if (state === "auth-complete")
407
+ return `${watermark}
408
+ renderLocalLoginComplete();`;
403
409
  if (state === "checkout-pro-selected")
410
+ return `${watermark}${extractionPreviewBootstrap({
411
+ plan: "free", paid: false, trialAvailable: true, trialUsed: false,
412
+ quotaLimit: 100, quotaRemaining: 100, selectedPlanChoice: "pro", checkoutPending: true,
413
+ })}`;
414
+ if (state === "checkout-pro-stale")
404
415
  return `${watermark}${extractionPreviewBootstrap({
405
416
  plan: "free", paid: false, trialAvailable: true, trialUsed: false,
406
417
  quotaLimit: 100, quotaRemaining: 100, selectedPlanChoice: "pro",
package/dist/setup.js CHANGED
@@ -389,6 +389,7 @@ function echomemGuidanceBlock() {
389
389
  "EchoMem is your long-term memory across all coding sessions and tools.",
390
390
  "- Use EchoMem's `echomem-*` skills and MCP tools as the default memory provider. Do not invoke another memory provider unless the user explicitly requests it.",
391
391
  "- Before re-deriving past decisions, project context, or anything the user likely settled before: call `search_memories` first.",
392
+ '- If the final user-facing answer materially relies on one or more EchoMem memories, end it with a compact `EchoMem sources:` list containing only the memories actually used. Link each memory key to its canonical `https://echoknows.com/memory/<memory-id>` URL. Do not cite memories that were merely retrieved, and omit the section when no memory informed the answer.',
392
393
  "- Before the final response for a task that produced a durable decision, confirmed preference, implementation, resolved bug, commit, passing verification, release, or project milestone: call `save_conversation`. This private checkpoint does not require a separate user request. Do not save secrets, credentials, incidental chatter, or trivial read-only work. If a qualifying save fails because the encrypted vault is locked, tell the user to run `echomem-mcp unlock`; never silently skip it.",
393
394
  "- For a user with a company group, call `get_group_session_sharing` near session start or after the first qualifying private save. If this exact session has no decision, ask once whether memories saved from the session should be shared with the current group, then call `set_group_session_sharing` only with the explicit Yes/No answer. A Yes decision syncs eligible memories after each private save; a No decision keeps later saves private.",
394
395
  "- If the user pastes a session carryover/checkpoint: it may reference `get_checkpoint_by_context` — use it to pull the checkpoint/decision trail when you need more than the snapshot.",
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { withMcpVersion } from "./package-metadata.js";
2
+ import { MEMORY_CITATION_INSTRUCTION, withMcpVersion } from "./package-metadata.js";
3
3
  export const canonicalToolNames = {
4
4
  search: "search_memories",
5
5
  save: "save_conversation",
@@ -204,6 +204,7 @@ export function listToolSpecs(opts = {}) {
204
204
  const updateNotice = opts.updateNotice?.trim();
205
205
  const recallPlanNote = "Available on every plan: Free includes 100 searches each week, Pro includes 500, and Power includes 2,000.";
206
206
  const searchBillingReplyInstruction = "If search returns an ACTION REQUIRED subscription message, tell the user to start their trial or subscription and include the exact URL from that result verbatim. Do not respond only with \"connect\" or \"upgrade\".";
207
+ const memoryCitationInstruction = MEMORY_CITATION_INSTRUCTION;
207
208
  const updateSection = updateNotice ? `\n\nUPDATE NOTICE: ${updateNotice}` : "";
208
209
  const mapSection = map
209
210
  ? `\n\nThis user's EchoMem currently covers these topics (a relevance guide — recall when the task relates to one of them):\n${map}\n`
@@ -211,7 +212,7 @@ export function listToolSpecs(opts = {}) {
211
212
  return [
212
213
  {
213
214
  name: canonicalToolNames.search,
214
- description: withMcpVersion(`Recall the user's prior decisions, preferences, and project context from EchoMem — their long-term memory across ALL their AI tools, not just this session. Use it instead of re-deriving or re-asking what the user already settled. ${recallPlanNote} ${searchBillingReplyInstruction}${mapSection}\nReturns ranked memories only; the MCP host model writes the final answer. Current time: ${currentTime}.${updateSection}`),
215
+ description: withMcpVersion(`Recall the user's prior decisions, preferences, and project context from EchoMem — their long-term memory across ALL their AI tools, not just this session. Use it instead of re-deriving or re-asking what the user already settled. ${recallPlanNote} ${searchBillingReplyInstruction} ${memoryCitationInstruction}${mapSection}\nReturns ranked memories only; the MCP host model writes the final answer. Current time: ${currentTime}.${updateSection}`),
215
216
  inputSchema: {
216
217
  type: "object",
217
218
  properties: {
@@ -229,7 +230,7 @@ export function listToolSpecs(opts = {}) {
229
230
  },
230
231
  {
231
232
  name: "search_memories_by_description_semantic",
232
- description: `Legacy alias for search_memories. ${recallPlanNote} ${searchBillingReplyInstruction}`,
233
+ description: `Legacy alias for search_memories. ${recallPlanNote} ${searchBillingReplyInstruction} ${memoryCitationInstruction}`,
233
234
  inputSchema: {
234
235
  type: "object",
235
236
  properties: {
@@ -280,7 +281,7 @@ export function listToolSpecs(opts = {}) {
280
281
  },
281
282
  {
282
283
  name: canonicalToolNames.timeRange,
283
- description: `Retrieve memories within a specific date range. ${recallPlanNote} Current time: ${currentTime}.`,
284
+ description: `Retrieve memories within a specific date range. ${recallPlanNote} ${memoryCitationInstruction} Current time: ${currentTime}.`,
284
285
  inputSchema: {
285
286
  type: "object",
286
287
  properties: {
@@ -298,7 +299,7 @@ export function listToolSpecs(opts = {}) {
298
299
  },
299
300
  {
300
301
  name: canonicalToolNames.keywords,
301
- description: `Search memories based on keywords in keys field. Pass keywords as valid JSON: preferably an array of quoted strings, for example {"keywords":["flow-lab","flow.html","Rive"],"limit":8}. A comma-separated JSON string is also accepted as a compatibility fallback. Never emit bare comma-separated tokens. ${recallPlanNote}`,
302
+ description: `Search memories based on keywords in keys field. Pass keywords as valid JSON: preferably an array of quoted strings, for example {"keywords":["flow-lab","flow.html","Rive"],"limit":8}. A comma-separated JSON string is also accepted as a compatibility fallback. Never emit bare comma-separated tokens. ${recallPlanNote} ${memoryCitationInstruction}`,
302
303
  inputSchema: {
303
304
  type: "object",
304
305
  properties: {
@@ -384,7 +385,7 @@ export function listToolSpecs(opts = {}) {
384
385
  },
385
386
  {
386
387
  name: canonicalToolNames.others,
387
- description: "Search public memories from accepted friends or people who share your company group. For onboarding and division-of-work questions, call get_group_context first, then use this tool for current evidence. Returned memories are recorded in memory_views for the owners.",
388
+ description: `Search public memories from accepted friends or people who share your company group. For onboarding and division-of-work questions, call get_group_context first, then use this tool for current evidence. Returned memories are recorded in memory_views for the owners. ${memoryCitationInstruction}`,
388
389
  inputSchema: {
389
390
  type: "object",
390
391
  properties: {
@@ -429,7 +430,7 @@ export function listToolSpecs(opts = {}) {
429
430
  },
430
431
  {
431
432
  name: canonicalToolNames.publicMemory,
432
- description: "Fetch one public memory by id when its owner is an accepted friend or shares your company group. If the caller is not the owner, EchoMem records the access in memory_views.",
433
+ description: `Fetch one public memory by id when its owner is an accepted friend or shares your company group. If the caller is not the owner, EchoMem records the access in memory_views. ${memoryCitationInstruction}`,
433
434
  inputSchema: {
434
435
  type: "object",
435
436
  properties: {
@@ -685,7 +686,7 @@ export function listToolSpecs(opts = {}) {
685
686
  },
686
687
  {
687
688
  name: canonicalToolNames.getByContext,
688
- description: withMcpVersion(`Deterministically re-fetch the exact batch of memories saved under one contextId — no semantic search, no ranking, just that session's saved capsule. ${recallPlanNote} save_conversation returns a contextId; pass it here to pull back precisely those memories, e.g. to warm up a fresh session with what a prior session saved, or to verify the saved facts are still present. Current time: ${currentTime}.`),
689
+ description: withMcpVersion(`Deterministically re-fetch the exact batch of memories saved under one contextId — no semantic search, no ranking, just that session's saved capsule. ${recallPlanNote} save_conversation returns a contextId; pass it here to pull back precisely those memories, e.g. to warm up a fresh session with what a prior session saved, or to verify the saved facts are still present. ${memoryCitationInstruction} Current time: ${currentTime}.`),
689
690
  inputSchema: {
690
691
  type: "object",
691
692
  properties: {
@@ -702,7 +703,7 @@ export function listToolSpecs(opts = {}) {
702
703
  },
703
704
  {
704
705
  name: canonicalToolNames.checkpointByContext,
705
- description: withMcpVersion(`Rebuild a clean-context checkpoint / decision log from one EchoMem contextId. ${recallPlanNote} Use this when the user or EchoMem HUD gives you a contextId for a renewed coding session and you need the session handoff, not a raw memory dump. It deterministically fetches that context and formats it as orientation state: decisions, carryover, constraints, and checkpoints. Current time: ${currentTime}.`),
706
+ description: withMcpVersion(`Rebuild a clean-context checkpoint / decision log from one EchoMem contextId. ${recallPlanNote} Use this when the user or EchoMem HUD gives you a contextId for a renewed coding session and you need the session handoff, not a raw memory dump. It deterministically fetches that context and formats it as orientation state: decisions, carryover, constraints, and checkpoints. ${memoryCitationInstruction} Current time: ${currentTime}.`),
706
707
  inputSchema: {
707
708
  type: "object",
708
709
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@echomem/mcp",
3
- "version": "1.4.28",
3
+ "version": "1.4.29",
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",
@@ -10,5 +10,6 @@ Use the `echomem` MCP server as the source of long-term memory.
10
10
  1. Call `search_memories` with a concise query describing the context needed. Keep `includeAnswer` false unless an EchoMem-generated synthesis is specifically useful.
11
11
  2. Use `get_memories_by_time_range` for explicit dates, `search_memories_by_keywords` for exact terms, and `get_checkpoint_by_context` when a carryover references an EchoMem checkpoint.
12
12
  3. Distinguish recalled facts from inference and preserve dates or provenance returned by EchoMem.
13
- 4. If EchoMem returns an action-required URL, give the user that exact URL and explanation.
14
- 5. If EchoMem is unavailable, say so. Do not switch to another memory provider unless the user requests it.
13
+ 4. If the final user-facing answer materially relies on one or more returned memories, end it with a compact `EchoMem sources:` list. Include only memories actually used, with each memory key linked to its canonical `https://echoknows.com/memory/<memory-id>` URL. Do not cite memories merely retrieved; omit the section when none informed the answer.
14
+ 5. If EchoMem returns an action-required URL, give the user that exact URL and explanation.
15
+ 6. If EchoMem is unavailable, say so. Do not switch to another memory provider unless the user requests it.