@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.
@@ -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; omit it only when intentionally browsing peer memories, and optionally use target to scope a person. 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}`,
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.40",
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.