@echomem/mcp 1.4.51 → 1.4.52

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
@@ -169,6 +169,17 @@ function formatReconnectRequiredResult(error) {
169
169
  "No editor restart is needed. Keep all credentials out of chat.",
170
170
  ].join("\n");
171
171
  }
172
+ function formatWorkspaceTicketNotFoundResult(error) {
173
+ if (!axios.isAxiosError(error) || error.response?.status !== 404)
174
+ return null;
175
+ if (errorCodeFrom(error.response.data) !== "WORKSPACE_TICKET_NOT_FOUND")
176
+ return null;
177
+ return [
178
+ "🎫 This Echo workspace ticket no longer exists or is unavailable.",
179
+ "Do not retry this ticket ID.",
180
+ "Ask the user whether to continue without a ticket or attach this session to another ticket.",
181
+ ].join("\n");
182
+ }
172
183
  /** Map a thrown error to the telemetry error_kind taxonomy. */
173
184
  function classifyError(error) {
174
185
  if (error instanceof NoTokenError)
@@ -339,6 +350,9 @@ function detectMcpHostFromEnv() {
339
350
  return "cursor";
340
351
  if (process.env.WINDSURF_WORKSPACE_ID || process.env.WINDSURF_USER_ID)
341
352
  return "windsurf";
353
+ // Cowork embeds a Claude Code process, but this outer host ID is the distinguishing signal.
354
+ if (process.env.CLAUDE_CODE_HOST_SESSION_ID)
355
+ return "cowork";
342
356
  if (process.env.CLAUDE_CODE || process.env.CLAUDECODE || process.env.ANTHROPIC_CLAUDE_CODE) {
343
357
  return "claude_code";
344
358
  }
@@ -1411,8 +1425,10 @@ class EchoMemMCPServer {
1411
1425
  });
1412
1426
  }
1413
1427
  getMcpClientAnalytics() {
1414
- const hostPlatform = normalizeMcpHostPlatform(this.mcpClientName)
1415
- ?? detectMcpHostFromEnv()
1428
+ const environmentPlatform = detectMcpHostFromEnv();
1429
+ const hostPlatform = (environmentPlatform === "cowork" ? environmentPlatform : undefined)
1430
+ ?? normalizeMcpHostPlatform(this.mcpClientName)
1431
+ ?? environmentPlatform
1416
1432
  ?? "unknown";
1417
1433
  return {
1418
1434
  mcp_client_name: this.mcpClientName,
@@ -1735,6 +1751,13 @@ class EchoMemMCPServer {
1735
1751
  isError: true,
1736
1752
  };
1737
1753
  }
1754
+ const missingWorkspaceTicket = formatWorkspaceTicketNotFoundResult(error);
1755
+ if (missingWorkspaceTicket) {
1756
+ return {
1757
+ content: [{ type: "text", text: missingWorkspaceTicket }],
1758
+ isError: true,
1759
+ };
1760
+ }
1738
1761
  const upgradeRequired = formatUpgradeRequiredResult(error);
1739
1762
  if (upgradeRequired) {
1740
1763
  return {
@@ -2028,6 +2051,7 @@ Details: ${m.details || "N/A"}`)
2028
2051
  }
2029
2052
  const ticket = isRecord(result.ticket) ? result.ticket : {};
2030
2053
  const workspaceId = readString(ticket, "workspaceId") ?? parsed.workspaceId;
2054
+ const sourceSession = this.client.getBoundSourceSession();
2031
2055
  return {
2032
2056
  content: [{
2033
2057
  type: "text",
@@ -2037,6 +2061,8 @@ Details: ${m.details || "N/A"}`)
2037
2061
  : "This verified source session was already linked to the Echo workspace ticket.",
2038
2062
  `Ticket: ${parsed.ticketId}`,
2039
2063
  workspaceId ? `Workspace: ${workspaceId}` : "",
2064
+ sourceSession ? `Source session: ${sourceSession.canonicalKey}` : "",
2065
+ sourceSession ? `Context: ${sourceSession.contextId}` : "",
2040
2066
  "Retries are safe and do not create duplicate links or history events.",
2041
2067
  ].filter(Boolean).join("\n"),
2042
2068
  }],
@@ -361,14 +361,23 @@ export function resolveSourceSessionRequestContext(metadata, options = {}) {
361
361
  agentDepth: lineage.agentDepth,
362
362
  };
363
363
  }
364
+ const isClaudeHost = host.includes("claude") || host.includes("cowork");
365
+ const coworkCandidate = env.CLAUDE_CODE_HOST_SESSION_ID?.trim() ?? "";
366
+ // Cowork embeds Claude Code, so its MCP handshake may still identify the nested client as
367
+ // "Claude Code" and expose both IDs. The outer host session is the durable Cowork conversation;
368
+ // it must win over the nested CLI session whenever Anthropic supplies both host-owned values.
369
+ if (isClaudeHost && coworkCandidate) {
370
+ return {
371
+ sourceSession: verifiedSourceSession("cowork", coworkCandidate, SOURCE_SESSION_MCP_METADATA_EVIDENCE),
372
+ isSubagent: false,
373
+ lineageStatus: "not_applicable",
374
+ };
375
+ }
364
376
  if (host.includes("claude_desktop") || host === "claude" || host.includes("cowork")) {
365
- const candidate = env.CLAUDE_CODE_HOST_SESSION_ID?.trim() ?? "";
366
377
  return {
367
- sourceSession: candidate
368
- ? verifiedSourceSession("cowork", candidate, SOURCE_SESSION_MCP_METADATA_EVIDENCE)
369
- : null,
378
+ sourceSession: null,
370
379
  isSubagent: false,
371
- lineageStatus: candidate ? "not_applicable" : "unbound",
380
+ lineageStatus: "unbound",
372
381
  };
373
382
  }
374
383
  if (host.includes("claude_code")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@echomem/mcp",
3
- "version": "1.4.51",
3
+ "version": "1.4.52",
4
4
  "description": "EchoMem MCP bridge for cross-agent memory, local history import, and recall",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -33,7 +33,7 @@
33
33
  "test:billing-ui": "npm run build && node test/setup-ui.test.mjs billing",
34
34
  "test:config-safety": "npm run build && node test/config-safety.test.mjs",
35
35
  "test:mcp-control": "npm run build && node test/mcp-control.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/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/config-safety.test.mjs && node test/mcp-control.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 && node test/durable-entry.test.mjs && node test/durable-reexec.test.mjs && node test/doctor.test.mjs && node test/environment-matrix.test.mjs",
36
+ "test": "npm run build && node test/source-session.test.mjs && node test/cowork-ticket-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/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/config-safety.test.mjs && node test/mcp-control.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 && node test/durable-entry.test.mjs && node test/durable-reexec.test.mjs && node test/doctor.test.mjs && node test/environment-matrix.test.mjs",
37
37
  "prepack": "npm run build"
38
38
  },
39
39
  "dependencies": {