@sellable/mcp 0.1.224 → 0.1.225

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.
@@ -59,6 +59,10 @@ export const MAX_SIGNAL_DISCOVERY_POSTS = 10;
59
59
  // capacity math instead of raw visible engagement counts.
60
60
  const signalDiscoveryReactionFetchLimit = 1000;
61
61
  const signalDiscoveryCommentFetchLimit = 1000;
62
+ const prospeoCompanySearchTokenRefs = new Map();
63
+ let prospeoCompanySearchTokenRefCounter = 0;
64
+ const PROSPEO_COMPANY_SEARCH_TOKEN_REF_PREFIX = "mcp-prospeo-company-search-token:";
65
+ const MAX_PROSPEO_COMPANY_SEARCH_TOKEN_REFS = 200;
62
66
  const prospeoFilterValueSchema = {
63
67
  type: "object",
64
68
  description: "Include/exclude list filter (values must match Prospeo enums)",
@@ -477,12 +481,14 @@ search_prospeo_companies({
477
481
  }
478
482
  })
479
483
  Do not invent company_oids. When seedCompanies or seedDomains are present, omit company_oids and let the backend resolve real Prospeo company IDs.
484
+ After account approval, copy the companySearchToken exactly into confirm_prospeo_company_accounts; package-backed MCP may return a short mcp-prospeo-company-search-token:* reference to avoid long-token copy errors.
480
485
  For accounts in the news: { "company_news": { "categories": ["Funding & Investment"], "timeframe_days": 90 } }.
481
486
  For awards: { "company_awards": { "include": ["G2"], "match_mode": "CONTAINS" } }.
482
487
  For website traffic: { "company_website_traffic": { "min_monthly_visits": 50000 } }.
483
488
  For products/services, integrations, key customers, and Google discovery use "company_products_services", "company_integrations", "company_key_customers", and "company_google_discovery".
484
489
  For headcount by location: { "company_headcount_by_location": { "entries": [{ "country": "United States", "city": "Austin", "min_headcount": 10 }] } }.
485
490
  For structured ICP: { "company_icp": { "titles_include": ["Head of RevOps"], "company_sizes": ["midmarket"], "departments": { "include": ["Sales"], "match_mode": "ANY" }, "geographic_scope": "multi_country", "geographic_markets": ["United States", "Canada"] }, "company_headcount_range": ["101-200", "201-500", "501-1000"] }. Pair company_icp.company_sizes with company_headcount_range or rely on MCP normalization that derives the range; inspect the account sample for size drift. Product is not a company_icp.departments value; use titles_include for product roles. geographic_scope only accepts single_country or multi_country.
