scrapercity 1.0.6 → 1.0.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/AGENT_DOCS.txt +25 -0
- package/README.md +24 -0
- package/SKILL.md +24 -0
- package/bin/cli.mjs +10 -63
- package/bin/mcp.mjs +64 -426
- package/lib/catalog.generated.mjs +623 -0
- package/lib/client.mjs +26 -29
- package/package.json +6 -2
package/bin/mcp.mjs
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// bin/mcp.mjs - ScraperCity MCP Server (stdio transport)
|
|
3
|
-
// Connect from Claude Code / Cursor / any MCP client
|
|
3
|
+
// Connect from Claude Code / Cursor / any MCP client.
|
|
4
|
+
//
|
|
5
|
+
// The SCRAPER tools are generated from scraperConfigs.ts (see lib/catalog.generated.mjs)
|
|
6
|
+
// and dispatched generically: the tool's args are POSTed straight to its /api/v1/scrape
|
|
7
|
+
// endpoint. Nothing per-scraper is hand-written here — add a scraper to the config and it
|
|
8
|
+
// shows up automatically on the next publish. Only the non-scraper utility/DB tools below
|
|
9
|
+
// are hand-defined.
|
|
4
10
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
|
|
5
11
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
6
12
|
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'
|
|
7
13
|
import * as sc from '../lib/client.mjs'
|
|
14
|
+
import { TOOLS as SCRAPER_TOOLS, ENDPOINT_BY_TOOL } from '../lib/catalog.generated.mjs'
|
|
8
15
|
|
|
9
|
-
const server = new Server(
|
|
10
|
-
{ name: 'scrapercity', version: '1.0.0' },
|
|
11
|
-
{ capabilities: { tools: {} } }
|
|
12
|
-
)
|
|
16
|
+
const server = new Server({ name: 'scrapercity', version: '1.0.0' }, { capabilities: { tools: {} } })
|
|
13
17
|
|
|
14
|
-
// ──
|
|
15
|
-
const
|
|
18
|
+
// ── Non-scraper tools (utility + database), hand-defined ──────
|
|
19
|
+
const UTILITY_TOOLS = [
|
|
16
20
|
{
|
|
17
21
|
name: 'check_wallet',
|
|
18
22
|
description: 'Check account balance, plan, and billing info. Call this FIRST to verify credits before running any scrape.',
|
|
@@ -21,454 +25,88 @@ const TOOLS = [
|
|
|
21
25
|
{
|
|
22
26
|
name: 'list_runs',
|
|
23
27
|
description: 'List recent scraper runs with status, lead counts, and costs.',
|
|
24
|
-
inputSchema: {
|
|
25
|
-
type: '
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
limit: { type: 'number', description: 'Max runs to return (default 20, max 100)', default: 20 }
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
name: 'scrape_apollo',
|
|
34
|
-
description: 'Scrape leads from Apollo.io using a search URL. IMPORTANT: Apollo delivery takes 11-48+ hours. Use webhooks (configure at app.scrapercity.com/dashboard/webhooks) instead of polling. Returns a runId to check status later.',
|
|
35
|
-
inputSchema: {
|
|
36
|
-
type: 'object',
|
|
37
|
-
properties: {
|
|
38
|
-
url: { type: 'string', description: 'Full Apollo.io search URL (from the People search page)' },
|
|
39
|
-
count: { type: 'number', description: 'Number of leads to pull (min 500, max 50000)', default: 1000 },
|
|
40
|
-
fileName: { type: 'string', description: 'Name for this export', default: '' }
|
|
41
|
-
},
|
|
42
|
-
required: ['url']
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
name: 'scrape_apollo_filters',
|
|
47
|
-
description: 'Scrape leads from Apollo.io using filter parameters instead of a URL. Same 4-day delivery - use webhooks. At least one filter required.',
|
|
48
|
-
inputSchema: {
|
|
49
|
-
type: 'object',
|
|
50
|
-
properties: {
|
|
51
|
-
seniorityLevel: { type: 'string', description: 'e.g. "director", "vp", "c_suite", "manager", "senior"' },
|
|
52
|
-
functionDept: { type: 'string', description: 'e.g. "sales", "marketing", "engineering", "finance"' },
|
|
53
|
-
companyIndustry: { type: 'string', description: 'e.g. "computer software", "financial services"' },
|
|
54
|
-
personCountry: { type: 'string', description: 'e.g. "United States", "United Kingdom"' },
|
|
55
|
-
personState: { type: 'string', description: 'e.g. "California", "New York"' },
|
|
56
|
-
companyCountry: { type: 'string', description: 'Company HQ country' },
|
|
57
|
-
companyState: { type: 'string', description: 'Company HQ state' },
|
|
58
|
-
companySize: { type: 'string', description: 'e.g. "11-50", "51-200", "201-500", "501-1000", "1001-5000"' },
|
|
59
|
-
personTitles: { type: 'array', items: { type: 'string' }, description: 'Specific job titles to target' },
|
|
60
|
-
companyDomains: { type: 'array', items: { type: 'string' }, description: 'Specific company domains' },
|
|
61
|
-
companyKeywords: { type: 'array', items: { type: 'string' }, description: 'Company description keywords' },
|
|
62
|
-
personCities: { type: 'array', items: { type: 'string' }, description: 'Person city filter' },
|
|
63
|
-
companyCities: { type: 'array', items: { type: 'string' }, description: 'Company city filter' },
|
|
64
|
-
hasPhone: { type: 'boolean', description: 'Only return contacts with phone numbers', default: false },
|
|
65
|
-
count: { type: 'number', description: 'Number of leads (min 500, max 50000)', default: 1000 },
|
|
66
|
-
fileName: { type: 'string', description: 'Export name', default: 'Apollo Export' }
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
name: 'scrape_maps',
|
|
72
|
-
description: 'Scrape businesses from Google Maps. Returns businesses with names, addresses, phone numbers, websites, emails, ratings. Typically completes in 5-30 minutes.',
|
|
73
|
-
inputSchema: {
|
|
74
|
-
type: 'object',
|
|
75
|
-
properties: {
|
|
76
|
-
query: { type: 'string', description: 'Search keyword, e.g. "plumbers", "dentists", "restaurants"' },
|
|
77
|
-
location: { type: 'string', description: 'City/area, e.g. "Denver, CO", "London, UK"' },
|
|
78
|
-
limit: { type: 'number', description: 'Max places to scrape (default 500)', default: 500 }
|
|
79
|
-
},
|
|
80
|
-
required: ['query', 'location']
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
name: 'validate_emails',
|
|
85
|
-
description: 'Validate a list of email addresses. Returns deliverability status, catch-all detection, MX records, and company info. Typically completes in 1-10 minutes.',
|
|
86
|
-
inputSchema: {
|
|
87
|
-
type: 'object',
|
|
88
|
-
properties: {
|
|
89
|
-
emails: { type: 'array', items: { type: 'string' }, description: 'Email addresses to validate' }
|
|
90
|
-
},
|
|
91
|
-
required: ['emails']
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
name: 'find_emails',
|
|
96
|
-
description: 'Find business email addresses given a person name and company. Provide first/last name + domain or company name.',
|
|
97
|
-
inputSchema: {
|
|
98
|
-
type: 'object',
|
|
99
|
-
properties: {
|
|
100
|
-
contacts: {
|
|
101
|
-
type: 'array',
|
|
102
|
-
items: {
|
|
103
|
-
type: 'object',
|
|
104
|
-
properties: {
|
|
105
|
-
first_name: { type: 'string' },
|
|
106
|
-
last_name: { type: 'string' },
|
|
107
|
-
full_name: { type: 'string', description: 'Alternative to first/last' },
|
|
108
|
-
domain: { type: 'string', description: 'Company domain, e.g. "acme.com"' },
|
|
109
|
-
company_name: { type: 'string', description: 'Alternative to domain' }
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
description: 'Contacts to find emails for. Each needs a name AND a company/domain.'
|
|
113
|
-
},
|
|
114
|
-
autoValidateEmails: { type: 'boolean', description: 'Auto-validate found emails', default: false },
|
|
115
|
-
autoFindMobiles: { type: 'boolean', description: 'Auto-find mobile numbers', default: false }
|
|
116
|
-
},
|
|
117
|
-
required: ['contacts']
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
name: 'find_mobiles',
|
|
122
|
-
description: 'Find mobile phone numbers from LinkedIn URLs or work emails.',
|
|
123
|
-
inputSchema: {
|
|
124
|
-
type: 'object',
|
|
125
|
-
properties: {
|
|
126
|
-
inputs: { type: 'array', items: { type: 'string' }, description: 'LinkedIn profile URLs or work email addresses' }
|
|
127
|
-
},
|
|
128
|
-
required: ['inputs']
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
name: 'find_people',
|
|
133
|
-
description: 'Skip trace / people finder. Look up personal info by name, email, phone, or address.',
|
|
134
|
-
inputSchema: {
|
|
135
|
-
type: 'object',
|
|
136
|
-
properties: {
|
|
137
|
-
name: { type: 'array', items: { type: 'string' }, description: 'Full names to search' },
|
|
138
|
-
email: { type: 'array', items: { type: 'string' }, description: 'Email addresses to search' },
|
|
139
|
-
phone_number: { type: 'array', items: { type: 'string' }, description: 'Phone numbers to search' },
|
|
140
|
-
street_citystatezip: { type: 'array', items: { type: 'string' }, description: 'Addresses (format: "123 Main St, City, ST 12345")' },
|
|
141
|
-
max_results: { type: 'number', description: 'Results per search (default 1)', default: 1 }
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
name: 'scrape_store_leads',
|
|
147
|
-
description: 'Get ecommerce store data (Shopify, WooCommerce, etc). Returns domains, emails, phones, social profiles, revenue estimates. Instant results from cached database.',
|
|
148
|
-
inputSchema: {
|
|
149
|
-
type: 'object',
|
|
150
|
-
properties: {
|
|
151
|
-
platform: { type: 'string', description: 'e.g. "shopify", "woocommerce", "bigcommerce"', default: 'shopify' },
|
|
152
|
-
countryCode: { type: 'string', description: 'e.g. "US", "GB", "CA"' },
|
|
153
|
-
category: { type: 'string', description: 'Store category filter' },
|
|
154
|
-
city: { type: 'string', description: 'City filter' },
|
|
155
|
-
technologies: { type: 'string', description: 'Technology filter' },
|
|
156
|
-
apps: { type: 'string', description: 'Installed app filter' },
|
|
157
|
-
emails: { type: 'boolean', description: 'Only stores with emails', default: false },
|
|
158
|
-
phones: { type: 'boolean', description: 'Only stores with phones', default: false },
|
|
159
|
-
instagram: { type: 'boolean', description: 'Only stores with Instagram' },
|
|
160
|
-
facebook: { type: 'boolean', description: 'Only stores with Facebook' },
|
|
161
|
-
totalLeads: { type: 'number', description: 'Number of leads to pull', default: 1000 }
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
name: 'scrape_builtwith',
|
|
167
|
-
description: 'Find all websites using a specific technology. Returns domains with contact info. $4.99 per search.',
|
|
168
|
-
inputSchema: {
|
|
169
|
-
type: 'object',
|
|
170
|
-
properties: {
|
|
171
|
-
technology: { type: 'string', description: 'Technology name, e.g. "Shopify", "Stripe", "HubSpot"' },
|
|
172
|
-
fileName: { type: 'string', description: 'Export name' }
|
|
173
|
-
},
|
|
174
|
-
required: ['technology']
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
name: 'search_criminal_records',
|
|
179
|
-
description: 'Search criminal records by name. $1 per search, only charged if records found.',
|
|
180
|
-
inputSchema: {
|
|
181
|
-
type: 'object',
|
|
182
|
-
properties: {
|
|
183
|
-
name: { type: 'string', description: 'Full name to search' },
|
|
184
|
-
state: { type: 'string', description: 'US state code, e.g. "CA", "TX" (optional, searches all if omitted)' },
|
|
185
|
-
dob: { type: 'string', description: 'Date of birth "MM/DD/YYYY" (optional, improves accuracy)' }
|
|
186
|
-
},
|
|
187
|
-
required: ['name']
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
name: 'scrape_airbnb',
|
|
192
|
-
description: 'Scrape Airbnb host emails by city or listing URL. Returns host contact info including email addresses.',
|
|
193
|
-
inputSchema: {
|
|
194
|
-
type: 'object',
|
|
195
|
-
properties: {
|
|
196
|
-
mode: { type: 'string', enum: ['city', 'single', 'bulk'], description: 'Search mode', default: 'city' },
|
|
197
|
-
city: { type: 'array', items: { type: 'string' }, description: 'Cities to search (for city mode), e.g. ["Miami, FL"]' },
|
|
198
|
-
listingUrl: { type: 'string', description: 'Single Airbnb listing URL (for single mode)' },
|
|
199
|
-
bulkListings: { type: 'array', items: { type: 'string' }, description: 'Multiple listing URLs (for bulk mode)' },
|
|
200
|
-
maxResults: { type: 'number', description: 'Max results for billing cap', default: 100 },
|
|
201
|
-
checkin: { type: 'string', description: 'Check-in date YYYY-MM-DD' },
|
|
202
|
-
checkout: { type: 'string', description: 'Check-out date YYYY-MM-DD' },
|
|
203
|
-
maxPages: { type: 'number', description: 'Max pages to crawl per city', default: 1 },
|
|
204
|
-
onlyUniqueEmails: { type: 'boolean', description: 'Deduplicate by email', default: false },
|
|
205
|
-
onlyProHosts: { type: 'boolean', description: 'Only professional hosts', default: false }
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
name: 'scrape_youtube_email',
|
|
211
|
-
description: 'Find business emails for YouTube channels. Pass channel handles (@ChannelName) or URLs.',
|
|
212
|
-
inputSchema: {
|
|
213
|
-
type: 'object',
|
|
214
|
-
properties: {
|
|
215
|
-
channels: { type: 'array', items: { type: 'string' }, description: 'YouTube channel handles or URLs, e.g. ["@MrBeast", "https://youtube.com/@Channel"]' }
|
|
216
|
-
},
|
|
217
|
-
required: ['channels']
|
|
218
|
-
}
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
name: 'scrape_website_finder',
|
|
222
|
-
description: 'Find contact info (emails, phones, social links) from a list of website domains. Optionally filter by job title.',
|
|
223
|
-
inputSchema: {
|
|
224
|
-
type: 'object',
|
|
225
|
-
properties: {
|
|
226
|
-
domains: { type: 'array', items: { type: 'string' }, description: 'Website domains, e.g. ["acme.com", "example.com"]' },
|
|
227
|
-
jobTitle: { type: 'string', description: 'Filter contacts by job title, e.g. "CEO"' }
|
|
228
|
-
},
|
|
229
|
-
required: ['domains']
|
|
230
|
-
}
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
name: 'scrape_yelp',
|
|
234
|
-
description: 'Scrape business listings from Yelp. Search by keyword + location, or provide direct Yelp URLs.',
|
|
235
|
-
inputSchema: {
|
|
236
|
-
type: 'object',
|
|
237
|
-
properties: {
|
|
238
|
-
searchTerms: { type: 'array', items: { type: 'string' }, description: 'Search keywords, e.g. ["plumbers", "dentists"]' },
|
|
239
|
-
locations: { type: 'array', items: { type: 'string' }, description: 'Locations, e.g. ["Denver, CO"]' },
|
|
240
|
-
directUrls: { type: 'array', items: { type: 'string' }, description: 'Direct Yelp search/business URLs' },
|
|
241
|
-
searchLimit: { type: 'number', description: 'Max results per search', default: 10 }
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
name: 'scrape_angi',
|
|
247
|
-
description: 'Scrape service provider listings from Angi (Angie\'s List). Search by keyword and zip codes.',
|
|
248
|
-
inputSchema: {
|
|
249
|
-
type: 'object',
|
|
250
|
-
properties: {
|
|
251
|
-
keyword: { type: 'string', description: 'Service type, e.g. "plumbers", "electricians"' },
|
|
252
|
-
zipCodes: { type: 'array', items: { type: 'string' }, description: 'ZIP codes to search, e.g. ["80202", "80203"]' },
|
|
253
|
-
maxItems: { type: 'number', description: 'Max results', default: 100 }
|
|
254
|
-
},
|
|
255
|
-
required: ['keyword']
|
|
256
|
-
}
|
|
257
|
-
},
|
|
258
|
-
{
|
|
259
|
-
name: 'scrape_zillow_agents',
|
|
260
|
-
description: 'Scrape real estate agent listings from Zillow by location.',
|
|
261
|
-
inputSchema: {
|
|
262
|
-
type: 'object',
|
|
263
|
-
properties: {
|
|
264
|
-
location: { type: 'string', description: 'City or area, e.g. "Denver, CO"' },
|
|
265
|
-
specialty: { type: 'string', description: 'Agent specialty, e.g. "buyer", "seller"' },
|
|
266
|
-
language: { type: 'string', description: 'Language filter' },
|
|
267
|
-
searchLimit: { type: 'number', description: 'Max results', default: 10 }
|
|
268
|
-
},
|
|
269
|
-
required: ['location']
|
|
270
|
-
}
|
|
271
|
-
},
|
|
272
|
-
{
|
|
273
|
-
name: 'scrape_bizbuysell',
|
|
274
|
-
description: 'Scrape business-for-sale listings from BizBuySell. Provide search result URLs.',
|
|
275
|
-
inputSchema: {
|
|
276
|
-
type: 'object',
|
|
277
|
-
properties: {
|
|
278
|
-
startUrls: { type: 'array', items: { type: 'string' }, description: 'BizBuySell search URLs' },
|
|
279
|
-
maxItems: { type: 'number', description: 'Max listings to scrape', default: 100 }
|
|
280
|
-
},
|
|
281
|
-
required: ['startUrls']
|
|
282
|
-
}
|
|
283
|
-
},
|
|
284
|
-
{
|
|
285
|
-
name: 'scrape_crexi',
|
|
286
|
-
description: 'Scrape commercial real estate listings from Crexi. Provide search result URLs.',
|
|
287
|
-
inputSchema: {
|
|
288
|
-
type: 'object',
|
|
289
|
-
properties: {
|
|
290
|
-
startUrls: { type: 'array', items: { type: 'string' }, description: 'Crexi search URLs' }
|
|
291
|
-
},
|
|
292
|
-
required: ['startUrls']
|
|
293
|
-
}
|
|
294
|
-
},
|
|
295
|
-
{
|
|
296
|
-
name: 'scrape_property_lookup',
|
|
297
|
-
description: 'Look up property data by address. Optionally include owner contact information. $0.15 per address.',
|
|
298
|
-
inputSchema: {
|
|
299
|
-
type: 'object',
|
|
300
|
-
properties: {
|
|
301
|
-
addresses: { type: 'array', items: { type: 'string' }, description: 'Full addresses, e.g. ["123 Main St, Denver, CO 80202"]' },
|
|
302
|
-
includeOwnerContact: { type: 'boolean', description: 'Include property owner contact info', default: false }
|
|
303
|
-
},
|
|
304
|
-
required: ['addresses']
|
|
305
|
-
}
|
|
28
|
+
inputSchema: { type: 'object', properties: {
|
|
29
|
+
hours: { type: 'number', description: 'How many hours back to look (default 24, max 168)', default: 24 },
|
|
30
|
+
limit: { type: 'number', description: 'Max runs to return (default 20, max 100)', default: 20 }
|
|
31
|
+
} }
|
|
306
32
|
},
|
|
307
33
|
{
|
|
308
34
|
name: 'check_run_status',
|
|
309
35
|
description: 'Check the status of a scraper run. Returns status (RUNNING/SUCCEEDED/FAILED/CANCELLED), lead count, and download URL when complete.',
|
|
310
|
-
inputSchema: {
|
|
311
|
-
type: 'object',
|
|
312
|
-
properties: {
|
|
313
|
-
runId: { type: 'string', description: 'The run ID returned when starting a scrape' }
|
|
314
|
-
},
|
|
315
|
-
required: ['runId']
|
|
316
|
-
}
|
|
36
|
+
inputSchema: { type: 'object', properties: { runId: { type: 'string', description: 'The run ID returned when starting a scrape' } }, required: ['runId'] }
|
|
317
37
|
},
|
|
318
38
|
{
|
|
319
39
|
name: 'download_results',
|
|
320
40
|
description: 'Download the CSV results of a completed scraper run. Only works when status is SUCCEEDED.',
|
|
321
|
-
inputSchema: {
|
|
322
|
-
type: '
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
outputPath: { type: 'string', description: 'File path to save CSV (default: {runId}.csv)' }
|
|
326
|
-
},
|
|
327
|
-
required: ['runId']
|
|
328
|
-
}
|
|
41
|
+
inputSchema: { type: 'object', properties: {
|
|
42
|
+
runId: { type: 'string', description: 'The run ID to download results for' },
|
|
43
|
+
outputPath: { type: 'string', description: 'File path to save CSV (default: {runId}.csv)' }
|
|
44
|
+
}, required: ['runId'] }
|
|
329
45
|
},
|
|
330
46
|
{
|
|
331
47
|
name: 'cancel_run',
|
|
332
48
|
description: 'Cancel a running scraper job.',
|
|
333
|
-
inputSchema: {
|
|
334
|
-
type: 'object',
|
|
335
|
-
properties: {
|
|
336
|
-
runId: { type: 'string', description: 'The run ID to cancel' }
|
|
337
|
-
},
|
|
338
|
-
required: ['runId']
|
|
339
|
-
}
|
|
49
|
+
inputSchema: { type: 'object', properties: { runId: { type: 'string', description: 'The run ID to cancel' } }, required: ['runId'] }
|
|
340
50
|
},
|
|
341
51
|
{
|
|
342
52
|
name: 'query_lead_database',
|
|
343
|
-
description: 'Query the B2B lead database directly
|
|
344
|
-
inputSchema: {
|
|
345
|
-
type: '
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
limit: { type: 'number', description: 'Results per page (max 100)', default: 50 }
|
|
360
|
-
}
|
|
361
|
-
}
|
|
53
|
+
description: 'Query the B2B lead database directly. Returns contacts with names, emails, phones, titles, companies. 100 per request, paginate with page param. Included with the $149/mo plan.',
|
|
54
|
+
inputSchema: { type: 'object', properties: {
|
|
55
|
+
title: { type: 'string', description: 'Job title filter' },
|
|
56
|
+
industry: { type: 'string', description: 'Company industry' },
|
|
57
|
+
country: { type: 'string', description: 'Person country' },
|
|
58
|
+
state: { type: 'string', description: 'Person state' },
|
|
59
|
+
city: { type: 'string', description: 'Person city' },
|
|
60
|
+
companyName: { type: 'string', description: 'Company name' },
|
|
61
|
+
companyDomain: { type: 'string', description: 'Company domain' },
|
|
62
|
+
seniority: { type: 'string', description: 'e.g. "vp", "director", "c_suite"' },
|
|
63
|
+
department: { type: 'string', description: 'e.g. "sales", "engineering"' },
|
|
64
|
+
hasEmail: { type: 'boolean', description: 'Only contacts with email' },
|
|
65
|
+
hasPhone: { type: 'boolean', description: 'Only contacts with phone' },
|
|
66
|
+
page: { type: 'number', description: 'Page number (default 1)', default: 1 },
|
|
67
|
+
limit: { type: 'number', description: 'Results per page (max 100)', default: 50 }
|
|
68
|
+
} }
|
|
362
69
|
}
|
|
363
70
|
]
|
|
364
71
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
})
|
|
72
|
+
const TOOLS = [...UTILITY_TOOLS, ...SCRAPER_TOOLS]
|
|
73
|
+
|
|
74
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }))
|
|
369
75
|
|
|
370
|
-
// ── Tool execution handler ────────────────────────────────────
|
|
371
76
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
372
|
-
const { name, arguments: args } = request.params
|
|
77
|
+
const { name, arguments: args = {} } = request.params
|
|
373
78
|
try {
|
|
374
79
|
let result
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
result = await sc.
|
|
381
|
-
break
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
validate: args.autoValidateEmails || false,
|
|
399
|
-
mobiles: args.autoFindMobiles || false
|
|
400
|
-
})
|
|
401
|
-
break
|
|
402
|
-
case 'find_mobiles':
|
|
403
|
-
result = await sc.mobileFinder(args.inputs)
|
|
404
|
-
break
|
|
405
|
-
case 'find_people':
|
|
406
|
-
result = await sc.peopleFinder(args)
|
|
407
|
-
break
|
|
408
|
-
case 'scrape_store_leads':
|
|
409
|
-
result = await sc.storeLeads(args)
|
|
410
|
-
break
|
|
411
|
-
case 'scrape_builtwith':
|
|
412
|
-
result = await sc.builtwith(args.technology, args.fileName)
|
|
413
|
-
break
|
|
414
|
-
case 'search_criminal_records':
|
|
415
|
-
result = await sc.criminal(args.name, args.state, args.dob)
|
|
416
|
-
break
|
|
417
|
-
case 'scrape_airbnb':
|
|
418
|
-
result = await sc.airbnb(args)
|
|
419
|
-
break
|
|
420
|
-
case 'scrape_youtube_email':
|
|
421
|
-
result = await sc.youtubeEmail(args.channels)
|
|
422
|
-
break
|
|
423
|
-
case 'scrape_website_finder':
|
|
424
|
-
result = await sc.websiteFinder(args.domains, args.jobTitle)
|
|
425
|
-
break
|
|
426
|
-
case 'scrape_yelp':
|
|
427
|
-
result = await sc.yelp(args)
|
|
428
|
-
break
|
|
429
|
-
case 'scrape_angi':
|
|
430
|
-
result = await sc.angi(args.keyword, args.zipCodes || [], args.maxItems || 100)
|
|
431
|
-
break
|
|
432
|
-
case 'scrape_zillow_agents':
|
|
433
|
-
result = await sc.zillowAgents(args.location, { specialty: args.specialty, language: args.language, searchLimit: args.searchLimit || 10 })
|
|
434
|
-
break
|
|
435
|
-
case 'scrape_bizbuysell':
|
|
436
|
-
result = await sc.bizbuysell(args.startUrls, args.maxItems || 100)
|
|
437
|
-
break
|
|
438
|
-
case 'scrape_crexi':
|
|
439
|
-
result = await sc.crexi(args.startUrls)
|
|
440
|
-
break
|
|
441
|
-
case 'scrape_property_lookup':
|
|
442
|
-
result = await sc.propertyLookup(args.addresses, args.includeOwnerContact || false)
|
|
443
|
-
break
|
|
444
|
-
case 'check_run_status':
|
|
445
|
-
result = await sc.status(args.runId)
|
|
446
|
-
break
|
|
447
|
-
case 'download_results': {
|
|
448
|
-
const dl = await sc.download(args.runId, args.outputPath)
|
|
449
|
-
result = { success: true, path: dl.path, sizeKB: Math.round(dl.bytes / 1024) }
|
|
450
|
-
break
|
|
80
|
+
// Generated scraper tools: POST args straight to the canonical endpoint.
|
|
81
|
+
if (ENDPOINT_BY_TOOL[name]) {
|
|
82
|
+
result = await sc.postEndpoint(ENDPOINT_BY_TOOL[name], args)
|
|
83
|
+
} else {
|
|
84
|
+
switch (name) {
|
|
85
|
+
case 'check_wallet': result = await sc.wallet(); break
|
|
86
|
+
case 'list_runs': result = await sc.runs(args.hours || 24, args.limit || 20); break
|
|
87
|
+
case 'check_run_status': result = await sc.status(args.runId); break
|
|
88
|
+
case 'download_results': {
|
|
89
|
+
const dl = await sc.download(args.runId, args.outputPath)
|
|
90
|
+
result = { success: true, path: dl.path, sizeKB: Math.round(dl.bytes / 1024) }
|
|
91
|
+
break
|
|
92
|
+
}
|
|
93
|
+
case 'cancel_run': result = await sc.cancel(args.runId); break
|
|
94
|
+
case 'query_lead_database': {
|
|
95
|
+
const params = { ...args }
|
|
96
|
+
if (params.hasEmail) params.hasEmail = 'true'
|
|
97
|
+
if (params.hasPhone) params.hasPhone = 'true'
|
|
98
|
+
result = await sc.dbLeads(params)
|
|
99
|
+
break
|
|
100
|
+
}
|
|
101
|
+
default:
|
|
102
|
+
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true }
|
|
451
103
|
}
|
|
452
|
-
case 'cancel_run':
|
|
453
|
-
result = await sc.cancel(args.runId)
|
|
454
|
-
break
|
|
455
|
-
case 'query_lead_database': {
|
|
456
|
-
const params = { ...args }
|
|
457
|
-
if (params.hasEmail) { params.hasEmail = 'true'; }
|
|
458
|
-
if (params.hasPhone) { params.hasPhone = 'true'; }
|
|
459
|
-
result = await sc.dbLeads(params)
|
|
460
|
-
break
|
|
461
|
-
}
|
|
462
|
-
default:
|
|
463
|
-
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true }
|
|
464
104
|
}
|
|
465
|
-
|
|
466
105
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }
|
|
467
106
|
} catch (e) {
|
|
468
107
|
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true }
|
|
469
108
|
}
|
|
470
109
|
})
|
|
471
110
|
|
|
472
|
-
// ── Start ─────────────────────────────────────────────────────
|
|
473
111
|
const transport = new StdioServerTransport()
|
|
474
112
|
await server.connect(transport)
|