@coldiq/mcp 0.4.1 → 0.4.2
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/provider-availability.d.ts +3 -0
- package/dist/provider-availability.d.ts.map +1 -0
- package/dist/provider-availability.js +25 -0
- package/dist/provider-availability.js.map +1 -0
- package/dist/registry.d.ts +7 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +31 -20
- package/dist/registry.js.map +1 -1
- package/dist/tools/call-endpoint.js +1 -1
- package/dist/tools/call-endpoint.js.map +1 -1
- package/dist/tools/find-email.js +1 -1
- package/dist/tools/find-email.js.map +1 -1
- package/dist/tools/find-emails-bulk.js +1 -1
- package/dist/tools/find-emails-bulk.js.map +1 -1
- package/dist/tools/find-emails.js +1 -1
- package/dist/tools/find-emails.js.map +1 -1
- package/dist/tools/get-endpoint-details.js +2 -2
- package/dist/tools/get-endpoint-details.js.map +1 -1
- package/dist/tools/list-data-sources.js +2 -2
- package/dist/tools/list-data-sources.js.map +1 -1
- package/dist/tools/search-companies.d.ts.map +1 -1
- package/dist/tools/search-companies.js +5 -5
- package/dist/tools/search-companies.js.map +1 -1
- package/dist/utils/provider-display.js +5 -5
- package/dist/utils/provider-display.js.map +1 -1
- package/dist/utils/provider-resolver.d.ts +1 -0
- package/dist/utils/provider-resolver.d.ts.map +1 -1
- package/dist/utils/provider-resolver.js +3 -1
- package/dist/utils/provider-resolver.js.map +1 -1
- package/package.json +1 -1
- package/src/provider-availability.ts +27 -0
- package/src/registry.ts +35 -19
- package/src/tools/call-endpoint.ts +1 -1
- package/src/tools/find-email.ts +1 -1
- package/src/tools/find-emails-bulk.ts +1 -1
- package/src/tools/find-emails.ts +1 -1
- package/src/tools/get-endpoint-details.ts +2 -2
- package/src/tools/list-data-sources.ts +2 -2
- package/src/tools/search-companies.ts +5 -5
- package/src/utils/provider-display.ts +5 -5
- package/src/utils/provider-resolver.ts +4 -1
- package/test-linkupapi-live.ts +1 -1
- package/test-phone-live.ts +1 -1
- package/test-search-live.ts +1 -1
- package/tests/live/companyenrich-hybrid-probe.ts +1 -1
- package/tests/provider-availability.test.ts +83 -0
- package/tests/registry-enrich-person.test.ts +7 -5
- package/tests/registry-find-people.test.ts +2 -2
- package/tests/registry.test.ts +32 -32
- package/tests/tools/call-endpoint.test.ts +4 -4
- package/tests/tools/get-endpoint-details.test.ts +4 -4
- package/tests/tools/list-data-sources.test.ts +1 -1
- package/tests/tools/search-endpoints.test.ts +2 -2
- package/tests/utils/provider-display.test.ts +2 -2
|
@@ -46,11 +46,11 @@ const CATALOG: Array<{ capability: InsightCapability; tool: string; label: strin
|
|
|
46
46
|
// the accurate coverage. Keyed by capability -> branded display name.
|
|
47
47
|
const VENDOR_STRENGTH_OVERRIDES: Partial<Record<InsightCapability, Record<string, string>>> = {
|
|
48
48
|
search_companies: {
|
|
49
|
-
|
|
49
|
+
'Lima Data': 'LinkedIn-based company discovery, incl. Sales Navigator URLs',
|
|
50
50
|
LinkupAPI: 'live LinkedIn company search, incl. recently-funded and hiring companies',
|
|
51
51
|
},
|
|
52
52
|
find_email: {
|
|
53
|
-
|
|
53
|
+
'Lima Data': 'work-email lookup by name + domain or LinkedIn URL',
|
|
54
54
|
},
|
|
55
55
|
enrich_company: {
|
|
56
56
|
CompanyEnrich: 'firmographic enrichment from a domain, name or LinkedIn URL',
|
|
@@ -6,7 +6,7 @@ export const searchCompaniesName = 'search_companies'
|
|
|
6
6
|
|
|
7
7
|
export const searchCompaniesDescription =
|
|
8
8
|
'Search B2B companies by industry, size, geography, founding year, tech stack, funding, revenue, exclusion lists, and hiring signals. To build a lookalike TAM from example accounts, pass `similar_to_domains` (seed company domains) — the request routes to DiscoLike\'s lookalike discovery over 70M companies, optionally narrowed by keywords/geo/size. Routing is otherwise automatic: strict firmographic filters (revenue, employee bands, exclusion lists) get high-precision providers first; tech-stack and funding-stage filters route to specialized providers; loose keyword/geo searches fall back to broad providers; a LinkedIn Sales Navigator search URL enables URL-based discovery as a final fallback. For buying-intent topic discovery (find companies associated with intent keywords like "sales automation" or "lead generation"), pass `keywords` with `use_providers: ["theirstack"]` — this routes to TheirStack\'s intent-keyword index instead of the default full-text match. For "fast-growing" companies there is no exact workforce-growth % filter anywhere — pass min_workforce_growth_pct (and/or is_hiring / a recent-funding filter) and ColdIQ routes to active-hiring and recently-funded providers, the practical proxies for growth. ColdIQ automatically picks the best provider — pass use_providers only if you need a specific tool. Default limit: 25. ' +
|
|
9
|
-
'Response is compact by default (name, domain, website, linkedin_url, employees, revenue, industry, country, city, founded_year, categories) — everything needed to act on a company or feed it into find_people. Set fields="verbose" only when you specifically need the full firmographic record (funding history, technologies, keywords, NAICS codes, all socials, descriptions).'
|
|
9
|
+
'Employee and revenue bounds are exact when the selected provider returns exact values. Bucket-only providers widen non-aligned bounds to overlapping buckets and disclose that in `_meta.filter_approximations`. Response is compact by default (name, domain, website, linkedin_url, employees, revenue, industry, country, city, founded_year, categories) — everything needed to act on a company or feed it into find_people. Set fields="verbose" only when you specifically need the full firmographic record (funding history, technologies, keywords, NAICS codes, all socials, descriptions).'
|
|
10
10
|
|
|
11
11
|
export const searchCompaniesSchema = {
|
|
12
12
|
keywords: z.array(z.string()).optional().describe('Free-text topics, business models, or themes — e.g. ["SaaS", "fintech", "cybersecurity"]. Best for most discovery: matched broadly across company name, description, and tags. Prefer this over `industries` for terms like "SaaS" that are business models rather than formal industry categories.'),
|
|
@@ -15,8 +15,8 @@ export const searchCompaniesSchema = {
|
|
|
15
15
|
locations: z.array(z.string()).optional().describe('City/region filters as free-text strings (e.g. ["Paris, France", "San Francisco, California"]). Narrows results beyond country-level.'),
|
|
16
16
|
industries: z.array(z.string()).optional().describe('Formal industry-taxonomy categories — e.g. ["Financial Services", "Hospitals and Health Care"]. Use only for strict industry classification; for business models or themes like "SaaS" or "fintech", use `keywords` instead. Both fields are searched together when set.'),
|
|
17
17
|
technologies: z.array(z.string()).optional().describe('Tech stack filters — routes to tech-aware providers. Pass technology names or slugs e.g. ["Salesforce", "HubSpot"].'),
|
|
18
|
-
min_employees: z.number().optional().describe('Minimum employee count'),
|
|
19
|
-
max_employees: z.number().optional().describe('Maximum employee count'),
|
|
18
|
+
min_employees: z.number().optional().describe('Minimum employee count. Bucket-only providers may widen non-aligned bounds; see _meta.filter_approximations.'),
|
|
19
|
+
max_employees: z.number().optional().describe('Maximum employee count. Bucket-only providers may widen non-aligned bounds; see _meta.filter_approximations.'),
|
|
20
20
|
min_founded_year: z.number().optional().describe('Earliest founding year'),
|
|
21
21
|
max_founded_year: z.number().optional().describe('Latest founding year'),
|
|
22
22
|
funding_stages: z.array(z.string()).optional().describe('Funding stage filters e.g. ["seed", "series_a", "series_b"]. Exact stage IDs vary by provider.'),
|
|
@@ -24,8 +24,8 @@ export const searchCompaniesSchema = {
|
|
|
24
24
|
max_funding_amount: z.number().optional().describe('Maximum total funding raised (USD)'),
|
|
25
25
|
min_funding_year: z.number().optional().describe('Earliest year of last funding round'),
|
|
26
26
|
max_funding_year: z.number().optional().describe('Latest year of last funding round'),
|
|
27
|
-
min_revenue: z.number().optional().describe('Minimum estimated annual revenue (USD)'),
|
|
28
|
-
max_revenue: z.number().optional().describe('Maximum estimated annual revenue (USD)'),
|
|
27
|
+
min_revenue: z.number().optional().describe('Minimum estimated annual revenue (USD). Bucket-only providers may widen non-aligned bounds; see _meta.filter_approximations.'),
|
|
28
|
+
max_revenue: z.number().optional().describe('Maximum estimated annual revenue (USD). Bucket-only providers may widen non-aligned bounds; see _meta.filter_approximations.'),
|
|
29
29
|
exclude_domains: z.array(z.string()).optional().describe('Company domains to exclude from results'),
|
|
30
30
|
exclude_industries: z.array(z.string()).optional().describe('Industry names to exclude from results'),
|
|
31
31
|
exclude_countries: z.array(z.string()).optional().describe('2-letter ISO country codes to exclude from results'),
|
|
@@ -32,11 +32,11 @@ const DISPLAY_NAMES: Record<string, string> = {
|
|
|
32
32
|
'signalbase-job-change': 'SignalBase',
|
|
33
33
|
blitzapi: 'BlitzAPI',
|
|
34
34
|
'blitzapi-reverse-email': 'BlitzAPI',
|
|
35
|
-
limadata: '
|
|
36
|
-
'limadata-prospect-filter': '
|
|
37
|
-
'limadata-prospect-url': '
|
|
38
|
-
'limadata-work-email': '
|
|
39
|
-
'limadata-work-email-linkedin': '
|
|
35
|
+
limadata: 'Lima Data',
|
|
36
|
+
'limadata-prospect-filter': 'Lima Data',
|
|
37
|
+
'limadata-prospect-url': 'Lima Data',
|
|
38
|
+
'limadata-work-email': 'Lima Data',
|
|
39
|
+
'limadata-work-email-linkedin': 'Lima Data',
|
|
40
40
|
predictleads: 'PredictLeads',
|
|
41
41
|
'predictleads-financing': 'PredictLeads',
|
|
42
42
|
'predictleads-news': 'PredictLeads',
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { listCapabilityProviderIds, getProviderApplicabilityGuard } from '../registry.js'
|
|
2
2
|
import type { Capability } from '../registry.js'
|
|
3
|
+
import { isProviderEnabled } from '../provider-availability.js'
|
|
3
4
|
import { fuzzyMatch } from './fuzzy.js'
|
|
4
5
|
|
|
5
6
|
// find_emails uses a custom waterfall — its providers are not in the registry.
|
|
6
7
|
// Exported so find-emails.ts stays in sync without a second hardcoded list.
|
|
7
8
|
// Order = auto-route execution order: bulk providers first (Steps 1-3), then
|
|
8
9
|
// the single-find_email fallback providers used for stragglers (Step 4).
|
|
9
|
-
export const
|
|
10
|
+
export const CONFIGURED_FIND_EMAILS_PROVIDERS = [
|
|
10
11
|
'limadata-work-email',
|
|
11
12
|
'leadmagic',
|
|
12
13
|
'prospeo',
|
|
@@ -20,6 +21,8 @@ export const FIND_EMAILS_PROVIDERS = [
|
|
|
20
21
|
'icypeas',
|
|
21
22
|
]
|
|
22
23
|
|
|
24
|
+
export const FIND_EMAILS_PROVIDERS = CONFIGURED_FIND_EMAILS_PROVIDERS.filter(isProviderEnabled)
|
|
25
|
+
|
|
23
26
|
export function getProvidersForCapability(capability: Capability | 'find_emails'): string[] {
|
|
24
27
|
if (capability === 'find_emails') return FIND_EMAILS_PROVIDERS
|
|
25
28
|
return listCapabilityProviderIds(capability as Capability)
|
package/test-linkupapi-live.ts
CHANGED
|
@@ -121,7 +121,7 @@ async function run() {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
// ─── 8. search_companies — is_hiring ──────────────────────────────────────
|
|
124
|
-
//
|
|
124
|
+
// Lima Data handles is_hiring at p9. Linkup-hiring at p16.
|
|
125
125
|
{
|
|
126
126
|
const r = await searchCompaniesHandler({
|
|
127
127
|
is_hiring: true,
|
package/test-phone-live.ts
CHANGED
|
@@ -31,7 +31,7 @@ async function main() {
|
|
|
31
31
|
linkedin_url: 'https://www.linkedin.com/in/michel-lieben',
|
|
32
32
|
})
|
|
33
33
|
|
|
34
|
-
// Scenario 2: Name + domain only — Findymail skipped,
|
|
34
|
+
// Scenario 2: Name + domain only — Findymail skipped, Lima Data/AI Ark run
|
|
35
35
|
await run('Name + domain only (no LinkedIn URL)', {
|
|
36
36
|
first_name: 'Michel',
|
|
37
37
|
last_name: 'Lieben',
|
package/test-search-live.ts
CHANGED
|
@@ -35,7 +35,7 @@ async function main() {
|
|
|
35
35
|
|
|
36
36
|
// Hypothesis D: maybe array of buckets like ["1001-5000"] or letter codes
|
|
37
37
|
await count('D1: employees ["1001-5000"]', { employees: ['1001-5000'] })
|
|
38
|
-
await count('D2: employees ["F"]', { employees: ['F'] }) //
|
|
38
|
+
await count('D2: employees ["F"]', { employees: ['F'] }) // Lima Data uses letter buckets, just in case
|
|
39
39
|
|
|
40
40
|
// Hypothesis E: snake_case field name
|
|
41
41
|
await count('E: employees_from/employees_to', { employees_from: 1000, employees_to: 5000 })
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { isProviderEnabled as isWorkerProviderEnabled } from '../../src/lib/provider-availability.js'
|
|
3
|
+
import { isProviderEnabled } from '../src/provider-availability.js'
|
|
4
|
+
import { getConfiguredProviders, getProviders, type Capability } from '../src/registry.js'
|
|
5
|
+
import { findEmailSchema } from '../src/tools/find-email.js'
|
|
6
|
+
import { findEmailsSchema } from '../src/tools/find-emails.js'
|
|
7
|
+
import { findEmailsBulkSchema } from '../src/tools/find-emails-bulk.js'
|
|
8
|
+
import { getEndpointDetailsSchema } from '../src/tools/get-endpoint-details.js'
|
|
9
|
+
import { listDataSourcesHandler } from '../src/tools/list-data-sources.js'
|
|
10
|
+
import {
|
|
11
|
+
CONFIGURED_FIND_EMAILS_PROVIDERS,
|
|
12
|
+
FIND_EMAILS_PROVIDERS,
|
|
13
|
+
getProvidersForCapability,
|
|
14
|
+
resolvePreferredProviders,
|
|
15
|
+
} from '../src/utils/provider-resolver.js'
|
|
16
|
+
|
|
17
|
+
const CAPABILITIES: Capability[] = [
|
|
18
|
+
'search_companies',
|
|
19
|
+
'find_people',
|
|
20
|
+
'find_email',
|
|
21
|
+
'verify_email',
|
|
22
|
+
'find_phone',
|
|
23
|
+
'enrich_company',
|
|
24
|
+
'enrich_person',
|
|
25
|
+
'search_web',
|
|
26
|
+
'search_jobs',
|
|
27
|
+
'search_ads',
|
|
28
|
+
'search_places',
|
|
29
|
+
'get_place_reviews',
|
|
30
|
+
'find_influencers',
|
|
31
|
+
'search_reddit',
|
|
32
|
+
'search_seo',
|
|
33
|
+
'find_signals',
|
|
34
|
+
'fetch_page_content',
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
describe('standalone MCP provider availability', () => {
|
|
38
|
+
it('mirrors every People Data Labs alias from the Worker policy', () => {
|
|
39
|
+
for (const alias of ['people-data-labs', 'People Data Labs', 'pdl', 'pdl-person-enrich', 'pdl-person-identify']) {
|
|
40
|
+
expect(isProviderEnabled(alias), alias).toBe(false)
|
|
41
|
+
expect(isProviderEnabled(alias), alias).toBe(isWorkerProviderEnabled(alias))
|
|
42
|
+
}
|
|
43
|
+
expect(isProviderEnabled('apollo')).toBe(true)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('keeps dormant definitions testable but removes them from every active waterfall', () => {
|
|
47
|
+
expect(getConfiguredProviders('find_email').some((provider) => provider.id === 'pdl-person-enrich')).toBe(true)
|
|
48
|
+
expect(CONFIGURED_FIND_EMAILS_PROVIDERS).toContain('pdl-person-enrich')
|
|
49
|
+
|
|
50
|
+
for (const capability of CAPABILITIES) {
|
|
51
|
+
expect(getProviders(capability).every((provider) => isProviderEnabled(provider.id))).toBe(true)
|
|
52
|
+
}
|
|
53
|
+
expect(FIND_EMAILS_PROVIDERS.every(isProviderEnabled)).toBe(true)
|
|
54
|
+
expect(getProvidersForCapability('find_emails')).not.toContain('pdl-person-enrich')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('rejects an explicit disabled-provider pin without advertising it as available', () => {
|
|
58
|
+
for (const capability of ['find_email', 'find_emails'] as const) {
|
|
59
|
+
const result = resolvePreferredProviders(capability, {}, ['pdl-person-enrich'])
|
|
60
|
+
expect(result.ok).toBe(false)
|
|
61
|
+
if (!result.ok) {
|
|
62
|
+
expect(result.error.available_providers.every(isProviderEnabled)).toBe(true)
|
|
63
|
+
expect(result.error.error).not.toContain('Available providers: pdl')
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('omits the disabled provider from data-source discovery and public schema descriptions', async () => {
|
|
69
|
+
const result = await listDataSourcesHandler({})
|
|
70
|
+
const publicMetadata = [
|
|
71
|
+
result.content[0].text,
|
|
72
|
+
findEmailSchema.use_providers.description,
|
|
73
|
+
findEmailSchema.max_credits.description,
|
|
74
|
+
findEmailsSchema.use_providers.description,
|
|
75
|
+
findEmailsSchema.max_credits.description,
|
|
76
|
+
findEmailsBulkSchema.max_credits.description,
|
|
77
|
+
getEndpointDetailsSchema.api.description,
|
|
78
|
+
].join('\n').toLowerCase()
|
|
79
|
+
|
|
80
|
+
expect(publicMetadata).not.toContain('people data labs')
|
|
81
|
+
expect(publicMetadata).not.toMatch(/\bpdl\b/)
|
|
82
|
+
})
|
|
83
|
+
})
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { getProviders } from '../src/registry.js'
|
|
2
|
+
import { getConfiguredProviders, getProviders } from '../src/registry.js'
|
|
3
3
|
|
|
4
4
|
const providers = () => getProviders('enrich_person')
|
|
5
|
+
const configuredProviders = () => getConfiguredProviders('enrich_person')
|
|
5
6
|
const get = (id: string) => {
|
|
6
|
-
const p =
|
|
7
|
+
const p = configuredProviders().find((p) => p.id === id)
|
|
7
8
|
if (!p) throw new Error(`Provider '${id}' not found in enrich_person`)
|
|
8
9
|
return p
|
|
9
10
|
}
|
|
@@ -468,8 +469,9 @@ describe('enrich_person priority ordering', () => {
|
|
|
468
469
|
expect(providers()[0].id).toBe('linkupapi-profile-enrich')
|
|
469
470
|
})
|
|
470
471
|
|
|
471
|
-
it('pdl-person-identify
|
|
472
|
-
const
|
|
473
|
-
expect(
|
|
472
|
+
it('keeps pdl-person-identify last in the dormant configuration but out of the active chain', () => {
|
|
473
|
+
const configured = configuredProviders()
|
|
474
|
+
expect(configured[configured.length - 1].id).toBe('pdl-person-identify')
|
|
475
|
+
expect(providers().some((provider) => provider.id.startsWith('pdl'))).toBe(false)
|
|
474
476
|
})
|
|
475
477
|
})
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { getProviders } from '../src/registry.js'
|
|
2
|
+
import { getConfiguredProviders, getProviders } from '../src/registry.js'
|
|
3
3
|
|
|
4
4
|
const providers = () => getProviders('find_people')
|
|
5
5
|
const get = (id: string) => {
|
|
6
|
-
const p =
|
|
6
|
+
const p = getConfiguredProviders('find_people').find((p) => p.id === id)
|
|
7
7
|
if (!p) throw new Error(`Provider '${id}' not found in find_people`)
|
|
8
8
|
return p
|
|
9
9
|
}
|
package/tests/registry.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { getProviders, getSearchWebProviders, isoCountryToName, listCapabilityProviderIds } from '../src/registry.js'
|
|
2
|
+
import { getConfiguredProviders, getProviders, getSearchWebProviders, isoCountryToName, listCapabilityProviderIds } from '../src/registry.js'
|
|
3
3
|
import type { Capability, ProviderEntry } from '../src/registry.js'
|
|
4
4
|
|
|
5
5
|
const ALL_CAPABILITIES: Capability[] = [
|
|
@@ -291,7 +291,7 @@ describe('registry', () => {
|
|
|
291
291
|
expect((result.body as { max_results: number }).max_results).toBe(50)
|
|
292
292
|
})
|
|
293
293
|
|
|
294
|
-
it('
|
|
294
|
+
it('Lima Data search folds keywords+industries into query and maps employees to A-H buckets', () => {
|
|
295
295
|
const providers = getProviders('search_companies')
|
|
296
296
|
const ld = providers.find((p) => p.id === 'limadata')!
|
|
297
297
|
const result = ld.mapParams({
|
|
@@ -307,7 +307,7 @@ describe('registry', () => {
|
|
|
307
307
|
})
|
|
308
308
|
})
|
|
309
309
|
|
|
310
|
-
it('
|
|
310
|
+
it('Lima Data search uses fallback query when no keywords/industries provided', () => {
|
|
311
311
|
const providers = getProviders('search_companies')
|
|
312
312
|
const ld = providers.find((p) => p.id === 'limadata')!
|
|
313
313
|
const result = ld.mapParams({ min_employees: 51, max_employees: 200 })
|
|
@@ -427,7 +427,7 @@ describe('registry', () => {
|
|
|
427
427
|
})
|
|
428
428
|
|
|
429
429
|
|
|
430
|
-
it('
|
|
430
|
+
it('Lima Data prospect filter maps employees to company_headcount filter_type', () => {
|
|
431
431
|
const providers = getProviders('search_companies')
|
|
432
432
|
const ld = providers.find((p) => p.id === 'limadata-prospect-filter')!
|
|
433
433
|
const result = ld.mapParams({ min_employees: 51, max_employees: 500 })
|
|
@@ -436,7 +436,7 @@ describe('registry', () => {
|
|
|
436
436
|
})
|
|
437
437
|
})
|
|
438
438
|
|
|
439
|
-
it('
|
|
439
|
+
it('Lima Data prospect filter isApplicable requires a non-trivial employee range', () => {
|
|
440
440
|
const providers = getProviders('search_companies')
|
|
441
441
|
const ld = providers.find((p) => p.id === 'limadata-prospect-filter')!
|
|
442
442
|
expect(ld.isApplicable!({})).toBe(false)
|
|
@@ -444,14 +444,14 @@ describe('registry', () => {
|
|
|
444
444
|
expect(ld.isApplicable!({ min_employees: 51, max_employees: 500 })).toBe(true)
|
|
445
445
|
})
|
|
446
446
|
|
|
447
|
-
it('
|
|
447
|
+
it('Lima Data prospect URL passes search_url when provided', () => {
|
|
448
448
|
const providers = getProviders('search_companies')
|
|
449
449
|
const ld = providers.find((p) => p.id === 'limadata-prospect-url')!
|
|
450
450
|
const result = ld.mapParams({ linkedin_search_url: 'https://www.linkedin.com/sales/search/company?...' })
|
|
451
451
|
expect(result.body).toEqual({ search_url: 'https://www.linkedin.com/sales/search/company?...' })
|
|
452
452
|
})
|
|
453
453
|
|
|
454
|
-
it('
|
|
454
|
+
it('Lima Data prospect URL isApplicable requires linkedin_search_url', () => {
|
|
455
455
|
const providers = getProviders('search_companies')
|
|
456
456
|
const ld = providers.find((p) => p.id === 'limadata-prospect-url')!
|
|
457
457
|
expect(ld.isApplicable!({})).toBe(false)
|
|
@@ -459,12 +459,11 @@ describe('registry', () => {
|
|
|
459
459
|
expect(ld.isApplicable!({ linkedin_search_url: 'https://...' })).toBe(true)
|
|
460
460
|
})
|
|
461
461
|
|
|
462
|
-
it('search_companies
|
|
462
|
+
it('search_companies omits disabled providers while preserving priority order', () => {
|
|
463
463
|
const providers = getProviders('search_companies')
|
|
464
464
|
expect(providers.map((p) => p.id)).toEqual([
|
|
465
465
|
'companyenrich',
|
|
466
466
|
'fullenrich',
|
|
467
|
-
'pdl',
|
|
468
467
|
'theirstack',
|
|
469
468
|
'signalbase',
|
|
470
469
|
'blitzapi',
|
|
@@ -483,8 +482,8 @@ describe('registry', () => {
|
|
|
483
482
|
])
|
|
484
483
|
})
|
|
485
484
|
|
|
486
|
-
it('
|
|
487
|
-
const pdl =
|
|
485
|
+
it('keeps the dormant PDL company-search definition testable', () => {
|
|
486
|
+
const pdl = getConfiguredProviders('search_companies').find((p) => p.id === 'pdl')!
|
|
488
487
|
expect(pdl.endpoint).toBe('/pdl/company/search')
|
|
489
488
|
expect(pdl.method).toBe('POST')
|
|
490
489
|
})
|
|
@@ -598,8 +597,8 @@ describe('registry', () => {
|
|
|
598
597
|
})
|
|
599
598
|
})
|
|
600
599
|
|
|
601
|
-
it('
|
|
602
|
-
const pdl =
|
|
600
|
+
it('keeps the dormant PDL people-search definition testable', () => {
|
|
601
|
+
const pdl = getConfiguredProviders('find_people').find((p) => p.id === 'pdl')!
|
|
603
602
|
expect(pdl.endpoint).toBe('/pdl/person/search')
|
|
604
603
|
expect(pdl.method).toBe('POST')
|
|
605
604
|
})
|
|
@@ -707,7 +706,7 @@ describe('registry', () => {
|
|
|
707
706
|
expect(ids[1]).toBe('leadmagic')
|
|
708
707
|
})
|
|
709
708
|
|
|
710
|
-
it('
|
|
709
|
+
it('Lima Data work-email maps full_name + company_domain', () => {
|
|
711
710
|
const providers = getProviders('find_email')
|
|
712
711
|
const ld = providers.find((p) => p.id === 'limadata-work-email')!
|
|
713
712
|
const result = ld.mapParams({
|
|
@@ -718,7 +717,7 @@ describe('registry', () => {
|
|
|
718
717
|
expect(result.body).toEqual({ full_name: 'Michel Lieben', company_domain: 'coldiq.com' })
|
|
719
718
|
})
|
|
720
719
|
|
|
721
|
-
it('
|
|
720
|
+
it('Lima Data work-email hasResult accepts top-level email or nested data.email or emails array', () => {
|
|
722
721
|
const providers = getProviders('find_email')
|
|
723
722
|
const ld = providers.find((p) => p.id === 'limadata-work-email')!
|
|
724
723
|
expect(ld.hasResult({ email: 'michel@coldiq.com' })).toBe(true)
|
|
@@ -752,7 +751,7 @@ describe('registry', () => {
|
|
|
752
751
|
expect(blitz.hasResult({ email: null })).toBe(false)
|
|
753
752
|
})
|
|
754
753
|
|
|
755
|
-
it('
|
|
754
|
+
it('Lima Data work-email-linkedin maps linkedin_url passthrough', () => {
|
|
756
755
|
const providers = getProviders('find_email')
|
|
757
756
|
const ld = providers.find((p) => p.id === 'limadata-work-email-linkedin')!
|
|
758
757
|
const result = ld.mapParams({
|
|
@@ -761,7 +760,7 @@ describe('registry', () => {
|
|
|
761
760
|
expect(result.body).toEqual({ linkedin_url: 'https://linkedin.com/in/michel-lieben' })
|
|
762
761
|
})
|
|
763
762
|
|
|
764
|
-
it('
|
|
763
|
+
it('Lima Data work-email-linkedin isApplicable requires linkedin_url', () => {
|
|
765
764
|
const providers = getProviders('find_email')
|
|
766
765
|
const ld = providers.find((p) => p.id === 'limadata-work-email-linkedin')!
|
|
767
766
|
expect(ld.isApplicable!({})).toBe(false)
|
|
@@ -769,7 +768,7 @@ describe('registry', () => {
|
|
|
769
768
|
expect(ld.isApplicable!({ linkedin_url: 'https://...' })).toBe(true)
|
|
770
769
|
})
|
|
771
770
|
|
|
772
|
-
it('
|
|
771
|
+
it('Lima Data work-email isApplicable requires first_name + last_name + domain', () => {
|
|
773
772
|
const providers = getProviders('find_email')
|
|
774
773
|
const ld = providers.find((p) => p.id === 'limadata-work-email')!
|
|
775
774
|
expect(ld.isApplicable!({})).toBe(false)
|
|
@@ -840,10 +839,10 @@ describe('registry', () => {
|
|
|
840
839
|
})
|
|
841
840
|
|
|
842
841
|
describe('enrich_company', () => {
|
|
843
|
-
it('providers
|
|
842
|
+
it('providers preserve their order with disabled providers omitted', () => {
|
|
844
843
|
const ids = getProviders('enrich_company').map((p) => p.id)
|
|
845
844
|
expect(ids).toEqual([
|
|
846
|
-
'companyenrich', '
|
|
845
|
+
'companyenrich', 'apollo', 'limadata', 'prospeo',
|
|
847
846
|
'companyenrich_props', 'blitzapi', 'wiza', 'findymail', 'icypeas',
|
|
848
847
|
'builtwith', 'openmart', 'linkupapi-by-domain', 'linkupapi-by-url', 'discolike',
|
|
849
848
|
])
|
|
@@ -866,8 +865,8 @@ describe('registry', () => {
|
|
|
866
865
|
expect(apollo.isApplicable!({ name: 'Stripe' })).toBe(false)
|
|
867
866
|
})
|
|
868
867
|
|
|
869
|
-
it('
|
|
870
|
-
const pdl =
|
|
868
|
+
it('keeps the dormant PDL company-enrichment definition testable', () => {
|
|
869
|
+
const pdl = getConfiguredProviders('enrich_company').find((p) => p.id === 'pdl')!
|
|
871
870
|
expect(pdl.endpoint).toBe('/pdl/company/enrich')
|
|
872
871
|
expect(pdl.method).toBe('GET')
|
|
873
872
|
expect(pdl.mapParams({ domain: 'stripe.com' }).queryParams).toEqual({ website: 'stripe.com' })
|
|
@@ -899,7 +898,7 @@ describe('registry', () => {
|
|
|
899
898
|
expect(wiza.mapParams({ name: 'Stripe' }).body).toEqual({ company_name: 'Stripe' })
|
|
900
899
|
})
|
|
901
900
|
|
|
902
|
-
it('
|
|
901
|
+
it('Lima Data is gated on domain or linkedin_url', () => {
|
|
903
902
|
const ld = getProviders('enrich_company').find((p) => p.id === 'limadata')!
|
|
904
903
|
expect(ld.isApplicable!({ domain: 'stripe.com' })).toBe(true)
|
|
905
904
|
expect(ld.isApplicable!({ linkedin_url: 'https://linkedin.com/company/stripe' })).toBe(true)
|
|
@@ -1028,7 +1027,7 @@ describe('registry', () => {
|
|
|
1028
1027
|
expect(fm.hasResult({ phones: [] })).toBe(false)
|
|
1029
1028
|
})
|
|
1030
1029
|
|
|
1031
|
-
it('
|
|
1030
|
+
it('Lima Data isApplicable accepts linkedin_url or name+company', () => {
|
|
1032
1031
|
const ld = getProviders('find_phone').find((p) => p.id === 'limadata')!
|
|
1033
1032
|
expect(ld.isApplicable!({})).toBe(false)
|
|
1034
1033
|
expect(ld.isApplicable!({ first_name: 'Michel' })).toBe(false)
|
|
@@ -1038,14 +1037,14 @@ describe('registry', () => {
|
|
|
1038
1037
|
expect(ld.isApplicable!({ first_name: 'Michel', last_name: 'Lieben', company_domain: 'coldiq.com' })).toBe(true)
|
|
1039
1038
|
})
|
|
1040
1039
|
|
|
1041
|
-
it('
|
|
1040
|
+
it('Lima Data mapParams forwards linkedin_url path', () => {
|
|
1042
1041
|
const ld = getProviders('find_phone').find((p) => p.id === 'limadata')!
|
|
1043
1042
|
expect(ld.mapParams({ linkedin_url: 'https://linkedin.com/in/michel-lieben' }).body).toEqual({
|
|
1044
1043
|
linkedin_url: 'https://linkedin.com/in/michel-lieben',
|
|
1045
1044
|
})
|
|
1046
1045
|
})
|
|
1047
1046
|
|
|
1048
|
-
it('
|
|
1047
|
+
it('Lima Data mapParams forwards name+company path', () => {
|
|
1049
1048
|
const ld = getProviders('find_phone').find((p) => p.id === 'limadata')!
|
|
1050
1049
|
expect(ld.mapParams({ first_name: 'Michel', last_name: 'Lieben', company_domain: 'coldiq.com' }).body).toEqual({
|
|
1051
1050
|
name: 'Michel Lieben',
|
|
@@ -1053,7 +1052,7 @@ describe('registry', () => {
|
|
|
1053
1052
|
})
|
|
1054
1053
|
})
|
|
1055
1054
|
|
|
1056
|
-
it('
|
|
1055
|
+
it('Lima Data mapParams forwards company_name when no domain', () => {
|
|
1057
1056
|
const ld = getProviders('find_phone').find((p) => p.id === 'limadata')!
|
|
1058
1057
|
expect(ld.mapParams({ first_name: 'Michel', last_name: 'Lieben', company_name: 'ColdIQ' }).body).toEqual({
|
|
1059
1058
|
name: 'Michel Lieben',
|
|
@@ -1061,7 +1060,7 @@ describe('registry', () => {
|
|
|
1061
1060
|
})
|
|
1062
1061
|
})
|
|
1063
1062
|
|
|
1064
|
-
it('
|
|
1063
|
+
it('Lima Data hasResult checks multiple phone shapes including phone_numbers', () => {
|
|
1065
1064
|
const ld = getProviders('find_phone').find((p) => p.id === 'limadata')!
|
|
1066
1065
|
expect(ld.hasResult({ phone: '+33612345678' })).toBe(true)
|
|
1067
1066
|
expect(ld.hasResult({ phones: ['+33612345678'] })).toBe(true)
|
|
@@ -1249,12 +1248,13 @@ describe('registry', () => {
|
|
|
1249
1248
|
expect(apollo.hasResult(filtered)).toBe(false)
|
|
1250
1249
|
})
|
|
1251
1250
|
|
|
1252
|
-
it('
|
|
1251
|
+
it('top-tier active providers have no postFilter', () => {
|
|
1253
1252
|
const providers = getProviders('search_companies')
|
|
1254
|
-
for (const id of ['companyenrich', 'fullenrich', '
|
|
1253
|
+
for (const id of ['companyenrich', 'fullenrich', 'signalbase']) {
|
|
1255
1254
|
const p = providers.find((x) => x.id === id)!
|
|
1256
1255
|
expect(p.postFilter, `${id} should not have postFilter`).toBeUndefined()
|
|
1257
1256
|
}
|
|
1257
|
+
expect(getConfiguredProviders('search_companies').find((provider) => provider.id === 'pdl')?.postFilter).toBeUndefined()
|
|
1258
1258
|
})
|
|
1259
1259
|
|
|
1260
1260
|
it('Apollo isApplicable: rejects Phase B filters (technologies, funding, revenue, exclusions, workforce growth, is_hiring)', () => {
|
|
@@ -1347,13 +1347,13 @@ describe('registry', () => {
|
|
|
1347
1347
|
})
|
|
1348
1348
|
|
|
1349
1349
|
|
|
1350
|
-
it('
|
|
1350
|
+
it('Lima Data passes has_jobs: true when is_hiring is true', () => {
|
|
1351
1351
|
const ld = getProviders('search_companies').find((p) => p.id === 'limadata')!
|
|
1352
1352
|
const result = ld.mapParams({ keywords: ['SaaS'], is_hiring: true })
|
|
1353
1353
|
expect((result.body as Record<string, unknown>).has_jobs).toBe(true)
|
|
1354
1354
|
})
|
|
1355
1355
|
|
|
1356
|
-
it('
|
|
1356
|
+
it('Lima Data omits has_jobs when is_hiring is false or unset', () => {
|
|
1357
1357
|
const ld = getProviders('search_companies').find((p) => p.id === 'limadata')!
|
|
1358
1358
|
const falseResult = ld.mapParams({ keywords: ['SaaS'], is_hiring: false })
|
|
1359
1359
|
const unsetResult = ld.mapParams({ keywords: ['SaaS'] })
|
|
@@ -20,19 +20,19 @@ describe('call_endpoint handler', () => {
|
|
|
20
20
|
}) as typeof fetch
|
|
21
21
|
|
|
22
22
|
const params = { filters: [{ filter_type: 'current_title', values: ['Head of Growth'] }] }
|
|
23
|
-
const result = await callEndpointHandler({ api: '
|
|
23
|
+
const result = await callEndpointHandler({ api: 'limadata', path: '/limadata/prospect/people/filter', params })
|
|
24
24
|
|
|
25
25
|
expect(capturedUrl).toBe('http://test-api.local/v1/endpoints/use')
|
|
26
26
|
expect(capturedAuth).toBe('Bearer test-key')
|
|
27
|
-
expect(capturedBody).toEqual({ api: '
|
|
27
|
+
expect(capturedBody).toEqual({ api: 'limadata', path: '/limadata/prospect/people/filter', params })
|
|
28
28
|
expect(result.isError).toBeFalsy()
|
|
29
29
|
const parsed = JSON.parse(result.content[0].text)
|
|
30
30
|
expect(parsed.data.people).toHaveLength(1)
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
it('returns isError when the call fails (e.g. unknown endpoint 404)', async () => {
|
|
34
|
-
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({ error: 'Endpoint not found for api=
|
|
35
|
-
const result = await callEndpointHandler({ api: '
|
|
34
|
+
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({ error: 'Endpoint not found for api=limadata path=/nope', code: -32004 }), { status: 404 })) as typeof fetch
|
|
35
|
+
const result = await callEndpointHandler({ api: 'limadata', path: '/nope', params: {} })
|
|
36
36
|
expect(result.isError).toBe(true)
|
|
37
37
|
const parsed = JSON.parse(result.content[0].text)
|
|
38
38
|
expect(parsed.status).toBe(404)
|
|
@@ -14,13 +14,13 @@ describe('get_endpoint_details handler', () => {
|
|
|
14
14
|
globalThis.fetch = vi.fn(async (url: string | URL | Request, opts?: RequestInit) => {
|
|
15
15
|
capturedUrl = url.toString()
|
|
16
16
|
capturedBody = opts?.body ? JSON.parse(opts.body as string) : undefined
|
|
17
|
-
return new Response(JSON.stringify({ path: '/
|
|
17
|
+
return new Response(JSON.stringify({ path: '/limadata/prospect/people/filter', method: 'post' }), { status: 200 })
|
|
18
18
|
}) as typeof fetch
|
|
19
19
|
|
|
20
|
-
const result = await getEndpointDetailsHandler({ api: '
|
|
20
|
+
const result = await getEndpointDetailsHandler({ api: 'limadata', path: '/limadata/prospect/people/filter' })
|
|
21
21
|
|
|
22
22
|
expect(capturedUrl).toBe('http://test-api.local/v1/endpoints/details')
|
|
23
|
-
expect(capturedBody).toEqual({ api: '
|
|
23
|
+
expect(capturedBody).toEqual({ api: 'limadata', path: '/limadata/prospect/people/filter' })
|
|
24
24
|
expect(result.isError).toBeFalsy()
|
|
25
25
|
const parsed = JSON.parse(result.content[0].text)
|
|
26
26
|
expect(parsed.path).toContain('prospect/people/filter')
|
|
@@ -28,7 +28,7 @@ describe('get_endpoint_details handler', () => {
|
|
|
28
28
|
|
|
29
29
|
it('returns isError when the endpoint is unknown (404)', async () => {
|
|
30
30
|
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({ error: 'not found' }), { status: 404 })) as typeof fetch
|
|
31
|
-
const result = await getEndpointDetailsHandler({ api: '
|
|
31
|
+
const result = await getEndpointDetailsHandler({ api: 'limadata', path: '/nope' })
|
|
32
32
|
expect(result.isError).toBe(true)
|
|
33
33
|
const parsed = JSON.parse(result.content[0].text)
|
|
34
34
|
expect(parsed.status).toBe(404)
|
|
@@ -45,7 +45,7 @@ describe('list_data_sources handler', () => {
|
|
|
45
45
|
const companies = rowFor(caps, 'search_companies')!
|
|
46
46
|
const names = companies.sources.map((s) => s.name)
|
|
47
47
|
// search_companies has limadata + limadata-prospect-* + linkupapi-* internally
|
|
48
|
-
expect(names.filter((n) => n === '
|
|
48
|
+
expect(names.filter((n) => n === 'Lima Data')).toHaveLength(1)
|
|
49
49
|
expect(names.filter((n) => n === 'LinkupAPI')).toHaveLength(1)
|
|
50
50
|
// every row carries the metadata a frontend needs
|
|
51
51
|
for (const cap of caps) {
|
|
@@ -14,7 +14,7 @@ describe('search_endpoints handler', () => {
|
|
|
14
14
|
globalThis.fetch = vi.fn(async (url: string | URL | Request, opts?: RequestInit) => {
|
|
15
15
|
capturedUrl = url.toString()
|
|
16
16
|
capturedBody = opts?.body ? JSON.parse(opts.body as string) : undefined
|
|
17
|
-
return new Response(JSON.stringify({ results: [{ api: '
|
|
17
|
+
return new Response(JSON.stringify({ results: [{ api: 'limadata', provider: 'Lima Data', endpoints: [] }] }), { status: 200 })
|
|
18
18
|
}) as typeof fetch
|
|
19
19
|
|
|
20
20
|
const result = await searchEndpointsHandler({ prompt: 'find people by title', limit: 5 })
|
|
@@ -23,7 +23,7 @@ describe('search_endpoints handler', () => {
|
|
|
23
23
|
expect(capturedBody).toEqual({ prompt: 'find people by title', limit: 5 })
|
|
24
24
|
expect(result.isError).toBeFalsy()
|
|
25
25
|
const parsed = JSON.parse(result.content[0].text)
|
|
26
|
-
expect(parsed.results[0].api).toBe('
|
|
26
|
+
expect(parsed.results[0].api).toBe('limadata')
|
|
27
27
|
})
|
|
28
28
|
|
|
29
29
|
it('returns isError when the API responds non-2xx', async () => {
|
|
@@ -9,8 +9,8 @@ describe('providerDisplayName', () => {
|
|
|
9
9
|
})
|
|
10
10
|
|
|
11
11
|
it('collapses vendor variants to one brand', () => {
|
|
12
|
-
expect(providerDisplayName('limadata-work-email')).toBe('
|
|
13
|
-
expect(providerDisplayName('limadata-prospect-url')).toBe('
|
|
12
|
+
expect(providerDisplayName('limadata-work-email')).toBe('Lima Data')
|
|
13
|
+
expect(providerDisplayName('limadata-prospect-url')).toBe('Lima Data')
|
|
14
14
|
expect(providerDisplayName('signalbase-funding')).toBe('SignalBase')
|
|
15
15
|
expect(providerDisplayName('linkupapi-by-domain')).toBe('LinkupAPI')
|
|
16
16
|
})
|