@leadbay/mcp 0.12.1 → 0.14.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog — @leadbay/mcp
2
2
 
3
+ ## 0.13.0 — 2026-05-21
4
+
5
+ - **Agent memory v1**: added always-on recall/capture/review tools backed by
6
+ local append-only JSONL at `~/.leadbay/memory/{account_id}/`.
7
+ - Leads-touching tool responses now attach `_meta.agent_memory.summary` with
8
+ the consolidated top signals unless `LEADBAY_AGENT_MEMORY=off` is set.
9
+ - Server instructions, prompt descriptions, and workflow prompts now teach the
10
+ memory protocol, including capture of new taste signals and review-gated
11
+ retractions.
12
+ - Added `agent-memory://summary` resource and PostHog events for memory
13
+ capture/recall/prune.
14
+ - **Pin bumps**: every active `@leadbay/mcp@0.12` install/runtime reference in
15
+ docs, generated client config, DXT, MCP Registry metadata, and Claude plugin
16
+ metadata is now `@0.13`.
17
+
3
18
  ## 0.12.1 — 2026-05-21
4
19
 
5
20
  MCPB hotfix for Claude Desktop.
package/README.md CHANGED
@@ -21,10 +21,31 @@ A Model Context Protocol server that lets Claude Desktop, Cursor, Claude Code, a
21
21
 
22
22
  > **0.3.0 behavior change** — composite write tools (`refine_prompt`, `report_outreach`, `adjust_audience`, `bulk_qualify_leads`, `enrich_titles`, `answer_clarification`, `import_leads`) are **ON by default**. Set `LEADBAY_MCP_WRITE=0` (or `--no-write` on `install`) to restore the previous read-only behavior. `leadbay-mcp install` now also registers Claude Code at `--scope user` so Leadbay is visible from any project. See [MIGRATION.md](./MIGRATION.md).
23
23
 
24
+ ## Agent memory
25
+
26
+ Leadbay MCP keeps a local, per-account agent memory at
27
+ `~/.leadbay/memory/{account_id}/`. It stores append-only JSONL learnings
28
+ about user taste signals such as preferred sectors, regions, deal size,
29
+ communication style, and qualification rules.
30
+
31
+ The memory tools are always exposed:
32
+
33
+ - `leadbay_agent_memory_recall` reads the consolidated top signals.
34
+ - `leadbay_agent_memory_capture` appends a new learning after the user reveals
35
+ a material preference.
36
+ - `leadbay_agent_memory_review` lists entries and gates retractions or org
37
+ promotion through user confirmation.
38
+
39
+ The main leads-touching tools (`leadbay_account_status`,
40
+ `leadbay_pull_leads`, `leadbay_pull_followups`,
41
+ `leadbay_prepare_outreach`, `leadbay_research_lead_by_id`) also attach
42
+ `_meta.agent_memory.summary` automatically. Set `LEADBAY_AGENT_MEMORY=off`
43
+ to suppress this ambient metadata.
44
+
24
45
  ## 1. Install (one command)
25
46
 
26
47
  ```bash
27
- npx -y @leadbay/mcp@0.12 install --email you@yourcompany.com --region us
48
+ npx -y @leadbay/mcp@0.13 install --email you@yourcompany.com --region us
28
49
  # (you'll be prompted for your password — it's not echoed)
29
50
  ```
30
51
 
@@ -67,14 +88,14 @@ Claude Desktop 2026 ships the DXT (Desktop Extension) system — the legacy `cla
67
88
 
68
89
  If you installed Node from the official [nodejs.org](https://nodejs.org) `.pkg`, `/usr/local/lib/node_modules` is root-owned. Any of these works:
69
90
 
70
- - **Use `npx` (recommended, no global install):** all examples above use `npx -y @leadbay/mcp@0.12 ...` — no global install needed.
91
+ - **Use `npx` (recommended, no global install):** all examples above use `npx -y @leadbay/mcp@0.13 ...` — no global install needed.
71
92
  - **`sudo npm install -g @leadbay/mcp`** (enter your macOS password).
72
93
  - **Use a Node version manager** — [nvm](https://github.com/nvm-sh/nvm), [volta](https://volta.sh), [fnm](https://github.com/Schniz/fnm). They install Node under your home directory, so `npm install -g` works without sudo.
73
94
 
74
95
  ### If you'd rather mint a token without auto-install
75
96
 
76
97
  ```bash
77
- npx -y @leadbay/mcp@0.12 login \
98
+ npx -y @leadbay/mcp@0.13 login \
78
99
  --email you@yourcompany.com \
79
100
  --region us
80
101
  ```
@@ -92,7 +113,7 @@ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) o
92
113
  "mcpServers": {
93
114
  "leadbay": {
94
115
  "command": "npx",
95
- "args": ["-y", "@leadbay/mcp@0.12"],
116
+ "args": ["-y", "@leadbay/mcp@0.13"],
96
117
  "env": {
97
118
  "LEADBAY_TOKEN": "<paste-token-from-step-1>",
98
119
  "LEADBAY_REGION": "us"
@@ -113,7 +134,7 @@ In Cursor settings, add the MCP server:
113
134
  "mcp.servers": {
114
135
  "leadbay": {
115
136
  "command": "npx",
116
- "args": ["-y", "@leadbay/mcp@0.12"],
137
+ "args": ["-y", "@leadbay/mcp@0.13"],
117
138
  "env": { "LEADBAY_TOKEN": "<paste-token>", "LEADBAY_REGION": "us" }
118
139
  }
119
140
  }
@@ -126,7 +147,7 @@ In Cursor settings, add the MCP server:
126
147
  claude mcp add leadbay --scope user \
127
148
  --env LEADBAY_TOKEN=<paste-token> \
128
149
  --env LEADBAY_REGION=us \
129
- -- npx -y @leadbay/mcp@0.12
150
+ -- npx -y @leadbay/mcp@0.13
130
151
  ```
