@echomem/mcp 1.4.6 → 1.4.7

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
@@ -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, deleteMemorySchema, getByContextSchema, keywordsSchema, listToolSpecs, othersSchema, resolveCanonicalToolName, saveConversationSchema, searchMemoriesSchema, timeRangeSchema, } from "./v1-contract.js";
7
+ import { canonicalToolNames, deleteMemorySchema, getByContextSchema, keywordsSchema, listFriendsSchema, listToolSpecs, othersSchema, publicMemorySchema, resolveCanonicalToolName, saveConversationSchema, searchMemoriesSchema, searchUsersSchema, sendFriendRequestSchema, timeRangeSchema, } 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";
@@ -15,7 +15,6 @@ import { runCli } from "./setup.js";
15
15
  import { MCP_PACKAGE_VERSION, MCP_SERVER_INSTRUCTIONS } from "./package-metadata.js";
16
16
  import { checkLatestUpdateStatus, formatUpdateNotice, formatUpdateStatusText, startBackgroundUpdateCheck, } from "./update-check.js";
17
17
  const ECHO_API_BASE_URL = process.env.ECHO_API_BASE_URL || "https://echo-mem-chrome.vercel.app";
18
- const MEMORY_FEED_API_URL = process.env.MEMORY_FEED_API_URL || "https://memory-feed.vercel.app";
19
18
  /** Thrown when no API token is present yet — the model gets a "run login" nudge, not a hard error. */
20
19
  class NoTokenError extends Error {
21
20
  }
@@ -379,6 +378,22 @@ function inputAnalyticsForTool(canonicalName, args) {
379
378
  keyword_count: Array.isArray(a.keywords) ? a.keywords.length : 0,
380
379
  limit: numberArg(a, "limit"),
381
380
  };
381
+ case canonicalToolNames.searchUsers: {
382
+ const query = readString(a, "query");
383
+ const queryAnalytics = safeTextAnalytics("query", query);
384
+ return {
385
+ ...queryAnalytics,
386
+ query_preview_safe: queryAnalytics.query_preview,
387
+ query_length: query?.length ?? 0,
388
+ limit: numberArg(a, "limit"),
389
+ };
390
+ }
391
+ case canonicalToolNames.sendFriendRequest: {
392
+ const targetUserId = readString(a, "targetUserId");
393
+ return {
394
+ target_user_id_hash: targetUserId ? hashText(targetUserId) : undefined,
395
+ };
396
+ }
382
397
  case canonicalToolNames.others: {
383
398
  const query = readString(a, "query");
384
399
  const queryAnalytics = safeTextAnalytics("query", query);
@@ -386,6 +401,13 @@ function inputAnalyticsForTool(canonicalName, args) {
386
401
  ...queryAnalytics,
387
402
  query_preview_safe: queryAnalytics.query_preview,
388
403
  query_length: query?.length ?? 0,
404
+ limit: numberArg(a, "limit"),
405
+ };
406
+ }
407
+ case canonicalToolNames.publicMemory: {
408
+ const memoryId = readString(a, "memoryId");
409
+ return {
410
+ memory_id_hash: memoryId ? hashText(memoryId) : undefined,
389
411
  };
390
412
  }
391
413
  default:
@@ -773,21 +795,73 @@ class EchoMemApiClient {
773
795
  });
774
796
  return enc.enabled ? await this.decryptResult(response.data, enc.key) : response.data;
775
797
  }
