@coldiq/mcp 0.2.5 → 0.2.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/registry.d.ts.map +1 -1
- package/dist/registry.js +17 -1
- package/dist/registry.js.map +1 -1
- package/dist/tools/find-signals.d.ts +2 -0
- package/dist/tools/find-signals.d.ts.map +1 -1
- package/dist/tools/find-signals.js +37 -4
- package/dist/tools/find-signals.js.map +1 -1
- package/dist/tools/search-places.d.ts +6 -0
- package/dist/tools/search-places.d.ts.map +1 -1
- package/dist/tools/search-places.js +48 -4
- package/dist/tools/search-places.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 +57 -1
- package/dist/tools/verify-email.js.map +1 -1
- package/package.json +1 -1
- package/src/registry.ts +17 -1
- package/src/tools/find-signals.ts +36 -4
- package/src/tools/search-places.ts +45 -4
- package/src/tools/verify-email.ts +65 -1
- package/tests/registry-enrich-person.test.ts +30 -2
- package/tests/registry-find-signals.test.ts +31 -0
- package/tests/tools/find-signals.test.ts +62 -0
- package/tests/tools/search-places.test.ts +97 -1
- package/tests/tools/verify-email.test.ts +77 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"find-signals.d.ts","sourceRoot":"","sources":["../../src/tools/find-signals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,eAAe,iBAAiB,CAAA;AAE7C,eAAO,MAAM,sBAAsB,
|
|
1
|
+
{"version":3,"file":"find-signals.d.ts","sourceRoot":"","sources":["../../src/tools/find-signals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,eAAe,iBAAiB,CAAA;AAE7C,eAAO,MAAM,sBAAsB,QAOyL,CAAA;AAE5N,eAAO,MAAM,iBAAiB;;;;;;;;;;;CA6C7B,CAAA;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;;;;;GAsEtE"}
|
|
@@ -5,14 +5,15 @@ export const findSignalsName = 'find_signals';
|
|
|
5
5
|
export const findSignalsDescription = 'Retrieve sales intelligence signals — funding rounds, acquisitions, hiring activity, job changes, buying intent, news, and startup posts. ' +
|
|
6
6
|
'Each call targets one signal type. Two modes: ' +
|
|
7
7
|
'Company-targeted (funding | acquisition | hiring | job_change | intent): accepts companies/domains/industries/countries/since filters. ' +
|
|
8
|
-
'
|
|
8
|
+
'funding additionally accepts `round_type` (e.g. ["Series A", "Seed"]). ' +
|
|
9
|
+
'intent REQUIRES at least one of companies or domains and additionally accepts `topics` (e.g. ["sales-automation"]) to narrow by intent keyword. ' +
|
|
9
10
|
'Feed-style (news | startup_post): country and since only — does NOT filter by company. Passing companies/domains for these types is rejected. ' +
|
|
10
|
-
'hiring returns
|
|
11
|
+
'hiring returns individual job postings with company context (title, location, descriptionText, company industries) — for richer job-board queries with description/seniority/easy-apply filters use search_jobs instead.';
|
|
11
12
|
export const findSignalsSchema = {
|
|
12
13
|
signal_type: z
|
|
13
14
|
.enum(['funding', 'acquisition', 'hiring', 'job_change', 'news', 'intent', 'startup_post'])
|
|
14
15
|
.describe('Signal type to retrieve. ' +
|
|
15
|
-
'Company-targeted: "funding" (fundraising rounds), "acquisition" (M&A), "hiring" (
|
|
16
|
+
'Company-targeted: "funding" (fundraising rounds), "acquisition" (M&A), "hiring" (individual job postings indexed by Signalbase, with company context), ' +
|
|
16
17
|
'"job_change" (people who recently changed roles), "intent" (companies showing buying intent). ' +
|
|
17
18
|
'Feed-style (country/date filter only — company filter not supported): "news" (company news events), "startup_post" (Product Hunt, Hacker News, etc.)'),
|
|
18
19
|
companies: z
|
|
@@ -30,11 +31,19 @@ export const findSignalsSchema = {
|
|
|
30
31
|
industries: z
|
|
31
32
|
.array(z.string())
|
|
32
33
|
.optional()
|
|
33
|
-
.describe('Industry names to filter by (e.g. ["Software", "SaaS"]).
|
|
34
|
+
.describe('Industry names to filter by (e.g. ["Software", "SaaS"]). Forwarded to upstream for funding and acquisition. For hiring, filtered client-side against each row\'s `industries` field (case-insensitive substring match). Ignored for job_change, intent, news, startup_post (those signal types have no industry data to filter on).'),
|
|
34
35
|
countries: z
|
|
35
36
|
.array(z.string())
|
|
36
37
|
.optional()
|
|
37
38
|
.describe('ISO country codes or names to filter by (e.g. ["US", "GB"]). Works for all signal types.'),
|
|
39
|
+
round_type: z
|
|
40
|
+
.array(z.string())
|
|
41
|
+
.optional()
|
|
42
|
+
.describe('Funding round filter (e.g. ["Series A", "Seed"]). Only honored by signal_type=funding. Comma-joined and forwarded to Signalbase\'s `round` filter — case-insensitive substring match upstream, so "Series A" matches "Series A Extension" as well. Silently ignored for other signal types.'),
|
|
43
|
+
topics: z
|
|
44
|
+
.array(z.string())
|
|
45
|
+
.optional()
|
|
46
|
+
.describe('Intent topic / keyword slugs (e.g. ["sales-automation", "lead-generation"]). Only honored by signal_type=intent (forwarded to TheirStack as `keyword_slug_or`). Note: topics is supplemental — TheirStack still requires at least one of `companies` or `domains`, so topics narrows an existing company-targeted search rather than enabling pure topic discovery.'),
|
|
38
47
|
limit: z
|
|
39
48
|
.number()
|
|
40
49
|
.int()
|
|
@@ -84,6 +93,30 @@ export async function findSignalsHandler(input) {
|
|
|
84
93
|
typed.data.data = typed.data.data.slice(0, limit);
|
|
85
94
|
}
|
|
86
95
|
}
|
|
96
|
+
// Signalbase /hiring-signals does not accept an industry filter upstream, so the
|
|
97
|
+
// `industries` param would otherwise be silently dropped. Filter client-side:
|
|
98
|
+
// each hiring row carries an `industries` string (e.g. "Law Practice and Legal
|
|
99
|
+
// Services") which we substring-match against the user-supplied list.
|
|
100
|
+
if (restInput.signal_type === 'hiring' && Array.isArray(restInput.industries) && restInput.industries.length > 0) {
|
|
101
|
+
const wanted = restInput.industries
|
|
102
|
+
.map((s) => (typeof s === 'string' ? s.toLowerCase() : ''))
|
|
103
|
+
.filter((s) => s.length > 0);
|
|
104
|
+
if (wanted.length > 0) {
|
|
105
|
+
const typed = result;
|
|
106
|
+
const rows = typed.data?.data;
|
|
107
|
+
if (Array.isArray(rows)) {
|
|
108
|
+
typed.data.data = rows.filter((row) => {
|
|
109
|
+
if (!row || typeof row !== 'object')
|
|
110
|
+
return false;
|
|
111
|
+
const industriesField = row.industries;
|
|
112
|
+
if (typeof industriesField !== 'string' || industriesField.length === 0)
|
|
113
|
+
return false;
|
|
114
|
+
const haystack = industriesField.toLowerCase();
|
|
115
|
+
return wanted.some((needle) => haystack.includes(needle));
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
87
120
|
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
88
121
|
}
|
|
89
122
|
//# sourceMappingURL=find-signals.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"find-signals.js","sourceRoot":"","sources":["../../src/tools/find-signals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACtE,OAAO,EAAE,yBAAyB,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAA;AAEpG,MAAM,CAAC,MAAM,eAAe,GAAG,cAAc,CAAA;AAE7C,MAAM,CAAC,MAAM,sBAAsB,GACjC,4IAA4I;IAC5I,gDAAgD;IAChD,yIAAyI;IACzI,
|
|
1
|
+
{"version":3,"file":"find-signals.js","sourceRoot":"","sources":["../../src/tools/find-signals.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,6 +32,12 @@ export declare const searchPlacesSchema: {
|
|
|
32
32
|
language: z.ZodOptional<z.ZodString>;
|
|
33
33
|
use_providers: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
34
34
|
};
|
|
35
|
+
export declare function applyPlaceFilters(data: unknown, filters: {
|
|
36
|
+
minRating?: number;
|
|
37
|
+
maxRating?: number;
|
|
38
|
+
minReviews?: number;
|
|
39
|
+
maxReviews?: number;
|
|
40
|
+
}): unknown;
|
|
35
41
|
export declare function searchPlacesHandler(input: Record<string, unknown>): Promise<{
|
|
36
42
|
content: {
|
|
37
43
|
type: "text";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-places.d.ts","sourceRoot":"","sources":["../../src/tools/search-places.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,gBAAgB,kBAAkB,CAAA;AAE/C,eAAO,MAAM,uBAAuB,2eACmb,CAAA;AAEvd,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoC9B,CAAA;AAED,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"search-places.d.ts","sourceRoot":"","sources":["../../src/tools/search-places.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,gBAAgB,kBAAkB,CAAA;AAE/C,eAAO,MAAM,uBAAuB,2eACmb,CAAA;AAEvd,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoC9B,CAAA;AAaD,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,OAAO,EACb,OAAO,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5F,OAAO,CAmBT;AAED,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;;;;;GAiBvE"}
|
|
@@ -15,10 +15,10 @@ export const searchPlacesSchema = {
|
|
|
15
15
|
long: z.number().optional().describe('Openmart only. Longitude for radius search.'),
|
|
16
16
|
geo_radius: z.number().int().positive().optional().describe('Openmart only. Search radius in meters around lat/long.'),
|
|
17
17
|
tags: z.array(z.string()).max(100).optional().describe('Openmart only. Category tags (mutually exclusive with query upstream — query is ignored if tags non-empty).'),
|
|
18
|
-
min_overall_rating: z.number().min(0).max(5).optional().describe('Openmart
|
|
19
|
-
max_overall_rating: z.number().min(0).max(5).optional().describe('Openmart
|
|
20
|
-
min_total_reviews: z.number().int().min(0).optional().describe('Openmart
|
|
21
|
-
max_total_reviews: z.number().int().min(0).optional().describe('Openmart
|
|
18
|
+
min_overall_rating: z.number().min(0).max(5).optional().describe('Forwarded upstream to Openmart; applied client-side as a post-filter on Google Maps results (each place has a `totalScore` field). Use 0–5.'),
|
|
19
|
+
max_overall_rating: z.number().min(0).max(5).optional().describe('Forwarded upstream to Openmart; applied client-side as a post-filter on Google Maps results (each place has a `totalScore` field). Use 0–5.'),
|
|
20
|
+
min_total_reviews: z.number().int().min(0).optional().describe('Forwarded upstream to Openmart; applied client-side as a post-filter on Google Maps results (each place has a `reviewsCount` field).'),
|
|
21
|
+
max_total_reviews: z.number().int().min(0).optional().describe('Forwarded upstream to Openmart; applied client-side as a post-filter on Google Maps results (each place has a `reviewsCount` field).'),
|
|
22
22
|
ownership_type: z.enum(['INDEPENDENT', 'FAMILY', 'FRANCHISE', 'CHAIN']).optional().describe('Openmart only.'),
|
|
23
23
|
has_website: z.boolean().optional().describe('Openmart only.'),
|
|
24
24
|
has_valid_website: z.boolean().optional().describe('Openmart only.'),
|
|
@@ -34,6 +34,44 @@ export const searchPlacesSchema = {
|
|
|
34
34
|
language: z.string().optional().describe('Google Maps only. ISO 639-1 (e.g. "en", "fr"). Routes to Google Maps only when set.'),
|
|
35
35
|
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.`),
|
|
36
36
|
};
|
|
37
|
+
function asNumber(v) {
|
|
38
|
+
return typeof v === 'number' && Number.isFinite(v) ? v : undefined;
|
|
39
|
+
}
|
|
40
|
+
// Google Maps Scraper does not honor min/max rating or review-count filters
|
|
41
|
+
// upstream, so the params would otherwise be silently dropped. Filter client-side
|
|
42
|
+
// against the `totalScore` (rating) and `reviewsCount` fields on each place.
|
|
43
|
+
// Applied to all routes for consistency; on Openmart this is a no-op since the
|
|
44
|
+
// filters were already enforced upstream and Openmart returns `{ data: [...] }`
|
|
45
|
+
// rather than `{ places: [...] }`.
|
|
46
|
+
// Pure: returns a new object instead of mutating its argument.
|
|
47
|
+
export function applyPlaceFilters(data, filters) {
|
|
48
|
+
const { minRating, maxRating, minReviews, maxReviews } = filters;
|
|
49
|
+
if (minRating === undefined && maxRating === undefined && minReviews === undefined && maxReviews === undefined)
|
|
50
|
+
return data;
|
|
51
|
+
if (!data || typeof data !== 'object')
|
|
52
|
+
return data;
|
|
53
|
+
const d = data;
|
|
54
|
+
const places = d.places;
|
|
55
|
+
if (!Array.isArray(places))
|
|
56
|
+
return data;
|
|
57
|
+
const filtered = places.filter((place) => {
|
|
58
|
+
if (!place || typeof place !== 'object')
|
|
59
|
+
return false;
|
|
60
|
+
const p = place;
|
|
61
|
+
const rating = asNumber(p.totalScore);
|
|
62
|
+
const reviews = asNumber(p.reviewsCount);
|
|
63
|
+
if (minRating !== undefined && (rating === undefined || rating < minRating))
|
|
64
|
+
return false;
|
|
65
|
+
if (maxRating !== undefined && (rating === undefined || rating > maxRating))
|
|
66
|
+
return false;
|
|
67
|
+
if (minReviews !== undefined && (reviews === undefined || reviews < minReviews))
|
|
68
|
+
return false;
|
|
69
|
+
if (maxReviews !== undefined && (reviews === undefined || reviews > maxReviews))
|
|
70
|
+
return false;
|
|
71
|
+
return true;
|
|
72
|
+
});
|
|
73
|
+
return { ...d, places: filtered };
|
|
74
|
+
}
|
|
37
75
|
export async function searchPlacesHandler(input) {
|
|
38
76
|
const { use_providers: rawUseProviders, ...restInput } = input;
|
|
39
77
|
const resolved = resolvePreferredProviders('search_places', restInput, rawUseProviders);
|
|
@@ -44,6 +82,12 @@ export async function searchPlacesHandler(input) {
|
|
|
44
82
|
if (isExecutionError(result)) {
|
|
45
83
|
return { content: [{ type: 'text', text: JSON.stringify(result) }], isError: true };
|
|
46
84
|
}
|
|
85
|
+
result.data = applyPlaceFilters(result.data, {
|
|
86
|
+
minRating: asNumber(restInput.min_overall_rating),
|
|
87
|
+
maxRating: asNumber(restInput.max_overall_rating),
|
|
88
|
+
minReviews: asNumber(restInput.min_total_reviews),
|
|
89
|
+
maxReviews: asNumber(restInput.max_total_reviews),
|
|
90
|
+
});
|
|
47
91
|
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
48
92
|
}
|
|
49
93
|
//# sourceMappingURL=search-places.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-places.js","sourceRoot":"","sources":["../../src/tools/search-places.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACtE,OAAO,EAAE,yBAAyB,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAA;AAEpG,MAAM,CAAC,MAAM,gBAAgB,GAAG,eAAe,CAAA;AAE/C,MAAM,CAAC,MAAM,uBAAuB,GAClC,qdAAqd,CAAA;AAEvd,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iGAAiG,CAAC;IACxI,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,4HAA4H,CAAC;IACrK,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oCAAoC,CAAC;IAC1E,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,wGAAwG,CAAC;IACtK,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,yGAAyG,CAAC;IAE5K,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;IACpF,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IAC1D,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,4CAA4C,CAAC;IACjF,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;IACnF,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,yDAAyD,CAAC;IAEtH,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6GAA6G,CAAC;IAErK,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"search-places.js","sourceRoot":"","sources":["../../src/tools/search-places.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export declare const verifyEmailName = "verify_email";
|
|
3
|
-
export declare const verifyEmailDescription = "Check whether an email address is valid and deliverable. Returns
|
|
3
|
+
export declare const verifyEmailDescription = "Check whether an email address is valid and deliverable. Returns a normalized `status` field \u2014 one of \"valid\", \"invalid\", \"catch_all\", \"risky\", \"disposable\", \"unknown\" \u2014 alongside the raw provider response. Findymail (the default) only distinguishes valid vs invalid; richer status values come from providers like icypeas, instantly, or linkupapi. Use before cold outreach to protect sender reputation. ColdIQ automatically picks the best provider \u2014 pass use_providers only if you need a specific tool.";
|
|
4
4
|
export declare const verifyEmailSchema: {
|
|
5
5
|
email: z.ZodString;
|
|
6
6
|
use_providers: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
7
7
|
};
|
|
8
|
+
export declare function addNormalizedStatus(data: unknown, providerId: string): unknown;
|
|
8
9
|
export declare function verifyEmailHandler(input: Record<string, unknown>): Promise<{
|
|
9
10
|
content: {
|
|
10
11
|
type: "text";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify-email.d.ts","sourceRoot":"","sources":["../../src/tools/verify-email.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,eAAe,iBAAiB,CAAA;AAE7C,eAAO,MAAM,sBAAsB
|
|
1
|
+
{"version":3,"file":"verify-email.d.ts","sourceRoot":"","sources":["../../src/tools/verify-email.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,eAAe,iBAAiB,CAAA;AAE7C,eAAO,MAAM,sBAAsB,shBACud,CAAA;AAE1f,eAAO,MAAM,iBAAiB;;;CAG7B,CAAA;AA8BD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAiC9E;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;;;;;GAYtE"}
|
|
@@ -2,11 +2,66 @@ import { z } from 'zod';
|
|
|
2
2
|
import { executeWithFallback, isExecutionError } from '../executor.js';
|
|
3
3
|
import { resolvePreferredProviders, getProvidersForCapability } from '../utils/provider-resolver.js';
|
|
4
4
|
export const verifyEmailName = 'verify_email';
|
|
5
|
-
export const verifyEmailDescription = 'Check whether an email address is valid and deliverable. Returns
|
|
5
|
+
export const verifyEmailDescription = 'Check whether an email address is valid and deliverable. Returns a normalized `status` field — one of "valid", "invalid", "catch_all", "risky", "disposable", "unknown" — alongside the raw provider response. Findymail (the default) only distinguishes valid vs invalid; richer status values come from providers like icypeas, instantly, or linkupapi. Use before cold outreach to protect sender reputation. ColdIQ automatically picks the best provider — pass use_providers only if you need a specific tool.';
|
|
6
6
|
export const verifyEmailSchema = {
|
|
7
7
|
email: z.string().email().describe('Email address to verify'),
|
|
8
8
|
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.`),
|
|
9
9
|
};
|
|
10
|
+
const STATUS_ALIASES = {
|
|
11
|
+
valid: 'valid',
|
|
12
|
+
deliverable: 'valid',
|
|
13
|
+
ok: 'valid',
|
|
14
|
+
invalid: 'invalid',
|
|
15
|
+
undeliverable: 'invalid',
|
|
16
|
+
bounce: 'invalid',
|
|
17
|
+
bounced: 'invalid',
|
|
18
|
+
catch_all: 'catch_all',
|
|
19
|
+
catchall: 'catch_all',
|
|
20
|
+
'catch-all': 'catch_all',
|
|
21
|
+
accept_all: 'catch_all',
|
|
22
|
+
acceptall: 'catch_all',
|
|
23
|
+
risky: 'risky',
|
|
24
|
+
disposable: 'disposable',
|
|
25
|
+
unknown: 'unknown',
|
|
26
|
+
};
|
|
27
|
+
function normalizeStatus(raw) {
|
|
28
|
+
if (typeof raw !== 'string' || raw.length === 0)
|
|
29
|
+
return 'unknown';
|
|
30
|
+
return STATUS_ALIASES[raw.toLowerCase()] ?? 'unknown';
|
|
31
|
+
}
|
|
32
|
+
// Provider responses are heterogeneous. We surface a single `status` field on the
|
|
33
|
+
// payload while leaving the raw upstream fields untouched so callers that already
|
|
34
|
+
// depend on them keep working.
|
|
35
|
+
export function addNormalizedStatus(data, providerId) {
|
|
36
|
+
if (!data || typeof data !== 'object' || Array.isArray(data))
|
|
37
|
+
return data;
|
|
38
|
+
const d = { ...data };
|
|
39
|
+
// If the response already carries a status string, normalize it and stop.
|
|
40
|
+
if (typeof d.status === 'string') {
|
|
41
|
+
d.status = normalizeStatus(d.status);
|
|
42
|
+
return d;
|
|
43
|
+
}
|
|
44
|
+
if (providerId === 'findymail') {
|
|
45
|
+
// Findymail's /api/verify returns { email, verified: boolean, provider } — no
|
|
46
|
+
// catch_all/unknown distinction. Map the boolean to valid/invalid only.
|
|
47
|
+
d.status = d.verified === true ? 'valid' : 'invalid';
|
|
48
|
+
return d;
|
|
49
|
+
}
|
|
50
|
+
if (providerId === 'icypeas') {
|
|
51
|
+
const item = d.item;
|
|
52
|
+
d.status = normalizeStatus(item?.status);
|
|
53
|
+
return d;
|
|
54
|
+
}
|
|
55
|
+
if (providerId === 'linkupapi-validate') {
|
|
56
|
+
// LinkupAPI shapes vary; accept any of the common signal fields.
|
|
57
|
+
const truthy = d.verified === true || d.valid === true || d.result === 'valid';
|
|
58
|
+
d.status = truthy ? 'valid' : normalizeStatus(d.result ?? d.valid ?? d.verified);
|
|
59
|
+
return d;
|
|
60
|
+
}
|
|
61
|
+
// Unknown provider — leave the original payload alone and flag the gap.
|
|
62
|
+
d.status = 'unknown';
|
|
63
|
+
return d;
|
|
64
|
+
}
|
|
10
65
|
export async function verifyEmailHandler(input) {
|
|
11
66
|
const { use_providers: rawUseProviders, ...restInput } = input;
|
|
12
67
|
const resolved = resolvePreferredProviders('verify_email', restInput, rawUseProviders);
|
|
@@ -17,6 +72,7 @@ export async function verifyEmailHandler(input) {
|
|
|
17
72
|
if (isExecutionError(result)) {
|
|
18
73
|
return { content: [{ type: 'text', text: JSON.stringify(result) }], isError: true };
|
|
19
74
|
}
|
|
75
|
+
result.data = addNormalizedStatus(result.data, result._meta.provider);
|
|
20
76
|
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
21
77
|
}
|
|
22
78
|
//# sourceMappingURL=verify-email.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify-email.js","sourceRoot":"","sources":["../../src/tools/verify-email.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACtE,OAAO,EAAE,yBAAyB,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAA;AAEpG,MAAM,CAAC,MAAM,eAAe,GAAG,cAAc,CAAA;AAE7C,MAAM,CAAC,MAAM,sBAAsB,GACjC,
|
|
1
|
+
{"version":3,"file":"verify-email.js","sourceRoot":"","sources":["../../src/tools/verify-email.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/registry.ts
CHANGED
|
@@ -2909,8 +2909,22 @@ const enrichPersonProviders: ProviderEntry[] = [
|
|
|
2909
2909
|
},
|
|
2910
2910
|
}),
|
|
2911
2911
|
hasResult: (data) => {
|
|
2912
|
+
// Apollo /people/match always returns { person: {...} } even on weak matches
|
|
2913
|
+
// where every useful field is null (just echoing back the input first/last name).
|
|
2914
|
+
// Treat as a real match only when at least one enrichment field is populated,
|
|
2915
|
+
// so the executor falls through to the next provider instead of returning a
|
|
2916
|
+
// silent empty record.
|
|
2912
2917
|
const d = data as Record<string, unknown>
|
|
2913
|
-
|
|
2918
|
+
const person = d.person as Record<string, unknown> | undefined
|
|
2919
|
+
if (!person || typeof person !== 'object') return false
|
|
2920
|
+
return Boolean(
|
|
2921
|
+
person.linkedin_url ||
|
|
2922
|
+
person.email ||
|
|
2923
|
+
person.title ||
|
|
2924
|
+
person.headline ||
|
|
2925
|
+
person.organization_id ||
|
|
2926
|
+
isNonEmptyArray(person.employment_history),
|
|
2927
|
+
)
|
|
2914
2928
|
},
|
|
2915
2929
|
},
|
|
2916
2930
|
{
|
|
@@ -3049,6 +3063,7 @@ const findSignalsProviders: ProviderEntry[] = [
|
|
|
3049
3063
|
if (typeof input.since === 'string') qp.dateFrom = input.since
|
|
3050
3064
|
if (isNonEmptyArray(input.industries)) qp.industry = (input.industries as string[]).join(',')
|
|
3051
3065
|
if (isNonEmptyArray(input.countries)) qp.countries = (input.countries as string[]).join(',')
|
|
3066
|
+
if (isNonEmptyArray(input.round_type)) qp.round = (input.round_type as string[]).join(',')
|
|
3052
3067
|
qp.limit = String(Math.min((input.limit as number | undefined) ?? 25, 100))
|
|
3053
3068
|
return { queryParams: qp }
|
|
3054
3069
|
},
|
|
@@ -3157,6 +3172,7 @@ const findSignalsProviders: ProviderEntry[] = [
|
|
|
3157
3172
|
body: {
|
|
3158
3173
|
...(isNonEmptyArray(input.companies) && { company_name_or: input.companies }),
|
|
3159
3174
|
...(isNonEmptyArray(input.domains) && { company_domain: (input.domains as string[])[0] }),
|
|
3175
|
+
...(isNonEmptyArray(input.topics) && { keyword_slug_or: input.topics }),
|
|
3160
3176
|
},
|
|
3161
3177
|
}),
|
|
3162
3178
|
hasResult: (data) => isNonEmptyArray((data as Record<string, unknown>).data),
|
|
@@ -8,16 +8,17 @@ export const findSignalsDescription =
|
|
|
8
8
|
'Retrieve sales intelligence signals — funding rounds, acquisitions, hiring activity, job changes, buying intent, news, and startup posts. ' +
|
|
9
9
|
'Each call targets one signal type. Two modes: ' +
|
|
10
10
|
'Company-targeted (funding | acquisition | hiring | job_change | intent): accepts companies/domains/industries/countries/since filters. ' +
|
|
11
|
-
'
|
|
11
|
+
'funding additionally accepts `round_type` (e.g. ["Series A", "Seed"]). ' +
|
|
12
|
+
'intent REQUIRES at least one of companies or domains and additionally accepts `topics` (e.g. ["sales-automation"]) to narrow by intent keyword. ' +
|
|
12
13
|
'Feed-style (news | startup_post): country and since only — does NOT filter by company. Passing companies/domains for these types is rejected. ' +
|
|
13
|
-
'hiring returns
|
|
14
|
+
'hiring returns individual job postings with company context (title, location, descriptionText, company industries) — for richer job-board queries with description/seniority/easy-apply filters use search_jobs instead.'
|
|
14
15
|
|
|
15
16
|
export const findSignalsSchema = {
|
|
16
17
|
signal_type: z
|
|
17
18
|
.enum(['funding', 'acquisition', 'hiring', 'job_change', 'news', 'intent', 'startup_post'])
|
|
18
19
|
.describe(
|
|
19
20
|
'Signal type to retrieve. ' +
|
|
20
|
-
'Company-targeted: "funding" (fundraising rounds), "acquisition" (M&A), "hiring" (
|
|
21
|
+
'Company-targeted: "funding" (fundraising rounds), "acquisition" (M&A), "hiring" (individual job postings indexed by Signalbase, with company context), ' +
|
|
21
22
|
'"job_change" (people who recently changed roles), "intent" (companies showing buying intent). ' +
|
|
22
23
|
'Feed-style (country/date filter only — company filter not supported): "news" (company news events), "startup_post" (Product Hunt, Hacker News, etc.)'
|
|
23
24
|
),
|
|
@@ -36,11 +37,19 @@ export const findSignalsSchema = {
|
|
|
36
37
|
industries: z
|
|
37
38
|
.array(z.string())
|
|
38
39
|
.optional()
|
|
39
|
-
.describe('Industry names to filter by (e.g. ["Software", "SaaS"]).
|
|
40
|
+
.describe('Industry names to filter by (e.g. ["Software", "SaaS"]). Forwarded to upstream for funding and acquisition. For hiring, filtered client-side against each row\'s `industries` field (case-insensitive substring match). Ignored for job_change, intent, news, startup_post (those signal types have no industry data to filter on).'),
|
|
40
41
|
countries: z
|
|
41
42
|
.array(z.string())
|
|
42
43
|
.optional()
|
|
43
44
|
.describe('ISO country codes or names to filter by (e.g. ["US", "GB"]). Works for all signal types.'),
|
|
45
|
+
round_type: z
|
|
46
|
+
.array(z.string())
|
|
47
|
+
.optional()
|
|
48
|
+
.describe('Funding round filter (e.g. ["Series A", "Seed"]). Only honored by signal_type=funding. Comma-joined and forwarded to Signalbase\'s `round` filter — case-insensitive substring match upstream, so "Series A" matches "Series A Extension" as well. Silently ignored for other signal types.'),
|
|
49
|
+
topics: z
|
|
50
|
+
.array(z.string())
|
|
51
|
+
.optional()
|
|
52
|
+
.describe('Intent topic / keyword slugs (e.g. ["sales-automation", "lead-generation"]). Only honored by signal_type=intent (forwarded to TheirStack as `keyword_slug_or`). Note: topics is supplemental — TheirStack still requires at least one of `companies` or `domains`, so topics narrows an existing company-targeted search rather than enabling pure topic discovery.'),
|
|
44
53
|
limit: z
|
|
45
54
|
.number()
|
|
46
55
|
.int()
|
|
@@ -97,5 +106,28 @@ export async function findSignalsHandler(input: Record<string, unknown>) {
|
|
|
97
106
|
}
|
|
98
107
|
}
|
|
99
108
|
|
|
109
|
+
// Signalbase /hiring-signals does not accept an industry filter upstream, so the
|
|
110
|
+
// `industries` param would otherwise be silently dropped. Filter client-side:
|
|
111
|
+
// each hiring row carries an `industries` string (e.g. "Law Practice and Legal
|
|
112
|
+
// Services") which we substring-match against the user-supplied list.
|
|
113
|
+
if (restInput.signal_type === 'hiring' && Array.isArray(restInput.industries) && restInput.industries.length > 0) {
|
|
114
|
+
const wanted = (restInput.industries as unknown[])
|
|
115
|
+
.map((s) => (typeof s === 'string' ? s.toLowerCase() : ''))
|
|
116
|
+
.filter((s) => s.length > 0)
|
|
117
|
+
if (wanted.length > 0) {
|
|
118
|
+
const typed = result as { data?: { data?: unknown[] } }
|
|
119
|
+
const rows = typed.data?.data
|
|
120
|
+
if (Array.isArray(rows)) {
|
|
121
|
+
typed.data!.data = rows.filter((row) => {
|
|
122
|
+
if (!row || typeof row !== 'object') return false
|
|
123
|
+
const industriesField = (row as Record<string, unknown>).industries
|
|
124
|
+
if (typeof industriesField !== 'string' || industriesField.length === 0) return false
|
|
125
|
+
const haystack = industriesField.toLowerCase()
|
|
126
|
+
return wanted.some((needle) => haystack.includes(needle))
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
100
132
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result) }] }
|
|
101
133
|
}
|
|
@@ -22,10 +22,10 @@ export const searchPlacesSchema = {
|
|
|
22
22
|
|
|
23
23
|
tags: z.array(z.string()).max(100).optional().describe('Openmart only. Category tags (mutually exclusive with query upstream — query is ignored if tags non-empty).'),
|
|
24
24
|
|
|
25
|
-
min_overall_rating: z.number().min(0).max(5).optional().describe('Openmart
|
|
26
|
-
max_overall_rating: z.number().min(0).max(5).optional().describe('Openmart
|
|
27
|
-
min_total_reviews: z.number().int().min(0).optional().describe('Openmart
|
|
28
|
-
max_total_reviews: z.number().int().min(0).optional().describe('Openmart
|
|
25
|
+
min_overall_rating: z.number().min(0).max(5).optional().describe('Forwarded upstream to Openmart; applied client-side as a post-filter on Google Maps results (each place has a `totalScore` field). Use 0–5.'),
|
|
26
|
+
max_overall_rating: z.number().min(0).max(5).optional().describe('Forwarded upstream to Openmart; applied client-side as a post-filter on Google Maps results (each place has a `totalScore` field). Use 0–5.'),
|
|
27
|
+
min_total_reviews: z.number().int().min(0).optional().describe('Forwarded upstream to Openmart; applied client-side as a post-filter on Google Maps results (each place has a `reviewsCount` field).'),
|
|
28
|
+
max_total_reviews: z.number().int().min(0).optional().describe('Forwarded upstream to Openmart; applied client-side as a post-filter on Google Maps results (each place has a `reviewsCount` field).'),
|
|
29
29
|
|
|
30
30
|
ownership_type: z.enum(['INDEPENDENT', 'FAMILY', 'FRANCHISE', 'CHAIN']).optional().describe('Openmart only.'),
|
|
31
31
|
has_website: z.boolean().optional().describe('Openmart only.'),
|
|
@@ -45,6 +45,41 @@ export const searchPlacesSchema = {
|
|
|
45
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.`),
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
function asNumber(v: unknown): number | undefined {
|
|
49
|
+
return typeof v === 'number' && Number.isFinite(v) ? v : undefined
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Google Maps Scraper does not honor min/max rating or review-count filters
|
|
53
|
+
// upstream, so the params would otherwise be silently dropped. Filter client-side
|
|
54
|
+
// against the `totalScore` (rating) and `reviewsCount` fields on each place.
|
|
55
|
+
// Applied to all routes for consistency; on Openmart this is a no-op since the
|
|
56
|
+
// filters were already enforced upstream and Openmart returns `{ data: [...] }`
|
|
57
|
+
// rather than `{ places: [...] }`.
|
|
58
|
+
// Pure: returns a new object instead of mutating its argument.
|
|
59
|
+
export function applyPlaceFilters(
|
|
60
|
+
data: unknown,
|
|
61
|
+
filters: { minRating?: number; maxRating?: number; minReviews?: number; maxReviews?: number },
|
|
62
|
+
): unknown {
|
|
63
|
+
const { minRating, maxRating, minReviews, maxReviews } = filters
|
|
64
|
+
if (minRating === undefined && maxRating === undefined && minReviews === undefined && maxReviews === undefined) return data
|
|
65
|
+
if (!data || typeof data !== 'object') return data
|
|
66
|
+
const d = data as Record<string, unknown>
|
|
67
|
+
const places = d.places
|
|
68
|
+
if (!Array.isArray(places)) return data
|
|
69
|
+
const filtered = places.filter((place) => {
|
|
70
|
+
if (!place || typeof place !== 'object') return false
|
|
71
|
+
const p = place as Record<string, unknown>
|
|
72
|
+
const rating = asNumber(p.totalScore)
|
|
73
|
+
const reviews = asNumber(p.reviewsCount)
|
|
74
|
+
if (minRating !== undefined && (rating === undefined || rating < minRating)) return false
|
|
75
|
+
if (maxRating !== undefined && (rating === undefined || rating > maxRating)) return false
|
|
76
|
+
if (minReviews !== undefined && (reviews === undefined || reviews < minReviews)) return false
|
|
77
|
+
if (maxReviews !== undefined && (reviews === undefined || reviews > maxReviews)) return false
|
|
78
|
+
return true
|
|
79
|
+
})
|
|
80
|
+
return { ...d, places: filtered }
|
|
81
|
+
}
|
|
82
|
+
|
|
48
83
|
export async function searchPlacesHandler(input: Record<string, unknown>) {
|
|
49
84
|
const { use_providers: rawUseProviders, ...restInput } = input
|
|
50
85
|
const resolved = resolvePreferredProviders('search_places', restInput, rawUseProviders)
|
|
@@ -55,5 +90,11 @@ export async function searchPlacesHandler(input: Record<string, unknown>) {
|
|
|
55
90
|
if (isExecutionError(result)) {
|
|
56
91
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result) }], isError: true }
|
|
57
92
|
}
|
|
93
|
+
result.data = applyPlaceFilters(result.data, {
|
|
94
|
+
minRating: asNumber(restInput.min_overall_rating),
|
|
95
|
+
maxRating: asNumber(restInput.max_overall_rating),
|
|
96
|
+
minReviews: asNumber(restInput.min_total_reviews),
|
|
97
|
+
maxReviews: asNumber(restInput.max_total_reviews),
|
|
98
|
+
})
|
|
58
99
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result) }] }
|
|
59
100
|
}
|
|
@@ -5,13 +5,76 @@ import { resolvePreferredProviders, getProvidersForCapability } from '../utils/p
|
|
|
5
5
|
export const verifyEmailName = 'verify_email'
|
|
6
6
|
|
|
7
7
|
export const verifyEmailDescription =
|
|
8
|
-
'Check whether an email address is valid and deliverable. Returns
|
|
8
|
+
'Check whether an email address is valid and deliverable. Returns a normalized `status` field — one of "valid", "invalid", "catch_all", "risky", "disposable", "unknown" — alongside the raw provider response. Findymail (the default) only distinguishes valid vs invalid; richer status values come from providers like icypeas, instantly, or linkupapi. Use before cold outreach to protect sender reputation. ColdIQ automatically picks the best provider — pass use_providers only if you need a specific tool.'
|
|
9
9
|
|
|
10
10
|
export const verifyEmailSchema = {
|
|
11
11
|
email: z.string().email().describe('Email address to verify'),
|
|
12
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.`),
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
type NormalizedStatus = 'valid' | 'invalid' | 'catch_all' | 'risky' | 'disposable' | 'unknown'
|
|
16
|
+
|
|
17
|
+
const STATUS_ALIASES: Record<string, NormalizedStatus> = {
|
|
18
|
+
valid: 'valid',
|
|
19
|
+
deliverable: 'valid',
|
|
20
|
+
ok: 'valid',
|
|
21
|
+
invalid: 'invalid',
|
|
22
|
+
undeliverable: 'invalid',
|
|
23
|
+
bounce: 'invalid',
|
|
24
|
+
bounced: 'invalid',
|
|
25
|
+
catch_all: 'catch_all',
|
|
26
|
+
catchall: 'catch_all',
|
|
27
|
+
'catch-all': 'catch_all',
|
|
28
|
+
accept_all: 'catch_all',
|
|
29
|
+
acceptall: 'catch_all',
|
|
30
|
+
risky: 'risky',
|
|
31
|
+
disposable: 'disposable',
|
|
32
|
+
unknown: 'unknown',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeStatus(raw: unknown): NormalizedStatus {
|
|
36
|
+
if (typeof raw !== 'string' || raw.length === 0) return 'unknown'
|
|
37
|
+
return STATUS_ALIASES[raw.toLowerCase()] ?? 'unknown'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Provider responses are heterogeneous. We surface a single `status` field on the
|
|
41
|
+
// payload while leaving the raw upstream fields untouched so callers that already
|
|
42
|
+
// depend on them keep working.
|
|
43
|
+
export function addNormalizedStatus(data: unknown, providerId: string): unknown {
|
|
44
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) return data
|
|
45
|
+
const d = { ...(data as Record<string, unknown>) }
|
|
46
|
+
|
|
47
|
+
// If the response already carries a status string, normalize it and stop.
|
|
48
|
+
if (typeof d.status === 'string') {
|
|
49
|
+
d.status = normalizeStatus(d.status)
|
|
50
|
+
return d
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (providerId === 'findymail') {
|
|
54
|
+
// Findymail's /api/verify returns { email, verified: boolean, provider } — no
|
|
55
|
+
// catch_all/unknown distinction. Map the boolean to valid/invalid only.
|
|
56
|
+
d.status = d.verified === true ? 'valid' : 'invalid'
|
|
57
|
+
return d
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (providerId === 'icypeas') {
|
|
61
|
+
const item = d.item as Record<string, unknown> | undefined
|
|
62
|
+
d.status = normalizeStatus(item?.status)
|
|
63
|
+
return d
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (providerId === 'linkupapi-validate') {
|
|
67
|
+
// LinkupAPI shapes vary; accept any of the common signal fields.
|
|
68
|
+
const truthy = d.verified === true || d.valid === true || d.result === 'valid'
|
|
69
|
+
d.status = truthy ? 'valid' : normalizeStatus(d.result ?? d.valid ?? d.verified)
|
|
70
|
+
return d
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Unknown provider — leave the original payload alone and flag the gap.
|
|
74
|
+
d.status = 'unknown'
|
|
75
|
+
return d
|
|
76
|
+
}
|
|
77
|
+
|
|
15
78
|
export async function verifyEmailHandler(input: Record<string, unknown>) {
|
|
16
79
|
const { use_providers: rawUseProviders, ...restInput } = input
|
|
17
80
|
const resolved = resolvePreferredProviders('verify_email', restInput, rawUseProviders)
|
|
@@ -22,5 +85,6 @@ export async function verifyEmailHandler(input: Record<string, unknown>) {
|
|
|
22
85
|
if (isExecutionError(result)) {
|
|
23
86
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result) }], isError: true }
|
|
24
87
|
}
|
|
88
|
+
result.data = addNormalizedStatus(result.data, result._meta.provider)
|
|
25
89
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result) }] }
|
|
26
90
|
}
|