@coldiq/mcp 5.4.5 → 5.4.7
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/index.js +44 -36
- package/dist/index.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +17 -12
- package/dist/registry.js.map +1 -1
- package/dist/utils/provider-resolver.js +1 -1
- package/package.json +2 -2
- package/src/index.ts +51 -37
- package/src/registry.ts +16 -12
- package/src/utils/provider-resolver.ts +1 -1
- package/tests/executor.test.ts +2 -2
- package/tests/instructions.test.ts +3 -3
- package/tests/registry-enrich-person.test.ts +4 -4
- package/tests/registry-find-people.test.ts +3 -2
- package/tests/registry-search-companies.test.ts +11 -2
- package/tests/registry.test.ts +9 -8
- package/tests/sdk-zod4-compatibility.test.ts +59 -0
- package/tests/tool-json-schema.test.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coldiq/mcp",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"node": ">=18.18"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
22
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
23
23
|
"undici": "^6.25.0",
|
|
24
24
|
"zod": "4.5.4"
|
|
25
25
|
},
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
3
|
+
import { McpServer, type ToolCallback } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
4
4
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
5
|
+
import { z } from 'zod'
|
|
5
6
|
import { initClient } from './client.js'
|
|
6
7
|
import { MCP_SERVER_INSTRUCTIONS } from './instructions.js'
|
|
7
8
|
import { withKnowledgeFirstRequirement } from './knowledge-policy.js'
|
|
@@ -220,48 +221,61 @@ const server = new McpServer(
|
|
|
220
221
|
},
|
|
221
222
|
)
|
|
222
223
|
|
|
224
|
+
function registerPublicTool<Fields extends Record<string, z.ZodType>>(
|
|
225
|
+
name: string,
|
|
226
|
+
description: string,
|
|
227
|
+
schema: Fields,
|
|
228
|
+
handler: ToolCallback<z.ZodObject<Fields>>,
|
|
229
|
+
): void {
|
|
230
|
+
const inputSchema = z.strictObject(schema)
|
|
231
|
+
server.registerTool<never, typeof inputSchema>(name, {
|
|
232
|
+
description,
|
|
233
|
+
inputSchema,
|
|
234
|
+
}, handler)
|
|
235
|
+
}
|
|
236
|
+
|
|
223
237
|
// Register knowledge retrieval before every operational tool so clients that
|
|
224
238
|
// preserve registration order surface the mandatory prerequisite first.
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
239
|
+
registerPublicTool(searchKnowledgeName, searchKnowledgeDescription, searchKnowledgeSchema, searchKnowledgeHandler)
|
|
240
|
+
registerPublicTool(searchCompaniesName, withKnowledgeFirstRequirement(searchCompaniesDescription), searchCompaniesSchema, searchCompaniesHandler)
|
|
241
|
+
registerPublicTool(findPeopleName, withKnowledgeFirstRequirement(findPeopleDescription), findPeopleSchema, findPeopleHandler)
|
|
242
|
+
registerPublicTool(findEmailName, withKnowledgeFirstRequirement(findEmailDescription), findEmailSchema, findEmailHandler)
|
|
243
|
+
registerPublicTool(enrichPersonName, withKnowledgeFirstRequirement(enrichPersonDescription), enrichPersonSchema, enrichPersonHandler)
|
|
244
|
+
registerPublicTool(findEmailsName, withKnowledgeFirstRequirement(findEmailsDescription), findEmailsSchema, findEmailsHandler)
|
|
245
|
+
registerPublicTool(verifyEmailName, withKnowledgeFirstRequirement(verifyEmailDescription), verifyEmailSchema, verifyEmailHandler)
|
|
246
|
+
registerPublicTool(verifyEmailsBulkName, withKnowledgeFirstRequirement(verifyEmailsBulkDescription), verifyEmailsBulkSchema, verifyEmailsBulkHandler)
|
|
247
|
+
registerPublicTool(findEmailsBulkName, withKnowledgeFirstRequirement(findEmailsBulkDescription), findEmailsBulkSchema, findEmailsBulkHandler)
|
|
248
|
+
registerPublicTool(enrichPersonBulkName, withKnowledgeFirstRequirement(enrichPersonBulkDescription), enrichPersonBulkSchema, enrichPersonBulkHandler)
|
|
249
|
+
registerPublicTool(enrichCompanyBulkName, withKnowledgeFirstRequirement(enrichCompanyBulkDescription), enrichCompanyBulkSchema, enrichCompanyBulkHandler)
|
|
250
|
+
registerPublicTool(findPhoneBulkName, withKnowledgeFirstRequirement(findPhoneBulkDescription), findPhoneBulkSchema, findPhoneBulkHandler)
|
|
251
|
+
registerPublicTool(getBulkJobName, withKnowledgeFirstRequirement(getBulkJobDescription), getBulkJobSchema, getBulkJobHandler)
|
|
252
|
+
registerPublicTool(cancelBulkJobName, withKnowledgeFirstRequirement(cancelBulkJobDescription), cancelBulkJobSchema, cancelBulkJobHandler)
|
|
253
|
+
registerPublicTool(findPhoneName, withKnowledgeFirstRequirement(findPhoneDescription), findPhoneSchema, findPhoneHandler)
|
|
254
|
+
registerPublicTool(enrichCompanyName, withKnowledgeFirstRequirement(enrichCompanyDescription), enrichCompanySchema, enrichCompanyHandler)
|
|
255
|
+
registerPublicTool(searchWebName, withKnowledgeFirstRequirement(searchWebDescription), searchWebSchema, searchWebHandler)
|
|
256
|
+
registerPublicTool(searchJobsName, withKnowledgeFirstRequirement(searchJobsDescription), searchJobsSchema, searchJobsHandler)
|
|
257
|
+
registerPublicTool(searchAdsName, withKnowledgeFirstRequirement(searchAdsDescription), searchAdsSchema, searchAdsHandler)
|
|
258
|
+
registerPublicTool(searchPlacesName, withKnowledgeFirstRequirement(searchPlacesDescription), searchPlacesSchema, searchPlacesHandler)
|
|
259
|
+
registerPublicTool(findInfluencersName, withKnowledgeFirstRequirement(findInfluencersDescription), findInfluencersSchema, findInfluencersHandler)
|
|
260
|
+
registerPublicTool(searchRedditName, withKnowledgeFirstRequirement(searchRedditDescription), searchRedditSchema, searchRedditHandler)
|
|
261
|
+
registerPublicTool(searchSeoName, withKnowledgeFirstRequirement(searchSeoDescription), searchSeoSchema, searchSeoHandler)
|
|
262
|
+
registerPublicTool(findSignalsName, withKnowledgeFirstRequirement(findSignalsDescription), findSignalsSchema, findSignalsHandler)
|
|
263
|
+
registerPublicTool(fetchPageContentName, withKnowledgeFirstRequirement(fetchPageContentDescription), fetchPageContentSchema, fetchPageContentHandler)
|
|
264
|
+
registerPublicTool(getCreditBalanceName, getCreditBalanceDescription, getCreditBalanceSchema, getCreditBalanceHandler)
|
|
265
|
+
registerPublicTool(getSessionUsageName, getSessionUsageDescription, getSessionUsageSchema, getSessionUsageHandler)
|
|
266
|
+
registerPublicTool(listDataSourcesName, withKnowledgeFirstRequirement(listDataSourcesDescription), listDataSourcesSchema, listDataSourcesHandler)
|
|
267
|
+
registerPublicTool(listSkillsName, listSkillsDescription, listSkillsSchema, listSkillsHandler)
|
|
268
|
+
registerPublicTool(loadSkillName, loadSkillDescription, loadSkillSchema, loadSkillHandler)
|
|
269
|
+
registerPublicTool(extractPostEngagementName, withKnowledgeFirstRequirement(extractPostEngagementDescription), extractPostEngagementSchema, extractPostEngagementHandler)
|
|
270
|
+
registerPublicTool(getPlaceReviewsName, withKnowledgeFirstRequirement(getPlaceReviewsDescription), getPlaceReviewsSchema, getPlaceReviewsHandler)
|
|
257
271
|
// ColdIQ Visitor ID: read the leads identified on the account's own websites.
|
|
258
272
|
// Listing the websites themselves needs no tool — search_endpoints/call_endpoint
|
|
259
273
|
// already reach GET /visitor-id/websites.
|
|
260
|
-
|
|
274
|
+
registerPublicTool(getWebsiteVisitorsName, withKnowledgeFirstRequirement(getWebsiteVisitorsDescription), getWebsiteVisitorsSchema, getWebsiteVisitorsHandler)
|
|
261
275
|
// Generic endpoint discovery + invoke: reach any integrated provider without a dedicated tool.
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
276
|
+
registerPublicTool(searchEndpointsName, withKnowledgeFirstRequirement(searchEndpointsDescription), searchEndpointsSchema, searchEndpointsHandler)
|
|
277
|
+
registerPublicTool(getEndpointDetailsName, withKnowledgeFirstRequirement(getEndpointDetailsDescription), getEndpointDetailsSchema, getEndpointDetailsHandler)
|
|
278
|
+
registerPublicTool(callEndpointName, withKnowledgeFirstRequirement(callEndpointDescription), callEndpointSchema, callEndpointHandler)
|
|
265
279
|
|
|
266
280
|
// Connect via stdio transport
|
|
267
281
|
const transport = new StdioServerTransport()
|
package/src/registry.ts
CHANGED
|
@@ -475,9 +475,12 @@ const searchCompaniesProviders: ProviderEntry[] = [
|
|
|
475
475
|
id: 'apollo',
|
|
476
476
|
endpoint: '/apollo/organizations/search',
|
|
477
477
|
method: 'POST',
|
|
478
|
-
|
|
478
|
+
// Keep Apollo behind the normal company databases. Its limited seat is a
|
|
479
|
+
// recovery source, before the costly Lima Data prospect and DiscoLike tails.
|
|
480
|
+
priority: 19,
|
|
479
481
|
isApplicable: (input) => {
|
|
480
482
|
// Apollo has no founded-year, technologies, funding, revenue, exclusion, or hiring filters — skip when set.
|
|
483
|
+
if (input.linkedin_search_url) return false
|
|
481
484
|
if (typeof input.min_founded_year === 'number' || typeof input.max_founded_year === 'number') return false
|
|
482
485
|
if (isNonEmptyArray(input.technologies)) return false
|
|
483
486
|
if (isNonEmptyArray(input.funding_stages)) return false
|
|
@@ -891,7 +894,7 @@ const searchCompaniesProviders: ProviderEntry[] = [
|
|
|
891
894
|
id: 'limadata-prospect-filter',
|
|
892
895
|
endpoint: '/limadata/prospect/companies/filter',
|
|
893
896
|
method: 'POST',
|
|
894
|
-
priority:
|
|
897
|
+
priority: 20,
|
|
895
898
|
mapParams: (input) => {
|
|
896
899
|
// Lima Data prospect filter uses [{filter_type, values}]. We can only reliably
|
|
897
900
|
// map company_headcount (size buckets); industries/locations need LinkedIn IDs.
|
|
@@ -923,7 +926,7 @@ const searchCompaniesProviders: ProviderEntry[] = [
|
|
|
923
926
|
id: 'limadata-prospect-url',
|
|
924
927
|
endpoint: '/limadata/prospect/companies/search-url',
|
|
925
928
|
method: 'POST',
|
|
926
|
-
priority:
|
|
929
|
+
priority: 21,
|
|
927
930
|
mapParams: (input) => ({
|
|
928
931
|
body: { search_url: input.linkedin_search_url as string },
|
|
929
932
|
}),
|
|
@@ -1087,7 +1090,7 @@ const searchCompaniesProviders: ProviderEntry[] = [
|
|
|
1087
1090
|
id: 'discolike',
|
|
1088
1091
|
endpoint: '/discolike/discover',
|
|
1089
1092
|
method: 'GET',
|
|
1090
|
-
priority:
|
|
1093
|
+
priority: 22,
|
|
1091
1094
|
isApplicable: (input) => {
|
|
1092
1095
|
if (!isNonEmptyArray(input.similar_to_domains) && !isNonEmptyArray(input.keywords) && !isNonEmptyArray(input.industries)) return false
|
|
1093
1096
|
if (isNonEmptyArray(input.locations)) return false
|
|
@@ -1277,7 +1280,8 @@ const findPeopleProviders: ProviderEntry[] = [
|
|
|
1277
1280
|
id: 'apollo',
|
|
1278
1281
|
endpoint: '/apollo/people/search',
|
|
1279
1282
|
method: 'POST',
|
|
1280
|
-
|
|
1283
|
+
// Apollo is the last active people-search recovery source in this mirror.
|
|
1284
|
+
priority: 11,
|
|
1281
1285
|
// Apollo caps per_page at 100 and rejects larger values with a 400. The
|
|
1282
1286
|
// find_people schema permits limit up to 500 (a waterfall-wide ceiling),
|
|
1283
1287
|
// so clamp here to keep Apollo from breaking the request when the caller
|
|
@@ -1614,7 +1618,7 @@ const findEmailProviders: ProviderEntry[] = [
|
|
|
1614
1618
|
id: 'limadata-work-email',
|
|
1615
1619
|
endpoint: '/limadata/find/work-email',
|
|
1616
1620
|
method: 'POST',
|
|
1617
|
-
priority:
|
|
1621
|
+
priority: 4,
|
|
1618
1622
|
mapParams: (input) => {
|
|
1619
1623
|
const fullName = [input.first_name, input.last_name].filter(Boolean).join(' ')
|
|
1620
1624
|
return {
|
|
@@ -1711,7 +1715,7 @@ const findEmailProviders: ProviderEntry[] = [
|
|
|
1711
1715
|
id: 'apollo-people-match',
|
|
1712
1716
|
endpoint: '/apollo/people/match',
|
|
1713
1717
|
method: 'POST',
|
|
1714
|
-
priority:
|
|
1718
|
+
priority: 5,
|
|
1715
1719
|
isApplicable: (input) =>
|
|
1716
1720
|
(typeof input.linkedin_url === 'string' && (input.linkedin_url as string).length > 0) ||
|
|
1717
1721
|
(!!input.first_name && !!input.last_name && !!(input.domain || input.company_name)),
|
|
@@ -2066,7 +2070,7 @@ const enrichCompanyProviders: ProviderEntry[] = [
|
|
|
2066
2070
|
id: 'apollo',
|
|
2067
2071
|
endpoint: '/apollo/organizations/enrich',
|
|
2068
2072
|
method: 'POST',
|
|
2069
|
-
priority:
|
|
2073
|
+
priority: 7,
|
|
2070
2074
|
isApplicable: (input) => !!input.domain,
|
|
2071
2075
|
mapParams: (input) => ({ body: { domain: input.domain } }),
|
|
2072
2076
|
hasResult: (data) => {
|
|
@@ -2095,7 +2099,7 @@ const enrichCompanyProviders: ProviderEntry[] = [
|
|
|
2095
2099
|
id: 'findymail',
|
|
2096
2100
|
endpoint: '/findymail/search/company',
|
|
2097
2101
|
method: 'POST',
|
|
2098
|
-
priority:
|
|
2102
|
+
priority: 6,
|
|
2099
2103
|
mapParams: (input) => ({
|
|
2100
2104
|
body: {
|
|
2101
2105
|
...(input.domain ? { domain: input.domain } : {}),
|
|
@@ -3296,7 +3300,7 @@ const enrichPersonProviders: ProviderEntry[] = [
|
|
|
3296
3300
|
id: 'apollo-people-match',
|
|
3297
3301
|
endpoint: '/apollo/people/match',
|
|
3298
3302
|
method: 'POST',
|
|
3299
|
-
priority:
|
|
3303
|
+
priority: 11,
|
|
3300
3304
|
mapParams: (input) => ({
|
|
3301
3305
|
body: {
|
|
3302
3306
|
first_name: input.first_name,
|
|
@@ -3414,7 +3418,7 @@ const enrichPersonProviders: ProviderEntry[] = [
|
|
|
3414
3418
|
id: 'icypeas-reverse-email-lookup',
|
|
3415
3419
|
endpoint: '/icypeas/reverse-email-lookup',
|
|
3416
3420
|
method: 'POST',
|
|
3417
|
-
priority:
|
|
3421
|
+
priority: 12,
|
|
3418
3422
|
isApplicable: (input) => typeof input.email === 'string' && (input.email as string).includes('@'),
|
|
3419
3423
|
mapParams: (input) => ({ body: { email: input.email } }),
|
|
3420
3424
|
hasResult: isIcypeasReverseEmailFound,
|
|
@@ -3423,7 +3427,7 @@ const enrichPersonProviders: ProviderEntry[] = [
|
|
|
3423
3427
|
id: 'pdl-person-identify',
|
|
3424
3428
|
endpoint: '/pdl/person/identify',
|
|
3425
3429
|
method: 'POST',
|
|
3426
|
-
priority:
|
|
3430
|
+
priority: 13,
|
|
3427
3431
|
mapParams: (input) => ({
|
|
3428
3432
|
body: {
|
|
3429
3433
|
email: input.email,
|
package/tests/executor.test.ts
CHANGED
|
@@ -310,7 +310,7 @@ describe('executor postFilter integration', () => {
|
|
|
310
310
|
})
|
|
311
311
|
|
|
312
312
|
it('invokes Apollo postFilter — strips accounts and drops wrong-country orgs → falls through to error', async () => {
|
|
313
|
-
// Apollo is
|
|
313
|
+
// Apollo is a late recovery source. Mock earlier providers so Apollo is reached.
|
|
314
314
|
// Apollo returns orgs with wrong country + accounts key.
|
|
315
315
|
// postFilter should: strip accounts, drop German org, keep no French orgs → hasResult false → error.
|
|
316
316
|
let callCount = 0
|
|
@@ -348,7 +348,7 @@ describe('executor postFilter integration', () => {
|
|
|
348
348
|
})
|
|
349
349
|
|
|
350
350
|
it('passes raw data through unchanged when postFilter is not set (enrich_company)', async () => {
|
|
351
|
-
//
|
|
351
|
+
// Company enrichment providers have no postFilter.
|
|
352
352
|
// Verify a successful response is returned as-is.
|
|
353
353
|
globalThis.fetch = vi.fn(async () =>
|
|
354
354
|
new Response(JSON.stringify({ name: 'ColdIQ', domain: 'coldiq.com' }), { status: 200 })
|
|
@@ -50,7 +50,7 @@ describe('MCP knowledge-first policy', () => {
|
|
|
50
50
|
const indexPath = fileURLToPath(new URL('../src/index.ts', import.meta.url))
|
|
51
51
|
const registrations = readFileSync(indexPath, 'utf8')
|
|
52
52
|
.split('\n')
|
|
53
|
-
.filter((line) => line.trimStart().startsWith('
|
|
53
|
+
.filter((line) => line.trimStart().startsWith('registerPublicTool('))
|
|
54
54
|
const unguardedCompatibilityTools = [
|
|
55
55
|
'getCreditBalanceName',
|
|
56
56
|
'getSessionUsageName',
|
|
@@ -58,9 +58,9 @@ describe('MCP knowledge-first policy', () => {
|
|
|
58
58
|
'loadSkillName',
|
|
59
59
|
]
|
|
60
60
|
|
|
61
|
-
expect(registrations[0]).toContain('
|
|
61
|
+
expect(registrations[0]).toContain('registerPublicTool(searchKnowledgeName, searchKnowledgeDescription')
|
|
62
62
|
for (const registration of registrations.slice(1)) {
|
|
63
|
-
if (unguardedCompatibilityTools.some((name) => registration.includes(`
|
|
63
|
+
if (unguardedCompatibilityTools.some((name) => registration.includes(`registerPublicTool(${name},`))) continue
|
|
64
64
|
expect(registration).toContain('withKnowledgeFirstRequirement(')
|
|
65
65
|
}
|
|
66
66
|
})
|
|
@@ -133,8 +133,8 @@ describe('apollo-people-match', () => {
|
|
|
133
133
|
expect(p().hasResult({})).toBe(false)
|
|
134
134
|
})
|
|
135
135
|
|
|
136
|
-
it('priority is
|
|
137
|
-
expect(p().priority).toBe(
|
|
136
|
+
it('priority is 11', () => {
|
|
137
|
+
expect(p().priority).toBe(11)
|
|
138
138
|
})
|
|
139
139
|
})
|
|
140
140
|
|
|
@@ -450,8 +450,8 @@ describe('pdl-person-identify', () => {
|
|
|
450
450
|
expect(p().hasResult({ status: 404, matches: [] })).toBe(false)
|
|
451
451
|
})
|
|
452
452
|
|
|
453
|
-
it('priority is
|
|
454
|
-
expect(p().priority).toBe(
|
|
453
|
+
it('priority is 13 (last dormant fallback)', () => {
|
|
454
|
+
expect(p().priority).toBe(13)
|
|
455
455
|
})
|
|
456
456
|
})
|
|
457
457
|
|
|
@@ -897,8 +897,9 @@ describe('find_people priority ordering', () => {
|
|
|
897
897
|
expect(providers()[0].id).toBe('leadsfactory')
|
|
898
898
|
})
|
|
899
899
|
|
|
900
|
-
it('
|
|
900
|
+
it('Apollo is last in the active wired set', () => {
|
|
901
901
|
const ps = providers()
|
|
902
|
-
expect(ps[ps.length - 1].id).toBe('
|
|
902
|
+
expect(ps[ps.length - 1].id).toBe('apollo')
|
|
903
|
+
expect(ps[ps.length - 1].priority).toBe(11)
|
|
903
904
|
})
|
|
904
905
|
})
|
|
@@ -291,6 +291,11 @@ describe('apollo search_companies', () => {
|
|
|
291
291
|
const body = p().mapParams({ keywords: ['SaaS'] }).body as Record<string, unknown>
|
|
292
292
|
expect(body.limit).toBe(25)
|
|
293
293
|
})
|
|
294
|
+
|
|
295
|
+
it('is a recovery source and does not handle a LinkedIn search URL', () => {
|
|
296
|
+
expect(p().priority).toBe(19)
|
|
297
|
+
expect(p().isApplicable?.({ linkedin_search_url: 'https://www.linkedin.com/sales/search/company' })).toBe(false)
|
|
298
|
+
})
|
|
294
299
|
})
|
|
295
300
|
|
|
296
301
|
describe('theirstack search_companies', () => {
|
|
@@ -418,9 +423,13 @@ describe('search_companies priority ordering', () => {
|
|
|
418
423
|
}
|
|
419
424
|
})
|
|
420
425
|
|
|
421
|
-
it('
|
|
426
|
+
it('keeps Apollo before the costly tail and DiscoLike last', () => {
|
|
422
427
|
const ps = providers()
|
|
423
428
|
expect(ps[ps.length - 1].id).toBe('discolike')
|
|
424
|
-
expect(ps[ps.length -
|
|
429
|
+
expect(ps[ps.length - 1].priority).toBe(22)
|
|
430
|
+
expect(ps.findIndex((provider) => provider.id === 'ai-ark-companies'))
|
|
431
|
+
.toBeLessThan(ps.findIndex((provider) => provider.id === 'apollo'))
|
|
432
|
+
expect(ps.findIndex((provider) => provider.id === 'apollo'))
|
|
433
|
+
.toBeLessThan(ps.findIndex((provider) => provider.id === 'limadata-prospect-filter'))
|
|
425
434
|
})
|
|
426
435
|
})
|
package/tests/registry.test.ts
CHANGED
|
@@ -472,17 +472,17 @@ describe('registry', () => {
|
|
|
472
472
|
'fullenrich',
|
|
473
473
|
'theirstack',
|
|
474
474
|
'signalbase',
|
|
475
|
-
'apollo',
|
|
476
475
|
'limadata',
|
|
477
476
|
'predictleads',
|
|
478
477
|
'sumble',
|
|
479
|
-
'limadata-prospect-filter',
|
|
480
|
-
'limadata-prospect-url',
|
|
481
478
|
'linkupapi-search',
|
|
482
479
|
'linkupapi-fundraising',
|
|
483
480
|
'linkupapi-hiring',
|
|
484
481
|
'prospeo-search-company',
|
|
485
482
|
'ai-ark-companies',
|
|
483
|
+
'apollo',
|
|
484
|
+
'limadata-prospect-filter',
|
|
485
|
+
'limadata-prospect-url',
|
|
486
486
|
'discolike',
|
|
487
487
|
])
|
|
488
488
|
})
|
|
@@ -695,11 +695,11 @@ describe('registry', () => {
|
|
|
695
695
|
expect(wiza.priority).toBe(7)
|
|
696
696
|
})
|
|
697
697
|
|
|
698
|
-
it('mirrors the server order: findymail → leadmagic → prospeo →
|
|
698
|
+
it('mirrors the server order: findymail → leadmagic → prospeo → limadata → apollo → icypeas → wiza', () => {
|
|
699
699
|
const ids = getProviders('find_email').map((p) => p.id)
|
|
700
700
|
expect(ids).toEqual([
|
|
701
|
-
'findymail', 'leadmagic', 'prospeo', '
|
|
702
|
-
'
|
|
701
|
+
'findymail', 'leadmagic', 'prospeo', 'limadata-work-email',
|
|
702
|
+
'apollo-people-match', 'icypeas', 'wiza',
|
|
703
703
|
])
|
|
704
704
|
})
|
|
705
705
|
|
|
@@ -798,8 +798,8 @@ describe('registry', () => {
|
|
|
798
798
|
it('providers preserve their order with disabled providers omitted', () => {
|
|
799
799
|
const ids = getProviders('enrich_company').map((p) => p.id)
|
|
800
800
|
expect(ids).toEqual([
|
|
801
|
-
'
|
|
802
|
-
'
|
|
801
|
+
'limadata', 'prospeo', 'findymail',
|
|
802
|
+
'apollo', 'wiza', 'icypeas',
|
|
803
803
|
'builtwith', 'openmart', 'linkupapi-by-domain', 'linkupapi-by-url', 'discolike',
|
|
804
804
|
])
|
|
805
805
|
})
|
|
@@ -1215,6 +1215,7 @@ describe('registry', () => {
|
|
|
1215
1215
|
expect(apollo.isApplicable!({ exclude_countries: ['CN'] })).toBe(false)
|
|
1216
1216
|
expect(apollo.isApplicable!({ min_workforce_growth_pct: 10 })).toBe(false)
|
|
1217
1217
|
expect(apollo.isApplicable!({ is_hiring: true })).toBe(false)
|
|
1218
|
+
expect(apollo.isApplicable?.({ linkedin_search_url: 'https://www.linkedin.com/sales/search/company' })).toBe(false)
|
|
1218
1219
|
})
|
|
1219
1220
|
|
|
1220
1221
|
it('SignalBase isApplicable: skips for advanced filters it cannot apply', () => {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
|
2
|
+
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'
|
|
3
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
5
|
+
import { z } from 'zod'
|
|
6
|
+
|
|
7
|
+
describe('MCP SDK Zod 4 compatibility', () => {
|
|
8
|
+
const closeCallbacks: Array<() => Promise<void>> = []
|
|
9
|
+
|
|
10
|
+
afterEach(async () => {
|
|
11
|
+
await Promise.all(closeCallbacks.splice(0).map((close) => close()))
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('publishes populated input properties and required fields over the wire', async () => {
|
|
15
|
+
const server = new McpServer({ name: 'schema-contract-server', version: '1.0.0' })
|
|
16
|
+
server.registerTool(
|
|
17
|
+
'schema_contract',
|
|
18
|
+
{
|
|
19
|
+
description: 'Verify Zod 4 tool schema conversion.',
|
|
20
|
+
inputSchema: z.strictObject({
|
|
21
|
+
platform: z.enum(['linkedin', 'instagram']),
|
|
22
|
+
limit: z.number().int().min(1).max(100).default(25),
|
|
23
|
+
}),
|
|
24
|
+
},
|
|
25
|
+
async () => ({ content: [{ type: 'text', text: 'ok' }] }),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const client = new Client({ name: 'schema-contract-client', version: '1.0.0' })
|
|
29
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair()
|
|
30
|
+
closeCallbacks.push(() => client.close(), () => server.close())
|
|
31
|
+
|
|
32
|
+
await server.connect(serverTransport)
|
|
33
|
+
await client.connect(clientTransport)
|
|
34
|
+
|
|
35
|
+
const result = await client.listTools()
|
|
36
|
+
expect(result.tools).toHaveLength(1)
|
|
37
|
+
expect(result.tools[0]?.inputSchema).toMatchObject({
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {
|
|
40
|
+
platform: { type: 'string', enum: ['linkedin', 'instagram'] },
|
|
41
|
+
limit: { type: 'integer', minimum: 1, maximum: 100, default: 25 },
|
|
42
|
+
},
|
|
43
|
+
required: ['platform'],
|
|
44
|
+
additionalProperties: false,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const invalidCall = await client.callTool({
|
|
48
|
+
name: 'schema_contract',
|
|
49
|
+
arguments: { platform: 'linkedin', unexpected: true },
|
|
50
|
+
})
|
|
51
|
+
expect(invalidCall.isError).toBe(true)
|
|
52
|
+
expect(invalidCall.content).toEqual([
|
|
53
|
+
expect.objectContaining({
|
|
54
|
+
type: 'text',
|
|
55
|
+
text: expect.stringContaining('Unrecognized key: "unexpected"'),
|
|
56
|
+
}),
|
|
57
|
+
])
|
|
58
|
+
})
|
|
59
|
+
})
|
|
@@ -14,7 +14,8 @@ describe('public MCP tool JSON schemas', () => {
|
|
|
14
14
|
.sort()
|
|
15
15
|
const indexSource = readFileSync(new URL('../src/index.ts', import.meta.url), 'utf8')
|
|
16
16
|
|
|
17
|
-
expect(indexSource.match(/
|
|
17
|
+
expect(indexSource.match(/registerPublicTool\(/g)).toHaveLength(toolFiles.length)
|
|
18
|
+
expect(indexSource).not.toContain('server.tool(')
|
|
18
19
|
|
|
19
20
|
for (const file of toolFiles) {
|
|
20
21
|
const exports = await import(new URL(`../src/tools/${file}`, import.meta.url).href)
|