491
+ Use company_key_customers as a standalone first-pass account filter. Do not combine company_key_customers with company_website_search, company_icp, company_keywords, or broad AI Attributes in the first call. Do not use AI, API, GTM, or SaaS as company keyword terms; use confirmed attributes or spell out artificial intelligence, application programming interface, go to market, and software as a service. Do not send company_keywords.exclude without an include keyword. For post-confirm people search, prefer person_job_title.boolean_search for long role synonym lists.
486
492
  company_intent is unsupported; support-channel AI Attribute guesses like phone/email/chat/ticket/social are not exposed. public API rows do not include lookalike tier/score/reason.`;
487
493
  function loadSignalDiscoveryConfig() {
488
494
  if (!existsSync(signalProviderConfigPath)) {
@@ -2310,14 +2316,15 @@ export async function searchProspeoCompanies(input) {
2310
2316
  campaignOfferId: input?.campaignOfferId,
2311
2317
  });
2312
2318
  const api = getApi();
2319
+ const safeInput = normalizeProspeoCompanySearchInputForMcp(input);
2313
2320
  const response = await api.post("/api/v3/prospeo/company-search", removeUndefinedValues({
2314
- campaignOfferId: input?.campaignOfferId,
2315
- seedCompanies: input?.seedCompanies,
2316
- seedDomains: input?.seedDomains,
2317
- filters: input?.filters ?? {},
2318
- limit: input?.limit,
2319
- page: input?.page,
2320
- sort: input?.sort,
2321
+ campaignOfferId: safeInput?.campaignOfferId,
2322
+ seedCompanies: safeInput?.seedCompanies,
2323
+ seedDomains: safeInput?.seedDomains,
2324
+ filters: safeInput?.filters ?? {},
2325
+ limit: safeInput?.limit,
2326
+ page: safeInput?.page,
2327
+ sort: safeInput?.sort,
2321
2328
  }));
2322
2329
  return compactProspeoCompanySearchResponse(response);
2323
2330
  }
@@ -2346,7 +2353,7 @@ export async function confirmProspeoCompanyAccounts(input) {
2346
2353
  const api = getApi();
2347
2354
  const response = await api.post("/api/v3/prospeo/company-search/confirm-domain-filter", removeUndefinedValues({
2348
2355
  campaignOfferId: input?.campaignOfferId,
2349
- companySearchToken: input.companySearchToken,
2356
+ companySearchToken: resolveProspeoCompanySearchTokenRef(input.companySearchToken),
2350
2357
  selectedCompanyIds: input.selectedCompanyIds,
2351
2358
  name: input.name,
2352
2359
  }));
@@ -2355,6 +2362,49 @@ export async function confirmProspeoCompanyAccounts(input) {
2355
2362
  function removeUndefinedValues(input) {
2356
2363
  return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
2357
2364
  }
2365
+ function normalizeProspeoCompanySearchInputForMcp(input) {
2366
+ const filters = input?.filters && typeof input.filters === "object"
2367
+ ? clonePlainObject(input.filters)
2368
+ : {};
2369
+ const hasSeeds = (input.seedCompanies?.length ?? 0) > 0 ||
2370
+ (input.seedDomains?.length ?? 0) > 0;
2371
+ if (hasSeeds) {
2372
+ const lookalike = filters.company_lookalike;
2373
+ if (lookalike &&
2374
+ typeof lookalike === "object" &&
2375
+ !Array.isArray(lookalike) &&
2376
+ "company_oids" in lookalike) {
2377
+ delete lookalike.company_oids;
2378
+ }
2379
+ }
2380
+ return {
2381
+ ...input,
2382
+ filters,
2383
+ };
2384
+ }
2385
+ function clonePlainObject(input) {
2386
+ return JSON.parse(JSON.stringify(input));
2387
+ }
2388
+ function storeProspeoCompanySearchTokenRef(token) {
2389
+ if (typeof token !== "string" || token.length === 0) {
2390
+ return token;
2391
+ }
2392
+ if (token.startsWith(PROSPEO_COMPANY_SEARCH_TOKEN_REF_PREFIX)) {
2393
+ return token;
2394
+ }
2395
+ const ref = `${PROSPEO_COMPANY_SEARCH_TOKEN_REF_PREFIX}${Date.now().toString(36)}-${(++prospeoCompanySearchTokenRefCounter).toString(36)}`;
2396
+ prospeoCompanySearchTokenRefs.set(ref, token);
2397
+ while (prospeoCompanySearchTokenRefs.size > MAX_PROSPEO_COMPANY_SEARCH_TOKEN_REFS) {
2398
+ const oldest = prospeoCompanySearchTokenRefs.keys().next().value;
2399
+ if (!oldest)
2400
+ break;
2401
+ prospeoCompanySearchTokenRefs.delete(oldest);
2402
+ }
2403
+ return ref;
2404
+ }
2405
+ function resolveProspeoCompanySearchTokenRef(token) {
2406
+ return prospeoCompanySearchTokenRefs.get(token) ?? token;
2407
+ }
2358
2408
  function compactProspeoCompanySearchResponse(response) {
2359
2409
  if (!response || typeof response !== "object") {
2360
2410
  return response;
@@ -2384,7 +2434,7 @@ function compactProspeoCompanySearchResponse(response) {
2384
2434
  },
2385
2435
  requestedFilters: response.normalizedFilters ?? response.filters ?? {},
2386
2436
  warnings: Array.isArray(response.warnings) ? response.warnings : [],
2387
- companySearchToken: response.companySearchToken ?? null,
2437
+ companySearchToken: storeProspeoCompanySearchTokenRef(response.companySearchToken),
2388
2438
  nextStep: "Review accounts, then call confirm_prospeo_company_accounts with companySearchToken and selectedCompanyIds. These accounts are not people leads yet.",
2389
2439
  };
2390
2440
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.224",
3
+ "version": "0.1.225",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -214,9 +214,12 @@ before person search, use the Prospeo account approval flow:
214
214
  First return an account sample and ask the user to approve the account set.
215
215
  Only call `confirm_prospeo_company_accounts` with the `companySearchToken`
216
216
  returned by `search_prospeo_companies` and selected Prospeo company IDs; never
217
- reconstruct raw account rows or domains manually. Account rows are not people
218
- leads yet. The confirmation creates the `domainFilterId` that constrains the
219
- follow-on `search_prospeo` people search.
217
+ reconstruct raw account rows or domains manually; always copy the `companySearchToken` exactly.
218
+ Package-backed MCP may return a short `mcp-prospeo-company-search-token:*`
219
+ reference to avoid long-token copy errors. Account rows are not people leads
220
+ yet. The confirmation creates the `domainFilterId` that constrains the follow-on
221
+ `search_prospeo` people search.
222
+ Always copy the `companySearchToken` exactly.
220
223
 
221
224
  Prospeo company/account search is useful when the source plan depends on
222
225
  website traffic (`company_website_traffic`), confirmed AI Attributes including
@@ -238,6 +241,22 @@ company_icp.departments value; use `titles_include` for product roles.
238
241
  `uses_ai` when that is the actual signal. Do not use `company_intent`, and do
239
242
  not invent unsupported support-channel filters or AI Attribute guesses like
240
243
  phone/email/chat/ticket/social.
244
+ Use `company_key_customers` as a standalone first-pass account filter; in short,
245
+ run company_key_customers as a standalone first-pass. Do not combine
246
+ `company_key_customers` with `company_website_search`, `company_icp`,
247
+ `company_keywords`, or broad AI Attributes in the first call. Do not use `AI`,
248
+ `API`, `GTM`, or `SaaS` as company keyword terms; use confirmed attributes or
249
+ spell out artificial intelligence, application programming interface, go to
250
+ market, and software as a service. Do not send `company_keywords.exclude`
251
+ without an include keyword, and do not duplicate `company_industry` when
252
+ `company_icp.industries` already carries the industry. For post-confirm people
253
+ search, prefer `person_job_title.boolean_search` for long role synonym lists
254
+ instead of many `person_job_title.include` values plus broad department/seniority
255
+ filters.
256
+ Do not use `AI`, `API`, `GTM`, or `SaaS` as company keyword terms.
257
+ Do not combine `company_key_customers` with ICP, website-search, keyword,
258
+ attribute, industry, or headcount filters until the standalone pass proves
259
+ useful.
241
260
 
242
261
  After scouting, ask for a second approval on Start Import. For
243
262
  LinkedIn engagement (`signal-discovery` internally), name how many
@@ -414,15 +414,33 @@ Use first for broad persona expansion, ABM/domain targeting, hiring-led targetin
414
414
  - `company_keywords.include/exclude` values must be at least 3 characters; use
415
415
  `artificial intelligence` instead of `AI`, or use confirmed attributes such
416
416
  as `uses_ai` when that is the actual signal.
417
+ - Use `company_key_customers` as a standalone first-pass account filter; in
418
+ short, run company_key_customers as a standalone first-pass. Do not
419
+ combine `company_key_customers` with `company_website_search`, `company_icp`,
420
+ `company_keywords`, or broad AI Attributes in the first call.
421
+ - Do not combine `company_key_customers` with ICP, website-search, keyword,
422
+ attribute, industry, or headcount filters until the standalone pass proves
423
+ useful.
424
+ - Do not use `AI`, `API`, `GTM`, or `SaaS` as company keyword terms; use
425
+ confirmed attributes or spell out artificial intelligence, application
426
+ programming interface, go to market, and software as a service.
427
+ - Do not send `company_keywords.exclude` unless at least one include keyword is
428
+ present. Do not duplicate `company_industry` when `company_icp.industries`
429
+ already carries the industry.
417
430
  - Do not use `company_intent`. Do not invent unsupported support-channel filters
418
431
  or AI Attribute guesses like phone/email/chat/ticket/social.
419
432
  - Company/account search returns an account sample only; account rows are not people leads yet. Ask the user to approve the account sample.
420
433
  - After approval, call `confirm_prospeo_company_accounts` with the
421
434
  `companySearchToken` and selected Prospeo company IDs from
422
435
  `search_prospeo_companies`; do not reconstruct account rows or domains
423
- manually.
436
+ manually. Always copy the `companySearchToken` exactly; package-backed MCP may
437
+ return a short `mcp-prospeo-company-search-token:*` reference to avoid
438
+ long-token copy errors.
424
439
  - Use the returned `domainFilterId` in the follow-on `search_prospeo` people
425
440
  search.
441
+ - For post-confirm people search, prefer `person_job_title.boolean_search` for
442
+ long role synonym lists instead of many `person_job_title.include` values plus
443
+ broad department/seniority filters.
426
444
  - Prospeo is the terminal fallback for this chain. If projected fit is still
427
445
  below the 10% planning floor after reasonable Prospeo refinement, stop and ask
428
446
  for a tighter ICP/source direction instead of inventing another provider.
@@ -80,7 +80,7 @@ confirm_prospeo_company_accounts({
80
80
  })
81
81
  ```
