@echomem/mcp 1.4.40 → 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/README.md +21 -0
- package/dist/hud/cli.js +25 -4
- package/dist/hud/hooks.js +58 -0
- package/dist/index.js +382 -247
- package/dist/package-metadata.js +2 -1
- package/dist/setup.js +146 -47
- package/dist/source-session-hook.js +103 -0
- package/dist/source-session.js +261 -0
- package/dist/v1-contract.js +75 -3
- package/package.json +2 -2
- package/templates/echomem-recall.md +3 -0
- package/dist/forensics-10-problems.js +0 -633
package/dist/v1-contract.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { MCP_DESKTOP_MANAGED, MCP_VAULT_UNLOCK_INSTRUCTION, MEMORY_CITATION_INSTRUCTION, SAVED_MEMORY_RECEIPT_INSTRUCTION, withMcpVersion, } from "./package-metadata.js";
|
|
3
3
|
export const canonicalToolNames = {
|
|
4
|
+
bindSourceSession: "bind_source_session",
|
|
4
5
|
search: "search_memories",
|
|
5
6
|
save: "save_conversation",
|
|
6
7
|
timeRange: "get_memories_by_time_range",
|
|
@@ -59,6 +60,7 @@ const READ_ONLY_TOOL_NAMES = new Set([
|
|
|
59
60
|
canonicalToolNames.recompose,
|
|
60
61
|
]);
|
|
61
62
|
const IDEMPOTENT_WRITE_TOOL_NAMES = new Set([
|
|
63
|
+
canonicalToolNames.bindSourceSession,
|
|
62
64
|
canonicalToolNames.recordCitations,
|
|
63
65
|
canonicalToolNames.setGroupSessionSharing,
|
|
64
66
|
canonicalToolNames.updateGroupProfile,
|
|
@@ -77,6 +79,7 @@ const REQUIRES_USER_INTERACTION_TOOL_NAMES = new Set([
|
|
|
77
79
|
canonicalToolNames.delete,
|
|
78
80
|
]);
|
|
79
81
|
const ALWAYS_LOAD_TOOL_NAMES = new Set([
|
|
82
|
+
canonicalToolNames.bindSourceSession,
|
|
80
83
|
canonicalToolNames.search,
|
|
81
84
|
canonicalToolNames.save,
|
|
82
85
|
]);
|
|
@@ -162,6 +165,42 @@ const triggerMetadataSchema = {
|
|
|
162
165
|
triggerMessage: z.string().optional(),
|
|
163
166
|
triggerMessageRole: z.string().optional(),
|
|
164
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
|
+
}
|
|
165
204
|
export const searchMemoriesSchema = z.object({
|
|
166
205
|
...triggerMetadataSchema,
|
|
167
206
|
query: z.string().trim().min(1).optional(),
|
|
@@ -187,6 +226,9 @@ export const saveConversationSchema = z.object({
|
|
|
187
226
|
}))
|
|
188
227
|
.optional(),
|
|
189
228
|
});
|
|
229
|
+
export const bindSourceSessionSchema = z.object({
|
|
230
|
+
bindingToken: z.string().uuid(),
|
|
231
|
+
});
|
|
190
232
|
const dateBoundarySchema = z.string().trim().min(1).refine((value) => Number.isFinite(Date.parse(value)), "Expected an ISO-8601 date or date-time string");
|
|
191
233
|
export const timeRangeSchema = z.object({
|
|
192
234
|
...triggerMetadataSchema,
|
|
@@ -229,6 +271,8 @@ export const sendFriendRequestSchema = z.object({
|
|
|
229
271
|
});
|
|
230
272
|
export const othersSchema = z.object({
|
|
231
273
|
...triggerMetadataSchema,
|
|
274
|
+
...workspaceSelectorSchema,
|
|
275
|
+
scope: z.enum(["group", "friends"]).optional(),
|
|
232
276
|
query: z.string().trim().optional().default(""),
|
|
233
277
|
limit: z.number().int().min(1).max(50).optional().default(10),
|
|
234
278
|
target: z.string().optional(),
|
|
@@ -243,6 +287,7 @@ export const othersSchema = z.object({
|
|
|
243
287
|
});
|
|
244
288
|
export const publicMemorySchema = z.object({
|
|
245
289
|
...triggerMetadataSchema,
|
|
290
|
+
...workspaceSelectorSchema,
|
|
246
291
|
memoryId: z.string().min(1),
|
|
247
292
|
});
|
|
248
293
|
export const recordMemoryCitationsSchema = z.object({
|
|
@@ -252,6 +297,7 @@ export const recordMemoryCitationsSchema = z.object({
|
|
|
252
297
|
});
|
|
253
298
|
export const groupContextSchema = z.object({
|
|
254
299
|
...triggerMetadataSchema,
|
|
300
|
+
...workspaceSelectorSchema,
|
|
255
301
|
});
|
|
256
302
|
export const getGroupSessionSharingSchema = z.object({
|
|
257
303
|
...triggerMetadataSchema,
|
|
@@ -276,6 +322,7 @@ export const createGroupSchema = z.object({
|
|
|
276
322
|
});
|
|
277
323
|
export const createGroupInviteSchema = z.object({
|
|
278
324
|
...triggerMetadataSchema,
|
|
325
|
+
...workspaceSelectorSchema,
|
|
279
326
|
expiresInDays: z.number().int().min(1).max(30).optional(),
|
|
280
327
|
maxUses: z.number().int().min(1).max(100).optional(),
|
|
281
328
|
});
|
|
@@ -288,6 +335,7 @@ export const joinGroupSchema = z.object({
|
|
|
288
335
|
});
|
|
289
336
|
export const prepareGroupPublicationSchema = z.object({
|
|
290
337
|
...triggerMetadataSchema,
|
|
338
|
+
...workspaceSelectorSchema,
|
|
291
339
|
scope: z.enum(["bootstrap", "since_last_scan", "context", "time_range"]).default("since_last_scan"),
|
|
292
340
|
contextId: z.string().min(1).optional(),
|
|
293
341
|
startAt: z.string().optional(),
|
|
@@ -304,6 +352,7 @@ export const flagPublicationAttentionSchema = z.object({
|
|
|
304
352
|
});
|
|
305
353
|
export const updateGroupProfileSchema = z.object({
|
|
306
354
|
...triggerMetadataSchema,
|
|
355
|
+
...workspaceSelectorSchema,
|
|
307
356
|
displayName: z.string().min(1).max(120).optional(),
|
|
308
357
|
title: z.string().min(1).max(160),
|
|
309
358
|
responsibilitySummary: z.string().min(1).max(1000),
|
|
@@ -319,11 +368,13 @@ export const completeGroupPublicationSchema = z.object({
|
|
|
319
368
|
});
|
|
320
369
|
export const publishToGroupSchema = z.object({
|
|
321
370
|
...triggerMetadataSchema,
|
|
371
|
+
...workspaceSelectorSchema,
|
|
322
372
|
memoryId: z.string().min(1),
|
|
323
373
|
acknowledgedFlaggedMemoryIds: z.array(z.string().min(1)).max(1).optional(),
|
|
324
374
|
});
|
|
325
375
|
export const publishBatchToGroupSchema = z.object({
|
|
326
376
|
...triggerMetadataSchema,
|
|
377
|
+
...workspaceSelectorSchema,
|
|
327
378
|
memoryIds: z.array(z.string().min(1)).min(1).max(50),
|
|
328
379
|
contextId: z.string().min(1).optional(),
|
|
329
380
|
selectionReason: z.string().max(500).optional(),
|
|
@@ -403,6 +454,22 @@ export function listToolSpecs(opts = {}) {
|
|
|
403
454
|
required: ["startDate", "endDate"],
|
|
404
455
|
};
|
|
405
456
|
const tools = [
|
|
457
|
+
{
|
|
458
|
+
name: canonicalToolNames.bindSourceSession,
|
|
459
|
+
title: "Bind this agent conversation to its source session",
|
|
460
|
+
description: withMcpVersion("Compatibility fallback for clients where EchoMem's automatic SessionStart binding is unavailable or reports that the session is unbound. Generate a fresh random UUID for bindingToken and pass only that token. EchoMem finds this exact tool call in the local host JSONL, derives the real provider session ID itself, and idempotently binds the authenticated backend context. Never reuse a bindingToken, guess a session ID, or copy a token between conversations. After success, later saves, recalls, citations, sharing decisions, and MCP activity from this bridge are attached to the returned context automatically. Do not call it routinely when automatic binding is active."),
|
|
461
|
+
inputSchema: {
|
|
462
|
+
type: "object",
|
|
463
|
+
properties: {
|
|
464
|
+
bindingToken: {
|
|
465
|
+
type: "string",
|
|
466
|
+
format: "uuid",
|
|
467
|
+
description: "A fresh random UUID generated only for this invocation. The local bridge verifies it inside the current host JSONL.",
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
required: ["bindingToken"],
|
|
471
|
+
},
|
|
472
|
+
},
|
|
406
473
|
{
|
|
407
474
|
name: canonicalToolNames.search,
|
|
408
475
|
title: "Search your memories by topic",
|
|
@@ -551,10 +618,15 @@ export function listToolSpecs(opts = {}) {
|
|
|
551
618
|
{
|
|
552
619
|
name: canonicalToolNames.others,
|
|
553
620
|
title: "Search teammates' and friends' memories",
|
|
554
|
-
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}`,
|
|
555
622
|
inputSchema: {
|
|
556
623
|
type: "object",
|
|
557
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
|
+
},
|
|
558
630
|
query: {
|
|
559
631
|
type: "string",
|
|
560
632
|
description: "Optional peer-memory topic. Omit only for an intentional broad browse; never send this field as conversation.",
|
|
@@ -562,7 +634,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
562
634
|
limit: { type: "integer", minimum: 1, maximum: 50, default: 10 },
|
|
563
635
|
target: {
|
|
564
636
|
type: "string",
|
|
565
|
-
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.",
|
|
566
638
|
},
|
|
567
639
|
ownerUserId: {
|
|
568
640
|
type: "string",
|
|
@@ -999,5 +1071,5 @@ export function listToolSpecs(opts = {}) {
|
|
|
999
1071
|
},
|
|
1000
1072
|
},
|
|
1001
1073
|
];
|
|
1002
|
-
return tools.map(decorateLocalToolSpec);
|
|
1074
|
+
return injectWorkspaceSelector(tools).map(decorateLocalToolSpec);
|
|
1003
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/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": {
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
This project has EchoMem connected — the user's long-term memory across all their AI tools.
|
|
8
8
|
|
|
9
|
+
- EchoMem's local SessionStart hook automatically binds new Codex and Claude Code conversations to
|
|
10
|
+
their real provider session. Call `bind_source_session` with a fresh random UUID only as a
|
|
11
|
+
compatibility fallback when automatic binding is unavailable or reports that the session is unbound.
|
|
9
12
|
- **At the start of any non-trivial task**, call `search_memories` first to recall the user's
|
|
10
13
|
prior decisions, preferences, and constraints before planning or writing code. Don't re-derive
|
|
11
14
|
things they've already decided.
|