@sellable/mcp 0.1.55 → 0.1.56

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.
@@ -314,6 +314,7 @@ export declare const leadToolDefinitions: ({
314
314
  filters: {
315
315
  type: string;
316
316
  description: string;
317
+ items?: undefined;
317
318
  properties?: undefined;
318
319
  };
319
320
  companyFilters: {
@@ -473,6 +474,59 @@ export declare const leadToolDefinitions: ({
473
474
  filters: {
474
475
  type: string;
475
476
  description: string;
477
+ items: {
478
+ type: string;
479
+ properties: {
480
+ type: {
481
+ type: string;
482
+ };
483
+ values: {
484
+ type: string;
485
+ items: {
486
+ type: string;
487
+ properties: {
488
+ id: {
489
+ type: string;
490
+ };
491
+ text: {
492
+ type: string;
493
+ };
494
+ selectionType: {
495
+ type: string;
496
+ enum: string[];
497
+ };
498
+ parent: {
499
+ type: string;
500
+ properties: {
501
+ id: {
502
+ type: string;
503
+ };
504
+ };
505
+ };
506
+ };
507
+ required: string[];
508
+ };
509
+ };
510
+ rangeValue: {
511
+ type: string;
512
+ properties: {
513
+ min: {
514
+ type: string;
515
+ };
516
+ max: {
517
+ type: string;
518
+ };
519
+ };
520
+ required: string[];
521
+ };
522
+ selectedSubFilter: {
523
+ anyOf: {
524
+ type: string;
525
+ }[];
526
+ };
527
+ };
528
+ required: string[];
529
+ };
476
530
  properties?: undefined;
477
531
  };
478
532
  keywords: {
@@ -1347,6 +1401,7 @@ export declare const leadToolDefinitions: ({
1347
1401
  };
1348
1402
  };
1349
1403
  };
1404
+ items?: undefined;
1350
1405
  };
1351
1406
  page: {
1352
1407
  type: string;
@@ -2015,6 +2070,7 @@ export declare const leadToolDefinitions: ({
2015
2070
  })[];
2016
2071
  export declare function searchApollo(input: ApolloSearchInput): Promise<unknown>;
2017
2072
  export declare function searchSalesNav(input: SalesNavSearchInput): Promise<unknown>;
2073
+ export declare function normalizeSalesNavSearchInput(input: SalesNavSearchInput): SalesNavSearchInput;
2018
2074
  export declare function searchProspeo(input: ProspeoSearchInput): Promise<any>;
2019
2075
  export declare function loadCsvDomains(input: LoadCsvDomainsInput): Promise<{
2020
2076
  ok: boolean;
@@ -32,6 +32,46 @@ const prospeoFilterValueSchema = {
32
32
  exclude: { type: "array", items: { type: "string" } },
33
33
  },
34
34
  };
35
+ const salesNavFilterValueSchema = {
36
+ type: "object",
37
+ properties: {
38
+ id: { type: "string" },
39
+ text: { type: "string" },
40
+ selectionType: {
41
+ type: "string",
42
+ enum: ["INCLUDED", "EXCLUDED"],
43
+ },
44
+ parent: {
45
+ type: "object",
46
+ properties: {
47
+ id: { type: "string" },
48
+ },
49
+ },
50
+ },
51
+ required: ["id", "text", "selectionType"],
52
+ };
53
+ const salesNavFilterSchema = {
54
+ type: "object",
55
+ properties: {
56
+ type: { type: "string" },
57
+ values: {
58
+ type: "array",
59
+ items: salesNavFilterValueSchema,
60
+ },
61
+ rangeValue: {
62
+ type: "object",
63
+ properties: {
64
+ min: { type: "number" },
65
+ max: { type: "number" },
66
+ },
67
+ required: ["min", "max"],
68
+ },
69
+ selectedSubFilter: {
70
+ anyOf: [{ type: "string" }, { type: "number" }],
71
+ },
72
+ },
73
+ required: ["type"],
74
+ };
35
75
  const prospeoRangeFilterSchema = {
36
76
  type: "object",
37
77
  description: "Numeric range filter (min/max)",
@@ -540,7 +580,8 @@ export const leadToolDefinitions = [
540
580
  properties: {
541
581
  filters: {
542
582
  type: "array",
543
- description: "Sales Nav filter array",
583
+ description: "Sales Nav filter array. Pass filter objects, not JSON strings.",
584
+ items: salesNavFilterSchema,
544
585
  },
545
586
  keywords: {
546
587
  type: "string",
@@ -1312,7 +1353,30 @@ export async function searchSalesNav(input) {
1312
1353
  campaignOfferId: input?.campaignOfferId,
1313
1354
  });
1314
1355
  const api = getApi();
1315
- return api.post(`/api/v3/sales-nav/search`, input);
1356
+ return api.post(`/api/v3/sales-nav/search`, normalizeSalesNavSearchInput(input));
1357
+ }
1358
+ export function normalizeSalesNavSearchInput(input) {
1359
+ return {
1360
+ ...input,
1361
+ filters: normalizeSalesNavFilters(input.filters),
1362
+ };
1363
+ }
1364
+ function normalizeSalesNavFilters(filters) {
1365
+ return filters.map((filter, index) => {
1366
+ if (typeof filter !== "string") {
1367
+ return filter;
1368
+ }
1369
+ try {
1370
+ const parsed = JSON.parse(filter);
1371
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1372
+ throw new Error("not an object");
1373
+ }
1374
+ return parsed;
1375
+ }
1376
+ catch {
1377
+ throw new Error(`Invalid Sales Nav filter at index ${index}. Pass filter objects, not JSON strings.`);
1378
+ }
1379
+ });
1316
1380
  }
1317
1381
  export async function searchProspeo(input) {
1318
1382
  if (input?.campaignOfferId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.55",
3
+ "version": "0.1.56",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -423,7 +423,7 @@ should test for this campaign. Those can run in parallel and usually take
423
423
  batching is available, batch independent tool reads/lookups only. If real
424
424
  parallel execution is not available or not allowed, run the same DAG
425
425
  sequentially and use honest copy: `I’ll tighten the filter first, then draft
426
- the message from the same sample.` Never say `kicking off two workstreams`,
426
+ the message from the same sample.` Never say `kicking off two workstreams`,
427
427
  `in parallel`, or `background` unless parallel branches were actually
428
428
  launched.
429
429
  - Never run a downstream stage until the active `flow.v2.json` step's
@@ -574,14 +574,20 @@ Required behavior:
574
574
  - Branch C: Prospeo / domains only when the campaign has a domain/account
575
575
  path or the user supplied domains. Estimate email/contact scale and call out
576
576
  weaker LinkedIn activity.
577
- If the host cannot run these branches in parallel, run them sequentially and
578
- do not claim they ran in parallel. If only one source angle is credible, say
579
- that and run the best primary source plus one cheap fallback/quality check
580
- when available. For Signals-first campaigns, search multiple Signals keyword
581
- lanes and fetch top-post engagers in parallel when tool batching allows it.
582
- For Sales Nav-first or Prospeo/account-first campaigns, run multiple preview
583
- lanes for that provider in parallel and use Signals only as a warmth/quality
584
- check when relevant
577
+ If the host cannot run these branches in parallel, run them sequentially and
578
+ do not claim they ran in parallel. If only one source angle is credible, say
579
+ that and run the best primary source plus one cheap fallback/quality check
580
+ when available. For Signals-first campaigns, search multiple Signals keyword
581
+ lanes and fetch top-post engagers in parallel when tool batching allows it.
582
+ For Sales Nav-first or Prospeo/account-first campaigns, run multiple preview
583
+ lanes for that provider in parallel and use Signals only as a warmth/quality
584
+ check when relevant
585
+ - after every Sales Nav preview, validate that filters actually applied before
586
+ using the lane in `lead-review.md`: `searchUrl` should include filters, the
587
+ first page should match the intended roles/companies, and the result count
588
+ should be plausible. If Sales Nav returns a giant unfiltered pool, drops the
589
+ filters, or errors after one clean retry, call it out as a provider/tool
590
+ issue and do not recommend Sales Nav.
585
591
  - for Signals-first campaigns, raw post search volume is only inventory, not
586
592
  lead volume. `492 post results` means matching LinkedIn posts found across
587
593
  keyword lanes; it does not mean 492 prospects. The source decision must name
@@ -40,7 +40,7 @@ must contain:
40
40
  - a plain confirmation that the campaign was created and the approved brief was
41
41
  saved
42
42
  - a one-line orientation sentence: `Open this to watch lead sourcing, rubric
43
- scoring, and messaging populate live.`
43
+ scoring, and messaging populate live.`
44
44
  - the `watchUrl` captured from the `create_campaign` response
45
45
 
46
46
  Example skeleton:
@@ -293,6 +293,13 @@ Use first when LinkedIn activity plus tighter role / company filters matter.
293
293
  - When you fall back from the recently-posted slice, say clearly that the posted filter was tested, explain why it was dropped, and keep the best non-posted lane as the source of truth.
294
294
  - Use `RECENTLY_CHANGED_JOBS` when job-change activity is part of the targeting thesis.
295
295
  - If quality is poor, tighten the lane with role, industry, seniority, geography, or activity filters.
296
+ - After each Sales Nav preview, sanity-check the result before using it in the
297
+ decision: the returned `searchUrl` should include filters, the first page
298
+ should visibly match the intended roles and companies, and the count should
299
+ be plausible. If filters did not apply, the search errors, or the total looks
300
+ obviously unfiltered, retry once with clean filter objects. If it still fails,
301
+ mark Sales Nav as a provider/tool issue and do not include it as a winning
302
+ source.
296
303
  - If quality is good but scale is too small, widen the lane by adding/removing roles, expanding industries, widening headcount bands, or relaxing activity constraints.
297
304
  - Use as many smart refinement steps as needed within the remaining probe budget instead of returning the first under-scaled recipe.
298
305
  - If the lane is meant to support scalable outbound, do not stop at a merely borderline workable result when obvious expansion steps remain.
@@ -114,6 +114,7 @@ NEVER do these:
114
114
 
115
115
  <filter_format>
116
116
  ALL filter values MUST include id, text, and selectionType.
117
+ Pass `filters` as an array of objects. Do not stringify filters as JSON.
117
118
 
118
119
  CORRECT:
119
120
 
@@ -145,6 +146,21 @@ WRONG:
145
146
 
146
147
  </filter_format>
147
148
 
149
+ <result_validation>
150
+ After every `search_sales_nav` call, sanity-check the returned result before
151
+ using it in a source decision:
152
+
153
+ - `searchUrl` should include a `filters:List(...)` query, not only `keywords`.
154
+ - The sample rows should visibly match the intended role/company lane.
155
+ - The total result count should be plausible for the filters used.
156
+
157
+ If filters did not apply, the search errors, or the result count is obviously
158
+ unfiltered, do not present Sales Nav as a valid lane. Retry once with clean
159
+ filter objects if the issue was formatting. If it still fails, say plainly:
160
+ "Sales Nav did not apply the filters cleanly, so I’m not using it for this
161
+ decision." Then continue with Signals or Prospeo if they have usable evidence.
162
+ </result_validation>
163
+
148
164
  <static_filters>
149
165
  These filters need NO lookup - use these IDs directly:
150
166