@sellable/mcp 0.1.147 → 0.1.149

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.
@@ -9,6 +9,9 @@ export declare function selectSignalPostsForImport<T extends SignalPostForImport
9
9
  }): {
10
10
  posts: T[];
11
11
  estimatedEngagers: number;
12
+ availableEngagers: number;
13
+ targetEngagerCount: number | null;
14
+ targetReached: boolean;
12
15
  limited: boolean;
13
16
  };
14
17
  export type GetProviderPromptInput = {
@@ -343,13 +343,18 @@ export function selectSignalPostsForImport(posts, options) {
343
343
  const normalizedTargetEngagers = normalizePositiveInteger(options.targetEngagerCount);
344
344
  const normalizedMaxPosts = normalizePositiveInteger(options.maxPostsToScrape);
345
345
  if (!normalizedTargetEngagers && !normalizedMaxPosts) {
346
+ const availableEngagers = posts.reduce((sum, post) => sum + post.likes + post.comments, 0);
346
347
  return {
347
348
  posts,
348
- estimatedEngagers: posts.reduce((sum, post) => sum + post.likes + post.comments, 0),
349
+ estimatedEngagers: availableEngagers,
350
+ availableEngagers,
351
+ targetEngagerCount: null,
352
+ targetReached: true,
349
353
  limited: false,
350
354
  };
351
355
  }
352
356
  const ranked = [...posts].sort((a, b) => b.likes + b.comments - (a.likes + a.comments));
357
+ const availableEngagers = ranked.reduce((sum, post) => sum + post.likes + post.comments, 0);
353
358
  const selected = [];
354
359
  let estimatedEngagers = 0;
355
360
  for (const post of ranked) {
@@ -366,6 +371,10 @@ export function selectSignalPostsForImport(posts, options) {
366
371
  return {
367
372
  posts: selected.length > 0 ? selected : ranked,
368
373
  estimatedEngagers,
374
+ availableEngagers,
375
+ targetEngagerCount: normalizedTargetEngagers ?? null,
376
+ targetReached: !normalizedTargetEngagers ||
377
+ estimatedEngagers >= normalizedTargetEngagers,
369
378
  limited: selected.length > 0 && selected.length < posts.length,
370
379
  };
371
380
  }
@@ -2312,12 +2321,19 @@ export async function importLeads(input) {
2312
2321
  maxPostsToScrape,
2313
2322
  });
2314
2323
  const postsToScrape = importSelection.posts;
2324
+ if (!importSelection.targetReached && importSelection.targetEngagerCount) {
2325
+ const capClause = importSelection.availableEngagers >= importSelection.targetEngagerCount
2326
+ ? ` The selected posts can cover the target, but maxPostsToScrape=${normalizePositiveInteger(maxPostsToScrape)} prevents reaching it. Increase maxPostsToScrape or remove that cap.`
2327
+ : " Select/promote more right-content posts, run another narrow Signal Discovery search, or switch to Sales Nav recent activity if the lane cannot produce enough source candidates.";
2328
+ throw new Error(`Signal Discovery selected posts only cover about ${importSelection.estimatedEngagers.toLocaleString("en-US")} visible engagers, below the approved ${importSelection.targetEngagerCount.toLocaleString("en-US")} source-candidate target. Do not scrape this under-capacity post set.${capClause}`);
2329
+ }
2315
2330
  const effectiveHeadlineICPCriteria = headlineICPCriteria && headlineICPCriteria.length > 0
2316
2331
  ? headlineICPCriteria
2317
2332
  : rubricGuidelines;
2318
2333
  // Start the scrape job
2319
2334
  const result = await api.post(`/api/v3/campaigns/${campaignOfferId}/signal-leads/create`, {
2320
2335
  posts: postsToScrape,
2336
+ targetEngagerCount: importSelection.targetEngagerCount ?? undefined,
2321
2337
  ...(effectiveHeadlineICPCriteria &&
2322
2338
  effectiveHeadlineICPCriteria.length > 0
2323
2339
  ? {
@@ -2577,6 +2593,19 @@ export async function confirmLeadList(input) {
2577
2593
  const leadListConfig = leadListMeta.table?.config ?? null;
2578
2594
  const leadListRowCount = leadListMeta.rowCount ?? 0;
2579
2595
  const importProgress = leadListConfig?.importProgress ?? null;
2596
+ const signalSourceTargetLeadCount = resolvedProvider === "signal-discovery" &&
2597
+ typeof leadListConfig?.targetLeadCount === "number" &&
2598
+ Number.isFinite(leadListConfig.targetLeadCount) &&
2599
+ leadListConfig.targetLeadCount > 0
2600
+ ? leadListConfig.targetLeadCount
2601
+ : null;
2602
+ if (resolvedProvider === "signal-discovery" &&
2603
+ signalSourceTargetLeadCount !== null &&
2604
+ leadListConfig?.importStatus === "complete" &&
2605
+ leadListRowCount > 0 &&
2606
+ leadListRowCount < signalSourceTargetLeadCount) {
2607
+ throw new Error(`Signal Discovery source list is under capacity: it completed with ${leadListRowCount.toLocaleString("en-US")} source candidates, below the approved ${signalSourceTargetLeadCount.toLocaleString("en-US")} source-candidate target. Do not import the bounded review batch from this source. Select more posts, rerun Signal Discovery, or move to Sales Nav/Prospeo.`);
2608
+ }
2580
2609
  const progressProcessed = typeof importProgress?.processed === "number"
2581
2610
  ? importProgress.processed
2582
2611
  : null;
@@ -101,7 +101,7 @@ export const readinessToolDefinitions = [
101
101
  },
102
102
  targetLeadCount: {
103
103
  type: "number",
104
- description: "Target number of leads requested. Used as a fallback completion check when status is unavailable.",
104
+ description: "Target number of leads requested. Used as a fallback completion check when status is unavailable. For Signal Discovery, pass the approved source-candidate target; if the completed source list lands below it, the tool returns source_under_capacity instead of ready.",
105
105
  },
106
106
  timeoutMs: {
107
107
  type: "number",
@@ -472,6 +472,23 @@ export async function waitForLeadListReady(input) {
472
472
  }
473
473
  }
474
474
  if ((!requireRows || rowCount > 0) && importComplete) {
475
+ if (provider === "signal-discovery" &&
476
+ typeof targetLeadCount === "number" &&
477
+ rowCount > 0 &&
478
+ rowCount < targetLeadCount) {
479
+ return {
480
+ ready: false,
481
+ reason: "source_under_capacity",
482
+ leadListId,
483
+ provider: provider ?? null,
484
+ attempts,
485
+ elapsedMs: Date.now() - start,
486
+ rowCount,
487
+ status: lastStatus,
488
+ targetLeadCount,
489
+ error: `Signal Discovery completed with ${rowCount.toLocaleString("en-US")} source candidates, below the approved ${targetLeadCount.toLocaleString("en-US")} source-candidate target. Do not confirm this lead list; select more posts, rerun source discovery, or move to Sales Nav/Prospeo.`,
490
+ };
491
+ }
475
492
  return {
476
493
  ready: true,
477
494
  leadListId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.147",
3
+ "version": "0.1.149",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -127,9 +127,11 @@ rate with a conservative cleanup factor, cap the source candidates at the
127
127
  approved provider limit, and select only enough posts to reach that engager
128
128
  count. The planning floor is 10% projected fit after cleanup; if Signal
129
129
  Discovery falls below that floor, do not import the source list. Route back to
130
- find-leads and move to Sales Nav recent activity instead. The subsequent
131
- `confirm_lead_list` call still uses `targetLeadCount: <importLimit>` so only the
132
- bounded review batch enters the campaign table.
130
+ find-leads and move to Sales Nav recent activity instead. After the scrape,
131
+ `wait_for_lead_list_ready` must clear the approved source-candidate target; if
132
+ it reports `source_under_capacity`, do not call `confirm_lead_list`. The
133
+ subsequent `confirm_lead_list` call still uses `targetLeadCount: <importLimit>`
134
+ so only the bounded review batch enters the campaign table.
133
135
 
134
136
  For supplied direct lists, `confirm_lead_list` must receive
135
137
  `targetLeadCount: <importLimit>` or explicit `sourceRowIds` so a 100-row source
@@ -459,6 +459,9 @@ targetLeadCount, targetEngagerCount, maxPostsToScrape })` for the approved
459
459
  source-capacity plan without asking for another yes/no gate. Then
460
460
  `confirm_lead_list` imports only the bounded review batch into the campaign
461
461
  table. Do not confuse the source-candidate target with the review-batch size.
462
+ If the completed source scrape comes back below the approved source-candidate
463
+ target, do not call `confirm_lead_list`; select more posts, rerun source
464
+ discovery, or move to Sales Nav.
462
465
 
463
466
  The promotion/select step is required campaign state. Use post IDs from the
464
467
  current campaign-scoped search result, not stale IDs copied from a source review