@leadbay/mcp 0.14.1 → 0.15.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.15.0 — 2026-05-27
4
+
5
+ - **Sentry observability**: every non-2xx Leadbay API outcome now lands in
6
+ Sentry with the full envelope — `code`, `message`, `hint`, `endpoint`,
7
+ `region`, `http_status`, `latency_ms`, `retry_after`, agent `triggered_by`.
8
+ Previously business errors (NOT_FOUND, AUTH_EXPIRED, QUOTA_EXCEEDED,
9
+ FORBIDDEN, LEAD_NOT_FOUND, etc.) only landed in PostHog, and even the
10
+ unexpected throws that reached Sentry carried only a bare exception with
11
+ `tool` + `organization` tags. A new `source` tag (`business` vs
12
+ `unexpected`) plus per-event fingerprint `["mcp", tool, code]` keeps the
13
+ Sentry issue list groupable.
14
+ - HTTP status now propagates through `LeadbayError._meta.http_status` so
15
+ `API_ERROR` (catch-all unmapped statuses) is filterable by status in
16
+ Sentry instead of collapsing into one undifferentiated bucket.
17
+
3
18
  ## 0.13.0 — 2026-05-21
4
19
 
5
20
  - **Agent memory v1**: added always-on recall/capture/review tools backed by
package/dist/bin.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  granularWriteTools,
12
12
  resolveAgentMemorySummary,
13
13
  resolveRegion
14
- } from "./chunk-SX4SKXMM.js";
14
+ } from "./chunk-5IL7SC7L.js";
15
15
 
16
16
  // src/bin.ts
17
17
  import { realpathSync } from "fs";