798
+ async listFriends(args) {
799
+ listFriendsSchema.parse(args);
800
+ try {
801
+ const response = await this.axios.get("/api/extension/social/friends");
802
+ return response.data;
803
+ }
804
+ catch (error) {
805
+ throw new Error(`list_friends failed against ${ECHO_API_BASE_URL}: ${describeError(error)}`);
806
+ }
807
+ }
808
+ async searchUsers(args) {
809
+ const parsed = searchUsersSchema.parse(args);
810
+ try {
811
+ const response = await this.axios.post("/api/extension/social/users/search", {
812
+ query: parsed.query,
813
+ limit: parsed.limit,
814
+ });
815
+ return response.data;
816
+ }
817
+ catch (error) {
818
+ throw new Error(`search_users failed against ${ECHO_API_BASE_URL}: ${describeError(error)}`);
819
+ }
820
+ }
821
+ async sendFriendRequest(args) {
822
+ const parsed = sendFriendRequestSchema.parse(args);
823
+ try {
824
+ const response = await this.axios.post("/api/extension/social/friend-requests", {
825
+ receiverUserId: parsed.targetUserId,
826
+ });
827
+ return response.data;
828
+ }
829
+ catch (error) {
830
+ throw new Error(`send_friend_request failed against ${ECHO_API_BASE_URL}: ${describeError(error)}`);
831
+ }
832
+ }
776
833
  async searchOthersMemories(args) {
777
834
  const parsed = othersSchema.parse(args);
778
835
  try {
779
- const response = await axios.post(`${MEMORY_FEED_API_URL.replace(/\/$/, "")}/api/search/public`, {
836
+ const response = await this.axios.post("/api/extension/social/public-memories/search", {
780
837
  query: parsed.query,
781
- excludedMemoryIds: [],
782
- }, {
783
- headers: {
784
- "Content-Type": "application/json",
785
- },
838
+ limit: parsed.limit,
839
+ target: parsed.target,
840
+ ownerUserId: parsed.ownerUserId,
841
+ ownerName: parsed.ownerName,
842
+ targetFriendIds: parsed.targetFriendIds,
843
+ targetFriendNames: parsed.targetFriendNames,
844
+ recordAccess: parsed.recordAccess,
845
+ kPerUser: parsed.kPerUser,
846
+ similarityThreshold: parsed.similarityThreshold,
847
+ timeFrameDays: parsed.timeFrameDays,
848
+ requestId: this.sessionId,
849
+ source: "mcp_friend_public_memory_search",
786
850
  });
787
851
  return response.data;
788
852
  }
789
853
  catch (error) {
790
- throw new Error(`search_others_memories failed against ${MEMORY_FEED_API_URL}: ${describeError(error)}`);
854
+ throw new Error(`search_others_memories failed against ${ECHO_API_BASE_URL}: ${describeError(error)}`);
855
+ }
856
+ }
857
+ async getPublicMemory(args) {
858
+ const parsed = publicMemorySchema.parse(args);
859
+ try {
860
+ const response = await this.axios.get(`/api/extension/social/public-memories/${encodeURIComponent(parsed.memoryId)}?requestId=${encodeURIComponent(this.sessionId)}&source=mcp_friend_public_memory_fetch`);
861
+ return response.data;
862
+ }
863
+ catch (error) {
864
+ throw new Error(`get_public_memory failed against ${ECHO_API_BASE_URL}: ${describeError(error)}`);
791
865
  }
792
866
  }
793
867
  }
@@ -954,8 +1028,16 @@ class EchoMemMCPServer {
954
1028
  return await this.handleGetByContext(request.params.arguments);
955
1029
  case canonicalToolNames.keywords:
956
1030
  return await this.handleKeywords(request.params.arguments);
1031
+ case canonicalToolNames.friends:
1032
+ return await this.handleFriends(request.params.arguments);
1033
+ case canonicalToolNames.searchUsers:
1034
+ return await this.handleSearchUsers(request.params.arguments);
1035
+ case canonicalToolNames.sendFriendRequest:
1036
+ return await this.handleSendFriendRequest(request.params.arguments);
957
1037
  case canonicalToolNames.others:
958
1038
  return await this.handleOthers(request.params.arguments);
1039
+ case canonicalToolNames.publicMemory:
1040
+ return await this.handlePublicMemory(request.params.arguments);
959
1041
  case canonicalToolNames.delete:
960
1042
  return await this.handleDelete(request.params.arguments, rec);
961
1043
  default:
@@ -1251,26 +1333,177 @@ Details: ${m.details || "N/A"}`)
1251
1333
  const payload = await this.client.searchOthersMemories(args);
1252
1334
  const memories = payload?.memories ?? [];
1253
1335
  if (!memories.length) {
1336
+ const scope = parsed.ownerUserId
1337
+ ? ` for friend ${parsed.ownerUserId}`
1338
+ : parsed.ownerName
1339
+ ? ` for friend ${parsed.ownerName}`
1340
+ : parsed.target
1341
+ ? ` for friend ${parsed.target}`
1342
+ : parsed.targetFriendIds?.length
1343
+ ? ` for ${parsed.targetFriendIds.length} selected friends`
1344
+ : parsed.targetFriendNames?.length
1345
+ ? ` for ${parsed.targetFriendNames.length} named friends`
1346
+ : "";
1347
+ const queryLabel = parsed.query?.trim() ? ` matching the query: ${parsed.query}` : "";
1254
1348
  return {
1255
- content: [{ type: "text", text: `No others' memories found matching the query: ${parsed.query}` }],
1349
+ content: [{ type: "text", text: `No others' public memories found${scope}${queryLabel}` }],
1256
1350
  };
1257
1351
  }
