@sellable/mcp 0.1.225 → 0.1.226

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.
@@ -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)",
@@ -2375,13 +2416,185 @@ function normalizeProspeoCompanySearchInputForMcp(input) {
2375
2416
  !Array.isArray(lookalike) &&
2376
2417
  "company_oids" in lookalike) {
2377
2418
  delete lookalike.company_oids;
2419
+ removeEmptyObjectFilter(filters, "company_lookalike");
2378
2420
  }
2379
2421
  }
2422
+ normalizeMcpCompanyKeywords(filters);
2423
+ normalizeMcpCompanyIcp(filters);
2424
+ stripMcpDuplicateCompanyIndustry(filters);
2425
+ stripMcpKeyCustomerCompanionFilters(filters);
2380
2426
  return {
2381
2427
  ...input,
2382
2428
  filters,
2383
2429
  };
2384
2430
  }
2431
+ function normalizeProspeoSearchInputForMcp(input) {
2432
+ const filters = input?.filters && typeof input.filters === "object"
2433
+ ? clonePlainObject(input.filters)
2434
+ : {};
2435
+ normalizeMcpPersonJobTitle(filters);
2436
+ return {
2437
+ ...input,
2438
+ filters,
2439
+ };
2440
+ }
2441
+ function normalizeMcpCompanyKeywords(filters) {
2442
+ const keywords = filters.company_keywords;
2443
+ if (!isPlainObject(keywords)) {
2444
+ return;
2445
+ }
2446
+ const include = normalizeStringArray(keywords.include).map((keyword) => {
2447
+ const alias = prospeoCompanyKeywordIncludeAliases.get(keyword.toLowerCase());
2448
+ return alias ?? keyword;
2449
+ });
2450
+ const validInclude = include.filter((keyword) => keyword.length >= 3);
2451
+ if (validInclude.length > 0) {
2452
+ keywords.include = uniqueStrings(validInclude);
2453
+ }
2454
+ else {
2455
+ delete keywords.include;
2456
+ delete keywords.exclude;
2457
+ }
2458
+ if (keywords.exclude !== undefined && validInclude.length === 0) {
2459
+ delete keywords.exclude;
2460
+ }
2461
+ removeEmptyObjectFilter(filters, "company_keywords");
2462
+ }
2463
+ function normalizeMcpCompanyIcp(filters) {
2464
+ const icp = filters.company_icp;
2465
+ if (!isPlainObject(icp)) {
2466
+ return;
2467
+ }
2468
+ const scope = typeof icp.geographic_scope === "string"
2469
+ ? icp.geographic_scope.trim()
2470
+ : "";
2471
+ const scopeMarkets = expandMcpCompanyIcpMarket(scope);
2472
+ if (scopeMarkets.length > 0) {
2473
+ icp.geographic_scope = "multi_country";
2474
+ icp.geographic_markets = uniqueStrings([
2475
+ ...scopeMarkets,
2476
+ ...normalizeMcpCompanyIcpMarkets(icp.geographic_markets),
2477
+ ]);
2478
+ }
2479
+ else if (scope &&
2480
+ scope !== "single_country" &&
2481
+ scope !== "multi_country") {
2482
+ delete icp.geographic_scope;
2483
+ }
2484
+ if (icp.geographic_markets !== undefined) {
2485
+ icp.geographic_markets = normalizeMcpCompanyIcpMarkets(icp.geographic_markets);
2486
+ if (Array.isArray(icp.geographic_markets) &&
2487
+ icp.geographic_markets.length === 0) {
2488
+ delete icp.geographic_markets;
2489
+ }
2490
+ }
2491
+ normalizeMcpCompanyIcpDepartments(icp);
2492
+ removeEmptyObjectFilter(filters, "company_icp");
2493
+ }
2494
+ function normalizeMcpCompanyIcpDepartments(icp) {
2495
+ const departments = icp.departments;
2496
+ if (Array.isArray(departments)) {
2497
+ const valid = departments
2498
+ .filter((department) => typeof department === "string")
2499
+ .filter((department) => prospeoCompanyIcpDepartments.has(department));
2500
+ if (valid.length > 0) {
2501
+ icp.departments = uniqueStrings(valid);
2502
+ }
2503
+ else {
2504
+ delete icp.departments;
2505
+ }
2506
+ return;
2507
+ }
2508
+ if (!isPlainObject(departments)) {
2509
+ return;
2510
+ }
2511
+ const include = normalizeStringArray(departments.include).filter((department) => prospeoCompanyIcpDepartments.has(department));
2512
+ const exclude = normalizeStringArray(departments.exclude).filter((department) => prospeoCompanyIcpDepartments.has(department));
2513
+ if (include.length > 0) {
2514
+ departments.include = uniqueStrings(include);
2515
+ }
2516
+ else {
2517
+ delete departments.include;
2518
+ }
2519
+ if (exclude.length > 0) {
2520
+ departments.exclude = uniqueStrings(exclude);
2521
+ }
2522
+ else {
2523
+ delete departments.exclude;
2524
+ }
2525
+ if (departments.include === undefined && departments.exclude === undefined) {
2526
+ delete icp.departments;
2527
+ }
2528
+ }
2529
+ function normalizeMcpCompanyIcpMarkets(input) {
2530
+ const markets = normalizeStringArray(input);
2531
+ return uniqueStrings(markets.flatMap(expandMcpCompanyIcpMarket));
2532
+ }
2533
+ function expandMcpCompanyIcpMarket(market) {
2534
+ const trimmed = market.trim();
2535
+ if (!trimmed) {
2536
+ return [];
2537
+ }
2538
+ return prospeoCompanyIcpRegionAliases.get(trimmed.toLowerCase()) ?? [trimmed];
2539
+ }
2540
+ function stripMcpDuplicateCompanyIndustry(filters) {
2541
+ const icp = filters.company_icp;
2542
+ if (isPlainObject(icp) &&
2543
+ Array.isArray(icp.industries) &&
2544
+ icp.industries.length > 0) {
2545
+ delete filters.company_industry;
2546
+ }
2547
+ }
2548
+ function stripMcpKeyCustomerCompanionFilters(filters) {
2549
+ const keyCustomers = filters.company_key_customers;
2550
+ if (!isPlainObject(keyCustomers)) {
2551
+ return;
2552
+ }
2553
+ const include = normalizeStringArray(keyCustomers.include);
2554
+ if (include.length === 0) {
2555
+ return;
2556
+ }
2557
+ for (const filterName of prospeoKeyCustomerCompanionFilters) {
2558
+ delete filters[filterName];
2559
+ }
2560
+ }
2561
+ function normalizeMcpPersonJobTitle(filters) {
2562
+ const jobTitle = filters.person_job_title;
2563
+ if (!isPlainObject(jobTitle)) {
2564
+ return;
2565
+ }
2566
+ const include = normalizeStringArray(jobTitle.include);
2567
+ const exclude = normalizeStringArray(jobTitle.exclude);
2568
+ if (include.length > PROSPEO_LARGE_TITLE_INCLUDE_BOOLEAN_THRESHOLD &&
2569
+ exclude.length === 0 &&
2570
+ typeof jobTitle.boolean_search !== "string") {
2571
+ delete jobTitle.include;
2572
+ jobTitle.boolean_search = include
2573
+ .map((title) => `"${title.replace(/"/g, '\\"')}"`)
2574
+ .join(" OR ");
2575
+ }
2576
+ }
2577
+ function isPlainObject(input) {
2578
+ return Boolean(input) && typeof input === "object" && !Array.isArray(input);
2579
+ }
2580
+ function normalizeStringArray(input) {
2581
+ if (!Array.isArray(input)) {
2582
+ return [];
2583
+ }
2584
+ return input
2585
+ .filter((value) => typeof value === "string")
2586
+ .map((value) => value.trim())
2587
+ .filter((value) => value.length > 0);
2588
+ }
2589
+ function uniqueStrings(input) {
2590
+ return Array.from(new Set(input));
2591
+ }
2592
+ function removeEmptyObjectFilter(filters, filterName) {
2593
+ const filter = filters[filterName];
2594
+ if (isPlainObject(filter) && Object.keys(filter).length === 0) {
2595
+ delete filters[filterName];
2596
+ }
2597
+ }
2385
2598
  function clonePlainObject(input) {
2386
2599
  return JSON.parse(JSON.stringify(input));
2387
2600
  }
@@ -2514,7 +2727,8 @@ export async function searchProspeo(input) {
2514
2727
  nestedCompany?.names !== undefined) {
2515
2728
  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
2729
  }
2517
- const response = await api.post(`/api/v3/prospeo/search`, input);
2730
+ const safeInput = normalizeProspeoSearchInputForMcp(input);
2731
+ const response = await api.post(`/api/v3/prospeo/search`, safeInput);
2518
2732
  return compactProspeoSearchResponse(response);
2519
2733
  }
2520
2734
  function compactProspeoSearchResponse(response) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.225",
3
+ "version": "0.1.226",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -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