82
82
 
83
- Use the returned `domainFilterId` in `search_prospeo` with people filters. Do not reconstruct raw account rows or domains manually as Prospeo-sourced provenance.
83
+ Use the returned `domainFilterId` in `search_prospeo` with people filters. Do not reconstruct raw account rows or domains manually as Prospeo-sourced provenance. Always copy the `companySearchToken` exactly from `search_prospeo_companies`; package-backed MCP may return a short `mcp-prospeo-company-search-token:*` reference so the model does not have to copy a long signed backend token.
84
84
 
85
85
  When `seedDomains` or `seedCompanies` are present, omit `company_oids`; the MCP
86
86
  backend resolves real Prospeo company IDs. Do not invent company_oids such as
@@ -100,6 +100,14 @@ company ICP departments include Consumers, Customer Success, Data, Design,
100
100
  Engineering, Finance, HR, IT, Legal, Marketing, Operations, Procurement, SMB
101
101
  Owners, Sales, and Security.
102
102
 
103
+ Avoidable-400 guardrails:
104
+
105
+ - Use `company_key_customers` as a standalone first-pass account filter; in short, run company_key_customers as a standalone first-pass. Do not combine `company_key_customers` with `company_website_search`, `company_icp`, `company_keywords`, or broad AI Attributes in the first call; inspect the account sample, then refine if needed.
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
+ - Do not send `company_keywords.exclude` unless at least one include keyword is present.
108
+ - Do not duplicate `company_industry` when `company_icp.industries` already carries the industry.
109
+ - 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
+
103
111
  Unsupported/caveats:
104
112
 
105
113
  - `company_intent` is unsupported by Prospeo public API.
@@ -234,10 +242,13 @@ Preference rules:
234
242
  - If user input starts as company names, resolve names to domains first, then use `save_domain_filters`.
235
243
  - Prefer comprehensive `person_job_title.include` lists (synonyms + role variants) for role precision.
236
244
  - Use `person_department + person_seniority` as supporting constraints when title variance is expected.
245
+ - For long title synonym lists, prefer `person_job_title.boolean_search` with explicit OR terms instead of oversized include arrays.
237
246
  - In security, AppSec, SOC, RevOps, Demand Gen, and similar function-specific lanes, do not rely on bare `Head` / `Director` / `VP` widening by itself. Pair seniority with explicit function keywords in `person_job_title` and verify the sample for off-function titles like `Head of Social Media`.
238
247
  - Prefer `company_headcount_range` for most sizing; use `company_headcount_custom` for precise numeric bounds.
239
248
  - Prefer `company_industry` before `company_keywords`; use keywords for refinement, not first-pass targeting.
240
249
  - `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
+ - 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
+ - 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.
241
252
 
242
253
  ### Person Filters
243
254