1352
+ const metadata = [
1353
+ typeof payload?.friendCount === "number" ? `friendCount=${payload.friendCount}` : "",
1354
+ typeof payload?.searchedFriendCount === "number" ? `searchedFriendCount=${payload.searchedFriendCount}` : "",
1355
+ typeof payload?.memoryViewsInserted === "number" ? `memoryViewsInserted=${payload.memoryViewsInserted}` : "",
1356
+ ].filter(Boolean).join(", ");
1258
1357
  const formattedResults = memories
1259
- .map((m, idx) => `[${idx + 1}] User Name: ${m.user_name || "Anonymous"}
1358
+ .map((m, idx) => {
1359
+ const score = typeof m.final_score === "number"
1360
+ ? m.final_score
1361
+ : typeof m.similarity_score === "number"
1362
+ ? m.similarity_score
1363
+ : undefined;
1364
+ return `[${idx + 1}] Memory ID: ${m.id || "unknown"}
1365
+ User ID: ${m.user_id || "unknown"}
1366
+ User Name: ${m.username || m.user_name || m.name || "Anonymous"}
1260
1367
  Time: ${m.time} | Location: ${m.location}
1261
1368
  Category: ${m.category} | Object: ${m.object}
1262
- Description: ${m.description}
1263
- Details: ${m.details || "N/A"}`)
1369
+ ${typeof score === "number" ? `Score: ${score}\n` : ""}Description: ${m.description}
1370
+ Details: ${m.details || "N/A"}`;
1371
+ })
1264
1372
  .join("\n\n");
1265
1373
  return {
1266
1374
  content: [
1267
1375
  {
1268
1376
  type: "text",
1269
- text: `Found ${memories.length} others' memories:\n\n${formattedResults}`,
1377
+ text: `Found ${memories.length} others' public memories${metadata ? ` (${metadata})` : ""}:\n\n${formattedResults}`,
1270
1378
  },
1271
1379
  ],
1272
1380
  };
1273
1381
  }
