scrapercity 1.0.23 → 1.0.25

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 CHANGED
@@ -20,13 +20,21 @@ PRICING (auto-generated from the live catalog - do not edit)
20
20
  | `scrape_yelp` | /api/v1/scrape/yelp-scraper | $0.0099 per result |
21
21
  | `scrape_zillow_agents` | /api/v1/scrape/zillow-agents | $0.029 per agent |
22
22
  | `scrape_property_lookup` | /api/v1/scrape/property-lookup | $0.15 per property (+$0.10 for owner contact) |
23
- | `search_criminal_records` | /api/v1/scrape/criminal-records | .00 per search (only charged if records found) |
23
+ | `search_criminal_records` | /api/v1/scrape/criminal-records | $1.00 per search (only charged if records found) |
24
24
  | `scrape_store_leads` | /api/v1/scrape/store-leads | $0.0039 per lead |
25
25
  | `scrape_builtwith` | /api/v1/scrape/techstack | $4.99 per technology |
26
26
  | `find_emails` | /api/v1/scrape/email-finder | $0.05 per email found (FREE if not found) |
27
27
  | `find_mobiles` | /api/v1/scrape/mobile-finder | $0.25 per number found (FREE if not found) |
28
28
  <!-- GENERATED:pricing:end -->
29
29
 
30
+ SDKS: the same tools as the MCP server, regenerated from these docs on every release.
31
+ JavaScript: npm install scrapercity
32
+ import { configure, callTool, TOOLS, openaiTools, anthropicTools } from 'scrapercity'
33
+ Python: pip install scrapercity (extras: [langchain], [openai])
34
+ from scrapercity import ScraperCity, langchain_tools
35
+ ScraperCity().call_tool("query_lead_database", {"title": "CEO", "limit": 10})
36
+ Guide: https://scrapercity.com/sdk
37
+
30
38
  BASE: https://app.scrapercity.com
31
39
  AUTH: Authorization: Bearer <api_key>
32
40
  ALL RESPONSES: JSON
@@ -214,8 +222,10 @@ GET /api/v1/database/leads?title=CTO&country=United States&hasEmail=true&page=1&
214
222
  100k/day limit; fetching a lead you already have again is free.
215
223
  Returns: { data: [...leads], pagination: { page, limit, total, totalPages, has_more, next_after }, rate_limit,
216
224
  search_translation? }
217
- Lead fields include company_industry, company_size, employees_count, company_website,
218
- company_annual_revenue, company_city, company_state, company_country, keywords.
225
+ Lead fields include twitter_url, facebook_url, company_industry, company_size,
226
+ employees_count, company_website, company_phone, company_annual_revenue,
227
+ company_total_funding, company_founded_year, company_city, company_state,
228
+ company_country, keywords.
219
229
  409 with filters_not_applied_yet: the company filters listed can't be used yet (company
220
230
  data still being prepared right after launch). Nothing is returned or counted; retry later
221
231
  or drop those filters.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ScraperCity - B2B Lead Generation for AI Agents
2
2
 
3
- Pull leads, validate emails, find mobile numbers, and scrape business data - all from your AI agent, CLI, or code.
3
+ Pull leads, validate emails, find mobile numbers, and scrape business data - all from your AI agent, CLI, or code. One package: the MCP server, the CLI and the JavaScript SDK. The Python SDK is `pip install scrapercity`.
4
4
 
5
5
  ScraperCity gives AI agents access to 15+ B2B data tools: Apollo scraping, Google Maps extraction, email finding/validation, mobile number lookup, skip tracing, ecommerce store data, criminal records, and more.
6
6
 
@@ -66,6 +66,41 @@ curl -O https://app.scrapercity.com/api/downloads/RUN_ID \
66
66
  -H "Authorization: Bearer $SCRAPERCITY_API_KEY"
67
67
  ```
68
68
 
69
+ ### Option 5: JavaScript SDK
70
+
71
+ ```bash
72
+ npm install scrapercity
73
+ ```
74
+
75
+ ```js
76
+ import { configure, wallet, dbLeads, callTool, TOOLS, openaiTools, anthropicTools } from 'scrapercity'
77
+
78
+ configure({ apiKey: process.env.SCRAPERCITY_API_KEY })
79
+ await wallet()
80
+ await dbLeads({ title: 'CEO', country: ['United States'], hasEmail: 'true', limit: 25 })
81
+
82
+ // Every tool the MCP server offers, by name, with the same arguments
83
+ await callTool('plan_search', { query: 'dentists in Austin, Texas' })
84
+
85
+ // Hand the whole tool list to OpenAI or Anthropic tool calling
86
+ const tools = openaiTools() // or anthropicTools()
87
+ ```
88
+
89
+ ### Option 6: Python SDK
90
+
91
+ ```bash
92
+ pip install scrapercity # add [langchain] or [openai] for agent tools
93
+ ```
94
+
95
+ ```python
96
+ from scrapercity import ScraperCity, langchain_tools
97
+ sc = ScraperCity() # reads SCRAPERCITY_API_KEY
98
+ sc.call_tool("query_lead_database", {"title": "CEO", "limit": 10})
99
+ tools = langchain_tools() # LangChain / LangGraph
100
+ ```
101
+
102
+ Both SDKs carry the same tool list as the MCP server, regenerated from the live API docs on every release. Guide: [scrapercity.com/sdk](https://scrapercity.com/sdk). Python package: [pypi.org/project/scrapercity](https://pypi.org/project/scrapercity/).
103
+
69
104
  ## Pricing (auto-generated from the live catalog — do not edit)
70
105
 
71
106
  <!-- GENERATED:pricing:start -->
@@ -84,7 +119,7 @@ curl -O https://app.scrapercity.com/api/downloads/RUN_ID \
84
119
  | `scrape_yelp` | /api/v1/scrape/yelp-scraper | $0.0099 per result |
85
120
  | `scrape_zillow_agents` | /api/v1/scrape/zillow-agents | $0.029 per agent |
86
121
  | `scrape_property_lookup` | /api/v1/scrape/property-lookup | $0.15 per property (+$0.10 for owner contact) |
87
- | `search_criminal_records` | /api/v1/scrape/criminal-records | .00 per search (only charged if records found) |
122
+ | `search_criminal_records` | /api/v1/scrape/criminal-records | $1.00 per search (only charged if records found) |
88
123
  | `scrape_store_leads` | /api/v1/scrape/store-leads | $0.0039 per lead |
89
124
  | `scrape_builtwith` | /api/v1/scrape/techstack | $4.99 per technology |
90
125
  | `find_emails` | /api/v1/scrape/email-finder | $0.05 per email found (FREE if not found) |
@@ -146,6 +181,8 @@ Buy additional credits anytime. [scrapercity.com/pricing](https://scrapercity.co
146
181
 
147
182
  - [Agent Docs](https://scrapercity.com/agents) - setup guide
148
183
  - [API Reference](https://scrapercity.com/api-docs) - full documentation
184
+ - [SDK guide](https://scrapercity.com/sdk) - JavaScript and Python SDKs
185
+ - [Python SDK on PyPI](https://pypi.org/project/scrapercity/) - `pip install scrapercity`
149
186
  - [Skill File](https://scrapercity.com/agents/SKILL.md) - for Claude Code
150
187
  - [MCP Server Listing](https://scrapercity.com/agents) - configuration
151
188
 
package/SKILL.md CHANGED
@@ -5,6 +5,7 @@
5
5
  export SCRAPERCITY_API_KEY="your_key" # get from app.scrapercity.com/dashboard/api-docs
6
6
  npx scrapercity wallet # verify balance
7
7
  ```
