@sellable/mcp 0.1.231 → 0.1.232

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.
@@ -1497,7 +1497,7 @@ export const leadToolDefinitions = [
1497
1497
  },
1498
1498
  {
1499
1499
  name: "search_prospeo_companies",
1500
- description: 'Search Prospeo for company/account results through the public /search-company lane. Requires get_provider_prompt({ provider: "prospeo" }) first. Use this first for company lookalike/account asks such as "find companies like Red Rover that use AI and have an API", "find founders at companies like our best customers", or "find accounts in the news about funding or partnerships". Results are accounts, not finished people leads. Review the returned accounts with the user, then call confirm_prospeo_company_accounts with the companySearchToken to create a domainFilterId before using search_prospeo for people at those accounts. Supports company lookalike, confirmed AI Attributes including pricing, company news, awards, website search, products/services, integrations, key customers, operating languages, Google discovery, headcount by location, structured company ICP, and experimental website traffic/key executive filters. Do not invent company_oids; when seedCompanies or seedDomains are present, omit company_oids and let the backend resolve real Prospeo IDs. company_intent and support-channel guesses like phone/email/chat/ticket/social are unsupported. Public API rows do not include lookalike tier/score/reason. ' +
1500
+ description: 'Search Prospeo for company/account results through the public /search-company lane. Requires get_provider_prompt({ provider: "prospeo" }) first. Use this first for company lookalike/account asks such as "find companies like Red Rover that use AI and have an API", "find founders at companies like our best customers", or "find accounts in the news about funding or partnerships". Results are accounts, not finished people leads. Review the returned accounts with the user, then call confirm_prospeo_company_accounts with the companySearchToken to create a domainFilterId before using search_prospeo for people at those accounts. Supports company lookalike, confirmed AI Attributes including pricing, company news, awards, website search, products/services, integrations, key customers, operating languages, Google discovery, headcount by location, structured company ICP, and experimental website traffic/key executive filters. Do not invent company_oids; when seedCompanies or seedDomains are present, omit company_oids and let the backend resolve real Prospeo IDs. company_intent and support-channel guesses like phone/email/chat/ticket/social are unsupported. Public API rows do not include lookalike tier/score/reason. If output includes omittedFilters or warnings, report them as truthful MCP/backend safety handling rather than claiming Prospeo enforced those omitted constraints. ' +
1501
1501
  prospeoCompanyAccountWorkflowGuidance,
1502
1502
  inputSchema: {
1503
1503
  type: "object",
@@ -1630,7 +1630,7 @@ export const leadToolDefinitions = [
1630
1630
  },
1631
1631
  {
1632
1632
  name: "search_prospeo",
1633
- description: 'Search Prospeo for people using filters and optional domainFilterId. Requires get_provider_prompt({ provider: "prospeo" }) first. Use Prospeo first for hiring-led targeting because it supports company_job_posting_hiring_for and company_job_posting_quantity; Sales Nav does not filter companies by hiring role. When targeting known accounts, call load_csv_domains for CSV-on-disk workflows or save_domain_filters for pasted/raw domain lists, then pass domainFilterId. For company lookalike/account asks, call search_prospeo_companies first, review accounts, then confirm_prospeo_company_accounts to create a domainFilterId before using search_prospeo for people. Raw domain inputs and company-name targeting are NOT supported in this MCP tool. Strategy: start with 2-3 high-signal filters (title/seniority + industry or domainFilterId + headcount), add job-posting filters for hiring-led campaigns, then tighten one filter at a time. For security, AppSec, SOC, RevOps, Demand Gen, and similar function-specific lanes, do not widen with bare seniority labels like "Head" or "Director" alone; pair them with explicit function-title keywords and inspect the sample for off-function `Head of X` leakage. Prefer person location over company HQ unless HQ is explicitly needed. `campaignOfferId` routing rule: OMIT campaignOfferId ONLY in pre-mint Phase 84 `find leads` discovery mode (validating ICP before the commit gate). In every other context — post-mint lead additions, operator-driven searches on a live campaign, any search where the intent is to persist results to a specific campaign — you MUST pass campaignOfferId so the search shows up in that campaign\'s Contact Search panel. Post-mint create-campaign-v2 watch runs MUST pass currentStep: "prospeo". Omitting campaignOfferId post-mint orphans the search from the UI. Returns normalized results with pagination.',
1633
+ description: 'Search Prospeo for people using filters and optional domainFilterId. Requires get_provider_prompt({ provider: "prospeo" }) first. Use Prospeo first for hiring-led targeting because it supports company_job_posting_hiring_for and company_job_posting_quantity; Sales Nav does not filter companies by hiring role. When targeting known accounts, call load_csv_domains for CSV-on-disk workflows or save_domain_filters for pasted/raw domain lists, then pass domainFilterId. For company lookalike/account asks, call search_prospeo_companies first, review accounts, then confirm_prospeo_company_accounts to create a domainFilterId before using search_prospeo for people. Raw domain inputs and company-name targeting are NOT supported in this MCP tool. Strategy: start with 2-3 high-signal filters (title/seniority + industry or domainFilterId + headcount), add job-posting filters for hiring-led campaigns, then tighten one filter at a time. For security, AppSec, SOC, RevOps, Demand Gen, and similar function-specific lanes, do not widen with bare seniority labels like "Head" or "Director" alone; pair them with explicit function-title keywords and inspect the sample for off-function `Head of X` leakage. If output includes warnings/fallback, report that the MCP retried a rejected precise title request with a safer domain-filter people search instead of claiming the original precise filter succeeded. Prefer person location over company HQ unless HQ is explicitly needed. `campaignOfferId` routing rule: OMIT campaignOfferId ONLY in pre-mint Phase 84 `find leads` discovery mode (validating ICP before the commit gate). In every other context — post-mint lead additions, operator-driven searches on a live campaign, any search where the intent is to persist results to a specific campaign — you MUST pass campaignOfferId so the search shows up in that campaign\'s Contact Search panel. Post-mint create-campaign-v2 watch runs MUST pass currentStep: "prospeo". Omitting campaignOfferId post-mint orphans the search from the UI. Returns normalized results with pagination.',
1634
1634
  inputSchema: {
1635
1635
  type: "object",
1636
1636
  properties: {
@@ -2367,7 +2367,7 @@ export async function searchProspeoCompanies(input) {
2367
2367
  page: safeInput?.page,
2368
2368
  sort: safeInput?.sort,
2369
2369
  }));
2370
- return compactProspeoCompanySearchResponse(response);
2370
+ return compactProspeoCompanySearchResponse(response, safeInput.omittedFilters ?? []);
2371
2371
  }
2372
2372
  export async function confirmProspeoCompanyAccounts(input) {
2373
2373
  if (!input?.companySearchToken) {
@@ -2404,6 +2404,7 @@ function removeUndefinedValues(input) {
2404
2404
  return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
2405
2405
  }
2406
2406
  function normalizeProspeoCompanySearchInputForMcp(input) {
2407
+ const omittedFilters = [];
2407
2408
  const seedDomains = normalizeMcpSeeds(input.seedDomains, "domain");
2408
2409
  const seedCompanies = seedDomains.length > 0
2409
2410
  ? []
@@ -2418,22 +2419,28 @@ function normalizeProspeoCompanySearchInputForMcp(input) {
2418
2419
  typeof lookalike === "object" &&
2419
2420
  !Array.isArray(lookalike) &&
2420
2421
  "company_oids" in lookalike) {
2422
+ omittedFilters.push({
2423
+ field: "company_lookalike.company_oids",
2424
+ reason: "Dropped model-supplied company_oids because seedDomains/seedCompanies require backend resolution of real Prospeo IDs.",
2425
+ value: lookalike.company_oids,
2426
+ });
2421
2427
  delete lookalike.company_oids;
2422
2428
  removeEmptyObjectFilter(filters, "company_lookalike");
2423
2429
  }
2424
2430
  }
2425
- relaxMcpSeedMatchAll(filters, seedDomains.length + seedCompanies.length);
2426
- normalizeMcpCompanyKeywords(filters);
2427
- normalizeMcpCompanyWebsiteSearch(filters);
2431
+ normalizeMcpSeedMatchAll(filters, inferConcreteLookalikeSeedCount(filters, seedDomains.length + seedCompanies.length), omittedFilters);
2432
+ normalizeMcpCompanyKeywords(filters, omittedFilters);
2433
+ normalizeMcpCompanyWebsiteSearch(filters, omittedFilters);
2428
2434
  normalizeMcpCompanyIcp(filters);
2429
2435
  stripMcpDuplicateCompanyIndustry(filters);
2430
- stripMcpSeededLookalikeRiskyRefinements(filters, hasSeeds || hasMcpCompanyLookalikeOids(filters));
2436
+ stripMcpSeededLookalikeRiskyRefinements(filters, hasSeeds || hasMcpCompanyLookalikeOids(filters), omittedFilters);
2431
2437
  stripMcpKeyCustomerCompanionFilters(filters);
2432
2438
  return {
2433
2439
  ...input,
2434
2440
  seedCompanies: seedCompanies.length > 0 ? seedCompanies : undefined,
2435
2441
  seedDomains: seedDomains.length > 0 ? seedDomains : undefined,
2436
2442
  filters,
2443
+ omittedFilters: omittedFilters.length > 0 ? omittedFilters : undefined,
2437
2444
  };
2438
2445
  }
2439
2446
  function normalizeProspeoSearchInputForMcp(input) {
@@ -2446,7 +2453,7 @@ function normalizeProspeoSearchInputForMcp(input) {
2446
2453
  filters,
2447
2454
  };
2448
2455
  }
2449
- function normalizeMcpCompanyKeywords(filters) {
2456
+ function normalizeMcpCompanyKeywords(filters, omittedFilters = []) {
2450
2457
  const keywords = filters.company_keywords;
2451
2458
  if (!isPlainObject(keywords)) {
2452
2459
  return;
@@ -2460,6 +2467,13 @@ function normalizeMcpCompanyKeywords(filters) {
2460
2467
  keywords.include = uniqueStrings(validInclude);
2461
2468
  }
2462
2469
  else {
2470
+ if (keywords.exclude !== undefined) {
2471
+ omittedFilters.push({
2472
+ field: "company_keywords.exclude",
2473
+ reason: "Dropped exclude-only company_keywords because Prospeo company search requires a positive keyword include signal.",
2474
+ value: keywords.exclude,
2475
+ });
2476
+ }
2463
2477
  delete keywords.include;
2464
2478
  delete keywords.exclude;
2465
2479
  }
@@ -2468,7 +2482,7 @@ function normalizeMcpCompanyKeywords(filters) {
2468
2482
  }
2469
2483
  removeEmptyObjectFilter(filters, "company_keywords");
2470
2484
  }
2471
- function normalizeMcpCompanyWebsiteSearch(filters) {
2485
+ function normalizeMcpCompanyWebsiteSearch(filters, omittedFilters = []) {
2472
2486
  const websiteSearch = filters.company_website_search;
2473
2487
  if (!isPlainObject(websiteSearch)) {
2474
2488
  return;
@@ -2479,6 +2493,13 @@ function normalizeMcpCompanyWebsiteSearch(filters) {
2479
2493
  normalizeStringArray(websiteSearch.urls).length > 0 ||
2480
2494
  Object.entries(websiteSearch).some(([key, value]) => key.startsWith("has_") && typeof value === "boolean" && value);
2481
2495
  if (!hasPositiveWebsiteSignal) {
2496
+ if (websiteSearch.exclude_keywords !== undefined) {
2497
+ omittedFilters.push({
2498
+ field: "company_website_search.exclude_keywords",
2499
+ reason: "Dropped website-search exclusions because Prospeo needs a positive website signal before exclude_keywords are safe.",
2500
+ value: websiteSearch.exclude_keywords,
2501
+ });
2502
+ }
2482
2503
  delete websiteSearch.exclude_keywords;
2483
2504
  }
2484
2505
  removeEmptyObjectFilter(filters, "company_website_search");
@@ -2568,11 +2589,27 @@ function stripMcpDuplicateCompanyIndustry(filters) {
2568
2589
  delete filters.company_industry;
2569
2590
  }
2570
2591
  }
2571
- function relaxMcpSeedMatchAll(filters, seedCount) {
2572
- if (seedCount <= 1 || !isPlainObject(filters.company_lookalike)) {
2592
+ function inferConcreteLookalikeSeedCount(filters, explicitSeedCount) {
2593
+ if (explicitSeedCount > 0) {
2594
+ return explicitSeedCount;
2595
+ }
2596
+ const lookalike = filters.company_lookalike;
2597
+ if (!isPlainObject(lookalike) || !Array.isArray(lookalike.company_oids)) {
2598
+ return 0;
2599
+ }
2600
+ return lookalike.company_oids.filter((oid) => typeof oid === "string" && /^cccc[a-z0-9]+$/i.test(oid)).length;
2601
+ }
2602
+ function normalizeMcpSeedMatchAll(filters, concreteSeedCount, omittedFilters = []) {
2603
+ if (!isPlainObject(filters.company_lookalike)) {
2573
2604
  return;
2574
2605
  }
2575
- if (filters.company_lookalike.match_all === true) {
2606
+ if (filters.company_lookalike.match_all === true &&
2607
+ concreteSeedCount < 2) {
2608
+ omittedFilters.push({
2609
+ field: "company_lookalike.match_all",
2610
+ reason: "Dropped match_all because fewer than two concrete approved lookalike seeds remained after MCP seed normalization.",
2611
+ value: true,
2612
+ });
2576
2613
  delete filters.company_lookalike.match_all;
2577
2614
  }
2578
2615
  }
@@ -2582,7 +2619,7 @@ function hasMcpCompanyLookalikeOids(filters) {
2582
2619
  Array.isArray(lookalike.company_oids) &&
2583
2620
  lookalike.company_oids.some((oid) => typeof oid === "string" && oid.length > 0));
2584
2621
  }
2585
- function stripMcpSeededLookalikeRiskyRefinements(filters, hasSeeds) {
2622
+ function stripMcpSeededLookalikeRiskyRefinements(filters, hasSeeds, omittedFilters = []) {
2586
2623
  if (!hasSeeds || !isPlainObject(filters.company_lookalike)) {
2587
2624
  return;
2588
2625
  }
@@ -2593,17 +2630,43 @@ function stripMcpSeededLookalikeRiskyRefinements(filters, hasSeeds) {
2593
2630
  !filters.company_industry) {
2594
2631
  filters.company_industry = { include: icp.industries };
2595
2632
  }
2596
- delete filters.company_icp;
2597
- delete filters.company_keywords;
2598
- delete filters.company_website_search;
2599
- simplifyMcpSeededLookalikeAttributes(filters);
2633
+ if (filters.company_icp !== undefined) {
2634
+ omittedFilters.push({
2635
+ field: "company_icp",
2636
+ reason: "Dropped company_icp from seeded lookalike account search; use it as a later refinement after the seed returns a safe account sample.",
2637
+ value: filters.company_icp,
2638
+ });
2639
+ delete filters.company_icp;
2640
+ }
2641
+ if (filters.company_keywords !== undefined) {
2642
+ omittedFilters.push({
2643
+ field: "company_keywords",
2644
+ reason: "Dropped company_keywords from seeded lookalike account search to avoid Prospeo vendor 400s; use keywords as a later refinement after the seed works.",
2645
+ value: filters.company_keywords,
2646
+ });
2647
+ delete filters.company_keywords;
2648
+ }
2649
+ if (filters.company_website_search !== undefined) {
2650
+ omittedFilters.push({
2651
+ field: "company_website_search",
2652
+ reason: "Dropped company_website_search from seeded lookalike account search to avoid Prospeo vendor 400s; use website search as a later refinement after the seed works.",
2653
+ value: filters.company_website_search,
2654
+ });
2655
+ delete filters.company_website_search;
2656
+ }
2657
+ simplifyMcpSeededLookalikeAttributes(filters, omittedFilters);
2600
2658
  }
2601
- function simplifyMcpSeededLookalikeAttributes(filters) {
2659
+ function simplifyMcpSeededLookalikeAttributes(filters, omittedFilters = []) {
2602
2660
  const attributes = filters.company_attributes;
2603
2661
  if (!isPlainObject(attributes)) {
2604
2662
  return;
2605
2663
  }
2606
2664
  if (attributes.has_api === true && attributes.has_sso === true) {
2665
+ omittedFilters.push({
2666
+ field: "company_attributes.has_sso",
2667
+ reason: "Dropped has_sso from the first seeded lookalike call because has_api + has_sso is safer as a two-step refinement.",
2668
+ value: true,
2669
+ });
2607
2670
  delete attributes.has_sso;
2608
2671
  }
2609
2672
  }
@@ -2694,7 +2757,7 @@ function storeProspeoCompanySearchTokenRef(token) {
2694
2757
  function resolveProspeoCompanySearchTokenRef(token) {
2695
2758
  return prospeoCompanySearchTokenRefs.get(token) ?? token;
2696
2759
  }
2697
- function compactProspeoCompanySearchResponse(response) {
2760
+ function compactProspeoCompanySearchResponse(response, omittedFilters = []) {
2698
2761
  if (!response || typeof response !== "object") {
2699
2762
  return response;
2700
2763
  }
@@ -2704,7 +2767,7 @@ function compactProspeoCompanySearchResponse(response) {
2704
2767
  const sampleResults = accountResults
2705
2768
  .slice(0, 10)
2706
2769
  .map(compactProspeoCompanyAccount);
2707
- return {
2770
+ return removeUndefinedValues({
2708
2771
  success: response.success ?? true,
2709
2772
  totalCount: typeof response.totalCount === "number"
2710
2773
  ? response.totalCount
@@ -2723,9 +2786,19 @@ function compactProspeoCompanySearchResponse(response) {
2723
2786
  },
2724
2787
  requestedFilters: response.normalizedFilters ?? response.filters ?? {},
2725
2788
  warnings: Array.isArray(response.warnings) ? response.warnings : [],
2789
+ omittedFilters: omittedFilters.length > 0
2790
+ ? sanitizeMcpFilterAdjustments(omittedFilters)
2791
+ : undefined,
2726
2792
  companySearchToken: storeProspeoCompanySearchTokenRef(response.companySearchToken),
2727
2793
  nextStep: "Review accounts, then call confirm_prospeo_company_accounts with companySearchToken and selectedCompanyIds. These accounts are not people leads yet.",
2728
- };
2794
+ });
2795
+ }
2796
+ function sanitizeMcpFilterAdjustments(adjustments) {
2797
+ return adjustments.map((adjustment) => removeUndefinedValues({
2798
+ field: adjustment.field,
2799
+ reason: adjustment.reason,
2800
+ value: adjustment.value,
2801
+ }));
2729
2802
  }
2730
2803
  function compactProspeoCompanyAccount(account) {
2731
2804
  const compact = {
@@ -2804,10 +2877,99 @@ export async function searchProspeo(input) {
2804
2877
  throw new Error("search_prospeo does not accept filters.company.websites or filters.company.names. For known accounts, resolve names/domains into a domainFilterId with load_csv_domains or save_domain_filters. For company/account lookalikes, use search_prospeo_companies first, then confirm_prospeo_company_accounts.");
2805
2878
  }
2806
2879
  const safeInput = normalizeProspeoSearchInputForMcp(input);
2807
- const response = await api.post(`/api/v3/prospeo/search`, safeInput);
2808
- return compactProspeoSearchResponse(response);
2880
+ try {
2881
+ const response = await api.post(`/api/v3/prospeo/search`, safeInput);
2882
+ return compactProspeoSearchResponse(response);
2883
+ }
2884
+ catch (error) {
2885
+ const fallbackInput = buildProspeoPeopleSearchFallbackInput(safeInput, error);
2886
+ if (!fallbackInput) {
2887
+ throw error;
2888
+ }
2889
+ const fallbackResponse = await api.post(`/api/v3/prospeo/search`, fallbackInput);
2890
+ const keyword = String(fallbackInput.filters.person_name_or_job_title ?? "title keyword");
2891
+ return compactProspeoSearchResponse(fallbackResponse, {
2892
+ warnings: [
2893
+ `Prospeo rejected precise person-title filters for this domainFilterId; retried with person_name_or_job_title "${keyword}" plus seniority fallback.`,
2894
+ ],
2895
+ fallback: {
2896
+ reason: "precise_person_title_filters_rejected",
2897
+ originalStatus: getErrorStatus(error),
2898
+ filters: fallbackInput.filters,
2899
+ },
2900
+ });
2901
+ }
2902
+ }
2903
+ function getErrorStatus(error) {
2904
+ if (!error || typeof error !== "object") {
2905
+ return undefined;
2906
+ }
2907
+ const status = error.status;
2908
+ return typeof status === "number" ? status : undefined;
2909
+ }
2910
+ function buildProspeoPeopleSearchFallbackInput(input, error) {
2911
+ if (getErrorStatus(error) !== 400 || !input.domainFilterId) {
2912
+ return null;
2913
+ }
2914
+ const filters = input.filters;
2915
+ const jobTitle = filters?.person_job_title;
2916
+ if (!isPlainObject(jobTitle) || filters.person_name_or_job_title) {
2917
+ return null;
2918
+ }
2919
+ const keyword = inferProspeoPeopleFallbackKeyword(jobTitle);
2920
+ if (!keyword) {
2921
+ return null;
2922
+ }
2923
+ const fallbackFilters = {
2924
+ person_name_or_job_title: keyword,
2925
+ person_seniority: {
2926
+ include: [
2927
+ "C-Suite",
2928
+ "Vice President",
2929
+ "Head",
2930
+ "Director",
2931
+ "Manager",
2932
+ ],
2933
+ },
2934
+ max_person_per_company: typeof filters.max_person_per_company === "number"
2935
+ ? filters.max_person_per_company
2936
+ : 1,
2937
+ };
2938
+ if (isPlainObject(filters.person_location_search)) {
2939
+ fallbackFilters.person_location_search = filters.person_location_search;
2940
+ }
2941
+ return removeUndefinedValues({
2942
+ campaignOfferId: input.campaignOfferId,
2943
+ domainFilterId: input.domainFilterId,
2944
+ page: input.page,
2945
+ searchName: input.searchName,
2946
+ currentStep: input.currentStep,
2947
+ confirmed: input.confirmed,
2948
+ filters: fallbackFilters,
2949
+ });
2950
+ }
2951
+ function inferProspeoPeopleFallbackKeyword(jobTitle) {
2952
+ const text = [
2953
+ typeof jobTitle.boolean_search === "string" ? jobTitle.boolean_search : "",
2954
+ ...normalizeStringArray(jobTitle.include),
2955
+ ].join(" ");
2956
+ if (/security|ciso|appsec|soc/i.test(text))
2957
+ return "Security";
2958
+ if (/platform/i.test(text))
2959
+ return "Platform";
2960
+ if (/product/i.test(text))
2961
+ return "Product";
2962
+ if (/growth|gtm|go[-\s]?to[-\s]?market/i.test(text))
2963
+ return "Growth";
2964
+ if (/sales|revenue/i.test(text))
2965
+ return "Sales";
2966
+ if (/marketing|demand/i.test(text))
2967
+ return "Marketing";
2968
+ if (/engineering|technical|developer/i.test(text))
2969
+ return "Engineering";
2970
+ return null;
2809
2971
  }
2810
- function compactProspeoSearchResponse(response) {
2972
+ function compactProspeoSearchResponse(response, metadata = {}) {
2811
2973
  if (!response || typeof response !== "object") {
2812
2974
  return response;
2813
2975
  }
@@ -2815,7 +2977,7 @@ function compactProspeoSearchResponse(response) {
2815
2977
  ? response.people.results
2816
2978
  : [];
2817
2979
  const sampleResults = peopleResults.slice(0, 10).map(compactProspeoPerson);
2818
- return {
2980
+ return removeUndefinedValues({
2819
2981
  success: response.success ?? true,
2820
2982
  searchId: response.searchId ?? null,
2821
2983
  searchName: response.searchName ?? null,
@@ -2836,7 +2998,9 @@ function compactProspeoSearchResponse(response) {
2836
2998
  sampleCount: sampleResults.length,
2837
2999
  results: sampleResults,
2838
3000
  },
2839
- };
3001
+ warnings: metadata.warnings,
3002
+ fallback: metadata.fallback,
3003
+ });
2840
3004
  }
2841
3005
  function compactProspeoPerson(person) {
2842
3006
  const currentRole = Array.isArray(person?._prospeo?.job_history)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.231",
3
+ "version": "0.1.232",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -113,11 +113,24 @@ Avoidable-400 guardrails:
113
113
  seed or run a one-seed lookalike without `match_all`; do not invent a second
114
114
  seed from examples, competitors, or exclusions.
115
115
  - Prefer seed domains for single-seed lookalikes. For multi-seed `match_all`
116
- lookalikes, use concrete company names unless you already know the exact
117
- canonical Prospeo domains; guessed product or marketing domains can 400 with
118
- `match_all`. Do not mix `seedDomains` and `seedCompanies` in the same call.
116
+ lookalikes, use two or more concrete approved seed domains or company names;
117
+ never infer the second seed from examples, competitors, or exclusions. If
118
+ Prospeo rejects `match_all`, the backend may retry without `match_all` and
119
+ return a warning. Report that as a vendor fallback, not as proof that strict
120
+ `match_all` succeeded. Do not mix `seedDomains` and `seedCompanies` in the
121
+ same call.
119
122
  - Do not send `company_website_search.exclude_keywords` without a positive website include signal.
123
+ - If `search_prospeo_companies` returns `omittedFilters`, those fields were
124
+ intentionally not sent to Prospeo to avoid known public-API failures. Report
125
+ them as safety handling and keep the account sample separate from people
126
+ leads; use a follow-up refinement or manual review for those omitted
127
+ constraints instead of claiming Prospeo enforced them.
120
128
  - For post-confirm people search, prefer `person_job_title.boolean_search` for long role synonym lists instead of many `person_job_title.include` values plus broad department/seniority filters.
129
+ - If `search_prospeo` returns a `warnings` array or `fallback` object after a
130
+ domain-filter people search, report the retry honestly. The MCP may retry a
131
+ Prospeo 400 from precise title filters with `person_name_or_job_title` plus
132
+ seniority so the run produces a usable sample without hiding the vendor
133
+ limitation.
121
134
 
122
135
  Unsupported/caveats:
123
136