131
152
 
132
153
  > **`--scope user`** registers Leadbay globally for your account (visible from any project). Without it, `claude mcp add` defaults to project-local scope and the server only appears in conversations opened from the directory where you ran the command.
@@ -138,7 +159,7 @@ claude mcp add leadbay --scope user \
138
159
  Before starting Claude, run:
139
160
 
140
161
  ```bash
141
- LEADBAY_TOKEN=<paste-token> npx -y @leadbay/mcp@0.12 doctor
162
+ LEADBAY_TOKEN=<paste-token> npx -y @leadbay/mcp@0.13 doctor
142
163
  ```
143
164
 
144
165
  Expected output:
@@ -366,14 +387,14 @@ The user's literal text replaces `verification.ref` in the outreach record, and
366
387
  | `No enrichment credits remaining` | Out of quota | Contact Leadbay support to extend quota |
367
388
  | Claude Desktop "loading forever" on first use | `npx` cold-start fetching the package | First run takes ~10s. Prefer `npm install -g @leadbay/mcp` for faster startup. |
368
389
  | Claude Desktop doesn't show Leadbay tools | Server crashed at startup | Check `~/Library/Logs/Claude/mcp*.log` (macOS) or `%APPDATA%\Claude\logs\mcp*.log` (Windows). |
369
- | Claude Code can't find Leadbay in a new conversation | MCP server installed at project scope (default before 0.3.0) | Re-run with `--scope user`: `claude mcp remove leadbay && claude mcp add leadbay --scope user --env LEADBAY_TOKEN=… --env LEADBAY_REGION=us -- npx -y @leadbay/mcp@0.12` |
390
+ | Claude Code can't find Leadbay in a new conversation | MCP server installed at project scope (default before 0.3.0) | Re-run with `--scope user`: `claude mcp remove leadbay && claude mcp add leadbay --scope user --env LEADBAY_TOKEN=… --env LEADBAY_REGION=us -- npx -y @leadbay/mcp@0.13` |
370
391
  | Agent reports "tool not found" for `refine_prompt` / `adjust_audience` etc. | Pre-0.3.0 install with `LEADBAY_MCP_WRITE` unset (writes were off) | Either re-run `npx @leadbay/mcp install` or remove `LEADBAY_MCP_WRITE=0` from your client config (writes are on by default in 0.3.0+) |
371
392
 
372
393
  ## 5. Upgrade & rotation
373
394
 
