@sellable/mcp 0.1.225 → 0.1.227
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
|
@@ -63,6 +63,47 @@ const prospeoCompanySearchTokenRefs = new Map();
|
|
|
63
63
|
let prospeoCompanySearchTokenRefCounter = 0;
|
|
64
64
|
const PROSPEO_COMPANY_SEARCH_TOKEN_REF_PREFIX = "mcp-prospeo-company-search-token:";
|
|
65
65
|
const MAX_PROSPEO_COMPANY_SEARCH_TOKEN_REFS = 200;
|
|
66
|
+
const PROSPEO_LARGE_TITLE_INCLUDE_BOOLEAN_THRESHOLD = 8;
|
|
67
|
+
const prospeoCompanyKeywordIncludeAliases = new Map([
|
|
68
|
+
["ai", "artificial intelligence"],
|
|
69
|
+
["api", "application programming interface"],
|
|
70
|
+
["gtm", "go to market"],
|
|
71
|
+
["saas", "software as a service"],
|
|
72
|
+
]);
|
|
73
|
+
const prospeoCompanyIcpRegionAliases = new Map([
|
|
74
|
+
["north america", ["United States", "Canada"]],
|
|
75
|
+
["na", ["United States", "Canada"]],
|
|
76
|
+
["us", ["United States"]],
|
|
77
|
+
["usa", ["United States"]],
|
|
78
|
+
["u.s.", ["United States"]],
|
|
79
|
+
["u.s.a.", ["United States"]],
|
|
80
|
+
]);
|
|
81
|
+
const prospeoCompanyIcpDepartments = new Set([
|
|
82
|
+
"Consumers",
|
|
83
|
+
"Customer Success",
|
|
84
|
+
"Data",
|
|
85
|
+
"Design",
|
|
86
|
+
"Engineering",
|
|
87
|
+
"Finance",
|
|
88
|
+
"HR",
|
|
89
|
+
"IT",
|
|
90
|
+
"Legal",
|
|
91
|
+
"Marketing",
|
|
92
|
+
"Operations",
|
|
93
|
+
"Procurement",
|
|
94
|
+
"SMB Owners",
|
|
95
|
+
"Sales",
|
|
96
|
+
"Security",
|
|
97
|
+
]);
|
|
98
|
+
const prospeoKeyCustomerCompanionFilters = [
|
|
99
|
+
"company_attributes",
|
|
100
|
+
"company_headcount_range",
|
|
101
|
+
"company_headcount_custom",
|
|
102
|
+
"company_industry",
|
|
103
|
+
"company_keywords",
|
|
104
|
+
"company_website_search",
|
|
105
|
+
"company_icp",
|
|
106
|
+
];
|
|
66
107
|
const prospeoFilterValueSchema = {
|
|
67
108
|
type: "object",
|
|
68
109
|
description: "Include/exclude list filter (values must match Prospeo enums)",
|
|
@@ -2363,11 +2404,12 @@ function removeUndefinedValues(input) {
|
|
|
2363
2404
|
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
|
|
2364
2405
|
}
|
|
2365
2406
|
function normalizeProspeoCompanySearchInputForMcp(input) {
|
|
2407
|
+
const seedCompanies = normalizeMcpSeeds(input.seedCompanies, "company");
|
|
2408
|
+
const seedDomains = normalizeMcpSeeds(input.seedDomains, "domain");
|
|
2366
2409
|
const filters = input?.filters && typeof input.filters === "object"
|
|
2367
2410
|
? clonePlainObject(input.filters)
|
|
2368
2411
|
: {};
|
|
2369
|
-
const hasSeeds =
|
|
2370
|
-
(input.seedDomains?.length ?? 0) > 0;
|
|
2412
|
+
const hasSeeds = seedCompanies.length > 0 || seedDomains.length > 0;
|
|
2371
2413
|
if (hasSeeds) {
|
|
2372
2414
|
const lookalike = filters.company_lookalike;
|
|
2373
2415
|
if (lookalike &&
|
|
@@ -2375,13 +2417,233 @@ function normalizeProspeoCompanySearchInputForMcp(input) {
|
|
|
2375
2417
|
!Array.isArray(lookalike) &&
|
|
2376
2418
|
"company_oids" in lookalike) {
|
|
2377
2419
|
delete lookalike.company_oids;
|
|
2420
|
+
removeEmptyObjectFilter(filters, "company_lookalike");
|
|
2378
2421
|
}
|
|
2379
2422
|
}
|
|
2423
|
+
normalizeMcpCompanyKeywords(filters);
|
|
2424
|
+
normalizeMcpCompanyWebsiteSearch(filters);
|
|
2425
|
+
normalizeMcpCompanyIcp(filters);
|
|
2426
|
+
stripMcpDuplicateCompanyIndustry(filters);
|
|
2427
|
+
stripMcpSeededLookalikeRiskyRefinements(filters, hasSeeds);
|
|
2428
|
+
stripMcpKeyCustomerCompanionFilters(filters);
|
|
2380
2429
|
return {
|
|
2381
2430
|
...input,
|
|
2431
|
+
seedCompanies: seedCompanies.length > 0 ? seedCompanies : undefined,
|
|
2432
|
+
seedDomains: seedDomains.length > 0 ? seedDomains : undefined,
|
|
2382
2433
|
filters,
|
|
2383
2434
|
};
|
|
2384
2435
|
}
|
|
2436
|
+
function normalizeProspeoSearchInputForMcp(input) {
|
|
2437
|
+
const filters = input?.filters && typeof input.filters === "object"
|
|
2438
|
+
? clonePlainObject(input.filters)
|
|
2439
|
+
: {};
|
|
2440
|
+
normalizeMcpPersonJobTitle(filters);
|
|
2441
|
+
return {
|
|
2442
|
+
...input,
|
|
2443
|
+
filters,
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
function normalizeMcpCompanyKeywords(filters) {
|
|
2447
|
+
const keywords = filters.company_keywords;
|
|
2448
|
+
if (!isPlainObject(keywords)) {
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2451
|
+
const include = normalizeStringArray(keywords.include).map((keyword) => {
|
|
2452
|
+
const alias = prospeoCompanyKeywordIncludeAliases.get(keyword.toLowerCase());
|
|
2453
|
+
return alias ?? keyword;
|
|
2454
|
+
});
|
|
2455
|
+
const validInclude = include.filter((keyword) => keyword.length >= 3);
|
|
2456
|
+
if (validInclude.length > 0) {
|
|
2457
|
+
keywords.include = uniqueStrings(validInclude);
|
|
2458
|
+
}
|
|
2459
|
+
else {
|
|
2460
|
+
delete keywords.include;
|
|
2461
|
+
delete keywords.exclude;
|
|
2462
|
+
}
|
|
2463
|
+
if (keywords.exclude !== undefined && validInclude.length === 0) {
|
|
2464
|
+
delete keywords.exclude;
|
|
2465
|
+
}
|
|
2466
|
+
removeEmptyObjectFilter(filters, "company_keywords");
|
|
2467
|
+
}
|
|
2468
|
+
function normalizeMcpCompanyWebsiteSearch(filters) {
|
|
2469
|
+
const websiteSearch = filters.company_website_search;
|
|
2470
|
+
if (!isPlainObject(websiteSearch)) {
|
|
2471
|
+
return;
|
|
2472
|
+
}
|
|
2473
|
+
const includeKeywords = normalizeStringArray(websiteSearch.include_keywords);
|
|
2474
|
+
const hasPositiveWebsiteSignal = includeKeywords.length > 0 ||
|
|
2475
|
+
normalizeStringArray(websiteSearch.url_contains).length > 0 ||
|
|
2476
|
+
normalizeStringArray(websiteSearch.urls).length > 0 ||
|
|
2477
|
+
Object.entries(websiteSearch).some(([key, value]) => key.startsWith("has_") && typeof value === "boolean" && value);
|
|
2478
|
+
if (!hasPositiveWebsiteSignal) {
|
|
2479
|
+
delete websiteSearch.exclude_keywords;
|
|
2480
|
+
}
|
|
2481
|
+
removeEmptyObjectFilter(filters, "company_website_search");
|
|
2482
|
+
}
|
|
2483
|
+
function normalizeMcpCompanyIcp(filters) {
|
|
2484
|
+
const icp = filters.company_icp;
|
|
2485
|
+
if (!isPlainObject(icp)) {
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2488
|
+
const scope = typeof icp.geographic_scope === "string"
|
|
2489
|
+
? icp.geographic_scope.trim()
|
|
2490
|
+
: "";
|
|
2491
|
+
const scopeMarkets = expandMcpCompanyIcpMarket(scope);
|
|
2492
|
+
if (scopeMarkets.length > 0) {
|
|
2493
|
+
icp.geographic_scope = "multi_country";
|
|
2494
|
+
icp.geographic_markets = uniqueStrings([
|
|
2495
|
+
...scopeMarkets,
|
|
2496
|
+
...normalizeMcpCompanyIcpMarkets(icp.geographic_markets),
|
|
2497
|
+
]);
|
|
2498
|
+
}
|
|
2499
|
+
else if (scope &&
|
|
2500
|
+
scope !== "single_country" &&
|
|
2501
|
+
scope !== "multi_country") {
|
|
2502
|
+
delete icp.geographic_scope;
|
|
2503
|
+
}
|
|
2504
|
+
if (icp.geographic_markets !== undefined) {
|
|
2505
|
+
icp.geographic_markets = normalizeMcpCompanyIcpMarkets(icp.geographic_markets);
|
|
2506
|
+
if (Array.isArray(icp.geographic_markets) &&
|
|
2507
|
+
icp.geographic_markets.length === 0) {
|
|
2508
|
+
delete icp.geographic_markets;
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
normalizeMcpCompanyIcpDepartments(icp);
|
|
2512
|
+
removeEmptyObjectFilter(filters, "company_icp");
|
|
2513
|
+
}
|
|
2514
|
+
function normalizeMcpCompanyIcpDepartments(icp) {
|
|
2515
|
+
const departments = icp.departments;
|
|
2516
|
+
if (Array.isArray(departments)) {
|
|
2517
|
+
const valid = departments
|
|
2518
|
+
.filter((department) => typeof department === "string")
|
|
2519
|
+
.filter((department) => prospeoCompanyIcpDepartments.has(department));
|
|
2520
|
+
if (valid.length > 0) {
|
|
2521
|
+
icp.departments = uniqueStrings(valid);
|
|
2522
|
+
}
|
|
2523
|
+
else {
|
|
2524
|
+
delete icp.departments;
|
|
2525
|
+
}
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2528
|
+
if (!isPlainObject(departments)) {
|
|
2529
|
+
return;
|
|
2530
|
+
}
|
|
2531
|
+
const include = normalizeStringArray(departments.include).filter((department) => prospeoCompanyIcpDepartments.has(department));
|
|
2532
|
+
const exclude = normalizeStringArray(departments.exclude).filter((department) => prospeoCompanyIcpDepartments.has(department));
|
|
2533
|
+
if (include.length > 0) {
|
|
2534
|
+
departments.include = uniqueStrings(include);
|
|
2535
|
+
}
|
|
2536
|
+
else {
|
|
2537
|
+
delete departments.include;
|
|
2538
|
+
}
|
|
2539
|
+
if (exclude.length > 0) {
|
|
2540
|
+
departments.exclude = uniqueStrings(exclude);
|
|
2541
|
+
}
|
|
2542
|
+
else {
|
|
2543
|
+
delete departments.exclude;
|
|
2544
|
+
}
|
|
2545
|
+
if (departments.include === undefined && departments.exclude === undefined) {
|
|
2546
|
+
delete icp.departments;
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
function normalizeMcpCompanyIcpMarkets(input) {
|
|
2550
|
+
const markets = normalizeStringArray(input);
|
|
2551
|
+
return uniqueStrings(markets.flatMap(expandMcpCompanyIcpMarket));
|
|
2552
|
+
}
|
|
2553
|
+
function expandMcpCompanyIcpMarket(market) {
|
|
2554
|
+
const trimmed = market.trim();
|
|
2555
|
+
if (!trimmed) {
|
|
2556
|
+
return [];
|
|
2557
|
+
}
|
|
2558
|
+
return prospeoCompanyIcpRegionAliases.get(trimmed.toLowerCase()) ?? [trimmed];
|
|
2559
|
+
}
|
|
2560
|
+
function stripMcpDuplicateCompanyIndustry(filters) {
|
|
2561
|
+
const icp = filters.company_icp;
|
|
2562
|
+
if (isPlainObject(icp) &&
|
|
2563
|
+
Array.isArray(icp.industries) &&
|
|
2564
|
+
icp.industries.length > 0) {
|
|
2565
|
+
delete filters.company_industry;
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
function stripMcpSeededLookalikeRiskyRefinements(filters, hasSeeds) {
|
|
2569
|
+
if (!hasSeeds || !isPlainObject(filters.company_lookalike)) {
|
|
2570
|
+
return;
|
|
2571
|
+
}
|
|
2572
|
+
const icp = filters.company_icp;
|
|
2573
|
+
if (isPlainObject(icp) &&
|
|
2574
|
+
Array.isArray(icp.industries) &&
|
|
2575
|
+
icp.industries.length > 0 &&
|
|
2576
|
+
!filters.company_industry) {
|
|
2577
|
+
filters.company_industry = { include: icp.industries };
|
|
2578
|
+
}
|
|
2579
|
+
delete filters.company_icp;
|
|
2580
|
+
delete filters.company_keywords;
|
|
2581
|
+
delete filters.company_website_search;
|
|
2582
|
+
}
|
|
2583
|
+
function stripMcpKeyCustomerCompanionFilters(filters) {
|
|
2584
|
+
const keyCustomers = filters.company_key_customers;
|
|
2585
|
+
if (!isPlainObject(keyCustomers)) {
|
|
2586
|
+
return;
|
|
2587
|
+
}
|
|
2588
|
+
const include = normalizeStringArray(keyCustomers.include);
|
|
2589
|
+
if (include.length === 0) {
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
2592
|
+
for (const filterName of prospeoKeyCustomerCompanionFilters) {
|
|
2593
|
+
delete filters[filterName];
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
function normalizeMcpPersonJobTitle(filters) {
|
|
2597
|
+
const jobTitle = filters.person_job_title;
|
|
2598
|
+
if (!isPlainObject(jobTitle)) {
|
|
2599
|
+
return;
|
|
2600
|
+
}
|
|
2601
|
+
const include = normalizeStringArray(jobTitle.include);
|
|
2602
|
+
const exclude = normalizeStringArray(jobTitle.exclude);
|
|
2603
|
+
if (include.length > PROSPEO_LARGE_TITLE_INCLUDE_BOOLEAN_THRESHOLD &&
|
|
2604
|
+
exclude.length === 0 &&
|
|
2605
|
+
typeof jobTitle.boolean_search !== "string") {
|
|
2606
|
+
delete jobTitle.include;
|
|
2607
|
+
jobTitle.boolean_search = include
|
|
2608
|
+
.map((title) => `"${title.replace(/"/g, '\\"')}"`)
|
|
2609
|
+
.join(" OR ");
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
function isPlainObject(input) {
|
|
2613
|
+
return Boolean(input) && typeof input === "object" && !Array.isArray(input);
|
|
2614
|
+
}
|
|
2615
|
+
function normalizeStringArray(input) {
|
|
2616
|
+
if (!Array.isArray(input)) {
|
|
2617
|
+
return [];
|
|
2618
|
+
}
|
|
2619
|
+
return input
|
|
2620
|
+
.filter((value) => typeof value === "string")
|
|
2621
|
+
.map((value) => value.trim())
|
|
2622
|
+
.filter((value) => value.length > 0);
|
|
2623
|
+
}
|
|
2624
|
+
function normalizeMcpSeeds(input, kind) {
|
|
2625
|
+
return uniqueStrings(normalizeStringArray(input).filter((seed) => kind === "domain" ? isLikelyConcreteDomain(seed) : isLikelyConcreteSeedCompany(seed)));
|
|
2626
|
+
}
|
|
2627
|
+
function isLikelyConcreteDomain(input) {
|
|
2628
|
+
return /^[a-z0-9.-]+\.[a-z]{2,}$/i.test(input);
|
|
2629
|
+
}
|
|
2630
|
+
function isLikelyConcreteSeedCompany(input) {
|
|
2631
|
+
const normalized = input.toLowerCase();
|
|
2632
|
+
return !(normalized.includes("placeholder") ||
|
|
2633
|
+
normalized.includes("another approved") ||
|
|
2634
|
+
normalized.includes("approved seed") ||
|
|
2635
|
+
normalized.includes("best-customer seed") ||
|
|
2636
|
+
normalized.includes("example seed"));
|
|
2637
|
+
}
|
|
2638
|
+
function uniqueStrings(input) {
|
|
2639
|
+
return Array.from(new Set(input));
|
|
2640
|
+
}
|
|
2641
|
+
function removeEmptyObjectFilter(filters, filterName) {
|
|
2642
|
+
const filter = filters[filterName];
|
|
2643
|
+
if (isPlainObject(filter) && Object.keys(filter).length === 0) {
|
|
2644
|
+
delete filters[filterName];
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2385
2647
|
function clonePlainObject(input) {
|
|
2386
2648
|
return JSON.parse(JSON.stringify(input));
|
|
2387
2649
|
}
|
|
@@ -2514,7 +2776,8 @@ export async function searchProspeo(input) {
|
|
|
2514
2776
|
nestedCompany?.names !== undefined) {
|
|
2515
2777
|
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.");
|
|
2516
2778
|
}
|
|
2517
|
-
const
|
|
2779
|
+
const safeInput = normalizeProspeoSearchInputForMcp(input);
|
|
2780
|
+
const response = await api.post(`/api/v3/prospeo/search`, safeInput);
|
|
2518
2781
|
return compactProspeoSearchResponse(response);
|
|
2519
2782
|
}
|
|
2520
2783
|
function compactProspeoSearchResponse(response) {
|
package/package.json
CHANGED
|
@@ -219,7 +219,6 @@ Package-backed MCP may return a short `mcp-prospeo-company-search-token:*`
|
|
|
219
219
|
reference to avoid long-token copy errors. Account rows are not people leads
|
|
220
220
|
yet. The confirmation creates the `domainFilterId` that constrains the follow-on
|
|
221
221
|
`search_prospeo` people search.
|
|
222
|
-
Always copy the `companySearchToken` exactly.
|
|
223
222
|
|
|
224
223
|
Prospeo company/account search is useful when the source plan depends on
|
|
225
224
|
website traffic (`company_website_traffic`), confirmed AI Attributes including
|
|
@@ -253,6 +252,12 @@ without an include keyword, and do not duplicate `company_industry` when
|
|
|
253
252
|
search, prefer `person_job_title.boolean_search` for long role synonym lists
|
|
254
253
|
instead of many `person_job_title.include` values plus broad department/seniority
|
|
255
254
|
filters.
|
|
255
|
+
For seeded company lookalikes, keep the first call simple: resolved seed
|
|
256
|
+
company/domain plus `company_lookalike.minimum_tier` and simple confirmed
|
|
257
|
+
attributes, headcount, or industry. Do not add `company_website_search`,
|
|
258
|
+
`company_keywords`, or `company_icp` until the account sample proves the seed
|
|
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. Do not send `company_website_search.exclude_keywords` without a positive website include signal.
|
|
256
261
|
Do not use `AI`, `API`, `GTM`, or `SaaS` as company keyword terms.
|
|
257
262
|
Do not combine `company_key_customers` with ICP, website-search, keyword,
|
|
258
263
|
attribute, industry, or headcount filters until the standalone pass proves
|
|
@@ -427,6 +427,12 @@ Use first for broad persona expansion, ABM/domain targeting, hiring-led targetin
|
|
|
427
427
|
- Do not send `company_keywords.exclude` unless at least one include keyword is
|
|
428
428
|
present. Do not duplicate `company_industry` when `company_icp.industries`
|
|
429
429
|
already carries the industry.
|
|
430
|
+
- For seeded company lookalikes, keep the first call simple: resolved seed
|
|
431
|
+
company/domain plus `company_lookalike.minimum_tier` and simple confirmed
|
|
432
|
+
attributes, headcount, or industry. Do not add `company_website_search`,
|
|
433
|
+
`company_keywords`, or `company_icp` until the account sample proves the seed
|
|
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. Do not send `company_website_search.exclude_keywords` without a positive website include signal.
|
|
430
436
|
- Do not use `company_intent`. Do not invent unsupported support-channel filters
|
|
431
437
|
or AI Attribute guesses like phone/email/chat/ticket/social.
|
|
432
438
|
- Company/account search returns an account sample only; account rows are not people leads yet. Ask the user to approve the account sample.
|
|
@@ -106,6 +106,9 @@ Avoidable-400 guardrails:
|
|
|
106
106
|
- Do not use `AI`, `API`, `GTM`, or `SaaS` as company keyword terms. Use confirmed attributes such as `uses_ai` / `has_api`, or spell out `artificial intelligence`, `application programming interface`, `go to market`, and `software as a service`.
|
|
107
107
|
- Do not send `company_keywords.exclude` unless at least one include keyword is present.
|
|
108
108
|
- Do not duplicate `company_industry` when `company_icp.industries` already carries the industry.
|
|
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
|
+
- Do not send placeholder seed names such as `another approved best-customer seed`; use only concrete company names or domains you have actually resolved.
|
|
111
|
+
- Do not send `company_website_search.exclude_keywords` without a positive website include signal.
|
|
109
112
|
- 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.
|
|
110
113
|
|
|
111
114
|
Unsupported/caveats:
|
|
@@ -249,6 +252,7 @@ Preference rules:
|
|
|
249
252
|
- `company_keywords.include/exclude` values must be at least 3 characters; use `artificial intelligence` instead of `AI`, or use confirmed attributes such as `uses_ai` when that is the real signal.
|
|
250
253
|
- Do not use `AI`, `API`, `GTM`, or `SaaS` as company keyword terms; use longer phrases such as `artificial intelligence`, `application programming interface`, `go to market`, or `software as a service`.
|
|
251
254
|
- 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.
|
|
255
|
+
- 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.
|
|
252
256
|
|
|
253
257
|
### Person Filters
|
|
254
258
|
|