@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.
package/dist/tools/leads.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
2572
|
-
if (
|
|
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
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
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
|
-
|
|
2808
|
-
|
|
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
|
@@ -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
|
|
114
|
-
|
|
115
|
-
`match_all
|
|
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.
|