@coldiq/mcp 0.1.8 → 0.1.10
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/README.md +1 -1
- package/dist/executor.d.ts +8 -1
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +45 -18
- package/dist/executor.js.map +1 -1
- package/dist/registry.d.ts +9 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +16 -0
- package/dist/registry.js.map +1 -1
- package/dist/tools/enrich-company.d.ts +2 -1
- package/dist/tools/enrich-company.d.ts.map +1 -1
- package/dist/tools/enrich-company.js +10 -3
- package/dist/tools/enrich-company.js.map +1 -1
- package/dist/tools/enrich-person.d.ts +1 -0
- package/dist/tools/enrich-person.d.ts.map +1 -1
- package/dist/tools/enrich-person.js +10 -2
- package/dist/tools/enrich-person.js.map +1 -1
- package/dist/tools/fetch-page-content.d.ts +1 -0
- package/dist/tools/fetch-page-content.d.ts.map +1 -1
- package/dist/tools/fetch-page-content.js +8 -1
- package/dist/tools/fetch-page-content.js.map +1 -1
- package/dist/tools/find-email.d.ts +1 -0
- package/dist/tools/find-email.d.ts.map +1 -1
- package/dist/tools/find-email.js +9 -2
- package/dist/tools/find-email.js.map +1 -1
- package/dist/tools/find-emails.d.ts +8 -0
- package/dist/tools/find-emails.d.ts.map +1 -1
- package/dist/tools/find-emails.js +64 -33
- package/dist/tools/find-emails.js.map +1 -1
- package/dist/tools/find-influencers.d.ts +1 -0
- package/dist/tools/find-influencers.d.ts.map +1 -1
- package/dist/tools/find-influencers.js +8 -1
- package/dist/tools/find-influencers.js.map +1 -1
- package/dist/tools/find-people.d.ts +1 -0
- package/dist/tools/find-people.d.ts.map +1 -1
- package/dist/tools/find-people.js +12 -5
- package/dist/tools/find-people.js.map +1 -1
- package/dist/tools/find-phone.d.ts +1 -0
- package/dist/tools/find-phone.d.ts.map +1 -1
- package/dist/tools/find-phone.js +9 -2
- package/dist/tools/find-phone.js.map +1 -1
- package/dist/tools/find-signals.d.ts +1 -0
- package/dist/tools/find-signals.d.ts.map +1 -1
- package/dist/tools/find-signals.js +14 -7
- package/dist/tools/find-signals.js.map +1 -1
- package/dist/tools/search-ads.d.ts +1 -0
- package/dist/tools/search-ads.d.ts.map +1 -1
- package/dist/tools/search-ads.js +8 -1
- package/dist/tools/search-ads.js.map +1 -1
- package/dist/tools/search-companies.d.ts +2 -1
- package/dist/tools/search-companies.d.ts.map +1 -1
- package/dist/tools/search-companies.js +9 -2
- package/dist/tools/search-companies.js.map +1 -1
- package/dist/tools/search-jobs.d.ts +1 -0
- package/dist/tools/search-jobs.d.ts.map +1 -1
- package/dist/tools/search-jobs.js +9 -2
- package/dist/tools/search-jobs.js.map +1 -1
- package/dist/tools/search-places.d.ts +1 -0
- package/dist/tools/search-places.d.ts.map +1 -1
- package/dist/tools/search-places.js +8 -1
- package/dist/tools/search-places.js.map +1 -1
- package/dist/tools/search-reddit.d.ts +1 -0
- package/dist/tools/search-reddit.d.ts.map +1 -1
- package/dist/tools/search-reddit.js +8 -1
- package/dist/tools/search-reddit.js.map +1 -1
- package/dist/tools/search-seo.d.ts +1 -0
- package/dist/tools/search-seo.d.ts.map +1 -1
- package/dist/tools/search-seo.js +8 -1
- package/dist/tools/search-seo.js.map +1 -1
- package/dist/tools/search-web.d.ts +1 -0
- package/dist/tools/search-web.d.ts.map +1 -1
- package/dist/tools/search-web.js +10 -2
- package/dist/tools/search-web.js.map +1 -1
- package/dist/tools/verify-email.d.ts +2 -1
- package/dist/tools/verify-email.d.ts.map +1 -1
- package/dist/tools/verify-email.js +9 -2
- package/dist/tools/verify-email.js.map +1 -1
- package/dist/utils/fuzzy.d.ts +13 -0
- package/dist/utils/fuzzy.d.ts.map +1 -0
- package/dist/utils/fuzzy.js +46 -0
- package/dist/utils/fuzzy.js.map +1 -0
- package/dist/utils/provider-resolver.d.ts +34 -0
- package/dist/utils/provider-resolver.d.ts.map +1 -0
- package/dist/utils/provider-resolver.js +235 -0
- package/dist/utils/provider-resolver.js.map +1 -0
- package/package.json +1 -1
- package/src/executor.ts +55 -14
- package/src/registry.ts +20 -0
- package/src/tools/enrich-company.ts +10 -3
- package/src/tools/enrich-person.ts +10 -2
- package/src/tools/fetch-page-content.ts +8 -1
- package/src/tools/find-email.ts +9 -2
- package/src/tools/find-emails.ts +78 -41
- package/src/tools/find-influencers.ts +8 -1
- package/src/tools/find-people.ts +12 -5
- package/src/tools/find-phone.ts +9 -2
- package/src/tools/find-signals.ts +15 -7
- package/src/tools/search-ads.ts +8 -1
- package/src/tools/search-companies.ts +9 -2
- package/src/tools/search-jobs.ts +9 -2
- package/src/tools/search-places.ts +8 -1
- package/src/tools/search-reddit.ts +8 -1
- package/src/tools/search-seo.ts +8 -1
- package/src/tools/search-web.ts +10 -2
- package/src/tools/verify-email.ts +9 -2
- package/src/utils/fuzzy.ts +57 -0
- package/src/utils/provider-resolver.ts +306 -0
- package/tests/executor.test.ts +184 -4
- package/tests/registry.test.ts +22 -1
- package/tests/tools/find-email.test.ts +43 -0
- package/tests/tools/find-emails.test.ts +87 -0
- package/tests/tools/search-companies.test.ts +40 -0
- package/tests/utils/fuzzy.test.ts +63 -0
- package/tests/utils/provider-resolver.test.ts +145 -0
package/src/tools/find-emails.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { callApi } from '../client.js'
|
|
3
|
+
import { resolvePreferredProviders, buildAllFailedError, FIND_EMAILS_PROVIDERS } from '../utils/provider-resolver.js'
|
|
3
4
|
|
|
4
5
|
export const findEmailsName = 'find_emails'
|
|
5
6
|
|
|
@@ -10,7 +11,8 @@ export const findEmailsDescription =
|
|
|
10
11
|
'Much faster than calling find_email one-by-one. Max 50 people per call. ' +
|
|
11
12
|
'Each person needs a unique id to match results back. ' +
|
|
12
13
|
'Always pass first_name, last_name, and domain — they are the primary enrichment signal. ' +
|
|
13
|
-
'Also pass linkedin_url when available, but never rely on it alone as some providers cannot resolve vanity URLs.'
|
|
14
|
+
'Also pass linkedin_url when available, but never rely on it alone as some providers cannot resolve vanity URLs. ' +
|
|
15
|
+
'ColdIQ automatically picks the best waterfall — pass use_providers only if you need specific tools.'
|
|
14
16
|
|
|
15
17
|
export const findEmailsSchema = {
|
|
16
18
|
people: z
|
|
@@ -26,6 +28,7 @@ export const findEmailsSchema = {
|
|
|
26
28
|
.min(1)
|
|
27
29
|
.max(50)
|
|
28
30
|
.describe('People to find emails for (max 50)'),
|
|
31
|
+
use_providers: z.array(z.string()).optional().describe(`Optional ordered list of providers to use. Leave empty to let ColdIQ automatically run the best waterfall — recommended for most use cases. Available providers: ${FIND_EMAILS_PROVIDERS.join(', ')}. Provider names are matched fuzzily, so minor typos are tolerated.`),
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
interface PersonInput {
|
|
@@ -100,7 +103,10 @@ async function fullEnrichStep(misses: PersonInput[], results: EmailResult[]): Pr
|
|
|
100
103
|
}
|
|
101
104
|
}
|
|
102
105
|
|
|
103
|
-
async function findymailIcypeasStep(misses: PersonInput[], results: EmailResult[]): Promise<void> {
|
|
106
|
+
async function findymailIcypeasStep(misses: PersonInput[], results: EmailResult[], allowedProviders?: string[]): Promise<void> {
|
|
107
|
+
const useFindymail = !allowedProviders || allowedProviders.includes('findymail')
|
|
108
|
+
const useIcypeas = !allowedProviders || allowedProviders.includes('icypeas')
|
|
109
|
+
|
|
104
110
|
await Promise.all(
|
|
105
111
|
misses.map(async (person) => {
|
|
106
112
|
const hit = results.find((r) => r.id === person.id)
|
|
@@ -109,22 +115,24 @@ async function findymailIcypeasStep(misses: PersonInput[], results: EmailResult[
|
|
|
109
115
|
// Skip if the parallel FullEnrich branch already filled this person.
|
|
110
116
|
if (hit.email) return
|
|
111
117
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
name
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
if (useFindymail) {
|
|
119
|
+
const fullName = [person.first_name, person.last_name].filter(Boolean).join(' ')
|
|
120
|
+
|
|
121
|
+
const fmRes = await callApi('POST', '/findymail/search/name', {
|
|
122
|
+
name: fullName,
|
|
123
|
+
domain: person.domain,
|
|
124
|
+
})
|
|
125
|
+
if (!hit.email && fmRes.ok) {
|
|
126
|
+
const d = fmRes.data as Record<string, unknown>
|
|
127
|
+
if (typeof d.email === 'string' && d.email.includes('@')) {
|
|
128
|
+
hit.email = d.email
|
|
129
|
+
hit.provider = 'findymail'
|
|
130
|
+
return
|
|
131
|
+
}
|
|
124
132
|
}
|
|
125
133
|
}
|
|
126
134
|
|
|
127
|
-
if (hit.email) return
|
|
135
|
+
if (hit.email || !useIcypeas) return
|
|
128
136
|
|
|
129
137
|
const icyRes = await callApi('POST', '/icypeas/email-search', {
|
|
130
138
|
firstname: person.first_name,
|
|
@@ -149,40 +157,52 @@ async function findymailIcypeasStep(misses: PersonInput[], results: EmailResult[
|
|
|
149
157
|
}
|
|
150
158
|
|
|
151
159
|
export async function findEmailsHandler(input: Record<string, unknown>) {
|
|
152
|
-
const
|
|
153
|
-
const
|
|
160
|
+
const { use_providers: rawUseProviders, ...restInput } = input
|
|
161
|
+
const people = restInput.people as PersonInput[]
|
|
154
162
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
p.linkedin_url
|
|
159
|
-
? { identifier: p.id, linkedin_url: p.linkedin_url }
|
|
160
|
-
: { identifier: p.id, first_name: p.first_name, last_name: p.last_name, company_name: p.domain },
|
|
161
|
-
),
|
|
163
|
+
const resolved = resolvePreferredProviders('find_emails', restInput, rawUseProviders)
|
|
164
|
+
if (!resolved.ok) {
|
|
165
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(resolved.error, null, 2) }], isError: true }
|
|
162
166
|
}
|
|
163
167
|
|
|
164
|
-
const
|
|
168
|
+
const allowedProviders = resolved.providers
|
|
169
|
+
const isConstrained = allowedProviders.length > 0
|
|
170
|
+
|
|
171
|
+
const results: EmailResult[] = people.map((p) => ({ id: p.id, email: null, provider: null }))
|
|
165
172
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
173
|
+
// Step 1: Prospeo bulk — 1 call for all people
|
|
174
|
+
if (!isConstrained || allowedProviders.includes('prospeo')) {
|
|
175
|
+
const bulkBody = {
|
|
176
|
+
data: people.map((p) =>
|
|
177
|
+
p.linkedin_url
|
|
178
|
+
? { identifier: p.id, linkedin_url: p.linkedin_url }
|
|
179
|
+
: { identifier: p.id, first_name: p.first_name, last_name: p.last_name, company_name: p.domain },
|
|
180
|
+
),
|
|
172
181
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
182
|
+
|
|
183
|
+
const bulkRes = await callApi('POST', '/prospeo/bulk-enrich-person', bulkBody)
|
|
184
|
+
|
|
185
|
+
if (bulkRes.ok) {
|
|
186
|
+
const data = bulkRes.data as {
|
|
187
|
+
results?: Array<{
|
|
188
|
+
identifier: string
|
|
189
|
+
person?: { email?: { email?: string } }
|
|
190
|
+
}>
|
|
191
|
+
}
|
|
192
|
+
for (const item of data.results ?? []) {
|
|
193
|
+
const email = item.person?.email?.email
|
|
194
|
+
if (typeof email === 'string' && email.includes('@')) {
|
|
195
|
+
const hit = results.find((r) => r.id === item.identifier)
|
|
196
|
+
if (hit) {
|
|
197
|
+
hit.email = email
|
|
198
|
+
hit.provider = 'prospeo'
|
|
199
|
+
}
|
|
180
200
|
}
|
|
181
201
|
}
|
|
182
202
|
}
|
|
183
203
|
}
|
|
184
204
|
|
|
185
|
-
// Steps 2 & 3 run concurrently for
|
|
205
|
+
// Steps 2 & 3 run concurrently for misses.
|
|
186
206
|
// Step 2: FullEnrich bulk async (slow tail, better European coverage)
|
|
187
207
|
// Step 3: per-person FindyMail → IcyPeas waterfall
|
|
188
208
|
// Both branches write to the shared `results` array; every write site checks
|
|
@@ -191,15 +211,32 @@ export async function findEmailsHandler(input: Record<string, unknown>) {
|
|
|
191
211
|
const misses = missesOf(people, results)
|
|
192
212
|
|
|
193
213
|
if (misses.length > 0) {
|
|
194
|
-
|
|
214
|
+
const steps: Promise<void>[] = []
|
|
215
|
+
if (!isConstrained || allowedProviders.includes('fullenrich')) {
|
|
216
|
+
steps.push(fullEnrichStep(misses, results))
|
|
217
|
+
}
|
|
218
|
+
if (!isConstrained || allowedProviders.includes('findymail') || allowedProviders.includes('icypeas')) {
|
|
219
|
+
steps.push(findymailIcypeasStep(misses, results, isConstrained ? allowedProviders : undefined))
|
|
220
|
+
}
|
|
221
|
+
if (steps.length > 0) await Promise.all(steps)
|
|
195
222
|
}
|
|
196
223
|
|
|
197
224
|
const found = results.filter((r) => r.email !== null).length
|
|
225
|
+
|
|
226
|
+
if (isConstrained && found === 0) {
|
|
227
|
+
const err = buildAllFailedError(allowedProviders, 'find_emails', FIND_EMAILS_PROVIDERS)
|
|
228
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(err, null, 2) }], isError: true }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const meta = resolved.matchedFrom && Object.keys(resolved.matchedFrom).length > 0
|
|
232
|
+
? { matchedFrom: resolved.matchedFrom }
|
|
233
|
+
: undefined
|
|
234
|
+
|
|
198
235
|
return {
|
|
199
236
|
content: [
|
|
200
237
|
{
|
|
201
238
|
type: 'text' as const,
|
|
202
|
-
text: JSON.stringify({ data: { results, found, total: people.length } }, null, 2),
|
|
239
|
+
text: JSON.stringify({ data: { results, found, total: people.length }, ...(meta ? { _meta: meta } : {}) }, null, 2),
|
|
203
240
|
},
|
|
204
241
|
],
|
|
205
242
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { executeWithFallback, isExecutionError } from '../executor.js'
|
|
3
|
+
import { resolvePreferredProviders, getProvidersForCapability } from '../utils/provider-resolver.js'
|
|
3
4
|
|
|
4
5
|
export const findInfluencersName = 'find_influencers'
|
|
5
6
|
|
|
@@ -23,10 +24,16 @@ export const findInfluencersSchema = {
|
|
|
23
24
|
type: z.enum(['creator', 'business']).optional(),
|
|
24
25
|
handle: z.string().optional()
|
|
25
26
|
.describe('Find creators similar to this handle. Routes to lookalike search. No @ prefix.'),
|
|
27
|
+
use_providers: z.array(z.string()).optional().describe(`Optional ordered list of providers to use. Leave empty to let ColdIQ automatically pick the best tool for your inputs — recommended for most use cases. Available providers: ${getProvidersForCapability('find_influencers').join(', ')}. Provider names are matched fuzzily, so minor typos are tolerated.`),
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export async function findInfluencersHandler(input: Record<string, unknown>) {
|
|
29
|
-
const
|
|
31
|
+
const { use_providers: rawUseProviders, ...restInput } = input
|
|
32
|
+
const resolved = resolvePreferredProviders('find_influencers', restInput, rawUseProviders)
|
|
33
|
+
if (!resolved.ok) {
|
|
34
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(resolved.error, null, 2) }], isError: true }
|
|
35
|
+
}
|
|
36
|
+
const result = await executeWithFallback('find_influencers', restInput, { providers: resolved.providers, matchedFrom: resolved.matchedFrom })
|
|
30
37
|
if (isExecutionError(result)) {
|
|
31
38
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], isError: true }
|
|
32
39
|
}
|
package/src/tools/find-people.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { z } from 'zod'
|
|
|
2
2
|
import { executeWithFallback, isExecutionError } from '../executor.js'
|
|
3
3
|
import { callApi } from '../client.js'
|
|
4
4
|
import { getProviders } from '../registry.js'
|
|
5
|
+
import { resolvePreferredProviders, getProvidersForCapability } from '../utils/provider-resolver.js'
|
|
5
6
|
|
|
6
7
|
export const findPeopleName = 'find_people'
|
|
7
8
|
|
|
@@ -24,10 +25,16 @@ export const findPeopleSchema = {
|
|
|
24
25
|
locations: z.array(z.string()).optional().describe('Person or company locations'),
|
|
25
26
|
keywords: z.array(z.string()).optional().describe('Free-text keyword search terms (e.g. ["growth", "AI"])'),
|
|
26
27
|
limit: z.number().min(1).max(500).default(25).describe('Max results across all companies combined (default: 25, max: 500). For multiple companies multiply: e.g. 10 companies × 5 each = 50.'),
|
|
28
|
+
use_providers: z.array(z.string()).optional().describe(`Optional ordered list of providers to use. Leave empty to let ColdIQ automatically pick the best tool for your inputs — recommended for most use cases. Available providers: ${getProvidersForCapability('find_people').join(', ')}. Provider names are matched fuzzily, so minor typos are tolerated.`),
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export async function findPeopleHandler(input: Record<string, unknown>) {
|
|
30
|
-
const
|
|
32
|
+
const { use_providers: rawUseProviders, ...restInput } = input
|
|
33
|
+
const resolved = resolvePreferredProviders('find_people', restInput, rawUseProviders)
|
|
34
|
+
if (!resolved.ok) {
|
|
35
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(resolved.error, null, 2) }], isError: true }
|
|
36
|
+
}
|
|
37
|
+
const result = await executeWithFallback('find_people', restInput, { providers: resolved.providers, matchedFrom: resolved.matchedFrom })
|
|
31
38
|
if (isExecutionError(result)) {
|
|
32
39
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], isError: true }
|
|
33
40
|
}
|
|
@@ -35,16 +42,16 @@ export async function findPeopleHandler(input: Record<string, unknown>) {
|
|
|
35
42
|
// Gap-fill: if LeadsFactory missed some domains, try Apollo for those.
|
|
36
43
|
// Only runs when domains were the input — if we sent LinkedIn URLs, no_results_domains
|
|
37
44
|
// is unreliable (LF may resolve arbitrary domains from URLs, e.g. linkedin.com itself).
|
|
38
|
-
const inputHadDomains = Array.isArray(
|
|
39
|
-
const inputHadLinkedInUrls = Array.isArray(
|
|
40
|
-
if (result._meta.provider === 'leadsfactory' && inputHadDomains && !inputHadLinkedInUrls) {
|
|
45
|
+
const inputHadDomains = Array.isArray(restInput.company_domains) && (restInput.company_domains as unknown[]).length > 0
|
|
46
|
+
const inputHadLinkedInUrls = Array.isArray(restInput.company_linkedin_urls) && (restInput.company_linkedin_urls as unknown[]).length > 0
|
|
47
|
+
if (resolved.providers.length === 0 && result._meta.provider === 'leadsfactory' && inputHadDomains && !inputHadLinkedInUrls) {
|
|
41
48
|
const data = result.data as Record<string, unknown>
|
|
42
49
|
const missedDomains = (data.no_results_domains as string[] | undefined) ?? []
|
|
43
50
|
|
|
44
51
|
if (missedDomains.length > 0) {
|
|
45
52
|
const apollo = getProviders('find_people').find((p) => p.id === 'apollo')
|
|
46
53
|
if (apollo) {
|
|
47
|
-
const gapInput = { ...
|
|
54
|
+
const gapInput = { ...restInput, company_domains: missedDomains, company_linkedin_urls: undefined }
|
|
48
55
|
const payload = apollo.mapParams(gapInput)
|
|
49
56
|
const apolloRes = await callApi(apollo.method, apollo.endpoint, payload.body, payload.queryParams)
|
|
50
57
|
|
package/src/tools/find-phone.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { executeWithFallback, isExecutionError } from '../executor.js'
|
|
3
|
+
import { resolvePreferredProviders, getProvidersForCapability } from '../utils/provider-resolver.js'
|
|
3
4
|
|
|
4
5
|
export const findPhoneName = 'find_phone'
|
|
5
6
|
|
|
@@ -12,6 +13,7 @@ export const findPhoneSchema = {
|
|
|
12
13
|
last_name: z.string().optional().describe('Last name (required when no LinkedIn URL)'),
|
|
13
14
|
company_domain: z.string().optional().describe('Company domain, e.g. coldiq.com'),
|
|
14
15
|
company_name: z.string().optional().describe('Company name (alternative to company_domain)'),
|
|
16
|
+
use_providers: z.array(z.string()).optional().describe(`Optional ordered list of providers to use. Leave empty to let ColdIQ automatically pick the best tool for your inputs — recommended for most use cases. Available providers: ${getProvidersForCapability('find_phone').join(', ')}. Provider names are matched fuzzily, so minor typos are tolerated.`),
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
const inputSchema = z
|
|
@@ -37,7 +39,12 @@ const inputSchema = z
|
|
|
37
39
|
})
|
|
38
40
|
|
|
39
41
|
export async function findPhoneHandler(input: Record<string, unknown>) {
|
|
40
|
-
const
|
|
42
|
+
const { use_providers: rawUseProviders, ...restInput } = input
|
|
43
|
+
const resolved = resolvePreferredProviders('find_phone', restInput, rawUseProviders)
|
|
44
|
+
if (!resolved.ok) {
|
|
45
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(resolved.error, null, 2) }], isError: true }
|
|
46
|
+
}
|
|
47
|
+
const validation = inputSchema.safeParse(restInput)
|
|
41
48
|
if (!validation.success) {
|
|
42
49
|
const msg = validation.error.issues.map((i) => i.message).join('; ')
|
|
43
50
|
return {
|
|
@@ -45,7 +52,7 @@ export async function findPhoneHandler(input: Record<string, unknown>) {
|
|
|
45
52
|
isError: true,
|
|
46
53
|
}
|
|
47
54
|
}
|
|
48
|
-
const result = await executeWithFallback('find_phone',
|
|
55
|
+
const result = await executeWithFallback('find_phone', restInput, { providers: resolved.providers, matchedFrom: resolved.matchedFrom })
|
|
49
56
|
if (isExecutionError(result)) {
|
|
50
57
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], isError: true }
|
|
51
58
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { executeWithFallback, isExecutionError } from '../executor.js'
|
|
3
|
+
import { resolvePreferredProviders, getProvidersForCapability } from '../utils/provider-resolver.js'
|
|
3
4
|
|
|
4
5
|
export const findSignalsName = 'find_signals'
|
|
5
6
|
|
|
@@ -47,13 +48,15 @@ export const findSignalsSchema = {
|
|
|
47
48
|
.max(100)
|
|
48
49
|
.default(25)
|
|
49
50
|
.describe('Maximum number of signals to return (1–100).'),
|
|
51
|
+
use_providers: z.array(z.string()).optional().describe(`Optional ordered list of providers to use. Leave empty to let ColdIQ automatically pick the best tool for your inputs — recommended for most use cases. Available providers: ${getProvidersForCapability('find_signals').join(', ')}. Provider names are matched fuzzily, so minor typos are tolerated.`),
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
export async function findSignalsHandler(input: Record<string, unknown>) {
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
+
const { use_providers: rawUseProviders, ...restInput } = input
|
|
56
|
+
const hasCompanies = Array.isArray(restInput.companies) && (restInput.companies as unknown[]).length > 0
|
|
57
|
+
const hasDomains = Array.isArray(restInput.domains) && (restInput.domains as unknown[]).length > 0
|
|
55
58
|
|
|
56
|
-
if (
|
|
59
|
+
if (restInput.signal_type === 'intent' && !hasCompanies && !hasDomains) {
|
|
57
60
|
return {
|
|
58
61
|
content: [{
|
|
59
62
|
type: 'text' as const,
|
|
@@ -63,7 +66,7 @@ export async function findSignalsHandler(input: Record<string, unknown>) {
|
|
|
63
66
|
}
|
|
64
67
|
}
|
|
65
68
|
|
|
66
|
-
if ((
|
|
69
|
+
if ((restInput.signal_type === 'news' || restInput.signal_type === 'startup_post') && (hasCompanies || hasDomains)) {
|
|
67
70
|
return {
|
|
68
71
|
content: [{
|
|
69
72
|
type: 'text' as const,
|
|
@@ -75,14 +78,19 @@ export async function findSignalsHandler(input: Record<string, unknown>) {
|
|
|
75
78
|
}
|
|
76
79
|
}
|
|
77
80
|
|
|
78
|
-
const
|
|
81
|
+
const resolved = resolvePreferredProviders('find_signals', restInput, rawUseProviders)
|
|
82
|
+
if (!resolved.ok) {
|
|
83
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(resolved.error, null, 2) }], isError: true }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const result = await executeWithFallback('find_signals', restInput, { providers: resolved.providers, matchedFrom: resolved.matchedFrom })
|
|
79
87
|
if (isExecutionError(result)) {
|
|
80
88
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], isError: true }
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
// buying_intents upstream has no limit param — truncate to requested limit
|
|
84
|
-
if (
|
|
85
|
-
const limit = typeof
|
|
92
|
+
if (restInput.signal_type === 'intent') {
|
|
93
|
+
const limit = typeof restInput.limit === 'number' ? restInput.limit : 25
|
|
86
94
|
const typed = result as { data?: { data?: unknown[] } }
|
|
87
95
|
if (Array.isArray(typed.data?.data)) {
|
|
88
96
|
typed.data!.data = typed.data!.data.slice(0, limit)
|
package/src/tools/search-ads.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { executeWithFallback, isExecutionError } from '../executor.js'
|
|
3
|
+
import { resolvePreferredProviders, getProvidersForCapability } from '../utils/provider-resolver.js'
|
|
3
4
|
|
|
4
5
|
export const searchAdsName = 'search_ads'
|
|
5
6
|
|
|
@@ -33,10 +34,16 @@ export const searchAdsSchema = {
|
|
|
33
34
|
objective_type: z.enum(['AWARENESS', 'CONVERSIONS', 'APP_INSTALLS', 'TRAFFIC', 'VIDEO_VIEWABLE_IMPRESSIONS']).optional().describe('Reddit only.'),
|
|
34
35
|
|
|
35
36
|
platform: z.enum(['google', 'linkedin', 'meta', 'twitter', 'reddit']).optional().describe('Pin to one platform; skips all others. Useful for cross-platform comparison via separate calls.'),
|
|
37
|
+
use_providers: z.array(z.string()).optional().describe(`Optional ordered list of providers to use. Leave empty to let ColdIQ automatically pick the best tool for your inputs — recommended for most use cases. Available providers: ${getProvidersForCapability('search_ads').join(', ')}. Provider names are matched fuzzily, so minor typos are tolerated.`),
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export async function searchAdsHandler(input: Record<string, unknown>) {
|
|
39
|
-
const
|
|
41
|
+
const { use_providers: rawUseProviders, ...restInput } = input
|
|
42
|
+
const resolved = resolvePreferredProviders('search_ads', restInput, rawUseProviders)
|
|
43
|
+
if (!resolved.ok) {
|
|
44
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(resolved.error, null, 2) }], isError: true }
|
|
45
|
+
}
|
|
46
|
+
const result = await executeWithFallback('search_ads', restInput, { providers: resolved.providers, matchedFrom: resolved.matchedFrom })
|
|
40
47
|
if (isExecutionError(result)) {
|
|
41
48
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], isError: true }
|
|
42
49
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { executeWithFallback, isExecutionError } from '../executor.js'
|
|
3
|
+
import { resolvePreferredProviders, getProvidersForCapability } from '../utils/provider-resolver.js'
|
|
3
4
|
|
|
4
5
|
export const searchCompaniesName = 'search_companies'
|
|
5
6
|
|
|
6
7
|
export const searchCompaniesDescription =
|
|
7
|
-
'Search B2B companies by industry, size, geography, founding year, tech stack, funding, revenue, exclusion lists, and hiring signals. Routing is 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. Default limit: 25.'
|
|
8
|
+
'Search B2B companies by industry, size, geography, founding year, tech stack, funding, revenue, exclusion lists, and hiring signals. Routing is 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. ColdIQ automatically picks the best provider — pass use_providers only if you need a specific tool. Default limit: 25.'
|
|
8
9
|
|
|
9
10
|
export const searchCompaniesSchema = {
|
|
10
11
|
keywords: z.array(z.string()).optional().describe('Industry keywords or topics (e.g. ["SaaS", "fintech"])'),
|
|
@@ -30,10 +31,16 @@ export const searchCompaniesSchema = {
|
|
|
30
31
|
min_workforce_growth_pct: z.number().optional().describe('Minimum workforce growth % over the past 12 months (e.g. 10 for 10%)'),
|
|
31
32
|
linkedin_search_url: z.string().optional().describe('LinkedIn Sales Navigator company search URL — when provided, enables URL-based prospect discovery as a final fallback. Most users should leave this unset.'),
|
|
32
33
|
limit: z.number().min(1).max(100).default(25).describe('Max results to return (default: 25, max: 100)'),
|
|
34
|
+
use_providers: z.array(z.string()).optional().describe(`Optional ordered list of providers to use. Leave empty to let ColdIQ automatically pick the best tool for your inputs — recommended for most use cases. Available providers: ${getProvidersForCapability('search_companies').join(', ')}. Provider names are matched fuzzily, so minor typos are tolerated.`),
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export async function searchCompaniesHandler(input: Record<string, unknown>) {
|
|
36
|
-
const
|
|
38
|
+
const { use_providers: rawUseProviders, ...restInput } = input
|
|
39
|
+
const resolved = resolvePreferredProviders('search_companies', restInput, rawUseProviders)
|
|
40
|
+
if (!resolved.ok) {
|
|
41
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(resolved.error, null, 2) }], isError: true }
|
|
42
|
+
}
|
|
43
|
+
const result = await executeWithFallback('search_companies', restInput, { providers: resolved.providers, matchedFrom: resolved.matchedFrom })
|
|
37
44
|
if (isExecutionError(result)) {
|
|
38
45
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], isError: true }
|
|
39
46
|
}
|
package/src/tools/search-jobs.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { executeWithFallback, isExecutionError } from '../executor.js'
|
|
3
|
+
import { resolvePreferredProviders, getProvidersForCapability } from '../utils/provider-resolver.js'
|
|
3
4
|
|
|
4
5
|
const _CAREER_SITE_ONLY = ['ats_slugs', 'exclude_ats_slugs', 'company_domains', 'exclude_company_domains']
|
|
5
6
|
const _LINKEDIN_ONLY = ['seniority_levels', 'industries', 'organization_slugs', 'exclude_organization_slugs', 'min_employees', 'max_employees', 'easy_apply_only', 'exclude_easy_apply']
|
|
@@ -54,10 +55,12 @@ export const searchJobsSchema = {
|
|
|
54
55
|
max_employees: z.number().int().min(1).optional(),
|
|
55
56
|
easy_apply_only: z.boolean().optional().describe('Only LinkedIn Easy Apply jobs. Routes to LinkedIn source.'),
|
|
56
57
|
exclude_easy_apply: z.boolean().optional().describe('Exclude LinkedIn Easy Apply jobs. Routes to LinkedIn source.'),
|
|
58
|
+
use_providers: z.array(z.string()).optional().describe(`Optional ordered list of providers to use. Leave empty to let ColdIQ automatically pick the best tool for your inputs — recommended for most use cases. Available providers: ${getProvidersForCapability('search_jobs').join(', ')}. Provider names are matched fuzzily, so minor typos are tolerated.`),
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
export async function searchJobsHandler(input: Record<string, unknown>) {
|
|
60
|
-
|
|
62
|
+
const { use_providers: rawUseProviders, ...restInput } = input
|
|
63
|
+
if (_isFilterSet(restInput, _CAREER_SITE_ONLY) && _isFilterSet(restInput, _LINKEDIN_ONLY)) {
|
|
61
64
|
const err = {
|
|
62
65
|
error:
|
|
63
66
|
'Contradictory filters: ATS/domain filters (ats_slugs, company_domains) are Career Site only, ' +
|
|
@@ -65,7 +68,11 @@ export async function searchJobsHandler(input: Record<string, unknown>) {
|
|
|
65
68
|
}
|
|
66
69
|
return { content: [{ type: 'text' as const, text: JSON.stringify(err, null, 2) }], isError: true }
|
|
67
70
|
}
|
|
68
|
-
const
|
|
71
|
+
const resolved = resolvePreferredProviders('search_jobs', restInput, rawUseProviders)
|
|
72
|
+
if (!resolved.ok) {
|
|
73
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(resolved.error, null, 2) }], isError: true }
|
|
74
|
+
}
|
|
75
|
+
const result = await executeWithFallback('search_jobs', restInput, { providers: resolved.providers, matchedFrom: resolved.matchedFrom })
|
|
69
76
|
if (isExecutionError(result)) {
|
|
70
77
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], isError: true }
|
|
71
78
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { executeWithFallback, isExecutionError } from '../executor.js'
|
|
3
|
+
import { resolvePreferredProviders, getProvidersForCapability } from '../utils/provider-resolver.js'
|
|
3
4
|
|
|
4
5
|
export const searchPlacesName = 'search_places'
|
|
5
6
|
|
|
@@ -41,10 +42,16 @@ export const searchPlacesSchema = {
|
|
|
41
42
|
include_opening_hours: z.boolean().optional().describe('Google Maps only. Routes to Google Maps only when set.'),
|
|
42
43
|
include_additional_info: z.boolean().optional().describe('Google Maps only. Adds amenities/accessibility fields. Routes to Google Maps only when set.'),
|
|
43
44
|
language: z.string().optional().describe('Google Maps only. ISO 639-1 (e.g. "en", "fr"). Routes to Google Maps only when set.'),
|
|
45
|
+
use_providers: z.array(z.string()).optional().describe(`Optional ordered list of providers to use. Leave empty to let ColdIQ automatically pick the best tool for your inputs — recommended for most use cases. Available providers: ${getProvidersForCapability('search_places').join(', ')}. Provider names are matched fuzzily, so minor typos are tolerated.`),
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
export async function searchPlacesHandler(input: Record<string, unknown>) {
|
|
47
|
-
const
|
|
49
|
+
const { use_providers: rawUseProviders, ...restInput } = input
|
|
50
|
+
const resolved = resolvePreferredProviders('search_places', restInput, rawUseProviders)
|
|
51
|
+
if (!resolved.ok) {
|
|
52
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(resolved.error, null, 2) }], isError: true }
|
|
53
|
+
}
|
|
54
|
+
const result = await executeWithFallback('search_places', restInput, { providers: resolved.providers, matchedFrom: resolved.matchedFrom })
|
|
48
55
|
if (isExecutionError(result)) {
|
|
49
56
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], isError: true }
|
|
50
57
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { executeWithFallback, isExecutionError } from '../executor.js'
|
|
3
|
+
import { resolvePreferredProviders, getProvidersForCapability } from '../utils/provider-resolver.js'
|
|
3
4
|
|
|
4
5
|
export const searchRedditName = 'search_reddit'
|
|
5
6
|
|
|
@@ -23,10 +24,16 @@ export const searchRedditSchema = {
|
|
|
23
24
|
.describe('Max comments per post when type=comments.'),
|
|
24
25
|
include_comments: z.boolean().optional()
|
|
25
26
|
.describe('Include comments alongside posts.'),
|
|
27
|
+
use_providers: z.array(z.string()).optional().describe(`Optional ordered list of providers to use. Leave empty to let ColdIQ automatically pick the best tool for your inputs — recommended for most use cases. Available providers: ${getProvidersForCapability('search_reddit').join(', ')}. Provider names are matched fuzzily, so minor typos are tolerated.`),
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export async function searchRedditHandler(input: Record<string, unknown>) {
|
|
29
|
-
const
|
|
31
|
+
const { use_providers: rawUseProviders, ...restInput } = input
|
|
32
|
+
const resolved = resolvePreferredProviders('search_reddit', restInput, rawUseProviders)
|
|
33
|
+
if (!resolved.ok) {
|
|
34
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(resolved.error, null, 2) }], isError: true }
|
|
35
|
+
}
|
|
36
|
+
const result = await executeWithFallback('search_reddit', restInput, { providers: resolved.providers, matchedFrom: resolved.matchedFrom })
|
|
30
37
|
if (isExecutionError(result)) {
|
|
31
38
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], isError: true }
|
|
32
39
|
}
|
package/src/tools/search-seo.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { executeWithFallback, isExecutionError } from '../executor.js'
|
|
3
|
+
import { resolvePreferredProviders, getProvidersForCapability } from '../utils/provider-resolver.js'
|
|
3
4
|
|
|
4
5
|
export const searchSeoName = 'search_seo'
|
|
5
6
|
|
|
@@ -48,10 +49,16 @@ export const searchSeoSchema = {
|
|
|
48
49
|
.describe('page: "lighthouse" (Core Web Vitals audit) or "content" (parse page text).'),
|
|
49
50
|
enable_javascript: z.boolean().optional(),
|
|
50
51
|
full_data: z.boolean().optional().describe('Lighthouse only: include full audit data.'),
|
|
52
|
+
use_providers: z.array(z.string()).optional().describe(`Optional ordered list of providers to use. Leave empty to let ColdIQ automatically pick the best tool for your inputs — recommended for most use cases. Available providers: ${getProvidersForCapability('search_seo').join(', ')}. Provider names are matched fuzzily, so minor typos are tolerated.`),
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
export async function searchSeoHandler(input: Record<string, unknown>) {
|
|
54
|
-
const
|
|
56
|
+
const { use_providers: rawUseProviders, ...restInput } = input
|
|
57
|
+
const resolved = resolvePreferredProviders('search_seo', restInput, rawUseProviders)
|
|
58
|
+
if (!resolved.ok) {
|
|
59
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(resolved.error, null, 2) }], isError: true }
|
|
60
|
+
}
|
|
61
|
+
const result = await executeWithFallback('search_seo', restInput, { providers: resolved.providers, matchedFrom: resolved.matchedFrom })
|
|
55
62
|
if (isExecutionError(result)) {
|
|
56
63
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], isError: true }
|
|
57
64
|
}
|
package/src/tools/search-web.ts
CHANGED
|
@@ -1,22 +1,30 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { executeWithFallback, isExecutionError } from '../executor.js'
|
|
3
|
+
import { resolvePreferredProviders, getProvidersForCapability } from '../utils/provider-resolver.js'
|
|
3
4
|
|
|
4
5
|
export const searchWebName = 'search_web'
|
|
5
6
|
|
|
6
7
|
export const searchWebDescription =
|
|
7
8
|
'Search the web for market research, company news, or general lookups. Supports Google search (default) and neural/semantic search via Exa. ' +
|
|
8
9
|
'NEVER use this to find, verify, or look up people, contacts, LinkedIn profiles, or executives — even as a fallback when find_people returns uncertain results. ' +
|
|
9
|
-
'find_people is the only tool for people lookups; do not supplement or replace it with web search.'
|
|
10
|
+
'find_people is the only tool for people lookups; do not supplement or replace it with web search. ' +
|
|
11
|
+
'ColdIQ automatically picks the best provider — pass use_providers only if you need a specific tool.'
|
|
10
12
|
|
|
11
13
|
export const searchWebSchema = {
|
|
12
14
|
query: z.string().describe('Search query'),
|
|
13
15
|
num_results: z.number().min(1).max(100).default(10).describe('Number of results (default: 10, max: 100)'),
|
|
14
16
|
country: z.string().optional().describe('Country code for geo-targeting (e.g. "us", "fr")'),
|
|
15
17
|
search_type: z.enum(['general', 'neural']).default('general').describe('"general" for Google search (default), "neural" for semantic search via Exa'),
|
|
18
|
+
use_providers: z.array(z.string()).optional().describe(`Optional ordered list of providers to use. Leave empty to let ColdIQ automatically pick the best tool for your inputs — recommended for most use cases. Available providers: ${getProvidersForCapability('search_web').join(', ')}. Provider names are matched fuzzily, so minor typos are tolerated.`),
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
export async function searchWebHandler(input: Record<string, unknown>) {
|
|
19
|
-
const
|
|
22
|
+
const { use_providers: rawUseProviders, ...restInput } = input
|
|
23
|
+
const resolved = resolvePreferredProviders('search_web', restInput, rawUseProviders)
|
|
24
|
+
if (!resolved.ok) {
|
|
25
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(resolved.error, null, 2) }], isError: true }
|
|
26
|
+
}
|
|
27
|
+
const result = await executeWithFallback('search_web', restInput, { providers: resolved.providers, matchedFrom: resolved.matchedFrom })
|
|
20
28
|
if (isExecutionError(result)) {
|
|
21
29
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], isError: true }
|
|
22
30
|
}
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { executeWithFallback, isExecutionError } from '../executor.js'
|
|
3
|
+
import { resolvePreferredProviders, getProvidersForCapability } from '../utils/provider-resolver.js'
|
|
3
4
|
|
|
4
5
|
export const verifyEmailName = 'verify_email'
|
|
5
6
|
|
|
6
7
|
export const verifyEmailDescription =
|
|
7
|
-
'Check whether an email address is valid and deliverable. Returns verification status (valid, invalid, catch-all, unknown). Use before cold outreach to protect sender reputation.'
|
|
8
|
+
'Check whether an email address is valid and deliverable. Returns verification status (valid, invalid, catch-all, unknown). Use before cold outreach to protect sender reputation. ColdIQ automatically picks the best provider — pass use_providers only if you need a specific tool.'
|
|
8
9
|
|
|
9
10
|
export const verifyEmailSchema = {
|
|
10
11
|
email: z.string().email().describe('Email address to verify'),
|
|
12
|
+
use_providers: z.array(z.string()).optional().describe(`Optional ordered list of providers to use. Leave empty to let ColdIQ automatically pick the best tool for your inputs — recommended for most use cases. Available providers: ${getProvidersForCapability('verify_email').join(', ')}. Provider names are matched fuzzily, so minor typos are tolerated.`),
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export async function verifyEmailHandler(input: Record<string, unknown>) {
|
|
14
|
-
const
|
|
16
|
+
const { use_providers: rawUseProviders, ...restInput } = input
|
|
17
|
+
const resolved = resolvePreferredProviders('verify_email', restInput, rawUseProviders)
|
|
18
|
+
if (!resolved.ok) {
|
|
19
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(resolved.error, null, 2) }], isError: true }
|
|
20
|
+
}
|
|
21
|
+
const result = await executeWithFallback('verify_email', restInput, { providers: resolved.providers, matchedFrom: resolved.matchedFrom })
|
|
15
22
|
if (isExecutionError(result)) {
|
|
16
23
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], isError: true }
|
|
17
24
|
}
|