374
- **Upgrade**: change the pinned minor in your config, e.g. `"@leadbay/mcp@0.2"` → `"@leadbay/mcp@0.12"`, then restart the client. **0.3.0 enables composite write tools by default** — see [MIGRATION.md](./MIGRATION.md). See also the [changelog](https://github.com/leadbay/leadclaw/releases).
395
+ **Upgrade**: change the pinned minor in your config, e.g. `"@leadbay/mcp@0.2"` → `"@leadbay/mcp@0.13"`, then restart the client. **0.3.0 enables composite write tools by default** — see [MIGRATION.md](./MIGRATION.md). See also the [changelog](https://github.com/leadbay/leadclaw/releases).
375
396
 
376
- **Rotate token**: re-run `npx -y @leadbay/mcp@0.12 install --email you@yourcompany.com --region us` (or `login`) — the new session token replaces the old one in your MCP client config, and logging in again invalidates the prior session on most session backends.
397
+ **Rotate token**: re-run `npx -y @leadbay/mcp@0.13 install --email you@yourcompany.com --region us` (or `login`) — the new session token replaces the old one in your MCP client config, and logging in again invalidates the prior session on most session backends.
377
398
 
378
399
  ## 6. Advanced
379
400
 
@@ -486,7 +507,7 @@ After your first authenticated call, your PostHog `distinctId` is set to your Le
486
507
  "mcpServers": {
487
508
  "leadbay": {
488
509
  "command": "npx",
489
- "args": ["-y", "@leadbay/mcp@0.12"],
510
+ "args": ["-y", "@leadbay/mcp@0.13"],
490
511
  "env": {
491
512
  "LEADBAY_TOKEN": "u.…",
492
513
  "LEADBAY_REGION": "us",
package/dist/bin.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  LeadbayClient,
4
+ agentMemoryTools,
4
5
  compositeReadTools,
5
6
  compositeWriteTools,
6
7
  createClient,
@@ -8,8 +9,9 @@ import {
8
9
  formatLoginError,
9
10
  granularReadTools,
10
11
  granularWriteTools,
12
+ resolveAgentMemorySummary,
11
13
  resolveRegion
12
- } from "./chunk-J2Y4LCFM.js";
14
+ } from "./chunk-SX4SKXMM.js";
13
15
 
14
16
  // src/bin.ts
15
17
  import { realpathSync } from "fs";
@@ -34,6 +36,11 @@ import {
34
36
 
35
37
  // src/prompts.generated.ts
36
38
  var leadbay_daily_check_in = `
39
+ ## MEMORY
40
+
41
+ Before responding, glance at any \`_meta.agent_memory.summary\` returned by tool calls earlier in this session and reflect its top signals in your reasoning ("Filtering by your stated preference for healthcare"). After any material new taste signal from the user this conversation (sector, region, deal size, communication style, qualification rule, explicit retraction), call \`leadbay_agent_memory_capture\` to persist it: \`source:"user_stated"\` if literal, \`source:"inferred"\` with confidence <=6 if inferred.
42
+
43
+
37
44
  Run the Leadbay daily check-in for me. Treat this prompt the same way for any equivalent ask focused on NEW leads from the Discover wishlist: "get me leadbay leads", "best NEW leads to prospect today", "what's new today", "show me my batch", "let's prospect". For follow-up phrasings ("what should I follow up on", "leads I've already worked", "before my trip"), this is the wrong prompt \u2014 route to \`leadbay_followup_check_in\` instead. If the user's intent is ambiguous ("what should I work on?"), ASK once before picking an entry point.
38
45
 
39
46
  # Resilience rules for Leadbay long-running tools
@@ -411,6 +418,11 @@ If I declined the campaign step, end the turn \u2014 the map + drafts are enough
411
418
  Done. The map is the surface; the drafts are the action; the campaign is the persistence layer for managerial follow-up after the trip.
412
419
  `;
413
420
  var leadbay_prospecting_overview = `
421
+ ## MEMORY
422
+
423
+ Before responding, glance at any \`_meta.agent_memory.summary\` returned by tool calls earlier in this session and reflect its top signals in your reasoning ("Filtering by your stated preference for healthcare"). After any material new taste signal from the user this conversation (sector, region, deal size, communication style, qualification rule, explicit retraction), call \`leadbay_agent_memory_capture\` to persist it: \`source:"user_stated"\` if literal, \`source:"inferred"\` with confidence <=6 if inferred.
424
+
425
+
414
426
  # Leadbay Prospecting \u2014 Orientation
415
427
 
416
428
  You are working with Leadbay through the \`leadbay_*\` MCP tools. This prompt orients you to the user's mental model so you don't re-discover the workflow each session.
@@ -674,6 +686,11 @@ IRON LAW \u2014 DO NOT ANSWER CLARIFICATIONS ON THE USER'S BEHALF. If the respon
674
686
  If the response status is \`applied\`, tell me Leadbay is regenerating intelligence and recommend I check back in a few minutes via \`leadbay_account_status\` (\`computing_intelligence\` flips to false when ready). If the status is anything else, name it explicitly.
675
687
  `;
676
688
  var leadbay_research_a_domain = `
689
+ ## MEMORY
690
+
691
+ Before responding, glance at any \`_meta.agent_memory.summary\` returned by tool calls earlier in this session and reflect its top signals in your reasoning ("Filtering by your stated preference for healthcare"). After any material new taste signal from the user this conversation (sector, region, deal size, communication style, qualification rule, explicit retraction), call \`leadbay_agent_memory_capture\` to persist it: \`source:"user_stated"\` if literal, \`source:"inferred"\` with confidence <=6 if inferred.
692
+
693
+
677
694
  IRON LAW \u2014 NO FABRICATION. Every lead id, contact email, custom field id, mapping decision, and tool argument must trace to a value you read from the file the user attached or to an output from a leadbay_* tool call in this session. Do not invent values. Do not "fill in" a missing leadId with a name match. Do not synthesize a CRM id from a guess. If a value is missing, leave the field blank and say so.
678
695
 
679
696
 
@@ -1219,6 +1236,7 @@ function getPrompt(name, args = {}) {
1219
1236
  var LEAD_URI_RE = /^lead:\/\/([0-9a-f-]{36})\/profile$/i;
1220
1237
  var LENS_URI_RE = /^lens:\/\/(\d+)\/definition$/;
1221
1238
  var ORG_TASTE_URI = "org://taste-profile";
1239
+ var AGENT_MEMORY_SUMMARY_URI = "agent-memory://summary";
1222
1240
  function listResources() {
1223
1241
  return [
1224
1242
  {
@@ -1226,6 +1244,12 @@ function listResources() {
1226
1244
  name: "Org taste profile",
1227
1245
  description: "The org's qualification questions, intent tags, and ICP signals \u2014 the agent's knowledge base for what makes a lead a fit.",
1228
1246
  mimeType: "application/json"
1247
+ },
1248
+ {
1249
+ uri: AGENT_MEMORY_SUMMARY_URI,
1250
+ name: "Agent memory summary",
1251
+ description: "Consolidated top Leadbay agent-memory signals for this account. Local-file, read-only resource.",
1252
+ mimeType: "text/markdown"
1229
1253
  }
1230
1254
  ];
1231
1255
  }
@@ -1256,11 +1280,29 @@ function jsonContent(uri, value) {
1256
1280
  ]
1257
1281
  };
1258
1282
  }
1283
+ function textContent(uri, mimeType, text) {
1284
+ return {
1285
+ contents: [
1286
+ {
1287
+ uri,
1288
+ mimeType,
1289
+ text
1290
+ }
1291
+ ]
1292
+ };
1293
+ }
1259
1294
  async function readResource(uri, client) {
1260
1295
  if (uri === ORG_TASTE_URI) {
1261
1296
  const taste = await client.resolveTasteProfile();
1262
1297
  return jsonContent(uri, taste);
1263
1298
  }
1299
+ if (uri === AGENT_MEMORY_SUMMARY_URI) {
1300
+ const me = await client.resolveMe();
1301
+ const memory = await resolveAgentMemorySummary({
1302
+ accountId: me.organization.id
1303
+ });
1304
+ return textContent(uri, "text/markdown", memory.summary);
1305
+ }
1264
1306
  const leadMatch = LEAD_URI_RE.exec(uri);
1265
1307
  if (leadMatch) {
1266
1308
  const leadId = leadMatch[1];
@@ -1281,7 +1323,7 @@ async function readResource(uri, client) {
1281
1323
  return jsonContent(uri, { lensId, filter, scoring });
1282
1324
  }
1283
1325
  throw new Error(
1284
- `Unsupported resource URI: ${uri}. Supported schemes: lead://{uuid}/profile, lens://{id}/definition, org://taste-profile.`
1326
+ `Unsupported resource URI: ${uri}. Supported schemes: lead://{uuid}/profile, lens://{id}/definition, org://taste-profile, agent-memory://summary.`
1285
1327
  );
1286
1328
  }
1287
1329
 
@@ -1307,6 +1349,10 @@ var EV_MCP_UPDATE_PROMPTED = "mcp update prompted";
1307
1349
  var EV_MCP_UPDATE_INSTALL_CLICKED = "mcp update install_clicked";
1308
1350
  var EV_MCP_UPDATE_DISMISSED = "mcp update dismissed";
1309
1351
  var EV_MCP_VERSION_UPDATED = "mcp version updated";
1352
+ var EV_AGENT_MEMORY_CAPTURED = "agent_memory_captured";
1353
+ var EV_AGENT_MEMORY_RECALLED = "agent_memory_recalled";
1354
+ var EV_AGENT_MEMORY_PRUNED = "agent_memory_pruned";
1355
+ var EV_FRICTION_REPORTED = "mcp friction reported";
1310
1356
 
1311
1357
  // src/telemetry.ts
1312
1358
  var NOOP_TELEMETRY = {
@@ -1320,6 +1366,14 @@ var NOOP_TELEMETRY = {
1320
1366
  },
1321
1367
  captureStartup: () => {
1322
1368
  },
1369
+ captureAgentMemoryCaptured: () => {
1370
+ },
1371
+ captureAgentMemoryRecalled: () => {
1372
+ },
1373
+ captureAgentMemoryPruned: () => {
1374
+ },
1375
+ captureFrictionReported: () => {
1376
+ },
1323
1377
  captureException: () => {
1324
1378
  },
1325
1379
  captureUpdateCheck: () => {
@@ -1378,8 +1432,17 @@ function initTelemetry(opts) {
1378
1432
  sendDefaultPii: false,
1379
1433
  // Tag every captured event with the surface so Sentry views can
1380
1434
  // split MCP issues from web-app issues without per-call work.
1435
+ // Version is also encoded in `release` above, but a dedicated
1436
+ // `mcp_version` tag is filterable from Sentry's issue list without
1437
+ // expanding the release dropdown — load-bearing when triaging
1438
+ // "errors at reinstall on @0.13" vs older clients still on @0.11.
1381
1439
  initialScope: {
1382
- tags: { source: "mcp" }
1440
+ tags: {
1441
+ source: "mcp",
1442
+ mcp_version: version,
1443
+ node_version: process.versions.node,
1444
+ platform: process.platform
1445
+ }
1383
1446
  }
1384
1447
  });
1385
1448
  sentryReady = true;
@@ -1504,6 +1567,18 @@ function initTelemetry(opts) {
1504
1567
  captureStartup(props) {
1505
1568
  emit(EV_STARTUP, { ...props });
1506
1569
  },
1570
+ captureAgentMemoryCaptured(props) {
1571
+ emit(EV_AGENT_MEMORY_CAPTURED, { ...props });
1572
+ },
1573
+ captureAgentMemoryRecalled(props) {
1574
+ emit(EV_AGENT_MEMORY_RECALLED, { ...props });
1575
+ },
1576
+ captureAgentMemoryPruned(props) {
1577
+ emit(EV_AGENT_MEMORY_PRUNED, { ...props });
1578
+ },
1579
+ captureFrictionReported(props) {
1580
+ emit(EV_FRICTION_REPORTED, { ...props });
1581
+ },
1507
1582
  captureUpdateCheck(props) {
1508
1583
  emit(EV_MCP_UPDATE_CHECK, { ...props });
1509
1584
  },
@@ -1867,6 +1942,7 @@ function buildAcknowledgeUpdateTool(opts) {
1867
1942
  var VERIFICATION_MANDATE = "After every email, call, message, or meeting with a lead's contact, you MUST call leadbay_report_outreach with verification={source, ref} (gmail_message_id from the Gmail send, calendar_event_id from a booking, or user_confirmed='<the user's literal confirmation>'). Skipping or fabricating verification poisons the human team's pipeline.";
1868
1943
  var MENTAL_MODEL_PARAGRAPH = "How Leadbay works (mental model): Leadbay is a sales inbox, not a queryable database. Each day the user logs back in, a fresh batch of leads is delivered. Batch size is paced by how many leads the user has actually acted on recently \u2014 some workflows produce a big stream of smaller prospects, others a narrow stream of bigger ones. Pulling more won't produce more; the user acting on leads (outreach, skips, saves) does.";
1869
1944
  var QUOTA_AND_TOPUP_PARAGRAPH = "Quota & top-ups: when a tool returns QUOTA_EXCEEDED / 429, the user has TWO options \u2014 wait for the window reset (daily / weekly / monthly resets shown in leadbay_account_status), OR top up AI credits (top-ups clear the throttle IMMEDIATELY \u2014 they are not subject to the same window). Always offer BOTH options; default-recommending 'wait until tomorrow' is wrong when a 30-second top-up unblocks the same call. If the host exposes leadbay_create_topup_link, OFFER it on every quota wall: 'Want me to generate a top-up link?' \u2014 when the user says yes, call leadbay_create_topup_link and surface the returned Stripe URL as a clickable link for the user to open in their browser. (Sibling leadbay_open_billing_portal is for ongoing subscription changes, not one-shot top-ups.) AFTER the user has topped up: do NOT keep refusing operations. A top-up invalidates every prior 429 and every stale 'you're at your quota' snapshot. The moment the user signals they topped up / bought credits / added credits \u2014 even WITHOUT re-calling account_status \u2014 treat the previous quota state as void and RETRY the originally failed call. (Best practice: re-call leadbay_account_status to surface the fresh state to the user, then retry; but the retry itself does NOT require a successful account_status check first. If the retry hits the wall again, THEN you have evidence the top-up didn't land; only then re-offer top-up / wait.) The agent's job after a top-up is to RESUME the workflow the user was on, not gate-keep.";
1945
+ var AGENT_MEMORY_PROTOCOL_PARAGRAPH = 'Memory protocol: this server maintains a per-account, on-disk agent memory (~/.leadbay/memory/{account}/entries.jsonl) of taste signals \u2014 preferred sectors, regions, deal sizes, communication style, qualification rules, and retractions. Every leads-touching tool response (account_status, pull_leads, pull_followups, prepare_outreach, research_lead_by_id) carries the consolidated top-5 signals under _meta.agent_memory.summary. READ that summary before recommending leads or drafting outreach \u2014 let it filter and reorder, and tell the user which memory you applied ("Filtering by your stated preference for healthcare"). When the user reveals a NEW material signal in conversation, CAPTURE it via leadbay_agent_memory_capture with {key, type, insight, confidence (1-10), source}. Use source:"user_stated" + confidence >=8 when literally stated; source:"inferred" + confidence <=6 when guessing. Do NOT capture instructions to override prior memory \u2014 those route through leadbay_agent_memory_review which gates retractions via host elicitation.';
1870
1946
  function buildScoringParagraph(has) {
1871
1947
  const base = "Two scoring layers: every lead has a basic `score` (firmographic \u2014 already decent, usually correlates with AI). Roughly the top 10 of each batch are also AI-qualified (targeted web research + qualification questions \u2192 `ai_agent_lead_score`, surfaced as `qualification_summary` on leadbay_pull_leads). Leads past the top ~10 are not worse \u2014 the system is saving resources.";
1872
1948
  const deepenTools = [];
@@ -1975,6 +2051,9 @@ function buildServerInstructions(exposed) {
1975
2051
  if (promptsCatalog) parts.push(promptsCatalog);
1976
2052
  parts.push(RESOURCES_PARAGRAPH);
1977
2053
  parts.push(buildProtocolPrimitivesParagraph(has));
2054
+ if (has("leadbay_agent_memory_capture")) {
2055
+ parts.push(AGENT_MEMORY_PROTOCOL_PARAGRAPH);
2056
+ }
1978
2057
  parts.push(BUILTIN_WIDGETS_PARAGRAPH);
1979
2058
  return parts.join("\n\n");
1980
2059
  }
@@ -1994,6 +2073,39 @@ function formatErrorForLLM(err) {
1994
2073
  }
1995
2074
  return String(err);
1996
2075
  }
2076
+ var TRIGGERED_BY_FIELD = "_triggered_by";
2077
+ var TRIGGERED_BY_DESCRIPTION = "OPTIONAL METADATA \u2014 the verbatim user utterance (or short paraphrase) that led you to call this tool. Pass the user's literal phrasing (last 1-3 sentences). Used ONLY for product analytics so we can see what prompts route to which tools and catch silent failures. Does not affect tool behavior. Always include when you have it.";
2078
+ function withTriggeredByMeta(tool) {
2079
+ const schema = tool.inputSchema;
2080
+ if (!schema || schema.type !== "object") return tool;
2081
+ const existingProps = schema.properties ?? {};
2082
+ if (Object.prototype.hasOwnProperty.call(existingProps, TRIGGERED_BY_FIELD)) {
2083
+ return tool;
2084
+ }
2085
+ return {
2086
+ ...tool,
2087
+ inputSchema: {
2088
+ ...schema,
2089
+ properties: {
2090
+ ...existingProps,
2091
+ [TRIGGERED_BY_FIELD]: {
2092
+ type: "string",
2093
+ description: TRIGGERED_BY_DESCRIPTION
2094
+ }
2095
+ }
2096
+ }
2097
+ };
2098
+ }
2099
+ function extractTriggeredBy(args) {
2100
+ const raw = args[TRIGGERED_BY_FIELD];
2101
+ if (typeof raw !== "string" || raw.length === 0) {
2102
+ return { triggered_by: void 0, cleaned: args };
2103
+ }
2104
+ const { [TRIGGERED_BY_FIELD]: _omit, ...cleaned } = args;
2105
+ void _omit;
2106
+ const trimmed = raw.length > 500 ? `${raw.slice(0, 500)}\u2026` : raw;
2107
+ return { triggered_by: trimmed, cleaned };
2108
+ }
1997
2109
  function toolsListPayload(tools) {
1998
2110
  return tools.map((t) => {
1999
2111
  const out = {
@@ -2008,6 +2120,7 @@ function toolsListPayload(tools) {
2008
2120
  }
2009
2121
  function buildServer(client, opts = {}) {
2010
2122
  const exposedTools = [];
2123
+ exposedTools.push(...agentMemoryTools);
2011
2124
  exposedTools.push(...compositeReadTools);
2012
2125
  if (opts.includeWrite) {
2013
2126
  exposedTools.push(...compositeWriteTools);
@@ -2034,7 +2147,7 @@ function buildServer(client, opts = {}) {
2034
2147
  const toolByName = /* @__PURE__ */ new Map();
2035
2148
  for (const t of exposedTools) {
2036
2149
  if (!toolByName.has(t.name) && t.name !== "leadbay_login") {
2037
- toolByName.set(t.name, t);
2150
+ toolByName.set(t.name, withTriggeredByMeta(t));
2038
2151
  }
2039
2152
  }
2040
2153
  const exposedNames = new Set(toolByName.keys());
@@ -2152,6 +2265,46 @@ function buildServer(client, opts = {}) {
2152
2265
  }
2153
2266
  };
2154
2267
  const isLeadbayBusinessError = (err) => err != null && typeof err === "object" && err.error === true && typeof err.code === "string";
2268
+ const captureFrictionTelemetry = (toolName, result) => {
2269
+ if (toolName !== "leadbay_report_friction") return;
2270
+ if (!result || typeof result !== "object") return;
2271
+ const fr = result._friction;
2272
+ if (!fr || typeof fr !== "object") return;
2273
+ if (typeof fr.category !== "string" || typeof fr.user_quote !== "string") {
2274
+ return;
2275
+ }
2276
+ telemetry.captureFrictionReported({
2277
+ category: fr.category,
2278
+ user_quote: fr.user_quote,
2279
+ ...typeof fr.tool_called === "string" ? { tool_called: fr.tool_called } : {},
2280
+ ...typeof fr.severity === "string" ? { severity: fr.severity } : {},
2281
+ ...typeof fr.details === "string" ? { details: fr.details } : {}
2282
+ });
2283
+ };
2284
+ const captureAgentMemoryTelemetry = (toolName, result) => {
2285
+ if (!result || typeof result !== "object") return;
2286
+ const meta = result._meta ?? {};
2287
+ if (toolName === "leadbay_agent_memory_capture") {
2288
+ telemetry.captureAgentMemoryCaptured({
2289
+ source: result.captured?.source ?? meta.source,
2290
+ scope: result.captured?.scope ?? meta.scope,
2291
+ key: result.captured?.key,
2292
+ type: result.captured?.type,
2293
+ account_id_hash: meta.account_id_hash
2294
+ });
2295
+ } else if (toolName === "leadbay_agent_memory_recall") {
2296
+ telemetry.captureAgentMemoryRecalled({
2297
+ entries_returned: result.entries_returned,
2298
+ total_active: result.total_active,
2299
+ account_id_hash: meta.account_id_hash
2300
+ });
2301
+ } else if (toolName === "leadbay_agent_memory_review" && result.changed === true && (result.action === "retract" || result.action === "prune")) {
2302
+ telemetry.captureAgentMemoryPruned({
2303
+ action: result.action,
2304
+ account_id_hash: meta.account_id_hash
2305
+ });
2306
+ }
2307
+ };
2155
2308
  server.setRequestHandler(CallToolRequestSchema, async (req, extra) => {
2156
2309
  const callStart = Date.now();
2157
2310
  const name = req.params.name;
@@ -2168,7 +2321,8 @@ function buildServer(client, opts = {}) {
2168
2321
  isError: true
2169
2322
  };
2170
2323
  }
2171
- const args = req.params.arguments ?? {};
2324
+ const rawArgs = req.params.arguments ?? {};
2325
+ const { triggered_by, cleaned: args } = extractTriggeredBy(rawArgs);
2172
2326
  const progressToken = req.params?._meta?.progressToken;
2173
2327
  const progress = progressToken !== void 0 ? (params) => {
2174
2328
  extra.sendNotification({
@@ -2227,7 +2381,8 @@ function buildServer(client, opts = {}) {
2227
2381
  duration_ms: envDur,
2228
2382
  format: "error-envelope",
2229
2383
  bytes: envText.length,
2230
- error_code: envCode
2384
+ error_code: envCode,
2385
+ triggered_by
2231
2386
  });
2232
2387
  if (DEBUG_ON) {
2233
2388
  process.stderr.write(
@@ -2258,8 +2413,11 @@ function buildServer(client, opts = {}) {
2258
2413
  ok: true,
2259
2414
  duration_ms: mdDur,
2260
2415
  format: "markdown",
2261
- bytes: mdBytes
2416
+ bytes: mdBytes,
2417
+ triggered_by
2262
2418
  });
2419
+ captureAgentMemoryTelemetry(name, env.structured);
2420
+ captureFrictionTelemetry(name, env.structured);
2263
2421
  if (name === "leadbay_create_topup_link" && typeof env.structured?.url === "string") {
2264
2422
  telemetry.captureTopupLink({ tool: name });
2265
2423
  }
@@ -2287,8 +2445,11 @@ function buildServer(client, opts = {}) {
2287
2445
  ok: true,
2288
2446
  duration_ms: okDur,
2289
2447
  format: "json",
2290
- bytes: okBytes
2448
+ bytes: okBytes,
2449
+ triggered_by
2291
2450
  });
2451
+ captureAgentMemoryTelemetry(name, result);
2452
+ captureFrictionTelemetry(name, result);
2292
2453
  if (name === "leadbay_create_topup_link" && typeof result?.url === "string") {
2293
2454
  telemetry.captureTopupLink({ tool: name });
2294
2455
  }
@@ -2317,7 +2478,8 @@ function buildServer(client, opts = {}) {
2317
2478
  duration_ms: errDur,
2318
2479
  format: "error-envelope",
2319
2480
  bytes: errText.length,
2320
- error_code: code
2481
+ error_code: code,
2482
+ triggered_by
2321
2483
  });
2322
2484
  } else {
2323
2485
  telemetry.captureException(err, { tool: name });
@@ -2327,7 +2489,8 @@ function buildServer(client, opts = {}) {
2327
2489
  duration_ms: errDur,
2328
2490
  format: "error-envelope",
2329
2491
  bytes: errText.length,
2330
- error_code: code
2492
+ error_code: code,
2493
+ triggered_by
2331
2494
  });
2332
2495
  }
2333
2496
  if (DEBUG_ON) {
@@ -2542,7 +2705,7 @@ async function createDefaultUpdateStateStore(opts = {}) {
2542
2705
 
2543
2706
  // src/bin.ts
2544
2707
  import { createRequire } from "module";
2545
- var VERSION = "0.12.1";
2708
+ var VERSION = "0.14.0";
2546
2709
  var HELP = `
2547
2710
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
2548
2711
 
@@ -2591,7 +2754,7 @@ EXAMPLE Claude Desktop config (~/Library/Application Support/Claude/claude_deskt
2591
2754
  "mcpServers": {
2592
2755
  "leadbay": {
2593
2756
  "command": "npx",
2594
- "args": ["-y", "@leadbay/mcp@0.12"],
2757
+ "args": ["-y", "@leadbay/mcp@0.13"],
2595
2758
  "env": {
2596
2759
  "LEADBAY_TOKEN": "lb_...",
2597
2760
  "LEADBAY_REGION": "us",
@@ -2892,7 +3055,7 @@ async function runLogin(args) {
2892
3055
  let result;
2893
3056
  try {
2894
3057
  if (pinnedRegion && !allowFallback) {
2895
- const { REGIONS } = await import("./dist-YYVFSDMH.js");
3058
+ const { REGIONS } = await import("./dist-CRE74TH2.js");
2896
3059
  const baseUrl = REGIONS[pinnedRegion];
2897
3060
  const c = createClient({ region: pinnedRegion });
2898
3061
  const token = await loginAt(baseUrl, email, password);
@@ -2902,8 +3065,9 @@ async function runLogin(args) {
2902
3065
  result = await resolveRegion(email, password, pinnedRegion ?? void 0);
2903
3066
  }
2904
3067
  } catch (err) {
2905
- process.stderr.write(`leadbay-mcp login: ${err?.message ?? String(err)}
3068
+ process.stderr.write(`leadbay-mcp@${VERSION} login: ${err?.message ?? String(err)}
2906
3069
  `);
3070
+ await reportCliFailure("__login__", err);
2907
3071
  return 1;
2908
3072
  }
2909
3073
  const config = {
@@ -2911,7 +3075,7 @@ async function runLogin(args) {
2911
3075
  mcpServers: {
2912
3076
  leadbay: {
2913
3077
  command: "npx",
2914
- args: ["-y", "@leadbay/mcp@0.12"],
3078
+ args: ["-y", "@leadbay/mcp@0.13"],
2915
3079
  env: {
2916
3080
  LEADBAY_TOKEN: result.token,
2917
3081
  LEADBAY_REGION: result.region
@@ -2951,7 +3115,7 @@ Or for Claude Code (token included \u2014 same warning applies):
2951
3115
  claude mcp add leadbay --scope user \\
2952
3116
  --env LEADBAY_TOKEN=${result.token} \\
2953
3117
  --env LEADBAY_REGION=${result.region} \\
2954
- -- npx -y @leadbay/mcp@0.12
3118
+ -- npx -y @leadbay/mcp@0.13
2955
3119
 
2956
3120
  Restart your MCP client to pick up the new server.
2957
3121
  `
@@ -3057,7 +3221,7 @@ For Claude Code, run:
3057
3221
  claude mcp add leadbay --scope user \\
3058
3222
  --env LEADBAY_TOKEN=$(jq -r .mcpServers.leadbay.env.LEADBAY_TOKEN ${quotedPath}) \\
3059
3223
  --env LEADBAY_REGION=${result.region} \\
3060
- -- npx -y @leadbay/mcp@0.12
3224
+ -- npx -y @leadbay/mcp@0.13
3061
3225
  `
3062
3226
  );
3063
3227
  }
@@ -3237,7 +3401,7 @@ function buildClaudeCodeAddArgs(token, region, includeWrite, telemetryEnabled) {
3237
3401
  `LEADBAY_TELEMETRY_ENABLED=${telemetryEnabled ? "true" : "false"}`
3238
3402
  ];
3239
3403
  if (!includeWrite) args.push("--env", `LEADBAY_MCP_WRITE=0`);
3240
- args.push("--", "npx", "-y", "@leadbay/mcp@0.12");
3404
+ args.push("--", "npx", "-y", "@leadbay/mcp@0.13");
3241
3405
  return args;
3242
3406
  }
3243
3407
  async function installInClaudeCode(token, region, includeWrite, telemetryEnabled) {
@@ -3287,7 +3451,7 @@ async function installInJsonConfig(configPath, token, region, includeWrite, tele
3287
3451
  if (!includeWrite) env.LEADBAY_MCP_WRITE = "0";
3288
3452
  parsed.mcpServers.leadbay = {
3289
3453
  command: "npx",
3290
- args: ["-y", "@leadbay/mcp@0.12"],
3454
+ args: ["-y", "@leadbay/mcp@0.13"],
3291
3455
  env
3292
3456
  };
3293
3457
  const tmp = configPath + ".tmp";
@@ -3391,7 +3555,7 @@ leadbay-mcp install \u2014 detected MCP clients on this machine:
3391
3555
  let region;
3392
3556
  try {
3393
3557
  if (pinnedRegion && !allowFallback) {
3394
- const { REGIONS } = await import("./dist-YYVFSDMH.js");
3558
+ const { REGIONS } = await import("./dist-CRE74TH2.js");
3395
3559
  const baseUrl = REGIONS[pinnedRegion];
3396
3560
  token = await loginAt(baseUrl, email, password);
3397
3561
  region = pinnedRegion;
@@ -3401,8 +3565,9 @@ leadbay-mcp install \u2014 detected MCP clients on this machine:
3401
3565
  region = result.region;
3402
3566
  }
3403
3567
  } catch (err) {
3404
- process.stderr.write(`leadbay-mcp install: ${err?.message ?? String(err)}
3568
+ process.stderr.write(`leadbay-mcp@${VERSION} install: ${err?.message ?? String(err)}
3405
3569
  `);
3570
+ await reportCliFailure("__install_login__", err);
3406
3571
  return 1;
3407
3572
  }
3408
3573
  process.stderr.write(`Logged in to ${region.toUpperCase()} backend.
@@ -3454,17 +3619,26 @@ leadbay-mcp install \u2014 detected MCP clients on this machine:
3454
3619
  }
3455
3620
  results.push({ id: c.id, label: c.label, ...res });
3456
3621
  }
3457
- process.stderr.write("\n=== install summary ===\n");
3622
+ process.stderr.write(`
3623
+ === install summary (leadbay-mcp@${VERSION}) ===
3624
+ `);
3458
3625
  let anyOk = false;
3459
3626
  for (const r of results) {
3460
3627
  process.stderr.write(` ${r.ok ? "\u2713" : "\u2717"} ${r.label.padEnd(16)} ${r.message}
3461
3628
  `);
3462
- if (r.ok) anyOk = true;
3629
+ if (r.ok) {
3630
+ anyOk = true;
3631
+ } else if (!r.message.startsWith("skipped")) {
3632
+ await reportCliFailure(
3633
+ `install:${r.id}`,
3634
+ new Error(`${r.label}: ${r.message}`)
3635
+ );
3636
+ }
3463
3637
  }
3464
3638
  process.stderr.write(
3465
3639
  `
3466
3640
  The token was written into client config files but never printed to your terminal.
3467
- Verify with: LEADBAY_TOKEN=$(...) npx -y @leadbay/mcp@0.12 doctor
3641
+ Verify with: LEADBAY_TOKEN=$(...) npx -y @leadbay/mcp@0.13 doctor
3468
3642
  Restart your MCP client(s) to pick up the new server.
3469
3643
  If you ever leak the token, run \`leadbay-mcp login --email <you> --region <us|fr>\` to mint a fresh one (which invalidates the prior session).
3470
3644
  `
@@ -3490,6 +3664,7 @@ async function runDoctor() {
3490
3664
  const me = await client.request("GET", "/users/me");
3491
3665
  process.stdout.write(
3492
3666
  `Leadbay connection OK.
3667
+ Version: leadbay-mcp@${VERSION} (node ${process.versions.node}, ${process.platform})
3493
3668
  Region: ${baseUrl ? "(custom baseUrl)" : region}
3494
3669
  Base URL: ${client.baseUrl}
3495
3670
  Organization: ${me.organization.name} (${me.organization.id})
@@ -3503,25 +3678,41 @@ async function runDoctor() {
3503
3678
  if (err?.code === "AUTH_EXPIRED" || err?.code === "NOT_AUTHENTICATED") {
3504
3679
  process.stderr.write(
3505
3680
  `Leadbay: your LEADBAY_TOKEN is not valid for ${region}. ${err.hint}
3681
+ (leadbay-mcp@${VERSION})
3506
3682
  `
3507
3683
  );
3684
+ await reportCliFailure("__doctor_auth__", err);
3508
3685
  return 1;
3509
3686
  }
3510
3687
  }
3511
3688
  if (baseUrl) break;
3512
3689
  }
3513
3690
  process.stderr.write(
3514
- "Leadbay doctor: could not reach any Leadbay region with this token. Check the token and your network.\n"
3691
+ `Leadbay doctor: could not reach any Leadbay region with this token. Check the token and your network.
3692
+ (leadbay-mcp@${VERSION})
3693
+ `
3694
+ );
3695
+ await reportCliFailure(
3696
+ "__doctor_unreachable__",
3697
+ new Error("doctor: no region reachable with current token")
3515
3698
  );
3516
3699
  return 1;
3517
3700
  }
3701
+ async function reportCliFailure(label, err) {
3702
+ try {
3703
+ const bootTelemetry = initTelemetry({ version: VERSION });
3704
+ bootTelemetry.captureException(err, { tool: label });
3705
+ await bootTelemetry.shutdown();
3706
+ } catch {
3707
+ }
3708
+ }
3518
3709
  var startupSafetyNetsInstalled = false;
3519
3710
  function installStartupSafetyNets(logger) {
3520
3711
  if (startupSafetyNetsInstalled) return;
3521
3712
  startupSafetyNetsInstalled = true;
3522
3713
  const reportAndExit = (label, err) => {
3523
3714
  const msg = err instanceof Error ? err.stack ?? err.message : String(err);
3524
- process.stderr.write(`leadbay-mcp: ${label}: ${msg}
3715
+ process.stderr.write(`leadbay-mcp@${VERSION}: ${label}: ${msg}
3525
3716
  `);
3526
3717
  logger.error?.(`${label}: ${msg}`);
3527
3718
  try {