8
+ Writing code instead? Use an SDK with the same tools as the MCP server: `npm install scrapercity` (`import { callTool } from 'scrapercity'`) or `pip install scrapercity` (`ScraperCity().call_tool(...)`, plus LangChain and OpenAI Agents tools). Guide: https://scrapercity.com/sdk
8
9
 
9
10
  ## Core Pattern
10
11
  Every scraper follows: **start → poll → download**
@@ -33,7 +34,7 @@ Auth: `Authorization: Bearer $SCRAPERCITY_API_KEY` on all requests.
33
34
  | `scrape_yelp` | /api/v1/scrape/yelp-scraper | $0.0099 per result |
34
35
  | `scrape_zillow_agents` | /api/v1/scrape/zillow-agents | $0.029 per agent |
35
36
  | `scrape_property_lookup` | /api/v1/scrape/property-lookup | $0.15 per property (+$0.10 for owner contact) |
36
- | `search_criminal_records` | /api/v1/scrape/criminal-records | .00 per search (only charged if records found) |
37
+ | `search_criminal_records` | /api/v1/scrape/criminal-records | $1.00 per search (only charged if records found) |
37
38
  | `scrape_store_leads` | /api/v1/scrape/store-leads | $0.0039 per lead |
38
39
  | `scrape_builtwith` | /api/v1/scrape/techstack | $4.99 per technology |
39
40
  | `find_emails` | /api/v1/scrape/email-finder | $0.05 per email found (FREE if not found) |
package/bin/mcp.mjs CHANGED
@@ -2,249 +2,26 @@
2
2
  // bin/mcp.mjs - ScraperCity MCP Server (stdio transport)
3
3
  // Connect from Claude Code / Cursor / any MCP client.
4
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.
5
+ // The tool list and the dispatcher live in lib/tools.mjs, shared with the JavaScript SDK
6
+ // (lib/index.mjs); scripts/generate.mjs writes the same list into the Python SDK. Scraper tools
7
+ // are generated from scraperConfigs.ts and POSTed straight to their /api/v1/scrape endpoint;
8
+ // the hand-written tools (account, runs, databases, lookalikes) are in lib/utilityTools.mjs.
10
9
  import { Server } from '@modelcontextprotocol/sdk/server/index.js'
11
10
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
12
11
  import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'
13
- import * as sc from '../lib/client.mjs'
14
- import { TOOLS as SCRAPER_TOOLS, ENDPOINT_BY_TOOL } from '../lib/catalog.generated.mjs'
12
+ import { TOOLS, callTool } from '../lib/tools.mjs'
15
13
 
16
14
  const server = new Server({ name: 'scrapercity', version: '1.0.0' }, { capabilities: { tools: {} } })
17
15
 