@@ -1599,9 +1599,28 @@ function initTelemetry(opts) {
1599
1599
  try {
1600
1600
  Sentry.withScope((scope) => {
1601
1601
  scope.setTag("tool", ctx.tool);
1602
+ if (ctx.code) scope.setTag("error_code", ctx.code);
1603
+ if (ctx.endpoint) scope.setTag("endpoint", ctx.endpoint);
1604
+ if (ctx.region) scope.setTag("region", ctx.region);
1605
+ if (ctx.http_status !== void 0) {
1606
+ scope.setTag("http_status", String(ctx.http_status));
1607
+ }
1608
+ if (ctx.source) scope.setTag("source", ctx.source);
1602
1609
  if (me?.organization?.id) {
1603
1610
  scope.setTag("organization", me.organization.id);
1604
1611
  }
1612
+ if (ctx.message) scope.setExtra("message", ctx.message);
1613
+ if (ctx.hint) scope.setExtra("hint", ctx.hint);
1614
+ if (ctx.triggered_by) scope.setExtra("triggered_by", ctx.triggered_by);
1615
+ if (ctx.latency_ms !== void 0 && ctx.latency_ms !== null) {
1616
+ scope.setExtra("latency_ms", ctx.latency_ms);
1617
+ }
1618
+ if (ctx.retry_after !== void 0 && ctx.retry_after !== null) {
1619
+ scope.setExtra("retry_after", ctx.retry_after);
1620
+ }
1621
+ if (ctx.code && ctx.source === "business") {
1622
+ scope.setFingerprint(["mcp", ctx.tool, ctx.code]);
1623
+ }
1605
1624
  Sentry.captureException(err);
1606
1625
  });
1607
1626
  } catch (e) {
@@ -2265,6 +2284,22 @@ function buildServer(client, opts = {}) {
2265
2284
  }
2266
2285
  };
2267
2286
  const isLeadbayBusinessError = (err) => err != null && typeof err === "object" && err.error === true && typeof err.code === "string";
2287
+ const buildBusinessCtx = (toolName, envelope, triggered_by) => {
2288
+ const meta = envelope._meta ?? {};
2289
+ return {
2290
+ tool: toolName,
2291
+ code: envelope.code,
2292
+ message: envelope.message,
2293
+ hint: envelope.hint,
2294
+ endpoint: meta.endpoint,
2295
+ region: meta.region,
2296
+ latency_ms: meta.latency_ms ?? null,
2297
+ retry_after: meta.retry_after ?? null,
2298
+ http_status: meta.http_status,
2299
+ triggered_by,
2300
+ source: "business"
2301
+ };
2302
+ };
2268
2303
  const captureFrictionTelemetry = (toolName, result) => {
2269
2304
  if (toolName !== "leadbay_report_friction") return;
2270
2305
  if (!result || typeof result !== "object") return;
@@ -2384,6 +2419,10 @@ function buildServer(client, opts = {}) {
2384
2419
  error_code: envCode,
2385
2420
  triggered_by
2386
2421
  });
2422
+ telemetry.captureException(
2423
+ result,
2424
+ buildBusinessCtx(name, result, triggered_by)
2425
+ );
2387
2426
  if (DEBUG_ON) {
2388
2427
  process.stderr.write(
2389
2428
  `[leadbay-mcp debug] tool=${name} dur=${envDur}ms ok=false code=${envCode}
@@ -2481,8 +2520,14 @@ function buildServer(client, opts = {}) {
2481
2520
  error_code: code,
2482
2521
  triggered_by
2483
2522
  });
2523
+ telemetry.captureException(err, buildBusinessCtx(name, err, triggered_by));
2484
2524
  } else {
2485
- telemetry.captureException(err, { tool: name });
2525
+ telemetry.captureException(err, {
2526
+ tool: name,
2527
+ source: "unexpected",
2528
+ message: typeof err?.message === "string" ? err.message : void 0,
2529
+ triggered_by
2530
+ });
2486
2531
  telemetry.captureToolCall({
2487
2532
  tool: name,
2488
2533
  ok: false,
@@ -2705,7 +2750,7 @@ async function createDefaultUpdateStateStore(opts = {}) {
2705
2750
 
2706
2751
  // src/bin.ts
2707
2752
  import { createRequire } from "module";
2708
- var VERSION = "0.14.1";
2753
+ var VERSION = "0.15.0";
2709
2754
  var HELP = `
2710
2755
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
2711
2756
 
@@ -3055,7 +3100,7 @@ async function runLogin(args) {
3055
3100
  let result;
3056
3101
  try {
3057
3102
  if (pinnedRegion && !allowFallback) {
3058
- const { REGIONS } = await import("./dist-CRE74TH2.js");
3103
+ const { REGIONS } = await import("./dist-2NAFYPXG.js");
3059
3104
  const baseUrl = REGIONS[pinnedRegion];
3060
3105
  const c = createClient({ region: pinnedRegion });
3061
3106
  const token = await loginAt(baseUrl, email, password);
@@ -3555,7 +3600,7 @@ leadbay-mcp install \u2014 detected MCP clients on this machine:
3555
3600
  let region;
3556
3601
  try {
3557
3602
  if (pinnedRegion && !allowFallback) {
3558
- const { REGIONS } = await import("./dist-CRE74TH2.js");
3603
+ const { REGIONS } = await import("./dist-2NAFYPXG.js");
3559
3604
  const baseUrl = REGIONS[pinnedRegion];
3560
3605
  token = await loginAt(baseUrl, email, password);
3561
3606
  region = pinnedRegion;
@@ -416,7 +416,7 @@ var LeadbayClient = class {
416
416
  }
417
417
  const retryAfter = parseRetryAfter(headers["retry-after"]);
418
418
  if (status === 401) {
419
- return this.makeError("AUTH_EXPIRED", "Authentication token expired or invalid", "Your LEADBAY_TOKEN is no longer valid. Regenerate it: npx -y @leadbay/mcp login --email <you> --region <us|fr>, then restart your MCP client.", endpoint);
419
+ return this.makeError("AUTH_EXPIRED", "Authentication token expired or invalid", "Your LEADBAY_TOKEN is no longer valid. Regenerate it: npx -y @leadbay/mcp login --email <you> --region <us|fr>, then restart your MCP client.", endpoint, null, status);
420
420
  }
421
421
  if (status === 429 || status === 402 || parsed?.error === "quota_exceeded" || parsed?.error?.code === "quota_exceeded") {
422
422
  const hintBase = retryAfter ? `Wait ${retryAfter}s before retrying` : "Wait, then retry";
@@ -432,20 +432,21 @@ var LeadbayClient = class {
432
432
  // 429 is stale — retry the failed call.
433
433
  `${hintBase}, OR top up AI credits \u2014 top-ups clear the throttle immediately. Offer the user to generate a Stripe checkout URL via leadbay_create_topup_link, OR direct them to app.leadbay.ai \u2192 Billing. Check leadbay_account_status / leadbay_get_quota to see which resource window (daily/weekly/monthly) was hit. Once the user has topped up, the previous QUOTA_EXCEEDED is stale \u2014 re-call leadbay_account_status to refresh, then RETRY the original operation.`,
434
434
  endpoint,
435
- retryAfter
435
+ retryAfter,
436
+ status
436
437
  );
437
438
  }
438
439
  if (status === 403) {
439
440
  const msg = parsed?.message || parsed?.error || parsed?.error?.message || "";
440
441
  if (typeof msg === "string" && (msg.includes("suspend") || msg.includes("billing"))) {
441
- return this.makeError("BILLING_SUSPENDED", "Account billing is suspended", "Your Leadbay account billing is suspended. Contact Leadbay support.", endpoint);
442
+ return this.makeError("BILLING_SUSPENDED", "Account billing is suspended", "Your Leadbay account billing is suspended. Contact Leadbay support.", endpoint, null, status);
442
443
  }
443
- return this.makeError("FORBIDDEN", "Insufficient permissions", "Your token does not have access to this resource. Contact Leadbay support to verify account permissions.", endpoint);
444
+ return this.makeError("FORBIDDEN", "Insufficient permissions", "Your token does not have access to this resource. Contact Leadbay support to verify account permissions.", endpoint, null, status);
444
445
  }
445
446
  if (status === 404) {
446
- return this.makeError("NOT_FOUND", parsed?.message || parsed?.error?.message || "Resource not found", "Verify the ID is correct", endpoint);
447
+ return this.makeError("NOT_FOUND", parsed?.message || parsed?.error?.message || "Resource not found", "Verify the ID is correct", endpoint, null, status);
447
448
  }
448
- return this.makeError("API_ERROR", parsed?.message || parsed?.error?.message || `API error (${status})`, "Try again or check the Leadbay API status", endpoint);
449
+ return this.makeError("API_ERROR", parsed?.message || parsed?.error?.message || `API error (${status})`, "Try again or check the Leadbay API status", endpoint, null, status);
449
450
  }
450
451
  // /me cache (60s TTL). Separate from resolveOrgId() which still works for
451
452
  // legacy callers (it now delegates here).
@@ -524,14 +525,15 @@ var LeadbayClient = class {
524
525
  await this.resolveOrgId();
525
526
  await this.resolveTasteProfile();
526
527
  }
527
- makeError(code, message, hint, endpoint, retry_after) {
528
+ makeError(code, message, hint, endpoint, retry_after, http_status) {
528
529
  const out = { error: true, code, message, hint };
529
530
  if (endpoint || this._region) {
530
531
  out._meta = {
531
532
  region: this._region,
532
533
  endpoint: endpoint ?? "",
533
534
  latency_ms: this._lastMeta?.latency_ms ?? null,
534
- retry_after: retry_after ?? null
535
+ retry_after: retry_after ?? null,
536
+ ...http_status !== void 0 ? { http_status } : {}
535
537
  };
536
538
  }
537
539
  return out;
@@ -4983,7 +4985,7 @@ var leadbay_add_leads_to_campaign = `## WHEN TO USE
4983
4985
 
4984
4986
  Trigger phrases: "add leads to <name> campaign", "attach these to <campaign>", "put these in Q2 Push", "add to existing campaign".
4985
4987
 
4986
- Do NOT use for: "create a new campaign" \u2192 \`leadbay_create_campaign\`; "remove lead from campaign" \u2192 \`leadbay_create_campaign\`; "list campaigns" \u2192 \`leadbay_list_campaigns\`.
4988
+ Do NOT use for: "create a new campaign" \u2192 \`leadbay_create_campaign\`; "remove lead from campaign" \u2192 \`leadbay_remove_leads_from_campaign\`; "list campaigns" \u2192 \`leadbay_list_campaigns\`.
4987
4989
 
4988
4990
  Prefer when: existing campaign plus lead ids to attach; for a new campaign, use create_campaign
4989
4991
 
@@ -5021,7 +5023,7 @@ Attach a list of \`lead_ids\` to an existing campaign. Wraps \`POST /campaigns/{
5021
5023
 
5022
5024
  WHEN TO USE: the user has an existing campaign (created earlier in the session or visible via \`leadbay_list_campaigns\`) and wants to attach more leads to it.
5023
5025
 
5024
- WHEN NOT TO USE: to create a NEW campaign (use \`leadbay_create_campaign\` with \`lead_ids\` seeded); to list campaigns (\`leadbay_list_campaigns\`); to view per-lead progression (\`leadbay_campaign_progression\`); to remove leads (not currently exposed in the MCP \u2014 direct backend \`DELETE /campaigns/{id}/leads\` would be needed).
5026
+ WHEN NOT TO USE: to create a NEW campaign (use \`leadbay_create_campaign\` with \`lead_ids\` seeded); to list campaigns (\`leadbay_list_campaigns\`); to view per-lead progression (\`leadbay_campaign_progression\`); to remove leads (use \`leadbay_remove_leads_from_campaign\`).
5025
5027
 
5026
5028
  **Response**: \`{added: number, already_present: number}\`. Use both counts in the confirmation \u2014 silent dedup hides useful information from the user.
5027
5029
  `;
@@ -6793,6 +6795,48 @@ WHEN NOT TO USE: to change status \u2014 call leadbay_set_epilogue_status with t
6793
6795
 
6794
6796
  This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
6795
6797
  `;
6798
+ var leadbay_remove_leads_from_campaign = `## WHEN TO USE
6799
+
6800
+ Trigger phrases: "remove lead from campaign", "take this out of <campaign>", "remove these from Q2 Push", "delete lead from campaign", "clean up campaign".
6801
+
6802
+ Do NOT use for: "add leads to campaign" \u2192 \`leadbay_add_leads_to_campaign\`; "create a new campaign" \u2192 \`leadbay_create_campaign\`; "list campaigns" \u2192 \`leadbay_list_campaigns\`.
6803
+
6804
+ Prefer when: user wants to detach one or more leads from an existing campaign
6805
+
6806
+ **Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
6807
+
6808
+ Examples that SHOULD invoke this tool:
6809
+ - "Remove the Austin lead from my Q2 Push campaign."
6810
+ - "Take these 3 unqualified leads out of the Limoges Tour."
6811
+ - "Clean up campaign 1f12 \u2014 remove leads that bounced."
6812
+
6813
+ Examples that should NOT invoke this tool (sound similar, route elsewhere):
6814
+ - "Add the top 5 to my Q2 Push."
6815
+ - "Create a new campaign with these 9 leads."
6816
+ - "What campaigns do I have?"
6817
+
6818
+ ## RENDER (quick)
6819
+
6820
+ One-line confirmation: \u2705 Removed N leads from <campaign-name>.
6821
+ Offer NEXT STEPS chip for "View progression" (campaign_progression)
6822
+ or "Add different leads" (add_leads_to_campaign).
6823
+
6824
+ ---
6825
+
6826
+ Detach a list of \`lead_ids\` from an existing campaign. Wraps \`DELETE /campaigns/{id}/leads\`. The backend returns 204 with no body when the mutation succeeds, so this tool returns a synthetic \`{removed}\` count equal to the number of submitted lead IDs.
6827
+
6828
+ **Lead UUID source**: pass UUIDs returned from \`leadbay_pull_leads\`, \`leadbay_campaign_call_sheet\`, or \`leadbay_campaign_progression\`.
6829
+
6830
+ **Why batch**: prefer one call with N leads over N calls with one lead each \u2014 the backend rebuilds campaign membership server-side on each call.
6831
+
6832
+ ---
6833
+
6834
+ WHEN TO USE: the user wants to detach leads from an existing campaign \u2014 e.g. to clean out bounced contacts, remove disqualified leads, or correct an accidental add.
6835
+
6836
+ WHEN NOT TO USE: to add leads (use \`leadbay_add_leads_to_campaign\`); to create a new campaign (\`leadbay_create_campaign\`); to view campaign status (\`leadbay_campaign_progression\`).
6837
+
6838
+ **Response**: \`{removed: number}\` \u2014 synthetic count equal to the number of \`lead_ids\` submitted (backend returns 204 with no body).
6839
+ `;
6796
6840
  var leadbay_remove_pushback = `Clear an active pushback on one or more leads. They re-enter \`leadbay_pull_followups\` immediately. Use when the user changes their mind ("actually let's reach out now"), or when external context shifts (the company announces hiring / funding / a new product that makes them ripe sooner than the deferral window expected).
6797
6841
 
6798
6842
  Bulk-native: pass up to 1000 lead UUIDs per call. No status enum \u2014 pushback is binary (either an active window exists, or it doesn't).
@@ -12328,6 +12372,51 @@ var addLeadsToCampaign = {
12328
12372
  }
12329
12373
  };
12330
12374
 
12375
+ // ../core/dist/composite/remove-leads-from-campaign.js
12376
+ var removeLeadsFromCampaign = {
12377
+ name: "leadbay_remove_leads_from_campaign",
12378
+ annotations: {
12379
+ title: "Remove leads from a campaign",
12380
+ readOnlyHint: false,
12381
+ destructiveHint: true,
12382
+ idempotentHint: true,
12383
+ openWorldHint: true
12384
+ },
12385
+ description: leadbay_remove_leads_from_campaign,
12386
+ optional: true,
12387
+ inputSchema: {
12388
+ type: "object",
12389
+ properties: {
12390
+ campaign_id: {
12391
+ type: "string",
12392
+ description: "Campaign UUID (from leadbay_create_campaign or leadbay_list_campaigns)."
12393
+ },
12394
+ lead_ids: {
12395
+ type: "array",
12396
+ description: "Lead UUIDs to remove. Pass IDs sourced from Leadbay tools; invalid IDs are handled by the backend.",
12397
+ items: { type: "string" },
12398
+ minItems: 1
12399
+ }
12400
+ },
12401
+ required: ["campaign_id", "lead_ids"],
12402
+ additionalProperties: false
12403
+ },
12404
+ outputSchema: {
12405
+ type: "object",
12406
+ properties: {
12407
+ removed: { type: "number", description: "Number of leads submitted for removal (backend returns 204, no per-lead breakdown)." }
12408
+ },
12409
+ required: ["removed"]
12410
+ },
12411
+ execute: async (client, params) => {
12412
+ if (!params.lead_ids || params.lead_ids.length === 0) {
12413
+ throw client.makeError("INVALID_PARAMS", "lead_ids must be a non-empty array", "Pass at least one lead UUID to remove.");
12414
+ }
12415
+ await client.requestVoid("DELETE", `/campaigns/${params.campaign_id}/leads`, { lead_ids: params.lead_ids });
12416
+ return { removed: params.lead_ids.length };
12417
+ }
12418
+ };
12419
+
12331
12420
  // ../core/dist/composite/list-campaigns.js
12332
12421
  var listCampaigns = {
12333
12422
  name: "leadbay_list_campaigns",
@@ -17557,7 +17646,8 @@ var compositeWriteTools = [
17557
17646
  // Campaign write composites — persist a hand-picked cohort of leads.
17558
17647
  // Backend POST endpoints; gated behind LEADBAY_MCP_WRITE=1 in MCP.
17559
17648
  createCampaign,
17560
- addLeadsToCampaign
17649
+ addLeadsToCampaign,
17650
+ removeLeadsFromCampaign
17561
17651
  ];
17562
17652
  var compositeTools = [
17563
17653
  ...compositeReadTools,
@@ -17658,6 +17748,7 @@ export {
17658
17748
  tourPlan,
17659
17749
  createCampaign,
17660
17750
  addLeadsToCampaign,
17751
+ removeLeadsFromCampaign,
17661
17752
  listCampaigns,
17662
17753
  campaignProgression,
17663
17754
  campaignCallSheet,
@@ -102,6 +102,7 @@ import {
102
102
  recallOrderedTitles,
103
103
  refinePrompt,
104
104
  removeEpilogue,
105
+ removeLeadsFromCampaign,
105
106
  removePushback,
106
107
  reportFriction,
107
108
  reportOutreach,
@@ -123,7 +124,7 @@ import {
123
124
  updateLens,
124
125
  updateLensFilter,
125
126
  withAgentMemoryMeta
126
- } from "./chunk-SX4SKXMM.js";
127
+ } from "./chunk-5IL7SC7L.js";
127
128
  export {
128
129
  AgentMemoryCaptureInputSchema,
129
130
  AgentMemoryEntrySchema,
@@ -227,6 +228,7 @@ export {
227
228
  recallOrderedTitles,
228
229
  refinePrompt,
229
230
  removeEpilogue,
231
+ removeLeadsFromCampaign,
230
232
  removePushback,
231
233
  reportFriction,
232
234
  reportOutreach,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leadbay/mcp",
3
- "version": "0.14.1",
3
+ "version": "0.15.0",
4
4
  "mcpName": "io.github.leadbay/leadbay-mcp",
5
5
  "description": "Model Context Protocol (MCP) server for Leadbay — AI lead discovery, qualification, and enrichment for Claude Desktop, Cursor, and Claude Code.",
6
6
  "type": "module",