@sellable/mcp 0.1.523 → 0.1.525

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)
@@ -565,29 +562,47 @@ function sanitizePaidInmail(value) {
565
562
  const originalMaxStalenessSeconds = optionalNumberValue(value.maxStalenessSeconds);
566
563
  const maxStalenessSeconds = normalizePaidInmailMaxStalenessSeconds(value.maxStalenessSeconds);
567
564
  const checkedAt = stringValue(value.checkedAt) ?? null;
565
+ const balance = typeof value.balance === "number" ? value.balance : null;
568
566
  const availableCredits = numberValue(value.availableCredits);
569
567
  const threshold = numberValue(value.threshold);
570
- let status = stringValue(value.status) ?? "missing_credit_facts";
568
+ const originalStatus = stringValue(value.status) ?? "missing_credit_facts";
569
+ let status = originalStatus;
571
570
  let reason = stringValue(value.reason) ?? "";
572
571
  let freshnessPolicy;
573
- if (status === "stale_credit_facts" &&
574
- 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 {
575
586
  status = availableCredits < threshold ? "below_threshold" : "ok";
576
587
  reason =
577
588
  status === "below_threshold"
578
- ? "MCP normalized paid InMail freshness to 4h; cached credits are fresh enough but below threshold."
579
- : "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) {
580
594
  freshnessPolicy = {
581
595
  normalizedBy: "mcp",
582
- minMaxStalenessSeconds: MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS,
596
+ maxStalenessSeconds: MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS,
583
597
  originalMaxStalenessSeconds,
598
+ originalStatus,
584
599
  };
585
600
  }
586
601
  return {
587
602
  status,
588
603
  reason,
589
604
  checkedAt,
590
- balance: typeof value.balance === "number" ? value.balance : null,
605
+ balance,
591
606
  sentSince: numberValue(value.sentSince),
592
607
  availableCredits,
593
608
  threshold,
@@ -617,6 +632,40 @@ function filterFreshPaidInmailRefreshAction(action, paidInmail) {
617
632
  return (action.type !== "refresh_paid_inmail_credits" ||
618
633
  paidInmailNeedsRefresh(paidInmail));
619
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
+ }
620
669
  function sanitizeStructuredSenderPlans(value, selectedBySender) {
621
670
  if (!Array.isArray(value))
622
671
  return [];
@@ -635,6 +684,11 @@ function sanitizeStructuredSenderPlans(value, selectedBySender) {
635
684
  .filter((action) => Boolean(action))
636
685
  .filter((action) => filterFreshPaidInmailRefreshAction(action, paidInmail))
637
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;
638
692
  const manualAlternates = Array.isArray(plan.manualAlternates)
639
693
  ? plan.manualAlternates
640
694
  .map((action, index) => sanitizeStructuredAction(action, selectedBySender, "manual", index + 1))
@@ -681,7 +735,7 @@ function sanitizeStructuredSenderPlans(value, selectedBySender) {
681
735
  paidInmail,
682
736
  sourcePlan: sanitizeSourcePlan(plan.sourcePlan),
683
737
  refillReceipt: sanitizeRefillReceipt(plan.refillReceipt),
684
- nextActions,
738
+ nextActions: effectiveNextActions,
685
739
  manualAlternates,
686
740
  ...(isRecord(plan.emptyState)
687
741
  ? {
@@ -1004,6 +1058,7 @@ export async function getRefillTargetPlan(input = {}) {
1004
1058
  senders: input.senders,
1005
1059
  actionTypes: input.actionTypes,
1006
1060
  approvalMode: input.approvalMode,
1061
+ paidInmailCreditsMaxStalenessSeconds: MCP_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS,
1007
1062
  ...(workspaceId ? { workspaceId } : {}),
1008
1063
  }, workspaceId ?? undefined);
1009
1064
  const sanitized = sanitizeRefillTargetPlanResult(result);
@@ -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.523",
3
+ "version": "0.1.525",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",