1382
+ async handleFriends(args) {
1383
+ listFriendsSchema.parse(args);
1384
+ const payload = await this.client.listFriends(args);
1385
+ const friends = Array.isArray(payload?.friends) ? payload.friends : [];
1386
+ if (!friends.length) {
1387
+ return {
1388
+ content: [{ type: "text", text: "No accepted EchoMem friends found." }],
1389
+ };
1390
+ }
1391
+ const formatted = friends
1392
+ .map((friend, idx) => {
1393
+ const name = typeof friend.name === "string" && friend.name.trim()
1394
+ ? friend.name.trim()
1395
+ : "Unnamed friend";
1396
+ const userId = typeof friend.user_id === "string" ? friend.user_id : "unknown";
1397
+ const publicMemoryCount = typeof friend.publicMemoryCount === "number"
1398
+ ? friend.publicMemoryCount
1399
+ : 0;
1400
+ return `[${idx + 1}] ${name}\nUser ID: ${userId}\nPublic memories: ${publicMemoryCount}`;
1401
+ })
1402
+ .join("\n\n");
1403
+ const total = typeof payload?.totalPublicMemoryCount === "number"
1404
+ ? payload.totalPublicMemoryCount
1405
+ : friends.reduce((sum, friend) => sum + (typeof friend.publicMemoryCount === "number" ? friend.publicMemoryCount : 0), 0);
1406
+ return {
1407
+ content: [{
1408
+ type: "text",
1409
+ text: `Found ${friends.length} accepted friends (totalPublicMemoryCount=${total}):\n\n${formatted}`,
1410
+ }],
1411
+ };
1412
+ }
1413
+ async handleSearchUsers(args) {
1414
+ const parsed = searchUsersSchema.parse(args);
1415
+ const payload = await this.client.searchUsers(args);
1416
+ const users = Array.isArray(payload?.users) ? payload.users : [];
1417
+ if (!users.length) {
1418
+ return {
1419
+ content: [{ type: "text", text: `No EchoMem users found matching "${parsed.query}".` }],
1420
+ };
1421
+ }
1422
+ const formatted = users
1423
+ .map((user, idx) => {
1424
+ const displayName = typeof user.displayName === "string" && user.displayName.trim()
1425
+ ? user.displayName.trim()
1426
+ : typeof user.username === "string" && user.username.trim()
1427
+ ? user.username.trim()
1428
+ : "Unnamed user";
1429
+ const userId = typeof user.userId === "string" ? user.userId : "unknown";
1430
+ const totalMemoryCount = typeof user.totalMemoryCount === "number" ? user.totalMemoryCount : 0;
1431
+ const publicMemoryCount = typeof user.publicMemoryCount === "number" ? user.publicMemoryCount : 0;
1432
+ const relationship = typeof user.relationshipStatus === "string" ? user.relationshipStatus : "none";
1433
+ const pendingRequestId = typeof user.pendingRequestId === "string" && user.pendingRequestId
1434
+ ? `\nPending request ID: ${user.pendingRequestId}`
1435
+ : "";
1436
+ return `[${idx + 1}] ${displayName}\nUser ID: ${userId}\nTotal memories: ${totalMemoryCount}\nPublic memories: ${publicMemoryCount}\nRelationship: ${relationship}${pendingRequestId}`;
1437
+ })
1438
+ .join("\n\n");
1439
+ return {
1440
+ content: [{
1441
+ type: "text",
1442
+ text: `Found ${users.length} EchoMem users matching "${parsed.query}":\n\n${formatted}\n\nUse send_friend_request with the exact User ID to send a pending friend request.`,
1443
+ }],
1444
+ };
1445
+ }
1446
+ async handleSendFriendRequest(args) {
1447
+ sendFriendRequestSchema.parse(args);
1448
+ const payload = await this.client.sendFriendRequest(args);
1449
+ const result = isRecord(payload) ? payload : {};
1450
+ const targetUser = isRecord(result.targetUser) ? result.targetUser : {};
1451
+ const confirmation = isRecord(result.confirmation) ? result.confirmation : {};
1452
+ const request = isRecord(result.request) ? result.request : {};
1453
+ const created = result.created === true;
1454
+ const displayName = readString(targetUser, "displayName") ?? readString(targetUser, "username") ?? "Unnamed user";
1455
+ const userId = readString(targetUser, "userId") ?? "unknown";
1456
+ const relationship = readString(targetUser, "relationshipStatus") ?? "pending_sent";
1457
+ const requestId = readString(request, "id") ?? readString(targetUser, "pendingRequestId") ?? "unknown";
1458
+ const status = readString(confirmation, "status") ?? "pending";
1459
+ const requiresApproval = confirmation.requiresApproval === true;
1460
+ const message = readString(confirmation, "message") ?? "Friend request is pending approval.";
1461
+ return {
1462
+ content: [{
1463
+ type: "text",
1464
+ text: [
1465
+ created ? "Friend request sent." : "Friend request was not duplicated.",
1466
+ "",
1467
+ `Target: ${displayName}`,
1468
+ `Target User ID: ${userId}`,
1469
+ `Relationship: ${relationship}`,
1470
+ `Request ID: ${requestId}`,
1471
+ `Status: ${status}`,
1472
+ `Requires approval: ${requiresApproval ? "yes" : "no"}`,
1473
+ "",
1474
+ `Confirmation: ${message}`,
1475
+ ].join("\n"),
1476
+ }],
1477
+ };
1478
+ }
1479
+ async handlePublicMemory(args) {
1480
+ const parsed = publicMemorySchema.parse(args);
1481
+ const payload = await this.client.getPublicMemory(args);
1482
+ const memory = payload?.memory;
1483
+ if (!memory) {
1484
+ return {
1485
+ content: [{ type: "text", text: `Public memory ${parsed.memoryId} was not found or is not accessible.` }],
1486
+ };
1487
+ }
1488
+ const text = [
1489
+ `Memory ID: ${memory.id || parsed.memoryId}`,
1490
+ `Owner User ID: ${memory.owner_user_id || memory.user_id || "Unknown"}`,
1491
+ memory.time ? `Time: ${memory.time}` : "",
1492
+ memory.location ? `Location: ${memory.location}` : "",
1493
+ memory.category ? `Category: ${memory.category}` : "",
1494
+ memory.object ? `Object: ${memory.object}` : "",
1495
+ memory.emotion ? `Emotion: ${memory.emotion}` : "",
1496
+ memory.keys ? `Keys: ${memory.keys}` : "",
1497
+ `Description: ${memory.description || ""}`,
1498
+ memory.details ? `Details: ${memory.details}` : "",
1499
+ typeof payload.accessRecordsInserted === "number"
1500
+ ? `Access records inserted: ${payload.accessRecordsInserted}`
1501
+ : "",
1502
+ ].filter(Boolean).join("\n");
1503
+ return {
1504
+ content: [{ type: "text", text }],
1505
+ };
1506
+ }
1274
1507
  async handleDelete(args, rec) {
1275
1508
  const parsed = deleteMemorySchema.parse(args);
1276
1509
  if (rec) {
package/dist/setup.js CHANGED
@@ -275,8 +275,8 @@ export function resolveServerEntryVersion(entry) {
275
275
  }
276
276
  const candidates = [...args, command].filter((value) => typeof value === "string");
277
277
  for (const candidate of candidates) {
278
- // Direct local/dev entries often look like ".../packages/mcp-server/dist/index.js";
279
- // they do not contain the published package name, but walking upward still finds package.json.
278
+ if (!candidate.includes(MCP_PACKAGE_NAME) && !candidate.includes(`${path.sep}echomem-mcp`))
279
+ continue;
280
280
  const resolved = packageVersionFromPath(candidate);
281
281
  if (resolved)
282
282
  return resolved;
@@ -5,7 +5,11 @@ export const canonicalToolNames = {
5
5
  save: "save_conversation",
6
6
  timeRange: "get_memories_by_time_range",
7
7
  keywords: "search_memories_by_keywords",
8
+ friends: "list_friends",
9
+ searchUsers: "search_users",
10
+ sendFriendRequest: "send_friend_request",
8
11
  others: "search_others_memories",
12
+ publicMemory: "get_public_memory",
9
13
  report: "echomem_usage_report",
10
14
  updateStatus: "echomem_update_status",
11
15
  contextHealth: "echo_context_health",
@@ -59,9 +63,35 @@ export const keywordsSchema = z.object({
59
63
  keywords: z.array(z.string()),
60
64
  limit: z.number().optional().default(10),
61
65
  });
66
+ export const listFriendsSchema = z.object({
67
+ ...triggerMetadataSchema,
68
+ });
69
+ export const searchUsersSchema = z.object({
70
+ ...triggerMetadataSchema,
71
+ query: z.string().min(1),
72
+ limit: z.number().optional().default(10),
73
+ });
74
+ export const sendFriendRequestSchema = z.object({
75
+ ...triggerMetadataSchema,
76
+ targetUserId: z.string().min(1),
77
+ });
62
78
  export const othersSchema = z.object({
63
79
  ...triggerMetadataSchema,
64
- query: z.string(),
80
+ query: z.string().optional().default(""),
81
+ limit: z.number().optional().default(10),
82
+ target: z.string().optional(),
83
+ ownerUserId: z.string().optional(),
84
+ ownerName: z.string().optional(),
85
+ targetFriendIds: z.array(z.string()).optional(),
86
+ targetFriendNames: z.array(z.string()).optional(),
87
+ recordAccess: z.boolean().optional(),
88
+ kPerUser: z.number().optional(),
89
+ similarityThreshold: z.number().optional(),
90
+ timeFrameDays: z.number().optional(),
91
+ });
92
+ export const publicMemorySchema = z.object({
93
+ ...triggerMetadataSchema,
94
+ memoryId: z.string().min(1),
65
95
  });
66
96
  export const deleteMemorySchema = z.object({
67
97
  memoryId: z.string().min(1),
@@ -196,20 +226,121 @@ export function listToolSpecs(opts = {}) {
196
226
  required: ["keywords"],
197
227
  },
198
228
  },
229
+ {
230
+ name: canonicalToolNames.friends,
231
+ description: "Friends: list accepted EchoMem friends with each friend's public memory count. Use this before asking a specific friend by name.",
232
+ inputSchema: {
233
+ type: "object",
234
+ properties: {
235
+ triggerMessage: {
236
+ type: "string",
237
+ description: "Optional: the user's message that caused this friend-list lookup. EchoMem stores only a redacted analytics preview and hash.",
238
+ },
239
+ triggerMessageRole: { type: "string", default: "user" },
240
+ },
241
+ },
242
+ },
243
+ {
244
+ name: canonicalToolNames.searchUsers,
245
+ description: "Search EchoMem users by display name/username before sending a friend request. Results include total memories, public memories, and current relationship state.",
246
+ inputSchema: {
247
+ type: "object",
248
+ properties: {
249
+ query: {
250
+ type: "string",
251
+ description: "Name or username fragment to search, e.g. Kobe.",
252
+ },
253
+ limit: { type: "number", default: 10 },
254
+ triggerMessage: {
255
+ type: "string",
256
+ description: "Optional: the user's message that caused this user search. EchoMem stores only a redacted analytics preview and hash.",
257
+ },
258
+ triggerMessageRole: { type: "string", default: "user" },
259
+ },
260
+ required: ["query"],
261
+ },
262
+ },
263
+ {
264
+ name: canonicalToolNames.sendFriendRequest,
265
+ description: "Send a pending EchoMem friend request to an exact user id returned by search_users. The other user must approve before friend-gated memory access is allowed.",
266
+ inputSchema: {
267
+ type: "object",
268
+ properties: {
269
+ targetUserId: {
270
+ type: "string",
271
+ description: "Exact target user id from search_users.",
272
+ },
273
+ triggerMessage: {
274
+ type: "string",
275
+ description: "Optional: the user's message that caused this friend request. EchoMem stores only a redacted analytics preview and hash.",
276
+ },
277
+ triggerMessageRole: { type: "string", default: "user" },
278
+ },
279
+ required: ["targetUserId"],
280
+ },
281
+ },
199
282
  {
200
283
  name: canonicalToolNames.others,
201
- description: "Search other users' public memories.",
284
+ description: "Search accepted friends' public memories. Returned memories are recorded in the existing memory_views pipeline for the memory owners.",
202
285
  inputSchema: {
203
286
  type: "object",
204
287
  properties: {
205
288
  query: { type: "string" },
289
+ limit: { type: "number", default: 10 },
290
+ target: {
291
+ type: "string",
292
+ description: "Accepted-friend user id or exact display name/username. Prefer this for @Name asks.",
293
+ },
294
+ ownerUserId: {
295
+ type: "string",
296
+ description: "Optional accepted-friend user id to scope the search to one friend.",
297
+ },
298
+ ownerName: {
299
+ type: "string",
300
+ description: "Optional accepted-friend display name/username to scope the search to one friend.",
301
+ },
302
+ targetFriendIds: {
303
+ type: "array",
304
+ items: { type: "string" },
305
+ description: "Optional accepted-friend user ids to scope the search.",
306
+ },
307
+ targetFriendNames: {
308
+ type: "array",
309
+ items: { type: "string" },
310
+ description: "Optional accepted-friend display names/usernames to scope the search.",
311
+ },
312
+ recordAccess: {
313
+ type: "boolean",
314
+ description: "Defaults true. When true, returned public memories are recorded in memory_views.",
315
+ },
316
+ kPerUser: { type: "number", default: 5 },
317
+ similarityThreshold: { type: "number", default: 0.1 },
318
+ timeFrameDays: { type: "number" },
206
319
  triggerMessage: {
207
320
  type: "string",
208
321
  description: "Optional: the user's message that caused this public-memory search. EchoMem stores only a redacted analytics preview and hash.",
209
322
  },
210
323
  triggerMessageRole: { type: "string", default: "user" },
211
324
  },
212
- required: ["query"],
325
+ },
326
+ },
327
+ {
328
+ name: canonicalToolNames.publicMemory,
329
+ description: "Fetch one accepted friend's public memory by id. If the caller is not the owner, EchoMem records the access in the existing memory_views pipeline.",
330
+ inputSchema: {
331
+ type: "object",
332
+ properties: {
333
+ memoryId: {
334
+ type: "string",
335
+ description: "The public memory id to fetch.",
336
+ },
337
+ triggerMessage: {
338
+ type: "string",
339
+ description: "Optional: the user's message that caused this public-memory fetch. EchoMem stores only a redacted analytics preview and hash.",
340
+ },
341
+ triggerMessageRole: { type: "string", default: "user" },
342
+ },
343
+ required: ["memoryId"],
213
344
  },
214
345
  },
215
346
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@echomem/mcp",
3
- "version": "1.4.6",
3
+ "version": "1.4.7",
4
4
  "description": "EchoMem Cloud-First MCP Server",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",