@sellable/mcp 0.1.152 → 0.1.153

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.
@@ -2638,6 +2638,7 @@ export declare function confirmLeadList(input: ConfirmLeadListInput): Promise<{
2638
2638
  remainingCopyRowCount: number;
2639
2639
  copyStillRunning: boolean;
2640
2640
  trimmedOverflowRowCount: number;
2641
+ sourceShortfall: boolean;
2641
2642
  };
2642
2643
  message: string;
2643
2644
  }>;
@@ -2741,13 +2741,6 @@ export async function confirmLeadList(input) {
2741
2741
  leadListConfig.targetLeadCount > 0
2742
2742
  ? leadListConfig.targetLeadCount
2743
2743
  : null;
2744
- if (resolvedProvider === "signal-discovery" &&
2745
- signalSourceTargetLeadCount !== null &&
2746
- leadListConfig?.importStatus === "complete" &&
2747
- leadListRowCount > 0 &&
2748
- leadListRowCount < signalSourceTargetLeadCount) {
2749
- 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 confirm this source list. Select more posts, rerun Signal Discovery, or move to Sales Nav/Prospeo.`);
2750
- }
2751
2744
  const progressProcessed = typeof importProgress?.processed === "number"
2752
2745
  ? importProgress.processed
2753
2746
  : null;
@@ -2955,6 +2948,9 @@ export async function confirmLeadList(input) {
2955
2948
  remainingCopyRowCount: remainingRowCount,
2956
2949
  copyStillRunning,
2957
2950
  trimmedOverflowRowCount: 0,
2951
+ sourceShortfall: signalSourceTargetLeadCount !== null &&
2952
+ leadListRowCount > 0 &&
2953
+ leadListRowCount < signalSourceTargetLeadCount,
2958
2954
  },
2959
2955
  message: copyStillRunning
2960
2956
  ? `First ${importedRowCount.toLocaleString("en-US")} source candidate${importedRowCount === 1 ? "" : "s"} are copied into the campaign table and the rest of the confirmed source list is still copying. Use the first ${keptReviewRowCount.toLocaleString("en-US")} as the review/process sample. The watched campaign is now on filter-choice; ask add filters vs skip filters before loading post-lead workers.`
@@ -273,6 +273,7 @@ export declare function waitForLeadListReady(input: WaitForLeadListReadyInput):
273
273
  status?: undefined;
274
274
  targetLeadCount?: undefined;
275
275
  error?: undefined;
276
+ sourceShortfall?: undefined;
276
277
  warning?: undefined;
277
278
  } | {
278
279
  ready: boolean;
@@ -285,6 +286,7 @@ export declare function waitForLeadListReady(input: WaitForLeadListReadyInput):
285
286
  status: string;
286
287
  targetLeadCount: number | undefined;
287
288
  error?: undefined;
289
+ sourceShortfall?: undefined;
288
290
  warning?: undefined;
289
291
  } | {
290
292
  ready: boolean;
@@ -297,6 +299,7 @@ export declare function waitForLeadListReady(input: WaitForLeadListReadyInput):
297
299
  status: string | null;
298
300
  targetLeadCount: number | undefined;
299
301
  error: string;
302
+ sourceShortfall?: undefined;
300
303
  warning?: undefined;
301
304
  } | {
302
305
  ready: boolean;
@@ -309,6 +312,7 @@ export declare function waitForLeadListReady(input: WaitForLeadListReadyInput):
309
312
  status: string;
310
313
  targetLeadCount: number | undefined;
311
314
  error: string | null;
315
+ sourceShortfall?: undefined;
312
316
  warning?: undefined;
313
317
  } | {
314
318
  ready: boolean;
@@ -319,9 +323,10 @@ export declare function waitForLeadListReady(input: WaitForLeadListReadyInput):
319
323
  rowCount: number;
320
324
  status: string | null;
321
325
  targetLeadCount: number | null;
326
+ sourceShortfall: boolean;
327
+ warning: string | undefined;
322
328
  reason?: undefined;
323
329
  error?: undefined;
324
- warning?: undefined;
325
330
  } | {
326
331
  ready: boolean;
327
332
  reason: string;
@@ -334,5 +339,6 @@ export declare function waitForLeadListReady(input: WaitForLeadListReadyInput):
334
339
  targetLeadCount: number | undefined;
335
340
  warning: string | undefined;
336
341
  error: string | undefined;
342
+ sourceShortfall?: undefined;
337
343
  }>;
338
344
  export {};
@@ -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. 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.",
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 still returns ready with a source_shortfall warning so the confirmed list can be copied and the first review sample can proceed.",
105
105
  },
106
106
  timeoutMs: {
107
107
  type: "number",
@@ -472,23 +472,12 @@ 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" &&
475
+ const signalSourceShortfallTarget = provider === "signal-discovery" && typeof targetLeadCount === "number"
476
+ ? targetLeadCount
477
+ : null;
478
+ const signalSourceShortfall = signalSourceShortfallTarget !== null &&
477
479
  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
- }
480
+ rowCount < signalSourceShortfallTarget;
492
481
  return {
493
482
  ready: true,
494
483
  leadListId,
@@ -498,6 +487,10 @@ export async function waitForLeadListReady(input) {
498
487
  rowCount,
499
488
  status: lastStatus,
500
489
  targetLeadCount: targetLeadCount ?? null,
490
+ sourceShortfall: signalSourceShortfall,
491
+ warning: signalSourceShortfall
492
+ ? `Signal Discovery completed with ${rowCount.toLocaleString("en-US")} source candidates, below the approved ${signalSourceShortfallTarget.toLocaleString("en-US")} source-candidate target. Confirm/copy the completed list for the first review sample, then add more approved posts or switch provider if the sample quality/scale is not enough.`
493
+ : undefined,
501
494
  };
502
495
  }
503
496
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.152",
3
+ "version": "0.1.153",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -192,8 +192,8 @@ should be a compact `## Source Recommendation` block with:
192
192
  - a selected-post table with post author/topic, why it fits, and visible
193
193
  engagement
194
194
  - total visible pool and estimated good-fit pool
195
- - first pass: build the source list, copy it into the campaign, then process
196
- only the first 15 leads before scaling
195
+ - first pass: build the source list, copy all confirmed source rows into the
196
+ campaign, then process only the first 15 rows before scaling
197
197
  - fallback: switch to Sales Nav recent activity if sampled/projected fit falls
198
198
  below 10%, or if the review batch is vendor-heavy, agency-heavy, or off-ICP
199
199
 
@@ -209,7 +209,7 @@ Use Signal Discovery first.
209
209
  **Working assumption:** ~20% of raw post engagers become good-fit prospects<br>
210
210
  **Engagers needed:** ~1,500 raw engagers<br>
211
211
  **Planning floor:** continue only when sampled/projected fit is at least 10% after cleanup; below that, switch to Sales Nav recent activity<br>
212
- **Review checkpoint:** after the source list exists, copy the confirmed source list into the campaign and process only the first 15 leads for fit and message review<br>
212
+ **Review checkpoint:** after the source list exists, copy the entire confirmed source list into the campaign and process only the first 15 rows for fit and message review<br>
213
213
 
214
214
  ### Selected posts
215
215
 
@@ -230,8 +230,9 @@ This gives enough volume to target ~300 good-fit prospects after cleanup, while
230
230
  keeping the source tied to people already engaging with Claude Code outbound /
231
231
  AI-native sales workflows.
232
232
 
233
- **First pass:** build the source list, copy it into the campaign, then process
234
- only the first 15 leads so we can inspect quality before scaling.
233
+ **First pass:** build the source list, copy all confirmed source rows into the
234
+ campaign, then process only the first 15 rows so we can inspect quality before
235
+ scaling.
235
236
 
236
237
  **Fallback:** if sampled/projected fit falls below 10%, or if the review batch
237
238
  is too vendor-heavy, agency-heavy, or off-ICP, switch to Sales Nav recent
@@ -130,13 +130,23 @@ raw engagers. If the sampled fit rate is stronger or weaker, use the sampled
130
130
  rate with a conservative cleanup factor, cap the source candidates at the
131
131
  approved provider limit, and select only enough posts to reach that engager
132
132
  count. The planning floor is 10% projected fit after cleanup; if Signal
133
- Discovery falls below that floor, do not import the source list. Route back to
134
- find-leads and move to Sales Nav recent activity instead. After the scrape,
135
- `wait_for_lead_list_ready` must clear the approved source-candidate target; if
136
- it reports `source_under_capacity`, do not call `confirm_lead_list`. The
137
- subsequent `confirm_lead_list` call uses `reviewBatchLimit: 15`. It copies the
138
- confirmed source rows into the campaign table and returns only the first 15 rows
139
- as the review/process sample.
133
+ Discovery falls below that floor during the pre-scrape sample, revise the source
134
+ plan before importing. After an approved scrape starts, do not discard a nonempty
135
+ completed source list just because it lands below the source-candidate target:
136
+ confirm/copy the completed list for the first review sample, then add more posts
137
+ or switch provider if the sample quality or scale is not enough. The subsequent
138
+ `confirm_lead_list` call uses `reviewBatchLimit: 15`. It copies the confirmed
139
+ source rows into the campaign table and returns only the first 15 rows as the
140
+ review/process sample.
141
+
142
+ Harvest-specific interpretation: Signal Discovery post comments and reactions
143
+ are paged from Harvest at about 100 records per page, with production caps of
144
+ 1,500 reactions and 300 comments per selected post. The source list stores
145
+ headline-passing candidates after that scrape. Therefore `71 source rows` means
146
+ 71 candidates passed headline/list filtering and were inserted; it does not
147
+ mean the post scrape stopped after 71 raw engagers. Treat raw visible engagement,
148
+ raw fetched pages, inserted source rows, and the 15-row review/process sample as
149
+ separate numbers.
140
150
 
141
151
  For supplied direct lists, `confirm_lead_list` should receive
142
152
  `reviewBatchLimit: 15` so the campaign table can hold the confirmed list while
@@ -24,12 +24,34 @@ When the user asks to find posts or start searching, **IMMEDIATELY begin Round 1
24
24
 
25
25
  <lead_target>
26
26
 
27
- - **Default: provider max import count** (use this unless user specifies otherwise; see `lead-import-limits.json`, currently 2,500)
28
- - **Maximum: provider max import count**
27
+ - **Default create-campaign target: ~300 good-fit prospects after cleanup,
28
+ enrichment, and fit filtering.**
29
+ - **Working assumption when no stronger sample exists: ~20% of raw post
30
+ engagers become good-fit prospects, so plan around ~1,500 raw engagers.**
31
+ - Provider/import caps are internal execution limits, not customer-facing
32
+ promises. Do not tell the user the internal provider cap.
29
33
  - Quality > Quantity: a few hundred active users > 1000 potentially inactive profiles
30
34
  - Focus on ACTIVE platform users who will actually see and respond to outreach
31
35
  </lead_target>
32
36
 
37
+ <harvest_scrape_contract>
38
+
39
+ Production Signal Discovery uses Harvest for post comments and reactions.
40
+ Harvest returns about 100 comments or reactions per page. The source scrape
41
+ pages through Harvest up to the configured per-post caps, currently 1,500
42
+ reactions and 300 comments per selected post.
43
+
44
+ Important interpretation rule: a source list row count is the number of
45
+ headline-passing candidates inserted after scraping and filtering, not the raw
46
+ number of engagers fetched. If a selected post has 1,200 visible engagers and
47
+ the source list lands at 71 rows, read that as 71 headline-passing inserted
48
+ rows, not as "only 71 engagers were scraped." Do not reject a completed
49
+ non-empty source list solely because inserted rows are below the raw engager
50
+ target; confirm/copy it for the 15-row review sample, then decide whether to add
51
+ more posts or switch lanes based on sample quality and scale.
52
+
53
+ </harvest_scrape_contract>
54
+
33
55
  <multi_round_search>
34
56
 
35
57
  ## Multi-Round Search Strategy
@@ -98,12 +120,12 @@ You must estimate:
98
120
  - expected good-fit prospects per right-content post after dedupe/cleanup
99
121
  - `postsNeededForTarget`
100
122
  - number of right-content posts needed to reach the target good-fit lead
101
- count, defaulting to 150 for Signal Discovery unless the campaign says
123
+ count, defaulting to 300 for Signal Discovery unless the campaign says
102
124
  otherwise
103
125
  - `requiredEngagersToScrape`
104
- - `ceil(targetGoodFitLeads / sampledFitRate)`; for example 15 good-fit
105
- prospects per 100 engagers means a 150-good-fit source target needs about
106
- 1,000 engagers scraped
126
+ - `ceil(targetGoodFitLeads / sampledFitRate)`; for example the default
127
+ planning assumption of 20 good-fit prospects per 100 engagers means a
128
+ 300-good-fit source target needs about 1,500 raw engagers scraped
107
129
  - `planningFloor`
108
130
  - minimum acceptable sampled/projected fit rate after conservative cleanup;
109
131
  default 10%. Below this, do not scale Signal Discovery.
@@ -119,8 +141,8 @@ Use conservative logic:
119
141
  those posts in the watched Signal Discovery UI before sampling engagers
120
142
  - call `fetch_post_engagers`
121
143
  - fetch only a representative first sample, not a full scrape
122
- - default to `limit: 25` for one post or `limit: 20` per post when checking two posts
123
- - inspect roughly 25-40 engagers total
144
+ - default to a fast first sample of roughly 15 people for one post; when
145
+ comparing multiple posts, inspect roughly 15-40 people total
124
146
  - score them against `headlineICPCriteria` or a rough yes/no headline rubric
125
147
  - use headline and display-name cues only for this spot check; do not enrich people during viability estimation
126
148
  - use that sampled pass rate to extrapolate conservatively
@@ -130,7 +152,7 @@ Use conservative logic:
130
152
  - stale posts
131
153
  - adjacent communities that are not actual buyers
132
154
  4. Explain the pass-through briefly and explicitly:
133
- - preferred: "`sampledCount`: 40, `passCount`: 9, `passRate`: ~22%, `goodFitPer100Engagers`: ~22 before cleanup / ~13-16 after cleanup, `requiredEngagersToScrape`: ~940-1,150 for a 150-good-fit target after cleanup, `avgReachableEngagersPerPost`: 240, `goodFitProspectsPerPost`: ~31-38, `postsNeededForTarget`: ~4-5, `recentStrongPostCount`: 15-25, `freshEnoughPostCount`: 8-12, `projectedRange`: 150-190 from selected posts"
155
+ - preferred: "`sampledCount`: 40, `passCount`: 9, `passRate`: ~22%, `goodFitPer100Engagers`: ~22 before cleanup / ~13-16 after cleanup, `requiredEngagersToScrape`: ~1,875-2,300 for a 300-good-fit target after cleanup, `avgReachableEngagersPerPost`: 240, `goodFitProspectsPerPost`: ~31-38, `postsNeededForTarget`: ~8-10, `recentStrongPostCount`: 15-25, `freshEnoughPostCount`: 8-12, `projectedRange`: 300-380 from selected posts"
134
156
  - fallback: "I could not fetch engagers, so this is inferred from post and author quality only"
135
157
  5. If the evidence is too weak, say so and return a low-confidence estimate instead of pretending precision.
136
158
  6. If sampled/projected fit after cleanup is below the 10% planning floor,
@@ -385,7 +407,7 @@ For `create-campaign-v2` source approval, do not treat the default
385
407
  `selectionTarget` of 3 posts as enough by itself. Before the final source
386
408
  recommendation, estimate source capacity from real sample math:
387
409
 
388
- - source target good-fit leads (default 150 for Signal Discovery unless the
410
+ - source target good-fit leads (default 300 for Signal Discovery unless the
389
411
  campaign says otherwise)
390
412
  - eligible right-content posts by lane/content type
391
413
  - reachable engagers from those posts
@@ -461,8 +483,10 @@ source-capacity plan without asking for another yes/no gate. Then
461
483
  the campaign table and returns the first 15 review/process rows. Do not confuse
462
484
  the source-candidate target with the review sample size.
463
485
  If the completed source scrape comes back below the approved source-candidate
464
- target, do not call `confirm_lead_list`; select more posts, rerun source
465
- discovery, or move to Sales Nav.
486
+ target but has usable rows, still call `confirm_lead_list` so the completed
487
+ source list is copied into the campaign and the first 15 rows can be reviewed.
488
+ Treat the shortfall as a scale warning: add more approved posts or move to Sales
489
+ Nav only after the first sample proves the lane is too thin or off-ICP.
466
490
 
467
491
  The promotion/select step is required campaign state. Use post IDs from the
468
492
  current campaign-scoped search result, not stale IDs copied from a source review