18
- // ── Non-scraper tools (utility + database), hand-defined ──────
19
- const UTILITY_TOOLS = [
20
- {
21
- name: 'check_wallet',
22
- description: 'Check account balance, plan, and billing info. Call this FIRST to verify credits before running any scrape.',
23
- inputSchema: { type: 'object', properties: {} }
24
- },
25
- {
26
- name: 'list_runs',
27
- description: 'List recent scraper runs with status, lead counts, and costs.',
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
- } }
32
- },
33
- {
34
- name: 'check_run_status',
35
- description: 'Check the status of a scraper run. Returns status (RUNNING/SUCCEEDED/FAILED/CANCELLED), lead count, and download URL when complete.',
36
- inputSchema: { type: 'object', properties: { runId: { type: 'string', description: 'The run ID returned when starting a scrape' } }, required: ['runId'] }
37
- },
38
- {
39
- name: 'download_results',
40
- description: 'Download the CSV results of a completed scraper run. Only works when status is SUCCEEDED.',
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'] }
45
- },
46
- {
47
- name: 'cancel_run',
48
- description: 'Cancel a running scraper job.',
49
- inputSchema: { type: 'object', properties: { runId: { type: 'string', description: 'The run ID to cancel' } }, required: ['runId'] }
50
- },
51
- {
52
- name: 'plan_search',
53
- description: 'Turn a plain-English request into ready-to-run database searches. Describe who you want ("dentists in Austin", "websites that use Shopify", "CEOs of SaaS companies in Texas") or what you sell ("I sell life insurance"). Returns a few searches, each with its database, filters set to values that database really has, how many records match (leaving out what this account already has), a dashboard link, and apiParams to pass as they are to the matching query tool: query_lead_database for database/leads, query_local_business_database for database/local-businesses, query_ecommerce_database for database/ecommerce. Included with the $149/mo plan, like the database tools. Use it before a database query whenever you are not sure of the exact filter values.',
54
- inputSchema: { type: 'object', properties: {
55
- query: { type: 'string', description: 'What you are looking for or what you sell, in plain English' },
56
- database: { type: 'string', enum: ['leads', 'local', 'ecommerce'], description: 'Optional: plan in this database only (leads = people at companies, local = local businesses, ecommerce = online stores). Leave it out to let the planner pick the databases that fit.' }
57
- }, required: ['query'] }
58
- },
59
- {
60
- name: 'find_lookalike_groups',
61
- description: 'Group your customers into lookalike audiences. Send their websites or work emails (mike@acme.com reads acme.com and looks up Mike\'s title). Starts a background job and returns it with its id; then call get_lookalike_groups until status is done. Each group comes with ready-to-run database filters, a match count and the best tool to reach businesses like them. $0.01 per site read, from credits; your balance must cover $0.01 x every site. One job at a time. $149/mo plan and up.',
62
- inputSchema: { type: 'object', properties: {
63
- domains: { type: 'array', items: { type: 'string' }, description: 'Your customers\' websites, URLs or work emails. At least 3 company sites.' }
64
- }, required: ['domains'] }
65
- },
66
- {
67
- name: 'get_lookalike_groups',
68
- description: 'Read a lookalike job started with find_lookalike_groups: status (running, done, failed), percent, and when done the groups. Each group has name, description, sites (your customers in it), database (leads, local, ecommerce), filters, count and tool. Pass its filters to query_lead_database, query_local_business_database or query_ecommerce_database to pull the records.',
69
- inputSchema: { type: 'object', properties: {
70
- id: { type: 'string', description: 'The job id from find_lookalike_groups' }
71
- }, required: ['id'] }
72
- },
73
- {
74
- name: 'download_lookalike_csv',
75
- description: 'Save a finished lookalike job (find_lookalike_groups) as a CSV file, one row per uploaded site: whether it was read, what it sells and to whom, business model, industry, keywords, size, country, the contacts from your upload with their titles, and its group. No extra charge.',
76
- inputSchema: { type: 'object', properties: {
77
- id: { type: 'string', description: 'The job id from find_lookalike_groups' },
78
- outputPath: { type: 'string', description: 'File path to save the CSV (default: lookalike-{id}.csv)' }
79
- }, required: ['id'] }
80
- },
81
- {
82
- name: 'query_lead_database',
83
- description: 'Query the B2B lead database directly. Returns contacts with names, emails, phones, titles, companies (plus company website, revenue, HQ location and keywords). Up to 100 per request. Included with the $149/mo plan (100,000 new leads a day; leads you already have do not count again). For a daily pull of only new leads set excludeDelivered=true. For big pulls page with after: send after="0" first, then the pagination.next_after from each response until it is null. Have a saved people-search URL? Pass it as url to search with its filters right away (search_translation in the response says what was applied). Target by the technologies a company\'s website runs: technologies (any of), technologiesAll (all of), notTechnologies (none of), e.g. technologiesAll ["Klaviyo", "PayPal", "Shopify"] with notTechnologies ["Facebook Pixel"].',
84
- inputSchema: { type: 'object', properties: {
85
- url: { type: 'string', description: 'A people-search URL (the address of a people search with its filters). Its filters become the search; any other filter you pass overrides the matching one.' },
86
- title: { type: 'string', description: 'Job title filter, comma separated for several (partial match, e.g. "CEO, Founder")' },
87
- industry: { type: 'array', items: { type: 'string' }, description: 'Company industries (any match)' },
88
- country: { type: 'array', items: { type: 'string' }, description: 'Person countries, full names like "United States" (US, USA, UK, UAE also work)' },
89
- state: { type: 'array', items: { type: 'string' }, description: 'Person states' },
90
- city: { type: 'string', description: 'Person city, comma separated for several' },
91
- companyName: { type: 'string', description: 'Company name' },
92
- companyDomain: { type: 'string', description: 'Company domain(s), comma separated' },
93
- seniority: { type: 'array', items: { type: 'string' }, description: 'e.g. ["vp", "director", "c_suite"]' },
94
- department: { type: 'array', items: { type: 'string' }, description: 'e.g. ["sales", "engineering"]' },
95
- minEmployees: { type: 'number', description: 'Minimum company employee count' },
96
- maxEmployees: { type: 'number', description: 'Maximum company employee count' },
97
- keywords: { type: 'array', items: { type: 'string' }, description: 'Company keywords/tags, any match (e.g. ["saas", "fintech"])' },
98
- revenueMin: { type: 'number', description: 'Minimum company annual revenue, USD' },
99
- revenueMax: { type: 'number', description: 'Maximum company annual revenue, USD' },
100
- companyCountry: { type: 'array', items: { type: 'string' }, description: 'Company HQ countries (not where the person is)' },
101
- companyState: { type: 'array', items: { type: 'string' }, description: 'Company HQ states' },
102
- companyCity: { type: 'string', description: 'Company HQ city' },
103
- notTitle: { type: 'string', description: 'Exclude titles containing any of these (comma separated)' },
104
- notKeywords: { type: 'array', items: { type: 'string' }, description: 'Exclude companies with any of these keywords' },
105
- notIndustry: { type: 'array', items: { type: 'string' }, description: 'Exclude these industries' },
106
- technologies: { type: 'array', items: { type: 'string' }, description: 'Companies whose website runs ANY of these technologies, e.g. ["HubSpot"]' },
107
- technologiesAll: { type: 'array', items: { type: 'string' }, description: 'Companies whose website runs ALL of these technologies, e.g. ["Klaviyo", "PayPal"] (keep it to 1-3)' },
108
- notTechnologies: { type: 'array', items: { type: 'string' }, description: 'Leave out companies whose website runs any of these technologies, e.g. ["Facebook Pixel"]' },
109
- socialUrl: { type: 'string', description: 'Match a social profile URL' },
110
- hasEmail: { type: 'boolean', description: 'Only contacts with email' },
111
- hasPhone: { type: 'boolean', description: 'Only contacts with phone' },
112
- page: { type: 'number', description: 'Page number (default 1)', default: 1 },
113
- limit: { type: 'number', description: 'Results per page (max 100)', default: 50 },
114
- excludeDelivered: { type: 'boolean', description: 'Skip leads this account already has (from the API or unlocked in the dashboard). With this on, keep page at 1 or use after.' },
115
- after: { type: 'string', description: 'Cursor paging: return leads after this lead id. Use "0" to start, then the pagination.next_after from each response.' }
116
- } }
117
- },
118
- {
119
- name: 'query_local_business_database',
120
- description: 'Query the local business database directly. Returns businesses with phone numbers, emails, websites, addresses and ratings, many with a named contact person. Up to 100 per request. Included with the $149/mo plan (100,000 new businesses a day; businesses you already have do not count again). Set hasContact=true for only businesses with a named contact person. For a daily pull of only new businesses set excludeDelivered=true. For big pulls page with after: send after="0" first, then the pagination.next_after from each response until it is null. Search the way you would on Google Maps: keyword (what the businesses do or are called, e.g. ["thai restaurant"]) and location (cities, states, countries or postal codes, e.g. ["Houston TX", "Austin TX"]); searched_as in the response says how each was read. A search that finds nothing also returns lead_database: the same search in the Lead Database when it has 100+ people. Have a Google Maps search URL? Pass it as url to search with its keyword and place right away (search_translation in the response says what was applied).',
121
- inputSchema: { type: 'object', properties: {
122
- keyword: { type: 'array', items: { type: 'string' }, description: 'What the businesses do or are called, as typed on Google Maps, e.g. ["thai restaurant", "sushi"]. Matches business names and categories; any keyword. A place inside a keyword ("plumbers in Austin TX") is used as the location when no location is given.' },
123
- location: { type: 'array', items: { type: 'string' }, description: 'Cities, states, countries or postal codes, as typed, e.g. ["Houston TX", "78701", "Ontario", "Germany"] (up to 2,000; any of them).' },
124
- url: { type: 'string', description: 'A Google Maps search URL (the address of a search on Google Maps). Its keyword and place become the search; any other filter you pass overrides the matching one.' },
125
- category: { type: 'array', items: { type: 'string' }, description: 'Business categories, e.g. ["Dentist"]. A word also finds categories that contain it ("restaurant" finds "Mexican restaurant")' },
126
- country: { type: 'array', items: { type: 'string' }, description: 'Two-letter country codes, e.g. ["US"]' },
127
- state: { type: 'string', description: 'State or region, comma separated for several (US abbreviations work, e.g. "TX")' },
128
- city: { type: 'string', description: 'City (partial match)' },
129
- postalCode: { type: 'string', description: 'Postal or zip code' },
130
- box: { type: 'string', description: 'A map area as "minLat,minLng,maxLat,maxLng" (e.g. "30.1,-97.9,30.5,-97.5"). Only businesses inside it.' },
131
- query: { type: 'string', description: 'Search business names and descriptions' },
132
- title: { type: 'string', description: 'Business name, comma separated for several (partial match)' },
133
- notCategory: { type: 'array', items: { type: 'string' }, description: 'Exclude these business categories' },
134
- minRating: { type: 'number', description: 'Minimum Google rating (1-5)' },
135
- maxRating: { type: 'number', description: 'Maximum Google rating (1-5)' },
136
- minReviews: { type: 'number', description: 'Minimum number of reviews' },
137
- maxReviews: { type: 'number', description: 'Maximum number of reviews' },
138
- hasEmail: { type: 'boolean', description: 'Only businesses with an email address' },
139
- hasPhone: { type: 'boolean', description: 'Only businesses with a phone number' },
140
- hasWebsite: { type: 'boolean', description: 'Only businesses with a website' },
141
- hasContact: { type: 'boolean', description: 'Only businesses with a named contact person' },
142
- page: { type: 'number', description: 'Page number (default 1)', default: 1 },
143
- limit: { type: 'number', description: 'Results per page (max 100)', default: 50 },
144
- excludeDelivered: { type: 'boolean', description: 'Skip businesses this account already has (from the API or unlocked in the dashboard). With this on, keep page at 1 or use after.' },
145
- after: { type: 'string', description: 'Cursor paging: return businesses after this id. Use "0" to start, then the pagination.next_after from each response.' }
146
- } }
147
- },
148
- {
149
- name: 'query_ecommerce_database',
150
- description: 'Query the ecommerce store database directly. Returns online stores with platform, estimated monthly sales and visits, employee count, emails, phones and social links. Up to 100 per request. Included with the $149/mo plan (100,000 new stores a day; stores you already have do not count again). For a daily pull of only new stores set excludeDelivered=true. For big pulls page with after: send after="0" first, then the pagination.next_after from each response until it is null. Combine technologies (any of), technologiesAll (all of) and notTechnologies (none of) for exact targeting, e.g. technologiesAll ["Klaviyo", "PayPal"] with notTechnologies ["Facebook Pixel"].',
151
- inputSchema: { type: 'object', properties: {
152
- platform: { type: 'array', items: { type: 'string' }, description: 'Ecommerce platforms, e.g. ["shopify", "woocommerce"] (any match)' },
153
- country: { type: 'array', items: { type: 'string' }, description: 'Two-letter country codes, e.g. ["US"]' },
154
- state: { type: 'string', description: 'State (partial match)' },
155
- city: { type: 'string', description: 'City (partial match)' },
156
- category: { type: 'array', items: { type: 'string' }, description: 'Store categories (any match)' },
157
- merchant: { type: 'string', description: 'Merchant name (partial match)' },
158
- domain: { type: 'string', description: 'Store domain(s), comma or space separated' },
159
- technologies: { type: 'array', items: { type: 'string' }, description: 'Technologies the store uses (any match)' },
160
- technologiesAll: { type: 'array', items: { type: 'string' }, description: 'Stores that run ALL of these technologies, e.g. ["Klaviyo", "PayPal"]' },
161
- notTechnologies: { type: 'array', items: { type: 'string' }, description: 'Leave out stores that run any of these technologies, e.g. ["Facebook Pixel"]' },
162
- planExclude: { type: 'array', items: { type: 'string' }, description: 'Exclude stores on these plan tiers, e.g. ["Shopify Plus"]' },
163
- salesMin: { type: 'number', description: 'Minimum estimated monthly sales, USD' },
164
- salesMax: { type: 'number', description: 'Maximum estimated monthly sales, USD' },
165
- visitsMin: { type: 'number', description: 'Minimum estimated monthly visits' },
166
- visitsMax: { type: 'number', description: 'Maximum estimated monthly visits' },
167
- employeesMin: { type: 'number', description: 'Minimum employee count' },
168
- employeesMax: { type: 'number', description: 'Maximum employee count' },
169
- hasEmail: { type: 'boolean', description: 'Only stores with an email address' },
170
- hasPhone: { type: 'boolean', description: 'Only stores with a phone number' },
171
- hasInstagram: { type: 'boolean', description: 'Only stores with Instagram' },
172
- hasFacebook: { type: 'boolean', description: 'Only stores with Facebook' },
173
- hasTiktok: { type: 'boolean', description: 'Only stores with TikTok' },
174
- page: { type: 'number', description: 'Page number (default 1)', default: 1 },
175
- limit: { type: 'number', description: 'Results per page (max 100)', default: 50 },
176
- excludeDelivered: { type: 'boolean', description: 'Skip stores this account already has (from the API or unlocked in the dashboard). With this on, keep page at 1 or use after.' },
177
- after: { type: 'string', description: 'Cursor paging: return stores after this id. Use "0" to start, then the pagination.next_after from each response.' }
178
- } }
179
- }
180
- ]
181
-
182
- const TOOLS = [...UTILITY_TOOLS, ...SCRAPER_TOOLS]
183
-
184
16
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }))
185
17
 
