@echomem/mcp 1.4.41 → 1.4.42
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 +54 -3
- package/dist/setup.js +135 -39
- package/dist/v1-contract.js +53 -3
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1096,6 +1096,8 @@ class EchoMemApiClient {
|
|
|
1096
1096
|
kPerUser: parsed.kPerUser,
|
|
1097
1097
|
similarityThreshold: parsed.similarityThreshold,
|
|
1098
1098
|
timeFrameDays: parsed.timeFrameDays,
|
|
1099
|
+
workspaceId: parsed.workspaceId ?? parsed.groupId,
|
|
1100
|
+
scope: parsed.scope,
|
|
1099
1101
|
requestId: this.sessionId,
|
|
1100
1102
|
source: "mcp_friend_public_memory_search",
|
|
1101
1103
|
});
|
|
@@ -1108,7 +1110,14 @@ class EchoMemApiClient {
|
|
|
1108
1110
|
async getPublicMemory(args) {
|
|
1109
1111
|
const parsed = publicMemorySchema.parse(args ?? {});
|
|
1110
1112
|
try {
|
|
1111
|
-
const
|
|
1113
|
+
const workspaceId = parsed.workspaceId ?? parsed.groupId;
|
|
1114
|
+
const query = new URLSearchParams({
|
|
1115
|
+
requestId: this.sessionId,
|
|
1116
|
+
source: "mcp_friend_public_memory_fetch",
|
|
1117
|
+
});
|
|
1118
|
+
if (workspaceId)
|
|
1119
|
+
query.set("workspaceId", workspaceId);
|
|
1120
|
+
const response = await this.axios.get(`/api/extension/social/public-memories/${encodeURIComponent(parsed.memoryId)}?${query.toString()}`);
|
|
1112
1121
|
return response.data;
|
|
1113
1122
|
}
|
|
1114
1123
|
catch (error) {
|
|
@@ -1131,9 +1140,13 @@ class EchoMemApiClient {
|
|
|
1131
1140
|
}
|
|
1132
1141
|
}
|
|
1133
1142
|
async getGroupContext(args) {
|
|
1134
|
-
groupContextSchema.parse(args ?? {});
|
|
1143
|
+
const parsed = groupContextSchema.parse(args ?? {});
|
|
1135
1144
|
try {
|
|
1136
|
-
const
|
|
1145
|
+
const workspaceId = parsed.workspaceId ?? parsed.groupId;
|
|
1146
|
+
const path = workspaceId
|
|
1147
|
+
? `/api/extension/social/groups/current?workspaceId=${encodeURIComponent(workspaceId)}`
|
|
1148
|
+
: "/api/extension/social/groups/current";
|
|
1149
|
+
const response = await this.axios.get(path);
|
|
1137
1150
|
return response.data;
|
|
1138
1151
|
}
|
|
1139
1152
|
catch (error) {
|
|
@@ -1197,6 +1210,7 @@ class EchoMemApiClient {
|
|
|
1197
1210
|
const enc = await this.encState();
|
|
1198
1211
|
try {
|
|
1199
1212
|
const response = await this.axios.post(`/api/extension/social/groups/current/memories/${encodeURIComponent(parsed.memoryId)}/publish`, {
|
|
1213
|
+
workspaceId: parsed.workspaceId ?? parsed.groupId,
|
|
1200
1214
|
acknowledgedFlaggedMemoryIds: parsed.acknowledgedFlaggedMemoryIds,
|
|
1201
1215
|
}, {
|
|
1202
1216
|
headers: enc.enabled && enc.key ? { "X-Encryption-Key": enc.key } : undefined,
|
|
@@ -1212,6 +1226,7 @@ class EchoMemApiClient {
|
|
|
1212
1226
|
const enc = await this.encState();
|
|
1213
1227
|
try {
|
|
1214
1228
|
const response = await this.axios.post("/api/extension/social/groups/current/memories/publish-batch", {
|
|
1229
|
+
workspaceId: parsed.workspaceId ?? parsed.groupId,
|
|
1215
1230
|
memoryIds: parsed.memoryIds,
|
|
1216
1231
|
contextId: parsed.contextId,
|
|
1217
1232
|
selectionReason: parsed.selectionReason,
|
|
@@ -1995,6 +2010,28 @@ Details: ${m.details || "N/A"}`)
|
|
|
1995
2010
|
async handleOthers(args) {
|
|
1996
2011
|
const parsed = othersSchema.parse(args ?? {});
|
|
1997
2012
|
const payload = await this.client.searchOthersMemories(args);
|
|
2013
|
+
if (payload?.requiresWorkspaceSelection === true) {
|
|
2014
|
+
const availableWorkspaces = Array.isArray(payload?.availableWorkspaces)
|
|
2015
|
+
? payload.availableWorkspaces.filter(isRecord)
|
|
2016
|
+
: [];
|
|
2017
|
+
const choices = availableWorkspaces
|
|
2018
|
+
.map((workspace) => `${readString(workspace, "name") ?? "Unnamed workspace"} (${readString(workspace, "id") ?? "unknown id"})`)
|
|
2019
|
+
.join(", ");
|
|
2020
|
+
return {
|
|
2021
|
+
content: [{
|
|
2022
|
+
type: "text",
|
|
2023
|
+
text: `You belong to ${availableWorkspaces.length} workspaces: ${choices}. Ask the user which workspace's teammates to search, then call search_others_memories again with workspaceId set to one of those ids. (To search a specific friend instead, pass that person as target.)`,
|
|
2024
|
+
}],
|
|
2025
|
+
};
|
|
2026
|
+
}
|
|
2027
|
+
if (payload?.groupScopeUnavailable === true) {
|
|
2028
|
+
return {
|
|
2029
|
+
content: [{
|
|
2030
|
+
type: "text",
|
|
2031
|
+
text: "You are not in a company workspace, so there are no teammates to search. To search friends instead, call search_others_memories again with scope \"friends\", or pass a specific person as target.",
|
|
2032
|
+
}],
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
1998
2035
|
const memories = payload?.memories ?? [];
|
|
1999
2036
|
const authenticatedViewer = isRecord(payload?.authenticatedViewer)
|
|
2000
2037
|
? payload.authenticatedViewer
|
|
@@ -2216,6 +2253,20 @@ Details: ${m.details || "N/A"}`;
|
|
|
2216
2253
|
async handleGroupContext(args) {
|
|
2217
2254
|
groupContextSchema.parse(args ?? {});
|
|
2218
2255
|
const payload = await this.client.getGroupContext(args);
|
|
2256
|
+
if (payload?.requiresWorkspaceSelection === true) {
|
|
2257
|
+
const availableWorkspaces = Array.isArray(payload?.availableWorkspaces)
|
|
2258
|
+
? payload.availableWorkspaces.filter(isRecord)
|
|
2259
|
+
: [];
|
|
2260
|
+
const choices = availableWorkspaces
|
|
2261
|
+
.map((workspace) => `${readString(workspace, "name") ?? "Unnamed workspace"} (${readString(workspace, "id") ?? "unknown id"})`)
|
|
2262
|
+
.join(", ");
|
|
2263
|
+
return {
|
|
2264
|
+
content: [{
|
|
2265
|
+
type: "text",
|
|
2266
|
+
text: `You belong to ${availableWorkspaces.length} workspaces: ${choices}. Ask the user which workspace to show context for, then call get_group_context again with workspaceId set to one of those ids.`,
|
|
2267
|
+
}],
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2219
2270
|
const group = isRecord(payload?.group) ? payload.group : null;
|
|
2220
2271
|
const participants = Array.isArray(payload?.participants)
|
|
2221
2272
|
? payload.participants.filter(isRecord)
|
package/dist/setup.js
CHANGED
|
@@ -416,13 +416,70 @@ export function writeJsonClientConfig(configPath, entry) {
|
|
|
416
416
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
417
417
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
418
418
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
419
|
+
function readClaudeCodeConfigFile(configPath) {
|
|
420
|
+
try {
|
|
421
|
+
const parsed = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
422
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
423
|
+
? parsed
|
|
424
|
+
: {};
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
return {};
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function echoMemEntryFromServers(value) {
|
|
431
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
432
|
+
return undefined;
|
|
433
|
+
const entry = value.echomem;
|
|
434
|
+
return entry && typeof entry === "object" && !Array.isArray(entry)
|
|
435
|
+
? entry
|
|
436
|
+
: undefined;
|
|
437
|
+
}
|
|
438
|
+
function claudeEntriesMatch(actual, expected) {
|
|
439
|
+
if (!actual || actual.command !== expected.command)
|
|
440
|
+
return false;
|
|
441
|
+
const actualArgs = Array.isArray(actual.args) ? actual.args : [];
|
|
442
|
+
const expectedArgs = Array.isArray(expected.args) ? expected.args : [];
|
|
443
|
+
if (actualArgs.length !== expectedArgs.length || actualArgs.some((value, index) => value !== expectedArgs[index])) {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
const expectedEnv = expected.env;
|
|
447
|
+
if (!expectedEnv || typeof expectedEnv !== "object" || Array.isArray(expectedEnv))
|
|
448
|
+
return true;
|
|
449
|
+
const actualEnv = actual.env;
|
|
450
|
+
if (!actualEnv || typeof actualEnv !== "object" || Array.isArray(actualEnv))
|
|
451
|
+
return false;
|
|
452
|
+
return Object.entries(expectedEnv).every(([key, value]) => actualEnv[key] === value);
|
|
453
|
+
}
|
|
454
|
+
function claudeCodeLocalEchoMemProjects(configPath) {
|
|
455
|
+
const projects = readClaudeCodeConfigFile(configPath).projects;
|
|
456
|
+
if (!projects || typeof projects !== "object" || Array.isArray(projects))
|
|
457
|
+
return [];
|
|
458
|
+
return Object.entries(projects)
|
|
459
|
+
.filter(([, value]) => {
|
|
460
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
461
|
+
return false;
|
|
462
|
+
return Boolean(echoMemEntryFromServers(value.mcpServers));
|
|
463
|
+
})
|
|
464
|
+
.map(([projectPath]) => projectPath)
|
|
465
|
+
.sort();
|
|
466
|
+
}
|
|
467
|
+
export function writeClaudeCodeConfig(entry, options = {}) {
|
|
468
|
+
// EchoMem belongs at user scope so every Claude Code project resolves the same durable runtime.
|
|
469
|
+
// Older CLI versions wrote local/project entries, which take precedence over user scope and can
|
|
470
|
+
// keep launching a deleted npm cache or stale runtime. Migrate those only after user scope is safe.
|
|
471
|
+
const configPath = options.configPath ?? home(".claude.json");
|
|
472
|
+
const emptyResult = () => ({
|
|
473
|
+
state: "unavailable",
|
|
474
|
+
removedLocalProjects: [],
|
|
475
|
+
skippedLocalProjects: [],
|
|
476
|
+
failedLocalProjects: [],
|
|
477
|
+
restoredPreviousUserEntry: false,
|
|
478
|
+
});
|
|
479
|
+
const runClaude = (args, cwd) => {
|
|
424
480
|
try {
|
|
425
|
-
execFileSync("claude",
|
|
481
|
+
execFileSync("claude", args, {
|
|
482
|
+
cwd,
|
|
426
483
|
encoding: "utf8",
|
|
427
484
|
stdio: ["ignore", "pipe", "pipe"],
|
|
428
485
|
timeout: 10000,
|
|
@@ -433,39 +490,61 @@ export function writeClaudeCodeConfig(entry) {
|
|
|
433
490
|
return false;
|
|
434
491
|
}
|
|
435
492
|
};
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
return "unavailable";
|
|
453
|
-
}
|
|
454
|
-
// Claude Code's CLI will not replace a same-name server. Once the replacement entry is fully
|
|
455
|
-
// constructed, remove only EchoMem and immediately re-add it; sibling MCP servers remain.
|
|
456
|
-
try {
|
|
457
|
-
if (!removeFromScope("user"))
|
|
458
|
-
return "unavailable";
|
|
459
|
-
execFileSync("claude", addArguments, {
|
|
460
|
-
encoding: "utf8",
|
|
461
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
462
|
-
timeout: 10000,
|
|
463
|
-
});
|
|
464
|
-
return "wrote";
|
|
493
|
+
const addUser = (value) => runClaude([
|
|
494
|
+
"mcp", "add-json", "-s", "user", "echomem", JSON.stringify(value),
|
|
495
|
+
]);
|
|
496
|
+
const removeUser = () => runClaude(["mcp", "remove", "echomem", "-s", "user"]);
|
|
497
|
+
const before = readClaudeCodeConfigFile(configPath);
|
|
498
|
+
const previousUserEntry = echoMemEntryFromServers(before.mcpServers);
|
|
499
|
+
let restoredPreviousUserEntry = false;
|
|
500
|
+
// Avoid interrupting active/new sessions when the correct global entry is already installed.
|
|
501
|
+
if (!claudeEntriesMatch(previousUserEntry, entry)) {
|
|
502
|
+
if (previousUserEntry && !removeUser())
|
|
503
|
+
return emptyResult();
|
|
504
|
+
if (!addUser(entry)) {
|
|
505
|
+
if (previousUserEntry)
|
|
506
|
+
restoredPreviousUserEntry = addUser(previousUserEntry);
|
|
507
|
+
return { ...emptyResult(), restoredPreviousUserEntry };
|
|
508
|
+
}
|
|
465
509
|
}
|
|
466
|
-
|
|
467
|
-
|
|
510
|
+
const installedUserEntry = echoMemEntryFromServers(readClaudeCodeConfigFile(configPath).mcpServers);
|
|
511
|
+
if (!claudeEntriesMatch(installedUserEntry, entry)) {
|
|
512
|
+
return { ...emptyResult(), restoredPreviousUserEntry };
|
|
513
|
+
}
|
|
514
|
+
const removedLocalProjects = [];
|
|
515
|
+
const skippedLocalProjects = [];
|
|
516
|
+
const failedLocalProjects = [];
|
|
517
|
+
for (const projectPath of claudeCodeLocalEchoMemProjects(configPath)) {
|
|
518
|
+
// A deleted directory cannot currently shadow user scope. Do not recreate it or hand-edit
|
|
519
|
+
// ~/.claude.json, which also contains Claude account/session state.
|
|
520
|
+
if (!fs.existsSync(projectPath)) {
|
|
521
|
+
skippedLocalProjects.push(projectPath);
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
let projectCwd = projectPath;
|
|
525
|
+
try {
|
|
526
|
+
projectCwd = fs.realpathSync(projectPath);
|
|
527
|
+
}
|
|
528
|
+
catch {
|
|
529
|
+
/* The existence check above already established the safe fallback path. */
|
|
530
|
+
}
|
|
531
|
+
if (runClaude(["mcp", "remove", "echomem", "-s", "local"], projectCwd)) {
|
|
532
|
+
removedLocalProjects.push(projectPath);
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
failedLocalProjects.push(projectPath);
|
|
536
|
+
}
|
|
468
537
|
}
|
|
538
|
+
const remainingActiveProjects = claudeCodeLocalEchoMemProjects(configPath)
|
|
539
|
+
.filter((projectPath) => fs.existsSync(projectPath));
|
|
540
|
+
const unresolved = [...new Set([...failedLocalProjects, ...remainingActiveProjects])].sort();
|
|
541
|
+
return {
|
|
542
|
+
state: unresolved.length > 0 ? "needs-repair" : "wrote",
|
|
543
|
+
removedLocalProjects,
|
|
544
|
+
skippedLocalProjects,
|
|
545
|
+
failedLocalProjects: unresolved,
|
|
546
|
+
restoredPreviousUserEntry,
|
|
547
|
+
};
|
|
469
548
|
}
|
|
470
549
|
function readJsonClientEntry(configPath) {
|
|
471
550
|
try {
|
|
@@ -2668,6 +2747,7 @@ async function cmdSetup(flags) {
|
|
|
2668
2747
|
const entry = buildServerEntry({ devEntryPath: typeof flags.dev === "string" ? flags.dev : undefined });
|
|
2669
2748
|
const requested = typeof flags.client === "string" ? flags.client : undefined;
|
|
2670
2749
|
const targets = selectSetupTargets(requested, Boolean(flags.all));
|
|
2750
|
+
const configurationFailures = [];
|
|
2671
2751
|
if (targets.length === 0) {
|
|
2672
2752
|
console.log("No client auto-detected. Add this MCP server entry manually:\n");
|
|
2673
2753
|
console.log(JSON.stringify({ echomem: entry }, null, 2));
|
|
@@ -2688,15 +2768,31 @@ async function cmdSetup(flags) {
|
|
|
2688
2768
|
}
|
|
2689
2769
|
else {
|
|
2690
2770
|
const result = c.id === "claude-code" ? writeClaudeCodeConfig(entry) : "unavailable";
|
|
2691
|
-
if (result === "wrote") {
|
|
2771
|
+
if (result !== "unavailable" && result.state === "wrote") {
|
|
2692
2772
|
console.log(`✅ Wrote EchoMem MCP entry to ${c.label} via \`claude mcp add-json\` — start a new Claude Code session to load it.`);
|
|
2773
|
+
if (result.removedLocalProjects.length > 0) {
|
|
2774
|
+
console.log(`✅ Removed ${result.removedLocalProjects.length} stale Claude Code project-local EchoMem ${result.removedLocalProjects.length === 1 ? "entry" : "entries"}.`);
|
|
2775
|
+
}
|
|
2776
|
+
if (result.skippedLocalProjects.length > 0) {
|
|
2777
|
+
console.log(`ℹ️ Ignored ${result.skippedLocalProjects.length} EchoMem local ${result.skippedLocalProjects.length === 1 ? "entry" : "entries"} for deleted project directories; they cannot shadow the user entry.`);
|
|
2778
|
+
}
|
|
2693
2779
|
}
|
|
2694
2780
|
else {
|
|
2695
|
-
|
|
2781
|
+
const failedProjects = result === "unavailable" ? [] : result.failedLocalProjects;
|
|
2782
|
+
configurationFailures.push(failedProjects.length > 0
|
|
2783
|
+
? `${c.label} still has project-local EchoMem overrides in: ${failedProjects.join(", ")}`
|
|
2784
|
+
: `${c.label} user-scoped EchoMem entry could not be verified`);
|
|
2696
2785
|
}
|
|
2697
2786
|
}
|
|
2698
2787
|
}
|
|
2699
2788
|
}
|
|
2789
|
+
if (configurationFailures.length > 0) {
|
|
2790
|
+
throw new Error([
|
|
2791
|
+
"EchoMem MCP configuration is incomplete; onboarding was stopped before login/import.",
|
|
2792
|
+
...configurationFailures.map((failure) => `- ${failure}`),
|
|
2793
|
+
`Retry with: ${MCP_UPDATE_COMMAND} --client claude-code`,
|
|
2794
|
+
].join("\n"));
|
|
2795
|
+
}
|
|
2700
2796
|
if (!flags["no-agents-md"]) {
|
|
2701
2797
|
writeMemoryGuidanceForTargets(targets);
|
|
2702
2798
|
}
|
package/dist/v1-contract.js
CHANGED
|
@@ -165,6 +165,42 @@ const triggerMetadataSchema = {
|
|
|
165
165
|
triggerMessage: z.string().optional(),
|
|
166
166
|
triggerMessageRole: z.string().optional(),
|
|
167
167
|
};
|
|
168
|
+
// Canonical workspace selector for tools that act inside a company workspace.
|
|
169
|
+
// `workspaceId` is the current name; `groupId` is the legacy alias kept working
|
|
170
|
+
// so existing prompts keep functioning. Handlers normalize with
|
|
171
|
+
// normalizeWorkspaceId (workspaceId ?? groupId). Required only when the user
|
|
172
|
+
// belongs to more than one workspace; a single-workspace user may omit it.
|
|
173
|
+
const WORKSPACE_SELECTOR_DESCRIPTION = "Workspace to act in. Required when the user belongs to more than one workspace; omit it when they have a single workspace. If omitted with multiple workspaces, the tool returns the available workspaces so you can ask the user which to use.";
|
|
174
|
+
const workspaceSelectorSchema = {
|
|
175
|
+
workspaceId: z.string().uuid().optional().describe(WORKSPACE_SELECTOR_DESCRIPTION),
|
|
176
|
+
groupId: z.string().uuid().optional().describe("Legacy alias for workspaceId."),
|
|
177
|
+
};
|
|
178
|
+
// The advertised JSON-Schema counterpart of workspaceSelectorSchema. Tool
|
|
179
|
+
// inputSchemas are hand-written, so the selector must be injected into every
|
|
180
|
+
// workspace-scoped tool's properties or clients never learn they can pass it.
|
|
181
|
+
const workspaceSelectorProperties = {
|
|
182
|
+
workspaceId: { type: "string", description: WORKSPACE_SELECTOR_DESCRIPTION },
|
|
183
|
+
groupId: { type: "string", description: "Legacy alias for workspaceId." },
|
|
184
|
+
};
|
|
185
|
+
const WORKSPACE_SCOPED_TOOL_NAMES = new Set([
|
|
186
|
+
canonicalToolNames.others,
|
|
187
|
+
canonicalToolNames.publicMemory,
|
|
188
|
+
canonicalToolNames.groupContext,
|
|
189
|
+
canonicalToolNames.createGroupInvite,
|
|
190
|
+
canonicalToolNames.prepareGroupPublication,
|
|
191
|
+
canonicalToolNames.updateGroupProfile,
|
|
192
|
+
canonicalToolNames.publishToGroup,
|
|
193
|
+
canonicalToolNames.publishBatchToGroup,
|
|
194
|
+
]);
|
|
195
|
+
function injectWorkspaceSelector(specs) {
|
|
196
|
+
for (const spec of specs) {
|
|
197
|
+
if (!WORKSPACE_SCOPED_TOOL_NAMES.has(spec.name))
|
|
198
|
+
continue;
|
|
199
|
+
const existing = spec.inputSchema.properties ?? {};
|
|
200
|
+
spec.inputSchema.properties = { ...workspaceSelectorProperties, ...existing };
|
|
201
|
+
}
|
|
202
|
+
return specs;
|
|
203
|
+
}
|
|
168
204
|
export const searchMemoriesSchema = z.object({
|
|
169
205
|
...triggerMetadataSchema,
|
|
170
206
|
query: z.string().trim().min(1).optional(),
|
|
@@ -235,6 +271,8 @@ export const sendFriendRequestSchema = z.object({
|
|
|
235
271
|
});
|
|
236
272
|
export const othersSchema = z.object({
|
|
237
273
|
...triggerMetadataSchema,
|
|
274
|
+
...workspaceSelectorSchema,
|
|
275
|
+
scope: z.enum(["group", "friends"]).optional(),
|
|
238
276
|
query: z.string().trim().optional().default(""),
|
|
239
277
|
limit: z.number().int().min(1).max(50).optional().default(10),
|
|
240
278
|
target: z.string().optional(),
|
|
@@ -249,6 +287,7 @@ export const othersSchema = z.object({
|
|
|
249
287
|
});
|
|
250
288
|
export const publicMemorySchema = z.object({
|
|
251
289
|
...triggerMetadataSchema,
|
|
290
|
+
...workspaceSelectorSchema,
|
|
252
291
|
memoryId: z.string().min(1),
|
|
253
292
|
});
|
|
254
293
|
export const recordMemoryCitationsSchema = z.object({
|
|
@@ -258,6 +297,7 @@ export const recordMemoryCitationsSchema = z.object({
|
|
|
258
297
|
});
|
|
259
298
|
export const groupContextSchema = z.object({
|
|
260
299
|
...triggerMetadataSchema,
|
|
300
|
+
...workspaceSelectorSchema,
|
|
261
301
|
});
|
|
262
302
|
export const getGroupSessionSharingSchema = z.object({
|
|
263
303
|
...triggerMetadataSchema,
|
|
@@ -282,6 +322,7 @@ export const createGroupSchema = z.object({
|
|
|
282
322
|
});
|
|
283
323
|
export const createGroupInviteSchema = z.object({
|
|
284
324
|
...triggerMetadataSchema,
|
|
325
|
+
...workspaceSelectorSchema,
|
|
285
326
|
expiresInDays: z.number().int().min(1).max(30).optional(),
|
|
286
327
|
maxUses: z.number().int().min(1).max(100).optional(),
|
|
287
328
|
});
|
|
@@ -294,6 +335,7 @@ export const joinGroupSchema = z.object({
|
|
|
294
335
|
});
|
|
295
336
|
export const prepareGroupPublicationSchema = z.object({
|
|
296
337
|
...triggerMetadataSchema,
|
|
338
|
+
...workspaceSelectorSchema,
|
|
297
339
|
scope: z.enum(["bootstrap", "since_last_scan", "context", "time_range"]).default("since_last_scan"),
|
|
298
340
|
contextId: z.string().min(1).optional(),
|
|
299
341
|
startAt: z.string().optional(),
|
|
@@ -310,6 +352,7 @@ export const flagPublicationAttentionSchema = z.object({
|
|
|
310
352
|
});
|
|
311
353
|
export const updateGroupProfileSchema = z.object({
|
|
312
354
|
...triggerMetadataSchema,
|
|
355
|
+
...workspaceSelectorSchema,
|
|
313
356
|
displayName: z.string().min(1).max(120).optional(),
|
|
314
357
|
title: z.string().min(1).max(160),
|
|
315
358
|
responsibilitySummary: z.string().min(1).max(1000),
|
|
@@ -325,11 +368,13 @@ export const completeGroupPublicationSchema = z.object({
|
|
|
325
368
|
});
|
|
326
369
|
export const publishToGroupSchema = z.object({
|
|
327
370
|
...triggerMetadataSchema,
|
|
371
|
+
...workspaceSelectorSchema,
|
|
328
372
|
memoryId: z.string().min(1),
|
|
329
373
|
acknowledgedFlaggedMemoryIds: z.array(z.string().min(1)).max(1).optional(),
|
|
330
374
|
});
|
|
331
375
|
export const publishBatchToGroupSchema = z.object({
|
|
332
376
|
...triggerMetadataSchema,
|
|
377
|
+
...workspaceSelectorSchema,
|
|
333
378
|
memoryIds: z.array(z.string().min(1)).min(1).max(50),
|
|
334
379
|
contextId: z.string().min(1).optional(),
|
|
335
380
|
selectionReason: z.string().max(500).optional(),
|
|
@@ -573,10 +618,15 @@ export function listToolSpecs(opts = {}) {
|
|
|
573
618
|
{
|
|
574
619
|
name: canonicalToolNames.others,
|
|
575
620
|
title: "Search teammates' and friends' memories",
|
|
576
|
-
description: `PEER-MEMORY SEARCH for public memories owned by accepted friends or company-group members—not the user's own memories. Pass query for a topic
|
|
621
|
+
description: `PEER-MEMORY SEARCH for public memories owned by accepted friends or company-group members—not the user's own memories. Pass query for a topic. Set scope to pick the audience — 'group' (only workspace teammates) or 'friends' (only friends) — or target for one person; scope defaults to teammates. Do not use this tool for the user's private memories or the EchoMem user directory. EchoMem identifies the caller from the EchoMem credential and has already excluded only that authenticated user's own memories. Present every returned owner; never filter again using a Claude account, host profile, git identity, or inference. For onboarding and division-of-work questions, call get_group_context first. Returned memories are recorded in memory_views for the owners. ${memoryCitationInstruction}${groupMapSection}`,
|
|
577
622
|
inputSchema: {
|
|
578
623
|
type: "object",
|
|
579
624
|
properties: {
|
|
625
|
+
scope: {
|
|
626
|
+
type: "string",
|
|
627
|
+
enum: ["group", "friends"],
|
|
628
|
+
description: "Audience to search. 'group' = only the named workspace's teammates (you'll be asked which if the user is in several and names none). 'friends' = only accepted friends. For one specific person, omit scope and pass target instead. Omitting scope defaults to the group/teammates audience.",
|
|
629
|
+
},
|
|
580
630
|
query: {
|
|
581
631
|
type: "string",
|
|
582
632
|
description: "Optional peer-memory topic. Omit only for an intentional broad browse; never send this field as conversation.",
|
|
@@ -584,7 +634,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
584
634
|
limit: { type: "integer", minimum: 1, maximum: 50, default: 10 },
|
|
585
635
|
target: {
|
|
586
636
|
type: "string",
|
|
587
|
-
description: "Accessible friend or group-member user id or exact display name. Prefer this for @Name asks.",
|
|
637
|
+
description: "Accessible friend or group-member user id or exact display name. Prefer this for @Name asks or a single specific person.",
|
|
588
638
|
},
|
|
589
639
|
ownerUserId: {
|
|
590
640
|
type: "string",
|
|
@@ -1021,5 +1071,5 @@ export function listToolSpecs(opts = {}) {
|
|
|
1021
1071
|
},
|
|
1022
1072
|
},
|
|
1023
1073
|
];
|
|
1024
|
-
return tools.map(decorateLocalToolSpec);
|
|
1074
|
+
return injectWorkspaceSelector(tools).map(decorateLocalToolSpec);
|
|
1025
1075
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@echomem/mcp",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.42",
|
|
4
4
|
"description": "EchoMem MCP bridge: cloud-first memory tools and the Agent Doctor workspace forensics report (cost ledger + 3D repo city)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"test:ui": "npm run build && node test/setup-ui.test.mjs",
|
|
34
34
|
"test:onboarding-resilience": "npm run build && node test/onboarding-resilience.test.mjs",
|
|
35
35
|
"test:billing-ui": "npm run build && node test/setup-ui.test.mjs billing",
|
|
36
|
-
"test": "npm run build && node test/source-session.test.mjs && node test/source-session-hook.test.mjs && node test/local-data-paths.test.mjs && node test/crypto.test.mjs && node test/integration.test.mjs && node test/onboarding-resilience.test.mjs && node test/local-auth.test.mjs && node test/retrieval-only.test.mjs && node test/no-restart.test.mjs && node test/report.test.mjs && node test/forensics.test.mjs && node test/canonical-golden.test.mjs && node test/tools.test.mjs && node test/group-map.test.mjs && node test/update-check.test.mjs && node test/headless-runtime.test.mjs && node test/claude-code-config.test.mjs && node test/delete.test.mjs && node test/low-touch-tools.test.mjs && node test/migrate.test.mjs && node test/restart-recovery.test.mjs && node test/save-checkpoint-hook.test.mjs",
|
|
36
|
+
"test": "npm run build && node test/source-session.test.mjs && node test/source-session-hook.test.mjs && node test/local-data-paths.test.mjs && node test/crypto.test.mjs && node test/integration.test.mjs && node test/onboarding-resilience.test.mjs && node test/local-auth.test.mjs && node test/retrieval-only.test.mjs && node test/no-restart.test.mjs && node test/report.test.mjs && node test/forensics.test.mjs && node test/canonical-golden.test.mjs && node test/tools.test.mjs && node test/workspace-selector.test.mjs && node test/group-map.test.mjs && node test/update-check.test.mjs && node test/headless-runtime.test.mjs && node test/claude-code-config.test.mjs && node test/delete.test.mjs && node test/low-touch-tools.test.mjs && node test/migrate.test.mjs && node test/restart-recovery.test.mjs && node test/save-checkpoint-hook.test.mjs",
|
|
37
37
|
"prepack": "npm run build && node scripts/bundle-city.mjs"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|