@echomem/mcp 1.4.21 → 1.4.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/assets/hud/echo-pricing-free-sticker.png +0 -0
- package/assets/hud/echo-pricing-power-sticker.png +0 -0
- package/assets/hud/echo-pricing-pro-sticker.png +0 -0
- package/assets/hud/scan-loop/frame-01-ready.png +0 -0
- package/assets/hud/scan-loop/frame-02-open.png +0 -0
- package/assets/hud/scan-loop/frame-03-pull.png +0 -0
- package/assets/hud/scan-loop/frame-04-read.png +0 -0
- package/assets/hud/scan-loop/frame-05-file.png +0 -0
- package/assets/hud/scan-loop/frame-06-close.png +0 -0
- package/dist/hud/web.js +3 -3
- package/dist/index.js +264 -7
- package/dist/migrate.js +6 -5
- package/dist/package-metadata.js +2 -0
- package/dist/setup-page/client-core.js +144 -48
- package/dist/setup-page/client-extraction.js +260 -70
- package/dist/setup-page/client-lifecycle.js +45 -39
- package/dist/setup-page/client-report-audit.js +1 -1
- package/dist/setup-page/client-report-city.js +35 -22
- package/dist/setup-page/document.js +1 -1
- package/dist/setup-page/styles-extraction.js +330 -0
- package/dist/setup-page/styles-mvp.js +1079 -0
- package/dist/setup-page/styles-website-alignment.js +237 -5
- package/dist/setup-page/styles.js +2 -0
- package/dist/setup-preview.js +22 -6
- package/dist/setup.js +272 -40
- package/dist/v1-contract.js +226 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ HUD; the granular commands below still work.
|
|
|
66
66
|
| `npx -y @echomem/mcp@latest setup` | One-off setup without keeping a global CLI command |
|
|
67
67
|
| `echomem-mcp setup [--client cursor\|windsurf\|claude-desktop\|claude-code\|codex]` | Write client config + log in |
|
|
68
68
|
| `echomem-mcp setup --skip-login [--client cursor\|windsurf\|claude-desktop\|claude-code\|codex]` | Write client config without opening the browser or changing credentials |
|
|
69
|
-
| `npx -y @echomem/mcp@latest update --all` | One-shot update: repoint detected client configs
|
|
69
|
+
| `npx -y @echomem/mcp@latest update --all` | One-shot update: install the latest bridge durably and repoint detected client configs, with no browser login |
|
|
70
70
|
| `npx -y @echomem/mcp@latest update --client codex` | Update one client only |
|
|
71
71
|
| `echomem-mcp setup --with-hud [--client codex]` | Write client config + log in + launch the EchoMem context HUD |
|
|
72
72
|
| `echomem-mcp login` | Approve device in browser (or use `--token` / `--passphrase`) |
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/hud/web.js
CHANGED
|
@@ -1723,7 +1723,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1723
1723
|
if (previewParams.get("capture") === "1") hud.classList.add("capture-preview");
|
|
1724
1724
|
if (previewColor === "green" || previewColor === "amber" || previewColor === "red") {
|
|
1725
1725
|
const previewProcessingUsed = previewColor === "red" ? 200000 : previewColor === "amber" ? 163000 : 84000;
|
|
1726
|
-
const previewSearchUsed = previewColor === "red" ?
|
|
1726
|
+
const previewSearchUsed = previewColor === "red" ? 500 : previewColor === "amber" ? 410 : 160;
|
|
1727
1727
|
billingStatus = {
|
|
1728
1728
|
ok: true,
|
|
1729
1729
|
state: "ok",
|
|
@@ -1731,8 +1731,8 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1731
1731
|
paid: true,
|
|
1732
1732
|
pricingUrl: "https://yeahecho.com/pricing?source=hud_preview",
|
|
1733
1733
|
memoryProcessingQuota: { used: previewProcessingUsed, limit: 200000, remaining: 200000 - previewProcessingUsed, resetAt: new Date(Date.now() + 31 * 60 * 60 * 1000).toISOString() },
|
|
1734
|
-
memorySearchQuota: { used: previewSearchUsed, limit:
|
|
1735
|
-
historicalConversationQuota: { used: 191, limit:
|
|
1734
|
+
memorySearchQuota: { used: previewSearchUsed, limit: 500, remaining: 500 - previewSearchUsed, resetAt: new Date(Date.now() + 31 * 60 * 60 * 1000).toISOString() },
|
|
1735
|
+
historicalConversationQuota: { used: 191, limit: 500, remaining: 309 },
|
|
1736
1736
|
};
|
|
1737
1737
|
const sample = {
|
|
1738
1738
|
client: "codex",
|
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, listFriendsSchema, listToolSpecs, othersSchema, publicMemorySchema, resolveCanonicalToolName, saveConversationSchema, searchMemoriesSchema, searchUsersSchema, sendFriendRequestSchema, timeRangeSchema, } from "./v1-contract.js";
|
|
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";
|
|
8
8
|
import { KeyStore } from "./keystore.js";
|
|
9
9
|
import { EventLogger, hashText } from "./events.js";
|
|
10
10
|
import { buildReportText } from "./report.js";
|
|
@@ -488,12 +488,20 @@ function inputAnalyticsForTool(canonicalName, args) {
|
|
|
488
488
|
limit: numberArg(a, "limit"),
|
|
489
489
|
};
|
|
490
490
|
}
|
|
491
|
-
case canonicalToolNames.publicMemory:
|
|
491
|
+
case canonicalToolNames.publicMemory:
|
|
492
|
+
case canonicalToolNames.publishToGroup: {
|
|
492
493
|
const memoryId = readString(a, "memoryId");
|
|
493
494
|
return {
|
|
494
495
|
memory_id_hash: memoryId ? hashText(memoryId) : undefined,
|
|
495
496
|
};
|
|
496
497
|
}
|
|
498
|
+
case canonicalToolNames.publishBatchToGroup: {
|
|
499
|
+
const memoryIds = Array.isArray(a.memoryIds) ? a.memoryIds.filter((id) => typeof id === "string") : [];
|
|
500
|
+
return {
|
|
501
|
+
memory_count: memoryIds.length,
|
|
502
|
+
context_id_hash: typeof a.contextId === "string" ? hashText(a.contextId) : undefined,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
497
505
|
default:
|
|
498
506
|
return {};
|
|
499
507
|
}
|
|
@@ -944,6 +952,72 @@ class EchoMemApiClient {
|
|
|
944
952
|
throw new Error(`get_public_memory failed against ${ECHO_API_BASE_URL}: ${describeError(error)}`);
|
|
945
953
|
}
|
|
946
954
|
}
|
|
955
|
+
async getGroupContext(args) {
|
|
956
|
+
groupContextSchema.parse(args ?? {});
|
|
957
|
+
try {
|
|
958
|
+
const response = await this.axios.get("/api/extension/social/groups/current");
|
|
959
|
+
return response.data;
|
|
960
|
+
}
|
|
961
|
+
catch (error) {
|
|
962
|
+
throw new Error(`get_group_context failed against ${ECHO_API_BASE_URL}: ${describeError(error)}`);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
async createGroup(args) {
|
|
966
|
+
const parsed = createGroupSchema.parse(args ?? {});
|
|
967
|
+
const response = await this.axios.post("/api/extension/social/groups", parsed);
|
|
968
|
+
return response.data;
|
|
969
|
+
}
|
|
970
|
+
async createGroupInvite(args) {
|
|
971
|
+
const parsed = createGroupInviteSchema.parse(args ?? {});
|
|
972
|
+
const response = await this.axios.post("/api/extension/social/groups/current/invite", parsed);
|
|
973
|
+
return response.data;
|
|
974
|
+
}
|
|
975
|
+
async joinGroup(args) {
|
|
976
|
+
const parsed = joinGroupSchema.parse(args ?? {});
|
|
977
|
+
const response = await this.axios.post("/api/extension/social/groups/join", parsed);
|
|
978
|
+
return response.data;
|
|
979
|
+
}
|
|
980
|
+
async prepareGroupPublication(args) {
|
|
981
|
+
const parsed = prepareGroupPublicationSchema.parse(args ?? {});
|
|
982
|
+
const enc = await this.encState();
|
|
983
|
+
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
|
+
return response.data;
|
|
985
|
+
}
|
|
986
|
+
async completeGroupPublication(args) {
|
|
987
|
+
const parsed = completeGroupPublicationSchema.parse(args ?? {});
|
|
988
|
+
const enc = await this.encState();
|
|
989
|
+
const response = await this.axios.post("/api/extension/social/groups/current/publications/complete", parsed, { headers: enc.enabled && enc.key ? { "X-Encryption-Key": enc.key } : undefined });
|
|
990
|
+
return response.data;
|
|
991
|
+
}
|
|
992
|
+
async publishMemoryToGroup(args) {
|
|
993
|
+
const parsed = publishToGroupSchema.parse(args ?? {});
|
|
994
|
+
const enc = await this.encState();
|
|
995
|
+
try {
|
|
996
|
+
const response = await this.axios.post(`/api/extension/social/groups/current/memories/${encodeURIComponent(parsed.memoryId)}/publish`, {}, {
|
|
997
|
+
headers: enc.enabled && enc.key ? { "X-Encryption-Key": enc.key } : undefined,
|
|
998
|
+
});
|
|
999
|
+
return response.data;
|
|
1000
|
+
}
|
|
1001
|
+
catch (error) {
|
|
1002
|
+
throw new Error(`publish_memory_to_group failed against ${ECHO_API_BASE_URL}: ${describeError(error)}`);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
async publishMemoriesToGroup(args) {
|
|
1006
|
+
const parsed = publishBatchToGroupSchema.parse(args ?? {});
|
|
1007
|
+
const enc = await this.encState();
|
|
1008
|
+
try {
|
|
1009
|
+
const response = await this.axios.post("/api/extension/social/groups/current/memories/publish-batch", {
|
|
1010
|
+
memoryIds: parsed.memoryIds,
|
|
1011
|
+
contextId: parsed.contextId,
|
|
1012
|
+
}, {
|
|
1013
|
+
headers: enc.enabled && enc.key ? { "X-Encryption-Key": enc.key } : undefined,
|
|
1014
|
+
});
|
|
1015
|
+
return response.data;
|
|
1016
|
+
}
|
|
1017
|
+
catch (error) {
|
|
1018
|
+
throw new Error(`publish_memories_to_group failed against ${ECHO_API_BASE_URL}: ${describeError(error)}`);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
947
1021
|
}
|
|
948
1022
|
const SERVER_VERSION = MCP_PACKAGE_VERSION;
|
|
949
1023
|
class EchoMemMCPServer {
|
|
@@ -1121,6 +1195,22 @@ class EchoMemMCPServer {
|
|
|
1121
1195
|
return await this.handleOthers(request.params.arguments);
|
|
1122
1196
|
case canonicalToolNames.publicMemory:
|
|
1123
1197
|
return await this.handlePublicMemory(request.params.arguments);
|
|
1198
|
+
case canonicalToolNames.groupContext:
|
|
1199
|
+
return await this.handleGroupContext(request.params.arguments);
|
|
1200
|
+
case canonicalToolNames.createGroup:
|
|
1201
|
+
return await this.handleCreateGroup(request.params.arguments);
|
|
1202
|
+
case canonicalToolNames.createGroupInvite:
|
|
1203
|
+
return await this.handleCreateGroupInvite(request.params.arguments);
|
|
1204
|
+
case canonicalToolNames.joinGroup:
|
|
1205
|
+
return await this.handleJoinGroup(request.params.arguments);
|
|
1206
|
+
case canonicalToolNames.prepareGroupPublication:
|
|
1207
|
+
return await this.handlePrepareGroupPublication(request.params.arguments);
|
|
1208
|
+
case canonicalToolNames.completeGroupPublication:
|
|
1209
|
+
return await this.handleCompleteGroupPublication(request.params.arguments);
|
|
1210
|
+
case canonicalToolNames.publishToGroup:
|
|
1211
|
+
return await this.handlePublishToGroup(request.params.arguments);
|
|
1212
|
+
case canonicalToolNames.publishBatchToGroup:
|
|
1213
|
+
return await this.handlePublishBatchToGroup(request.params.arguments);
|
|
1124
1214
|
case canonicalToolNames.delete:
|
|
1125
1215
|
return await this.handleDelete(request.params.arguments, rec);
|
|
1126
1216
|
default:
|
|
@@ -1302,7 +1392,7 @@ class EchoMemMCPServer {
|
|
|
1302
1392
|
return { content: [{ type: "text", text: "No relevant memories found." }] };
|
|
1303
1393
|
}
|
|
1304
1394
|
const formattedResults = memories
|
|
1305
|
-
.map((m, idx) => `[Result ${idx + 1}] (Similarity: ${m.similarity_score?.toFixed(3) || "N/A"})
|
|
1395
|
+
.map((m, idx) => `[Result ${idx + 1}] Memory ID: ${m.id || "unknown"} (Similarity: ${m.similarity_score?.toFixed(3) || "N/A"})
|
|
1306
1396
|
Time: ${m.time} | Location: ${m.location}
|
|
1307
1397
|
Category: ${m.category} | Object: ${m.object} | Emotion: ${m.emotion}
|
|
1308
1398
|
Description: ${m.description}
|
|
@@ -1504,11 +1594,11 @@ Details: ${m.details || "N/A"}`)
|
|
|
1504
1594
|
const memories = payload?.memories ?? [];
|
|
1505
1595
|
if (!memories.length) {
|
|
1506
1596
|
const scope = parsed.ownerUserId
|
|
1507
|
-
? ` for
|
|
1597
|
+
? ` for peer ${parsed.ownerUserId}`
|
|
1508
1598
|
: parsed.ownerName
|
|
1509
|
-
? ` for
|
|
1599
|
+
? ` for peer ${parsed.ownerName}`
|
|
1510
1600
|
: parsed.target
|
|
1511
|
-
? ` for
|
|
1601
|
+
? ` for peer ${parsed.target}`
|
|
1512
1602
|
: parsed.targetFriendIds?.length
|
|
1513
1603
|
? ` for ${parsed.targetFriendIds.length} selected friends`
|
|
1514
1604
|
: parsed.targetFriendNames?.length
|
|
@@ -1521,7 +1611,12 @@ Details: ${m.details || "N/A"}`)
|
|
|
1521
1611
|
}
|
|
1522
1612
|
const metadata = [
|
|
1523
1613
|
typeof payload?.friendCount === "number" ? `friendCount=${payload.friendCount}` : "",
|
|
1524
|
-
typeof payload?.
|
|
1614
|
+
typeof payload?.groupPeerCount === "number" ? `groupPeerCount=${payload.groupPeerCount}` : "",
|
|
1615
|
+
typeof payload?.searchedPeerCount === "number"
|
|
1616
|
+
? `searchedPeerCount=${payload.searchedPeerCount}`
|
|
1617
|
+
: typeof payload?.searchedFriendCount === "number"
|
|
1618
|
+
? `searchedPeerCount=${payload.searchedFriendCount}`
|
|
1619
|
+
: "",
|
|
1525
1620
|
typeof payload?.memoryViewsInserted === "number" ? `memoryViewsInserted=${payload.memoryViewsInserted}` : "",
|
|
1526
1621
|
].filter(Boolean).join(", ");
|
|
1527
1622
|
const formattedResults = memories
|
|
@@ -1674,6 +1769,168 @@ Details: ${m.details || "N/A"}`;
|
|
|
1674
1769
|
content: [{ type: "text", text }],
|
|
1675
1770
|
};
|
|
1676
1771
|
}
|
|
1772
|
+
async handleGroupContext(args) {
|
|
1773
|
+
groupContextSchema.parse(args ?? {});
|
|
1774
|
+
const payload = await this.client.getGroupContext(args);
|
|
1775
|
+
const group = isRecord(payload?.group) ? payload.group : null;
|
|
1776
|
+
const participants = Array.isArray(payload?.participants)
|
|
1777
|
+
? payload.participants.filter(isRecord)
|
|
1778
|
+
: [];
|
|
1779
|
+
const coverage = isRecord(payload?.coverage) ? payload.coverage : {};
|
|
1780
|
+
if (!group) {
|
|
1781
|
+
return {
|
|
1782
|
+
content: [{
|
|
1783
|
+
type: "text",
|
|
1784
|
+
text: "You are not assigned to a company group yet. Ask an EchoMem administrator to add you before using group orientation or group publishing.",
|
|
1785
|
+
}],
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
const participantText = participants.map((participant, index) => [
|
|
1789
|
+
`[${index + 1}] ${readString(participant, "displayName") ?? "Unnamed participant"}`,
|
|
1790
|
+
`User ID: ${readString(participant, "userId") ?? "unknown"}`,
|
|
1791
|
+
`Title: ${readString(participant, "title") ?? "Not declared"}`,
|
|
1792
|
+
`Declared responsibility: ${readString(participant, "responsibilitySummary") ?? "Not provided"}`,
|
|
1793
|
+
`Published memories: ${readNumber(participant, "publicMemoryCount") ?? 0}`,
|
|
1794
|
+
].join("\n")).join("\n\n");
|
|
1795
|
+
const participantCount = readNumber(coverage, "participantCount") ?? participants.length;
|
|
1796
|
+
const coveredCount = readNumber(coverage, "participantsWithPublishedMemories") ?? 0;
|
|
1797
|
+
const totalPublished = readNumber(coverage, "totalPublishedMemories") ?? 0;
|
|
1798
|
+
const text = [
|
|
1799
|
+
`Company group: ${readString(group, "name") ?? "Unnamed group"}`,
|
|
1800
|
+
readString(group, "description") ? `Description: ${readString(group, "description")}` : "",
|
|
1801
|
+
`Coverage: ${coveredCount} of ${participantCount} participants have published memories (${totalPublished} total).`,
|
|
1802
|
+
"",
|
|
1803
|
+
participantText,
|
|
1804
|
+
"",
|
|
1805
|
+
"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.",
|
|
1806
|
+
].filter(Boolean).join("\n");
|
|
1807
|
+
return { content: [{ type: "text", text }] };
|
|
1808
|
+
}
|
|
1809
|
+
async handlePublishToGroup(args) {
|
|
1810
|
+
const parsed = publishToGroupSchema.parse(args ?? {});
|
|
1811
|
+
const payload = await this.client.publishMemoryToGroup(args);
|
|
1812
|
+
if (payload?.success === false) {
|
|
1813
|
+
throw new Error(`EchoMem API Error: ${payload.error || "publish failed"}`);
|
|
1814
|
+
}
|
|
1815
|
+
const group = isRecord(payload?.group) ? payload.group : {};
|
|
1816
|
+
const groupName = readString(group, "name") ?? "your company group";
|
|
1817
|
+
const memberCount = typeof payload?.searchableByGroupMemberCount === "number"
|
|
1818
|
+
? payload.searchableByGroupMemberCount
|
|
1819
|
+
: 0;
|
|
1820
|
+
const alreadyPublished = payload?.alreadyPublished === true;
|
|
1821
|
+
return {
|
|
1822
|
+
content: [{
|
|
1823
|
+
type: "text",
|
|
1824
|
+
text: alreadyPublished
|
|
1825
|
+
? `Memory ${parsed.memoryId} was already published to ${groupName}. It is searchable by up to ${memberCount} other group members.`
|
|
1826
|
+
: `Published memory ${parsed.memoryId} to ${groupName}. It is now searchable by up to ${memberCount} other group members.`,
|
|
1827
|
+
}],
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
async handleCreateGroup(args) {
|
|
1831
|
+
const parsed = createGroupSchema.parse(args ?? {});
|
|
1832
|
+
const payload = await this.client.createGroup(parsed);
|
|
1833
|
+
return { content: [{ type: "text", text: `Created company group ${payload?.group?.name ?? parsed.name}. Generate an invite code when you are ready to add teammates.` }] };
|
|
1834
|
+
}
|
|
1835
|
+
async handleCreateGroupInvite(args) {
|
|
1836
|
+
createGroupInviteSchema.parse(args ?? {});
|
|
1837
|
+
const payload = await this.client.createGroupInvite(args);
|
|
1838
|
+
return {
|
|
1839
|
+
content: [{
|
|
1840
|
+
type: "text",
|
|
1841
|
+
text: [
|
|
1842
|
+
`Invite code for ${payload?.group?.name ?? "your group"}: ${payload?.code}`,
|
|
1843
|
+
`Expires: ${payload?.expiresAt}`,
|
|
1844
|
+
`Maximum uses: ${payload?.maxUses}`,
|
|
1845
|
+
"Share this secret code only with intended teammates.",
|
|
1846
|
+
].join("\n"),
|
|
1847
|
+
}],
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
async handleJoinGroup(args) {
|
|
1851
|
+
const parsed = joinGroupSchema.parse(args ?? {});
|
|
1852
|
+
const payload = await this.client.joinGroup(parsed);
|
|
1853
|
+
return {
|
|
1854
|
+
content: [{
|
|
1855
|
+
type: "text",
|
|
1856
|
+
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.`,
|
|
1859
|
+
}],
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
async handlePrepareGroupPublication(args) {
|
|
1863
|
+
const parsed = prepareGroupPublicationSchema.parse(args ?? {});
|
|
1864
|
+
const payload = await this.client.prepareGroupPublication(parsed);
|
|
1865
|
+
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");
|
|
1875
|
+
return {
|
|
1876
|
+
content: [{
|
|
1877
|
+
type: "text",
|
|
1878
|
+
text: [
|
|
1879
|
+
`Prepared publication scan ${payload?.scanId} for ${payload?.group?.name ?? "your group"}.`,
|
|
1880
|
+
`Window: ${payload?.windowStart ?? "context start"} → ${payload?.windowEnd}`,
|
|
1881
|
+
`Candidates: ${candidates.length}. Nothing has been published.`,
|
|
1882
|
+
"",
|
|
1883
|
+
formatted,
|
|
1884
|
+
"",
|
|
1885
|
+
"Select exact memory IDs matching the user's instruction, show the preview, and ask for confirmation before calling complete_group_publication.",
|
|
1886
|
+
].join("\n"),
|
|
1887
|
+
}],
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
async handleCompleteGroupPublication(args) {
|
|
1891
|
+
const parsed = completeGroupPublicationSchema.parse(args ?? {});
|
|
1892
|
+
const payload = await this.client.completeGroupPublication(parsed);
|
|
1893
|
+
const memories = Array.isArray(payload?.memories) ? payload.memories : [];
|
|
1894
|
+
const published = memories.filter((memory) => memory.alreadyPublished !== true && memory.skippedDuplicate !== true).length;
|
|
1895
|
+
const duplicates = memories.filter((memory) => memory.skippedDuplicate === true).length;
|
|
1896
|
+
return {
|
|
1897
|
+
content: [{
|
|
1898
|
+
type: "text",
|
|
1899
|
+
text: [
|
|
1900
|
+
`Completed publication scan ${parsed.scanId}.`,
|
|
1901
|
+
`Newly published: ${published}.`,
|
|
1902
|
+
`Exact duplicates skipped: ${duplicates}.`,
|
|
1903
|
+
`Selected memory IDs: ${parsed.memoryIds.join(", ") || "(none)"}.`,
|
|
1904
|
+
"The scan cursor advanced; encrypted originals and global public settings were unchanged.",
|
|
1905
|
+
].join("\n"),
|
|
1906
|
+
}],
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
async handlePublishBatchToGroup(args) {
|
|
1910
|
+
const parsed = publishBatchToGroupSchema.parse(args ?? {});
|
|
1911
|
+
const payload = await this.client.publishMemoriesToGroup(parsed);
|
|
1912
|
+
if (payload?.success === false) {
|
|
1913
|
+
throw new Error(`EchoMem API Error: ${payload.error || "batch publish failed"}`);
|
|
1914
|
+
}
|
|
1915
|
+
const group = isRecord(payload?.group) ? payload.group : {};
|
|
1916
|
+
const groupName = readString(group, "name") ?? "your company group";
|
|
1917
|
+
const publishedCount = typeof payload?.publishedCount === "number" ? payload.publishedCount : 0;
|
|
1918
|
+
const alreadyPublishedCount = typeof payload?.alreadyPublishedCount === "number"
|
|
1919
|
+
? payload.alreadyPublishedCount
|
|
1920
|
+
: 0;
|
|
1921
|
+
return {
|
|
1922
|
+
content: [{
|
|
1923
|
+
type: "text",
|
|
1924
|
+
text: [
|
|
1925
|
+
`Published ${publishedCount} memory snapshots to ${groupName}.`,
|
|
1926
|
+
alreadyPublishedCount ? `${alreadyPublishedCount} were already published and were refreshed.` : "",
|
|
1927
|
+
`Selected memory IDs: ${parsed.memoryIds.join(", ")}`,
|
|
1928
|
+
parsed.selectionReason ? `Selection reason: ${parsed.selectionReason}` : "",
|
|
1929
|
+
"The encrypted originals and their global public settings were not changed.",
|
|
1930
|
+
].filter(Boolean).join("\n"),
|
|
1931
|
+
}],
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1677
1934
|
async handleDelete(args, rec) {
|
|
1678
1935
|
const parsed = deleteMemorySchema.parse(args ?? {});
|
|
1679
1936
|
if (rec) {
|
package/dist/migrate.js
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Why this lives in the bridge (client-side): the session logs exist ONLY on the user's machine
|
|
6
6
|
* (~/.codex/sessions, ~/.claude/projects). The bridge discovers each session, assembles it into a
|
|
7
|
-
* text-turn-only `## ` transcript, and feeds it to the
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* text-turn-only `## ` transcript, and feeds it to the shared durable import queue
|
|
8
|
+
* through MCP-owned policy entrypoints. POST /api/extension/mcp/historical-import-sessions
|
|
9
|
+
* creates one import_jobs row per session, then the bridge POSTs each transcript to
|
|
10
|
+
* /api/extension/mcp/historical-import-jobs/{id}/run. The web dashboard polls
|
|
10
11
|
* GET /import-sessions/{id} for live progress — the bridge owns local-file access, the server owns the
|
|
11
12
|
* queue + status. (Headless CLI also works; it just shows progress in the terminal.)
|
|
12
13
|
*
|
|
@@ -1353,7 +1354,7 @@ async function createImportSession(client, sessions, bareId, userTz, signal) {
|
|
|
1353
1354
|
sourceDate: s.firstTs,
|
|
1354
1355
|
userTz,
|
|
1355
1356
|
}));
|
|
1356
|
-
const res = await client.post("/api/extension/import-sessions", { items }, signal ? { signal } : undefined);
|
|
1357
|
+
const res = await client.post("/api/extension/mcp/historical-import-sessions", { items }, signal ? { signal } : undefined);
|
|
1357
1358
|
const data = res.data || {};
|
|
1358
1359
|
const serverConcurrency = Number(data.session?.concurrency);
|
|
1359
1360
|
return {
|
|
@@ -1378,7 +1379,7 @@ async function runImportJob(client, jobId, s, userTz, encKey) {
|
|
|
1378
1379
|
const cfg = encKey ? { headers: { "X-Encryption-Key": encKey } } : undefined;
|
|
1379
1380
|
for (let attempt = 0;; attempt++) {
|
|
1380
1381
|
try {
|
|
1381
|
-
const res = await client.post(`/api/extension/import-jobs/${jobId}/run`, body, cfg);
|
|
1382
|
+
const res = await client.post(`/api/extension/mcp/historical-import-jobs/${jobId}/run`, body, cfg);
|
|
1382
1383
|
const data = res.data || {};
|
|
1383
1384
|
if (data.claimed === false) {
|
|
1384
1385
|
// Already completed on a prior run → idempotent, not an error; otherwise it didn't start.
|
package/dist/package-metadata.js
CHANGED
|
@@ -28,6 +28,8 @@ 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
33
|
].join(" ");
|
|
32
34
|
export function withMcpVersion(description) {
|
|
33
35
|
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.`;
|