186
18
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
187
19
  const { name, arguments: args = {} } = request.params
188
20
  try {
189
- let result
190
- // Generated scraper tools: POST args straight to the canonical endpoint.
191
- if (ENDPOINT_BY_TOOL[name]) {
192
- result = await sc.postEndpoint(ENDPOINT_BY_TOOL[name], args)
193
- } else {
194
- switch (name) {
195
- case 'check_wallet': result = await sc.wallet(); break
196
- case 'list_runs': result = await sc.runs(args.hours || 24, args.limit || 20); break
197
- case 'check_run_status': result = await sc.status(args.runId); break
198
- case 'download_results': {
199
- const dl = await sc.download(args.runId, args.outputPath)
200
- result = { success: true, path: dl.path, sizeKB: Math.round(dl.bytes / 1024) }
201
- break
202
- }
203
- case 'cancel_run': result = await sc.cancel(args.runId); break
204
- case 'plan_search': result = await sc.planSearch(args.query, args.database); break
205
- case 'find_lookalike_groups': result = await sc.lookalikeStart(args.domains); break
206
- case 'get_lookalike_groups': result = await sc.lookalikeJob(args.id); break
207
- case 'download_lookalike_csv': {
208
- const dl = await sc.lookalikeCsv(args.id, args.outputPath)
209
- result = { success: true, path: dl.path, sizeKB: Math.round(dl.bytes / 1024) }
210
- break
211
- }
212
- case 'query_lead_database': {
213
- const params = { ...args }
214
- if (params.hasEmail) params.hasEmail = 'true'
215
- if (params.hasPhone) params.hasPhone = 'true'
216
- if (params.excludeDelivered) params.excludeDelivered = 'true'
217
- else delete params.excludeDelivered
218
- if (params.after !== undefined && params.after !== null) params.after = String(params.after)
219
- result = await sc.dbLeads(params)
220
- break
221
- }
222
- case 'query_local_business_database': {
223
- const params = { ...args }
224
- for (const k of ['hasEmail', 'hasPhone', 'hasWebsite', 'hasContact', 'excludeDelivered']) {
225
- if (params[k]) params[k] = 'true'
226
- else delete params[k]
227
- }
228
- if (params.after !== undefined && params.after !== null) params.after = String(params.after)
229
- result = await sc.dbLocalBusinesses(params)
230
- break
231
- }
232
- case 'query_ecommerce_database': {
233
- const params = { ...args }
234
- for (const k of ['hasEmail', 'hasPhone', 'hasInstagram', 'hasFacebook', 'hasTiktok', 'excludeDelivered']) {
235
- if (params[k]) params[k] = 'true'
236
- else delete params[k]
237
- }
238
- if (params.after !== undefined && params.after !== null) params.after = String(params.after)
239
- result = await sc.dbEcommerce(params)
240
- break
241
- }
242
- default:
243
- return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true }
244
- }
245
- }
21
+ const result = await callTool(name, args)
246
22
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }
247
23
  } catch (e) {
24
+ if (/^Unknown tool: /.test(e.message)) return { content: [{ type: 'text', text: e.message }], isError: true }
248
25
  return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true }
