@sellable/mcp 0.1.522 → 0.1.524

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.
@@ -330,9 +330,6 @@ function paidRefreshApprovalPacket(params) {
330
330
  const action = params.action.action ?? {};
331
331
  const ids = actionIds(action);
332
332
  const threshold = numberValue(action.oldThreshold) ?? numberValue(action.threshold);
333
- const maxStalenessSeconds = Math.max(numberValue(action.maxStalenessSeconds) ??
334
- numberValue(action.paidInmailCreditsMaxStalenessSeconds) ??
335
- MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS, MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS);
336
333
  return {
337
334
  approvalSource: "explicit_yolo_flag",
338
335
  workspaceId: params.workspaceId ?? null,
@@ -343,7 +340,7 @@ function paidRefreshApprovalPacket(params) {
343
340
  tableId: stringValue(ids.tableId) ?? stringValue(action.tableId) ?? null,
344
341
  columnId: stringValue(ids.columnId) ?? stringValue(action.columnId) ?? null,
345
342
  threshold: threshold ?? null,
346
- maxStalenessSeconds,
343
+ maxStalenessSeconds: MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS,
347
344
  targetPlanFingerprint: targetPlanFingerprint(params.targetPlan),
348
345
  };
349
346
  }
@@ -95,11 +95,8 @@ function numericRecord(value) {
95
95
  function campaignClassificationValue(value) {
96
96
  return value === "sales_nav_cascade" ? "sales_nav_cascade" : undefined;
97
97
  }
98
- function normalizePaidInmailMaxStalenessSeconds(value) {
99
- const numeric = optionalNumberValue(value);
100
- return numeric !== null && numeric > 0
101
- ? Math.max(Math.trunc(numeric), MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS)
102
- : MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS;
98
+ function normalizePaidInmailMaxStalenessSeconds(_value) {
99
+ return MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS;
103
100
  }
104
101
  function isFreshWithinMcpPaidInmailWindow(checkedAt, maxStalenessSeconds) {
105
102
  if (!checkedAt)
@@ -404,6 +401,7 @@ function sanitizeToolInput(value) {
404
401
  : undefined,
405
402
  columnRole: stringValue(value.columnRole),
406
403
  rowSelector: sanitizeRowSelector(value.rowSelector),
404
+ excludeRowIds: stringArray(value.excludeRowIds),
407
405
  oldThreshold: typeof value.oldThreshold === "number" ? value.oldThreshold : undefined,
408
406
  suggestedThreshold: typeof value.suggestedThreshold === "number"
409
407
  ? value.suggestedThreshold
@@ -564,29 +562,47 @@ function sanitizePaidInmail(value) {
564
562
  const originalMaxStalenessSeconds = optionalNumberValue(value.maxStalenessSeconds);
565
563
  const maxStalenessSeconds = normalizePaidInmailMaxStalenessSeconds(value.maxStalenessSeconds);
566
564
  const checkedAt = stringValue(value.checkedAt) ?? null;
565
+ const balance = typeof value.balance === "number" ? value.balance : null;
567
566
  const availableCredits = numberValue(value.availableCredits);
568
567
  const threshold = numberValue(value.threshold);
569
- let status = stringValue(value.status) ?? "missing_credit_facts";
568
+ const originalStatus = stringValue(value.status) ?? "missing_credit_facts";
569
+ let status = originalStatus;
570
570
  let reason = stringValue(value.reason) ?? "";
571
571
  let freshnessPolicy;
572
- if (status === "stale_credit_facts" &&
573
- isFreshWithinMcpPaidInmailWindow(checkedAt, maxStalenessSeconds)) {
572
+ if (originalStatus === "below_threshold") {
573
+ status = "below_threshold";
574
+ }
575
+ else if (!checkedAt || balance === null) {
576
+ status = "missing_credit_facts";
577
+ reason =
578
+ "MCP uses a fixed 4h paid InMail sender-credit freshness window; cached credits are missing.";
579
+ }
580
+ else if (!isFreshWithinMcpPaidInmailWindow(checkedAt, maxStalenessSeconds)) {
581
+ status = "stale_credit_facts";
582
+ reason =
583
+ "MCP uses a fixed 4h paid InMail sender-credit freshness window; cached credits are stale.";
584
+ }
585
+ else {
574
586
  status = availableCredits < threshold ? "below_threshold" : "ok";
575
587
  reason =
576
588
  status === "below_threshold"
577
- ? "MCP normalized paid InMail freshness to 4h; cached credits are fresh enough but below threshold."
578
- : "MCP normalized paid InMail freshness to 4h; cached credits are fresh enough.";
589
+ ? "MCP uses a fixed 4h paid InMail sender-credit freshness window; cached credits are fresh enough but below threshold."
590
+ : "MCP uses a fixed 4h paid InMail sender-credit freshness window; cached credits are fresh enough.";
591
+ }
592
+ if (status !== originalStatus ||
593
+ originalMaxStalenessSeconds !== maxStalenessSeconds) {
579
594
  freshnessPolicy = {
580
595
  normalizedBy: "mcp",
581
- minMaxStalenessSeconds: MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS,
596
+ maxStalenessSeconds: MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS,
582
597
  originalMaxStalenessSeconds,
598
+ originalStatus,
583
599
  };
584
600
  }
585
601
  return {
586
602
  status,
587
603
  reason,
588
604
  checkedAt,
589
- balance: typeof value.balance === "number" ? value.balance : null,
605
+ balance,
590
606
  sentSince: numberValue(value.sentSince),
591
607
  availableCredits,
592
608
  threshold,
@@ -616,6 +632,40 @@ function filterFreshPaidInmailRefreshAction(action, paidInmail) {
616
632
  return (action.type !== "refresh_paid_inmail_credits" ||
617
633
  paidInmailNeedsRefresh(paidInmail));
618
634
  }
635
+ function paidInmailRefreshActionForSenderPlan(plan, paidInmail) {
636
+ if (!paidInmailNeedsRefresh(paidInmail))
637
+ return null;
638
+ const senderId = stringValue(plan.senderId);
639
+ if (!senderId)
640
+ return null;
641
+ const campaignId = stringValue(paidInmail?.campaignId) ?? "unknown";
642
+ const columnId = stringValue(paidInmail?.columnId) ?? "unknown";
643
+ return {
644
+ type: "refresh_paid_inmail_credits",
645
+ senderId,
646
+ senderName: stringValue(plan.senderName) ?? "",
647
+ actionType: "send_inmail_closed",
648
+ campaignId: stringValue(paidInmail?.campaignId),
649
+ campaignName: stringValue(paidInmail?.campaignName),
650
+ tableId: stringValue(paidInmail?.tableId),
651
+ tableName: stringValue(paidInmail?.tableName),
652
+ columnId: stringValue(paidInmail?.columnId),
653
+ availableCredits: numberValue(paidInmail?.availableCredits),
654
+ oldThreshold: numberValue(paidInmail?.threshold),
655
+ maxStalenessSeconds: MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS,
656
+ actionKey: ["refresh_paid_inmail_credits", senderId, campaignId, columnId].join(":"),
657
+ rereadAfter: "get_refill_target_plan",
658
+ reason: stringValue(paidInmail?.reason) ??
659
+ "MCP paid InMail sender-credit facts require refresh.",
660
+ yoloEligible: true,
661
+ ids: {
662
+ senderId,
663
+ campaignId: stringValue(paidInmail?.campaignId),
664
+ tableId: stringValue(paidInmail?.tableId),
665
+ columnId: stringValue(paidInmail?.columnId),
666
+ },
667
+ };
668
+ }
619
669
  function sanitizeStructuredSenderPlans(value, selectedBySender) {
620
670
  if (!Array.isArray(value))
621
671
  return [];
@@ -634,6 +684,11 @@ function sanitizeStructuredSenderPlans(value, selectedBySender) {
634
684
  .filter((action) => Boolean(action))
635
685
  .filter((action) => filterFreshPaidInmailRefreshAction(action, paidInmail))
636
686
  : [];
687
+ const paidRefreshAction = paidInmailRefreshActionForSenderPlan(plan, paidInmail);
688
+ const effectiveNextActions = paidRefreshAction &&
689
+ !nextActions.some((action) => action.type === "refresh_paid_inmail_credits")
690
+ ? [paidRefreshAction, ...nextActions]
691
+ : nextActions;
637
692
  const manualAlternates = Array.isArray(plan.manualAlternates)
638
693
  ? plan.manualAlternates
639
694
  .map((action, index) => sanitizeStructuredAction(action, selectedBySender, "manual", index + 1))
@@ -680,7 +735,7 @@ function sanitizeStructuredSenderPlans(value, selectedBySender) {
680
735
  paidInmail,
681
736
  sourcePlan: sanitizeSourcePlan(plan.sourcePlan),
682
737
  refillReceipt: sanitizeRefillReceipt(plan.refillReceipt),
683
- nextActions,
738
+ nextActions: effectiveNextActions,
684
739
  manualAlternates,
685
740
  ...(isRecord(plan.emptyState)
686
741
  ? {
@@ -15,13 +15,7 @@ const DEFAULT_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS = 4 * 60 * 60;
15
15
  function numberOrNull(value) {
16
16
  return typeof value === "number" && Number.isFinite(value) ? value : null;
17
17
  }
18
- function normalizePaidInmailCreditsMaxStalenessSeconds(...values) {
19
- for (const value of values) {
20
- const numeric = numberOrNull(value);
21
- if (numeric === null || numeric <= 0)
22
- continue;
23
- return Math.max(Math.trunc(numeric), DEFAULT_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS);
24
- }
18
+ function normalizePaidInmailCreditsMaxStalenessSeconds(..._values) {
25
19
  return DEFAULT_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS;
26
20
  }
27
21
  function computeDisplayName(sender) {
@@ -183,11 +177,11 @@ export const senderToolDefinitions = [
183
177
  },
184
178
  maxStalenessSeconds: {
185
179
  type: "number",
186
- description: "Optional cache freshness window in seconds. Values below 4h are clamped to 4h.",
180
+ description: "Optional legacy cache freshness window in seconds. MCP refresh requests use the fixed 4h sender-credit freshness policy.",
187
181
  },
188
182
  paidInmailCreditsMaxStalenessSeconds: {
189
183
  type: "number",
190
- description: "Optional legacy alias for maxStalenessSeconds. Values below 4h are clamped to 4h.",
184
+ description: "Optional legacy alias for maxStalenessSeconds. MCP refresh requests use the fixed 4h sender-credit freshness policy.",
191
185
  },
192
186
  },
193
187
  required: ["senderId", "workspaceId"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.522",
3
+ "version": "0.1.524",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",