@sellable/mcp 0.1.230 → 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
- relaxMcpDomainSeedMatchAll(filters, seedDomains);
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 relaxMcpDomainSeedMatchAll(filters, seedDomains) {
2572
- if (seedDomains.length <= 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.230",
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",
@@ -257,7 +257,7 @@ company/domain plus `company_lookalike.minimum_tier` and simple confirmed
257
257
  attributes, headcount, or industry. Do not add `company_website_search`,
258
258
  `company_keywords`, or `company_icp` until the account sample proves the seed
259
259
  works. Do not send placeholder seed names like `another approved best-customer seed`,
260
- and only use concrete companies or domains you actually resolved. Prefer `seedDomains`
260
+ and only use concrete companies or domains you actually resolved. If another approved seed is referenced but not named, ask for it or run one seed without `match_all`; do not invent a second seed from examples, competitors, or exclusions. Prefer `seedDomains`
261
261
  for single-seed lookalikes. For multi-seed `match_all` lookalikes, use concrete company names unless you already know the exact canonical Prospeo domains; do not mix both in one seeded lookalike call. Do not combine `has_api` and `has_sso` in the first seeded lookalike call; start with `has_api`
262
262
  and refine after a valid account sample if SSO still matters. Do not send `company_website_search.exclude_keywords` without a positive website include signal.
263
263
  Do not use `AI`, `API`, `GTM`, or `SaaS` as company keyword terms.
@@ -432,7 +432,7 @@ Use first for broad persona expansion, ABM/domain targeting, hiring-led targetin
432
432
  attributes, headcount, or industry. Do not add `company_website_search`,
433
433
  `company_keywords`, or `company_icp` until the account sample proves the seed
434
434
  works. Do not send placeholder seed names like `another approved best-customer seed`,
435
- and only use concrete companies or domains you actually resolved. Prefer `seedDomains`
435
+ and only use concrete companies or domains you actually resolved. If another approved seed is referenced but not named, ask for it or run one seed without `match_all`; do not invent a second seed from examples, competitors, or exclusions. Prefer `seedDomains`
436
436
  for single-seed lookalikes. For multi-seed `match_all` lookalikes, use concrete company names unless you already know the exact canonical Prospeo domains; do not mix both in one seeded lookalike call. Do not combine `has_api` and `has_sso` in the first seeded lookalike call; start with
437
437
  `has_api` and refine after a valid account sample if SSO still matters. Do not send `company_website_search.exclude_keywords` without a positive website include signal.
438
438
  - Do not use `company_intent`. Do not invent unsupported support-channel filters
@@ -109,12 +109,28 @@ Avoidable-400 guardrails:
109
109
  - For seeded company lookalikes, keep the first call simple: seed company/domain + `company_lookalike.minimum_tier` + confirmed attributes, headcount, or industry. Do not add `company_website_search`, `company_keywords`, or `company_icp` until the account sample proves the seed works.
110
110
  - For seeded company lookalikes, do not combine `has_api` and `has_sso` in the first call; start with `has_api` and refine after a valid account sample if SSO still matters.
111
111
  - Do not send placeholder seed names such as `another approved best-customer seed`; use only concrete company names or domains you have actually resolved.
112
+ - If the user references another approved seed but does not name it, ask for the
113
+ seed or run a one-seed lookalike without `match_all`; do not invent a second
114
+ seed from examples, competitors, or exclusions.
112
115
  - Prefer seed domains for single-seed lookalikes. For multi-seed `match_all`
113
- lookalikes, use concrete company names unless you already know the exact
114
- canonical Prospeo domains; guessed product or marketing domains can 400 with
115
- `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.
116
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.
117
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.
118
134
 
119
135
  Unsupported/caveats:
120
136
 
@@ -259,6 +275,8 @@ Preference rules:
259
275
  - Run `company_key_customers` as a standalone first-pass filter. Do not combine `company_key_customers` with ICP, website-search, keyword, attribute, industry, or headcount filters until the standalone pass proves useful.
260
276
  - For seeded lookalikes, avoid first-call `company_website_search`, `company_keywords`, and `company_icp`; use resolved seeds plus lookalike tier and simple attributes/headcount/industry first.
261
277
  - For seeded lookalikes, avoid first-call `has_api` + `has_sso`; use one confirmed attribute first, then refine.
278
+ - Do not invent missing approved seeds. If a second seed is not named, ask for it
279
+ or run one seed without `match_all`.
262
280
  - Prefer `seedDomains` for single-seed lookalikes. For multi-seed `match_all`
263
281
  lookalikes, use concrete company names unless you know the exact canonical
264
282
  Prospeo domains; do not mix both in one seeded lookalike call.