249
26
  }
250
27
  })
@@ -1,6 +1,6 @@
1
1
  // AUTO-GENERATED by scripts/generate.mjs — DO NOT EDIT BY HAND.
2
2
  // Source of truth: scraperConfigs.ts (+ src/config/scrapers.ts). Regenerate: npm run generate
3
- // Generated: 2026-10-27T22:10:20.665Z
3
+ // Generated: 2026-10-30T00:34:05.480Z
4
4
  export const TOOLS = [
5
5
  {
6
6
  "name": "scrape_apollo",
@@ -392,7 +392,7 @@ export const TOOLS = [
392
392
  },
393
393
  {
394
394
  "name": "search_criminal_records",
395
- "description": "Search nationwide criminal records across Sex Offender Registry, Department of Corrections, Arrest Records, and Court Records. Free if no records are found. Submit one name per request. Cost: .00 per search (only charged if records found).",
395
+ "description": "Search nationwide criminal records across Sex Offender Registry, Department of Corrections, Arrest Records, and Court Records. Free if no records are found. Submit one name per request. Cost: $1.00 per search (only charged if records found).",
396
396
  "inputSchema": {
397
397
  "type": "object",
398
398
  "properties": {
package/lib/client.mjs CHANGED
@@ -4,11 +4,21 @@ import path from 'path'
4
4
  import os from 'os'
5
5
  import { ENDPOINTS } from './catalog.generated.mjs'
6
6
 
7
- const BASE = 'https://app.scrapercity.com'
7
+ const DEFAULT_BASE = 'https://app.scrapercity.com'
8
+ let BASE = DEFAULT_BASE
9
+ let KEY_OVERRIDE = null
8
10
  const RC_PATH = path.join(os.homedir(), '.scrapercityrc')
9
11
 
10
12
  // ── Auth ──────────────────────────────────────────────────────
13
+ // Library use: configure({ apiKey }) sets the key in code (it wins over the env var and the rc file).
14
+ export function configure({ apiKey, baseUrl } = {}) {
15
+ if (apiKey !== undefined) KEY_OVERRIDE = apiKey || null
16
+ if (baseUrl !== undefined) BASE = (baseUrl || DEFAULT_BASE).replace(/\/+$/, '')
17
+ }
18
+
11
19
  export function getApiKey() {
20
+ // 0) configure({ apiKey })
21
+ if (KEY_OVERRIDE) return KEY_OVERRIDE
12
22
  // 1) env var
13
23
  if (process.env.SCRAPERCITY_API_KEY) return process.env.SCRAPERCITY_API_KEY
14
24
  // 2) rc file
package/lib/index.mjs ADDED
@@ -0,0 +1,6 @@
1
+ // lib/index.mjs - the ScraperCity JavaScript SDK: `import { wallet, callTool } from 'scrapercity'`.
2
+ // Every API call (lib/client.mjs), the full tool list the MCP server serves, and helpers that hand
3
+ // that list to OpenAI or Anthropic tool calling.
4
+ export * from './client.mjs'
5
+ export { TOOLS, callTool, openaiTools, anthropicTools } from './tools.mjs'
6
+ export { ENDPOINTS, ENDPOINT_BY_TOOL } from './catalog.generated.mjs'
package/lib/tools.mjs ADDED
@@ -0,0 +1,71 @@
1
+ // lib/tools.mjs - the one tool list and the one dispatcher, shared by the MCP server (bin/mcp.mjs)
2
+ // and the JavaScript SDK (lib/index.mjs). The Python SDK gets the same list through
3
+ // scripts/generate.mjs (python/scrapercity/_catalog.py).
4
+ //
5
+ // Scraper tools are generated from the app's scraperConfigs.ts and dispatched generically: their
6
+ // args are POSTed straight to their /api/v1/scrape endpoint. The hand-written tools (account, runs,
7
+ // databases, lookalikes) live in lib/utilityTools.mjs and are dispatched below.
8
+ import * as sc from './client.mjs'
9
+ import { TOOLS as SCRAPER_TOOLS, ENDPOINT_BY_TOOL } from './catalog.generated.mjs'
10
+ import { UTILITY_TOOLS } from './utilityTools.mjs'
11
+
12
+ export const TOOLS = [...UTILITY_TOOLS, ...SCRAPER_TOOLS]
13
+
14
+ // The database routes take booleans as the string 'true' and leave a false one out.
15
+ function dbParams(args, flags) {
16
+ const params = { ...args }
17
+ for (const k of flags) {
18
+ if (params[k]) params[k] = 'true'
19
+ else delete params[k]
20
+ }
21
+ if (params.after !== undefined && params.after !== null) params.after = String(params.after)
22
+ return params
23
+ }
24
+
25
+ const UTILITY_HANDLERS = {
26
+ check_wallet: () => sc.wallet(),
27
+ list_runs: (a) => sc.runs(a.hours || 24, a.limit || 20),
28
+ check_run_status: (a) => sc.status(a.runId),
29
+ download_results: async (a) => {
30
+ const dl = await sc.download(a.runId, a.outputPath)
31
+ return { success: true, path: dl.path, sizeKB: Math.round(dl.bytes / 1024) }
32
+ },
33
+ cancel_run: (a) => sc.cancel(a.runId),
34
+ plan_search: (a) => sc.planSearch(a.query, a.database),
35
+ find_lookalike_groups: (a) => sc.lookalikeStart(a.domains),
36
+ get_lookalike_groups: (a) => sc.lookalikeJob(a.id),
37
+ download_lookalike_csv: async (a) => {
38
+ const dl = await sc.lookalikeCsv(a.id, a.outputPath)
39
+ return { success: true, path: dl.path, sizeKB: Math.round(dl.bytes / 1024) }
40
+ },
41
+ query_lead_database: (a) => sc.dbLeads(dbParams(a, ['hasEmail', 'hasPhone', 'excludeDelivered'])),
42
+ query_local_business_database: (a) =>
43
+ sc.dbLocalBusinesses(dbParams(a, ['hasEmail', 'hasPhone', 'hasWebsite', 'hasContact', 'excludeDelivered'])),
44
+ query_ecommerce_database: (a) =>
45
+ sc.dbEcommerce(dbParams(a, ['hasEmail', 'hasPhone', 'hasInstagram', 'hasFacebook', 'hasTiktok', 'excludeDelivered'])),
46
+ }
47
+
48
+ for (const t of UTILITY_TOOLS) {
49
+ if (!UTILITY_HANDLERS[t.name]) throw new Error(`lib/tools.mjs: no handler for tool ${t.name}`)
50
+ }
51
+
52
+ // Run any tool by name with the same arguments an MCP client would send. Returns the API's answer.
53
+ export async function callTool(name, args = {}) {
54
+ if (ENDPOINT_BY_TOOL[name]) return sc.postEndpoint(ENDPOINT_BY_TOOL[name], args)
55
+ const h = UTILITY_HANDLERS[name]
56
+ if (!h) throw new Error(`Unknown tool: ${name}`)
57
+ return h(args || {})
58
+ }
59
+
60
+ // The tool list in OpenAI function-calling format (chat.completions / responses `tools`).
61
+ export function openaiTools() {
62
+ return TOOLS.map(t => ({
63
+ type: 'function',
64
+ function: { name: t.name, description: t.description, parameters: t.inputSchema },
65
+ }))
66
+ }
67
+
68
+ // The tool list in Anthropic Messages API format (`tools` on messages.create).
69
+ export function anthropicTools() {
70
+ return TOOLS.map(t => ({ name: t.name, description: t.description, input_schema: t.inputSchema }))
71
+ }
@@ -0,0 +1,167 @@
1
+ // lib/utilityTools.mjs - the hand-written MCP tools (account, runs, databases, lookalikes).
2
+ // The scraper tools are generated (lib/catalog.generated.mjs). Both lists are shared by the MCP
3
+ // server, the JavaScript SDK (lib/index.mjs) and, through scripts/generate.mjs, the Python SDK.
4
+ // Every tool here needs a handler in lib/tools.mjs AND in python/scrapercity/tools.py:
5
+ // npm run generate stops if one is missing.
6
+ export const UTILITY_TOOLS = [
7
+ {
8
+ name: 'check_wallet',
9
+ description: 'Check account balance, plan, and billing info. Call this FIRST to verify credits before running any scrape.',
10
+ inputSchema: { type: 'object', properties: {} }
11
+ },
12
+ {
13
+ name: 'list_runs',
14
+ description: 'List recent scraper runs with status, lead counts, and costs.',
15
+ inputSchema: { type: 'object', properties: {
16
+ hours: { type: 'number', description: 'How many hours back to look (default 24, max 168)', default: 24 },
17
+ limit: { type: 'number', description: 'Max runs to return (default 20, max 100)', default: 20 }
18
+ } }
19
+ },
20
+ {
21
+ name: 'check_run_status',
22
+ description: 'Check the status of a scraper run. Returns status (RUNNING/SUCCEEDED/FAILED/CANCELLED), lead count, and download URL when complete.',
23
+ inputSchema: { type: 'object', properties: { runId: { type: 'string', description: 'The run ID returned when starting a scrape' } }, required: ['runId'] }
24
+ },
25
+ {
26
+ name: 'download_results',
27
+ description: 'Download the CSV results of a completed scraper run. Only works when status is SUCCEEDED.',
28
+ inputSchema: { type: 'object', properties: {
29
+ runId: { type: 'string', description: 'The run ID to download results for' },
30
+ outputPath: { type: 'string', description: 'File path to save CSV (default: {runId}.csv)' }
31
+ }, required: ['runId'] }
32
+ },
33
+ {
34
+ name: 'cancel_run',
35
+ description: 'Cancel a running scraper job.',
36
+ inputSchema: { type: 'object', properties: { runId: { type: 'string', description: 'The run ID to cancel' } }, required: ['runId'] }
37
+ },
38
+ {
39
+ name: 'plan_search',
40
+ description: 'Turn a plain-English request into ready-to-run database searches. Describe who you want ("dentists in Austin", "websites that use Shopify", "CEOs of SaaS companies in Texas") or what you sell ("I sell life insurance"). Returns a few searches, each with its database, filters set to values that database really has, how many records match (leaving out what this account already has), a dashboard link, and apiParams to pass as they are to the matching query tool: query_lead_database for database/leads, query_local_business_database for database/local-businesses, query_ecommerce_database for database/ecommerce. Included with the $149/mo plan, like the database tools. Use it before a database query whenever you are not sure of the exact filter values.',
41
+ inputSchema: { type: 'object', properties: {
42
+ query: { type: 'string', description: 'What you are looking for or what you sell, in plain English' },
43
+ database: { type: 'string', enum: ['leads', 'local', 'ecommerce'], description: 'Optional: plan in this database only (leads = people at companies, local = local businesses, ecommerce = online stores). Leave it out to let the planner pick the databases that fit.' }
44
+ }, required: ['query'] }
45
+ },
46
+ {
47
+ name: 'find_lookalike_groups',
48
+ description: 'Group your customers into lookalike audiences. Send their websites or work emails (mike@acme.com reads acme.com and looks up Mike\'s title). Starts a background job and returns it with its id; then call get_lookalike_groups until status is done. Each group comes with ready-to-run database filters, a match count and the best tool to reach businesses like them. $0.01 per site read, from credits; your balance must cover $0.01 x every site. One job at a time. $149/mo plan and up.',
49
+ inputSchema: { type: 'object', properties: {
50
+ domains: { type: 'array', items: { type: 'string' }, description: 'Your customers\' websites, URLs or work emails. At least 3 company sites.' }
51
+ }, required: ['domains'] }
52
+ },
53
+ {
54
+ name: 'get_lookalike_groups',
55
+ description: 'Read a lookalike job started with find_lookalike_groups: status (running, done, failed), percent, and when done the groups. Each group has name, description, sites (your customers in it), database (leads, local, ecommerce), filters, count and tool. Pass its filters to query_lead_database, query_local_business_database or query_ecommerce_database to pull the records.',
56
+ inputSchema: { type: 'object', properties: {
57
+ id: { type: 'string', description: 'The job id from find_lookalike_groups' }
58
+ }, required: ['id'] }
59
+ },
60
+ {
61
+ name: 'download_lookalike_csv',
62
+ description: 'Save a finished lookalike job (find_lookalike_groups) as a CSV file, one row per uploaded site: whether it was read, what it sells and to whom, business model, industry, keywords, size, country, the contacts from your upload with their titles, and its group. No extra charge.',
63
+ inputSchema: { type: 'object', properties: {
64
+ id: { type: 'string', description: 'The job id from find_lookalike_groups' },
65
+ outputPath: { type: 'string', description: 'File path to save the CSV (default: lookalike-{id}.csv)' }
66
+ }, required: ['id'] }
67
+ },
68
+ {
69
+ name: 'query_lead_database',
70
+ description: 'Query the B2B lead database directly. Returns contacts with names, emails, phones, titles, LinkedIn, Twitter and Facebook profiles, companies (plus company website, phone, revenue, total funding, founded year, HQ location and keywords). Up to 100 per request. Included with the $149/mo plan (100,000 new leads a day; leads you already have do not count again). For a daily pull of only new leads set excludeDelivered=true. For big pulls page with after: send after="0" first, then the pagination.next_after from each response until it is null. Have a saved people-search URL? Pass it as url to search with its filters right away (search_translation in the response says what was applied). Target by the technologies a company\'s website runs: technologies (any of), technologiesAll (all of), notTechnologies (none of), e.g. technologiesAll ["Klaviyo", "PayPal", "Shopify"] with notTechnologies ["Facebook Pixel"].',
71
+ inputSchema: { type: 'object', properties: {
72
+ url: { type: 'string', description: 'A people-search URL (the address of a people search with its filters). Its filters become the search; any other filter you pass overrides the matching one.' },
73
+ title: { type: 'string', description: 'Job title filter, comma separated for several (partial match, e.g. "CEO, Founder")' },
74
+ industry: { type: 'array', items: { type: 'string' }, description: 'Company industries (any match)' },
75
+ country: { type: 'array', items: { type: 'string' }, description: 'Person countries, full names like "United States" (US, USA, UK, UAE also work)' },
76
+ state: { type: 'array', items: { type: 'string' }, description: 'Person states' },
77
+ city: { type: 'string', description: 'Person city, comma separated for several' },
78
+ companyName: { type: 'string', description: 'Company name' },
79
+ companyDomain: { type: 'string', description: 'Company domain(s), comma separated' },
80
+ seniority: { type: 'array', items: { type: 'string' }, description: 'e.g. ["vp", "director", "c_suite"]' },
81
+ department: { type: 'array', items: { type: 'string' }, description: 'e.g. ["sales", "engineering"]' },
82
+ minEmployees: { type: 'number', description: 'Minimum company employee count' },
83
+ maxEmployees: { type: 'number', description: 'Maximum company employee count' },
84
+ keywords: { type: 'array', items: { type: 'string' }, description: 'Company keywords/tags, any match (e.g. ["saas", "fintech"])' },
85
+ revenueMin: { type: 'number', description: 'Minimum company annual revenue, USD' },
86
+ revenueMax: { type: 'number', description: 'Maximum company annual revenue, USD' },
87
+ companyCountry: { type: 'array', items: { type: 'string' }, description: 'Company HQ countries (not where the person is)' },
88
+ companyState: { type: 'array', items: { type: 'string' }, description: 'Company HQ states' },
89
+ companyCity: { type: 'string', description: 'Company HQ city' },
90
+ notTitle: { type: 'string', description: 'Exclude titles containing any of these (comma separated)' },
91
+ notKeywords: { type: 'array', items: { type: 'string' }, description: 'Exclude companies with any of these keywords' },
92
+ notIndustry: { type: 'array', items: { type: 'string' }, description: 'Exclude these industries' },
93
+ technologies: { type: 'array', items: { type: 'string' }, description: 'Companies whose website runs ANY of these technologies, e.g. ["HubSpot"]' },
94
+ technologiesAll: { type: 'array', items: { type: 'string' }, description: 'Companies whose website runs ALL of these technologies, e.g. ["Klaviyo", "PayPal"] (keep it to 1-3)' },
95
+ notTechnologies: { type: 'array', items: { type: 'string' }, description: 'Leave out companies whose website runs any of these technologies, e.g. ["Facebook Pixel"]' },
96
+ socialUrl: { type: 'string', description: 'Match a social profile URL' },
97
+ hasEmail: { type: 'boolean', description: 'Only contacts with email' },
98
+ hasPhone: { type: 'boolean', description: 'Only contacts with phone' },
99
+ page: { type: 'number', description: 'Page number (default 1)', default: 1 },
100
+ limit: { type: 'number', description: 'Results per page (max 100)', default: 50 },
101
+ excludeDelivered: { type: 'boolean', description: 'Skip leads this account already has (from the API or unlocked in the dashboard). With this on, keep page at 1 or use after.' },
102
+ after: { type: 'string', description: 'Cursor paging: return leads after this lead id. Use "0" to start, then the pagination.next_after from each response.' }
103
+ } }
104
+ },
105
+ {
106
+ name: 'query_local_business_database',
107
+ description: 'Query the local business database directly. Returns businesses with phone numbers, emails, websites, addresses and ratings, many with a named contact person. Up to 100 per request. Included with the $149/mo plan (100,000 new businesses a day; businesses you already have do not count again). Set hasContact=true for only businesses with a named contact person. For a daily pull of only new businesses set excludeDelivered=true. For big pulls page with after: send after="0" first, then the pagination.next_after from each response until it is null. Search the way you would on Google Maps: keyword (what the businesses do or are called, e.g. ["thai restaurant"]) and location (cities, states, countries or postal codes, e.g. ["Houston TX", "Austin TX"]); searched_as in the response says how each was read. A search that finds nothing also returns lead_database: the same search in the Lead Database when it has 100+ people. Have a Google Maps search URL? Pass it as url to search with its keyword and place right away (search_translation in the response says what was applied).',
108
+ inputSchema: { type: 'object', properties: {
109
+ keyword: { type: 'array', items: { type: 'string' }, description: 'What the businesses do or are called, as typed on Google Maps, e.g. ["thai restaurant", "sushi"]. Matches business names and categories; any keyword. A place inside a keyword ("plumbers in Austin TX") is used as the location when no location is given.' },
110
+ location: { type: 'array', items: { type: 'string' }, description: 'Cities, states, countries or postal codes, as typed, e.g. ["Houston TX", "78701", "Ontario", "Germany"] (up to 2,000; any of them).' },
111
+ url: { type: 'string', description: 'A Google Maps search URL (the address of a search on Google Maps). Its keyword and place become the search; any other filter you pass overrides the matching one.' },
112
+ category: { type: 'array', items: { type: 'string' }, description: 'Business categories, e.g. ["Dentist"]. A word also finds categories that contain it ("restaurant" finds "Mexican restaurant")' },
113
+ country: { type: 'array', items: { type: 'string' }, description: 'Two-letter country codes, e.g. ["US"]' },
114
+ state: { type: 'string', description: 'State or region, comma separated for several (US abbreviations work, e.g. "TX")' },
115
+ city: { type: 'string', description: 'City (partial match)' },
116
+ postalCode: { type: 'string', description: 'Postal or zip code' },
117
+ box: { type: 'string', description: 'A map area as "minLat,minLng,maxLat,maxLng" (e.g. "30.1,-97.9,30.5,-97.5"). Only businesses inside it.' },
118
+ query: { type: 'string', description: 'Search business names and descriptions' },
119
+ title: { type: 'string', description: 'Business name, comma separated for several (partial match)' },
120
+ notCategory: { type: 'array', items: { type: 'string' }, description: 'Exclude these business categories' },
121
+ minRating: { type: 'number', description: 'Minimum Google rating (1-5)' },
122
+ maxRating: { type: 'number', description: 'Maximum Google rating (1-5)' },
123
+ minReviews: { type: 'number', description: 'Minimum number of reviews' },
124
+ maxReviews: { type: 'number', description: 'Maximum number of reviews' },
125
+ hasEmail: { type: 'boolean', description: 'Only businesses with an email address' },
126
+ hasPhone: { type: 'boolean', description: 'Only businesses with a phone number' },
127
+ hasWebsite: { type: 'boolean', description: 'Only businesses with a website' },
128
+ hasContact: { type: 'boolean', description: 'Only businesses with a named contact person' },
129
+ page: { type: 'number', description: 'Page number (default 1)', default: 1 },
130
+ limit: { type: 'number', description: 'Results per page (max 100)', default: 50 },
131
+ excludeDelivered: { type: 'boolean', description: 'Skip businesses this account already has (from the API or unlocked in the dashboard). With this on, keep page at 1 or use after.' },
132
+ after: { type: 'string', description: 'Cursor paging: return businesses after this id. Use "0" to start, then the pagination.next_after from each response.' }
133
+ } }
134
+ },
135
+ {
136
+ name: 'query_ecommerce_database',
137
+ description: 'Query the ecommerce store database directly. Returns online stores with platform, estimated monthly sales and visits, employee count, emails, phones and social links. Up to 100 per request. Included with the $149/mo plan (100,000 new stores a day; stores you already have do not count again). For a daily pull of only new stores set excludeDelivered=true. For big pulls page with after: send after="0" first, then the pagination.next_after from each response until it is null. Combine technologies (any of), technologiesAll (all of) and notTechnologies (none of) for exact targeting, e.g. technologiesAll ["Klaviyo", "PayPal"] with notTechnologies ["Facebook Pixel"].',
138
+ inputSchema: { type: 'object', properties: {
139
+ platform: { type: 'array', items: { type: 'string' }, description: 'Ecommerce platforms, e.g. ["shopify", "woocommerce"] (any match)' },
140
+ country: { type: 'array', items: { type: 'string' }, description: 'Two-letter country codes, e.g. ["US"]' },
141
+ state: { type: 'string', description: 'State (partial match)' },
142
+ city: { type: 'string', description: 'City (partial match)' },
143
+ category: { type: 'array', items: { type: 'string' }, description: 'Store categories (any match)' },
144
+ merchant: { type: 'string', description: 'Merchant name (partial match)' },
145
+ domain: { type: 'string', description: 'Store domain(s), comma or space separated' },
146
+ technologies: { type: 'array', items: { type: 'string' }, description: 'Technologies the store uses (any match)' },
147
+ technologiesAll: { type: 'array', items: { type: 'string' }, description: 'Stores that run ALL of these technologies, e.g. ["Klaviyo", "PayPal"]' },
148
+ notTechnologies: { type: 'array', items: { type: 'string' }, description: 'Leave out stores that run any of these technologies, e.g. ["Facebook Pixel"]' },
149
+ planExclude: { type: 'array', items: { type: 'string' }, description: 'Exclude stores on these plan tiers, e.g. ["Shopify Plus"]' },
150
+ salesMin: { type: 'number', description: 'Minimum estimated monthly sales, USD' },
151
+ salesMax: { type: 'number', description: 'Maximum estimated monthly sales, USD' },
152
+ visitsMin: { type: 'number', description: 'Minimum estimated monthly visits' },
153
+ visitsMax: { type: 'number', description: 'Maximum estimated monthly visits' },
154
+ employeesMin: { type: 'number', description: 'Minimum employee count' },
155
+ employeesMax: { type: 'number', description: 'Maximum employee count' },
156
+ hasEmail: { type: 'boolean', description: 'Only stores with an email address' },
157
+ hasPhone: { type: 'boolean', description: 'Only stores with a phone number' },
158
+ hasInstagram: { type: 'boolean', description: 'Only stores with Instagram' },
159
+ hasFacebook: { type: 'boolean', description: 'Only stores with Facebook' },
160
+ hasTiktok: { type: 'boolean', description: 'Only stores with TikTok' },
161
+ page: { type: 'number', description: 'Page number (default 1)', default: 1 },
162
+ limit: { type: 'number', description: 'Results per page (max 100)', default: 50 },
163
+ excludeDelivered: { type: 'boolean', description: 'Skip stores this account already has (from the API or unlocked in the dashboard). With this on, keep page at 1 or use after.' },
164
+ after: { type: 'string', description: 'Cursor paging: return stores after this id. Use "0" to start, then the pagination.next_after from each response.' }
165
+ } }
166
+ }
167
+ ]
package/package.json CHANGED
@@ -1,8 +1,15 @@
1
1
  {
2
2
  "name": "scrapercity",
3
- "version": "1.0.23",
4
- "description": "ScraperCity CLI & MCP Server - B2B lead generation for AI agents",
3
+ "version": "1.0.25",
4
+ "description": "ScraperCity SDK, CLI & MCP Server - B2B lead generation for AI agents and developers",
5
5
  "type": "module",
6
+ "main": "./lib/index.mjs",
7
+ "exports": {
8
+ ".": "./lib/index.mjs",
9
+ "./lib/*": "./lib/*",
10
+ "./bin/*": "./bin/*",
11
+ "./package.json": "./package.json"
12
+ },
6
13
  "bin": {
7
14
  "scrapercity": "./bin/cli.mjs",
8
15
  "scrapercity-mcp": "./bin/mcp.mjs"
@@ -26,7 +33,14 @@
26
33
  "email-finder",
27
34
  "ai-agent",
28
35
  "claude-code",
29
- "cursor"
36
+ "cursor",
37
+ "sdk",
38
+ "api-client",
39
+ "javascript",
40
+ "langchain",
41
+ "openai",
42
+ "function-calling",
43
+ "tool-calling"
30
44
  ],
31
45
  "author": "ScraperCity",
32
46
  "license": "MIT",
@@ -43,6 +57,7 @@
43
57
  },
44
58
  "scripts": {
45
59
  "generate": "node scripts/generate.mjs",
46
- "prepublishOnly": "node scripts/generate.mjs --check"
60
+ "prepublishOnly": "node scripts/generate.mjs --check && bash scripts/publish-python.sh",
61
+ "test": "node --test test/*.test.mjs"
47
62
  }
48
63
  }