mcp-scraper 0.31.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin/api-server.cjs +55 -11
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +4 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +3 -3
- package/dist/bin/mcp-scraper-install.cjs +1 -1
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +1 -1
- package/dist/bin/mcp-stdio-server.cjs +9 -6
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +3 -3
- package/dist/bin/paa-harvest.cjs +26 -4
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +2 -2
- package/dist/{chunk-KAMKTSJQ.js → chunk-55M5CE5L.js} +2 -2
- package/dist/chunk-CPQF3PYW.js +7 -0
- package/dist/chunk-CPQF3PYW.js.map +1 -0
- package/dist/{chunk-ZQSHKWV4.js → chunk-PTHY7NYD.js} +4 -1
- package/dist/chunk-PTHY7NYD.js.map +1 -0
- package/dist/{chunk-YH5DRFWQ.js → chunk-RDCCU7CQ.js} +25 -6
- package/dist/chunk-RDCCU7CQ.js.map +1 -0
- package/dist/{chunk-ZOXL3VWO.js → chunk-YOOEAXWP.js} +8 -8
- package/dist/chunk-YOOEAXWP.js.map +1 -0
- package/dist/index.cjs +26 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +2 -2
- package/dist/{server-DS3ZSNNO.js → server-OBOTNFD6.js} +31 -8
- package/dist/{server-DS3ZSNNO.js.map → server-OBOTNFD6.js.map} +1 -1
- package/dist/{worker-RYP5EOV5.js → worker-7DJYUACV.js} +3 -3
- package/docs/mcp-tool-manifest.generated.json +7 -7
- package/package.json +1 -1
- package/dist/chunk-W3GYPHFL.js +0 -7
- package/dist/chunk-W3GYPHFL.js.map +0 -1
- package/dist/chunk-YH5DRFWQ.js.map +0 -1
- package/dist/chunk-ZOXL3VWO.js.map +0 -1
- package/dist/chunk-ZQSHKWV4.js.map +0 -1
- /package/dist/{chunk-KAMKTSJQ.js.map → chunk-55M5CE5L.js.map} +0 -0
- /package/dist/{worker-RYP5EOV5.js.map → worker-7DJYUACV.js.map} +0 -0
package/README.md
CHANGED
|
@@ -88,7 +88,7 @@ Build the branded one-click bundle:
|
|
|
88
88
|
npm run build:mcpb
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
The generated bundle is written to `build/mcpb/mcp-scraper-<version>.mcpb` and copied to `public/downloads/` for the hosted download. The current public bundle is `https://mcpscraper.dev/downloads/mcp-scraper.mcpb` (`0.
|
|
91
|
+
The generated bundle is written to `build/mcpb/mcp-scraper-<version>.mcpb` and copied to `public/downloads/` for the hosted download. The current public bundle is `https://mcpscraper.dev/downloads/mcp-scraper.mcpb` (`0.32.0`, SHA-256 `f31500fa60ffa96d2bea43feb4f5606a40fdc59b599dd51a11b8a54e5c9bce32`). Install it by opening or dragging it into Claude Desktop. Claude displays the `MCP Scraper` install card, icon, and API-key configuration field from the bundle manifest.
|
|
92
92
|
|
|
93
93
|
The MCPB install exposes every tool — web-intelligence plus all `browser_*` tools — through the one `mcp-scraper` server.
|
|
94
94
|
|
package/dist/bin/api-server.cjs
CHANGED
|
@@ -13834,6 +13834,15 @@ function positiveIntFromEnv(name, fallback) {
|
|
|
13834
13834
|
function proxyIdSuffix(proxyId) {
|
|
13835
13835
|
return proxyId ? proxyId.slice(-6) : null;
|
|
13836
13836
|
}
|
|
13837
|
+
function serpArrivalUrl(googleUrl) {
|
|
13838
|
+
const base = process.env.SERP_REDIRECT_BASE?.trim().replace(/\/+$/, "");
|
|
13839
|
+
if (!base) return googleUrl;
|
|
13840
|
+
return `${base}/g/${Buffer.from(googleUrl, "utf8").toString("base64url")}`;
|
|
13841
|
+
}
|
|
13842
|
+
function isUsCountry(country) {
|
|
13843
|
+
if (!country) return false;
|
|
13844
|
+
return /united states|u\.?s\.?a?\.?|^us$/i.test(country.trim());
|
|
13845
|
+
}
|
|
13837
13846
|
function errorText(err) {
|
|
13838
13847
|
return err instanceof Error ? err.message : String(err);
|
|
13839
13848
|
}
|
|
@@ -13988,7 +13997,7 @@ var init_BrowserDriver = __esm({
|
|
|
13988
13997
|
this.kernelProxyType = config.kernelProxyResolution?.proxyType ?? null;
|
|
13989
13998
|
let defaultProxyDisabled = null;
|
|
13990
13999
|
let defaultProxyDisableError = null;
|
|
13991
|
-
if (!config.kernelProxyId) {
|
|
14000
|
+
if (!config.kernelProxyId && !config.keepDefaultProxy) {
|
|
13992
14001
|
try {
|
|
13993
14002
|
await withTimeout2(
|
|
13994
14003
|
this.kernelClient.browsers.update(this.kernelSessionId, { disable_default_proxy: true }),
|
|
@@ -14038,9 +14047,15 @@ var init_BrowserDriver = __esm({
|
|
|
14038
14047
|
await this.installEsbuildHelperShims(this.context);
|
|
14039
14048
|
this.page = await this.context.newPage();
|
|
14040
14049
|
await this.page.setViewportSize(config.viewport);
|
|
14041
|
-
if (this.debugEnabled) {
|
|
14050
|
+
if (this.debugEnabled || config.requireUsEgress) {
|
|
14042
14051
|
this.debugSnapshot.networkLocation = await this.captureBrowserNetworkLocation();
|
|
14043
14052
|
}
|
|
14053
|
+
if (config.requireUsEgress) {
|
|
14054
|
+
const country = this.debugSnapshot.networkLocation?.country ?? null;
|
|
14055
|
+
if (!isUsCountry(country)) {
|
|
14056
|
+
throw new CaptchaError(`Non-US egress (${country ?? "unknown"}) \u2014 retrying with a fresh session`);
|
|
14057
|
+
}
|
|
14058
|
+
}
|
|
14044
14059
|
return;
|
|
14045
14060
|
}
|
|
14046
14061
|
const launchOpts = {
|
|
@@ -14180,6 +14195,7 @@ var init_BrowserDriver = __esm({
|
|
|
14180
14195
|
if (options?.num) params.set("num", String(options.num));
|
|
14181
14196
|
if (uule) params.set("uule", uule);
|
|
14182
14197
|
const url = "https://www.google.com/search?" + params.toString();
|
|
14198
|
+
const navUrl = serpArrivalUrl(url);
|
|
14183
14199
|
const navDebug = options?.debug ? {
|
|
14184
14200
|
requestedUrl: url,
|
|
14185
14201
|
finalUrl: null,
|
|
@@ -14192,7 +14208,7 @@ var init_BrowserDriver = __esm({
|
|
|
14192
14208
|
} : null;
|
|
14193
14209
|
if (navDebug) this.debugSnapshot.serpNavigation = navDebug;
|
|
14194
14210
|
try {
|
|
14195
|
-
await this.page.goto(
|
|
14211
|
+
await this.page.goto(navUrl, { waitUntil: "domcontentloaded", timeout: 45e3 });
|
|
14196
14212
|
} catch (err) {
|
|
14197
14213
|
await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: null, captchaDetected: null });
|
|
14198
14214
|
const diag = await this.captureDiagnostics(url);
|
|
@@ -17339,6 +17355,9 @@ var init_schemas3 = __esm({
|
|
|
17339
17355
|
device: import_zod17.z.enum(["desktop", "mobile"]).default("desktop"),
|
|
17340
17356
|
proxyMode: import_zod17.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE),
|
|
17341
17357
|
proxyZip: import_zod17.z.string().regex(/^\d{5}$/).optional(),
|
|
17358
|
+
keepDefaultProxy: import_zod17.z.boolean().optional(),
|
|
17359
|
+
requireUsEgress: import_zod17.z.boolean().optional(),
|
|
17360
|
+
maxAttempts: import_zod17.z.number().int().min(1).max(12).optional(),
|
|
17342
17361
|
debug: import_zod17.z.boolean().default(false),
|
|
17343
17362
|
depth: import_zod17.z.number().int().min(1).max(30).default(3),
|
|
17344
17363
|
maxQuestions: import_zod17.z.number().int().min(1).max(1e3).default(100),
|
|
@@ -19235,6 +19254,12 @@ function buildGoogleLocalResultsUrl(input) {
|
|
|
19235
19254
|
if (input.location) params.set("uule", encodeUule(normalizeLocation(input.location)));
|
|
19236
19255
|
return `https://www.google.com/search?${params.toString()}`;
|
|
19237
19256
|
}
|
|
19257
|
+
function buildGoogleOrganicSearchUrl(input) {
|
|
19258
|
+
const query = [input.query, input.location].filter(Boolean).join(" ");
|
|
19259
|
+
const params = new URLSearchParams({ q: query, gl: input.gl, hl: input.hl, pws: "0" });
|
|
19260
|
+
if (input.location) params.set("uule", encodeUule(normalizeLocation(input.location)));
|
|
19261
|
+
return `https://www.google.com/search?${params.toString()}`;
|
|
19262
|
+
}
|
|
19238
19263
|
var LOCAL_RESULTS_PAGE_LIMIT, LOCAL_RESULTS_WAIT_MS, MapsSearchExtractor;
|
|
19239
19264
|
var init_MapsSearchExtractor = __esm({
|
|
19240
19265
|
"src/extractor/MapsSearchExtractor.ts"() {
|
|
@@ -19251,13 +19276,14 @@ var init_MapsSearchExtractor = __esm({
|
|
|
19251
19276
|
async extract(options) {
|
|
19252
19277
|
const startMs = Date.now();
|
|
19253
19278
|
const searchQuery = [options.query, options.location].filter(Boolean).join(" ");
|
|
19254
|
-
const searchUrl =
|
|
19279
|
+
const searchUrl = buildGoogleOrganicSearchUrl(options);
|
|
19255
19280
|
const config = {
|
|
19256
19281
|
headless: options.headless,
|
|
19257
19282
|
kernelApiKey: options.kernelApiKey,
|
|
19258
19283
|
kernelProxyId: options.kernelProxyId,
|
|
19259
19284
|
kernelProxyResolution: options.kernelProxyResolution,
|
|
19260
19285
|
proxyMode: options.proxyMode,
|
|
19286
|
+
keepDefaultProxy: options.proxyMode !== "location" && options.proxyMode !== "configured" && !options.kernelProxyId,
|
|
19261
19287
|
viewport: { width: 1280, height: 900 },
|
|
19262
19288
|
locale: `${options.hl}-${options.gl.toUpperCase()}`,
|
|
19263
19289
|
debug: options.debug
|
|
@@ -19268,6 +19294,10 @@ var init_MapsSearchExtractor = __esm({
|
|
|
19268
19294
|
await page.goto(searchUrl, { waitUntil: "domcontentloaded", timeout: 6e4 });
|
|
19269
19295
|
await page.waitForTimeout(LOCAL_RESULTS_WAIT_MS);
|
|
19270
19296
|
if (await this.detectBlock(page)) throw new CaptchaError(RECAPTCHA_INSTRUCTIONS);
|
|
19297
|
+
const reachedFinder = await this.clickIntoLocalFinder(page);
|
|
19298
|
+
if (!reachedFinder) throw new ExtractionError('Google "More businesses" control was not found on the organic results page');
|
|
19299
|
+
await page.waitForTimeout(LOCAL_RESULTS_WAIT_MS);
|
|
19300
|
+
if (await this.detectBlock(page)) throw new CaptchaError(RECAPTCHA_INSTRUCTIONS);
|
|
19271
19301
|
const results = await this.collectResults(page, options);
|
|
19272
19302
|
return {
|
|
19273
19303
|
query: options.query,
|
|
@@ -19284,6 +19314,17 @@ var init_MapsSearchExtractor = __esm({
|
|
|
19284
19314
|
await this.driver.close();
|
|
19285
19315
|
}
|
|
19286
19316
|
}
|
|
19317
|
+
async clickIntoLocalFinder(page) {
|
|
19318
|
+
const control = page.getByRole("link", { name: /more (businesses|places|results)/i }).or(page.getByRole("button", { name: /more (businesses|places|results)/i })).first();
|
|
19319
|
+
const visible = await control.isVisible({ timeout: 1e4 }).catch(() => false);
|
|
19320
|
+
if (!visible) return false;
|
|
19321
|
+
await control.click({ timeout: 1e4 }).catch(() => {
|
|
19322
|
+
});
|
|
19323
|
+
await page.waitForTimeout(5e3);
|
|
19324
|
+
return page.evaluate(
|
|
19325
|
+
() => /[?&]udm=1/.test(location.href) || document.querySelectorAll('a[href*="/maps/place/"]').length > 0
|
|
19326
|
+
);
|
|
19327
|
+
}
|
|
19287
19328
|
async detectBlock(page) {
|
|
19288
19329
|
return page.evaluate(() => {
|
|
19289
19330
|
const text2 = document.body.innerText.slice(0, 2e3);
|
|
@@ -28145,6 +28186,8 @@ var init_PAAExtractor = __esm({
|
|
|
28145
28186
|
kernelProxyId: options.kernelProxyId,
|
|
28146
28187
|
kernelProxyResolution: options.kernelProxyResolution,
|
|
28147
28188
|
proxyMode: options.proxyMode,
|
|
28189
|
+
keepDefaultProxy: options.keepDefaultProxy,
|
|
28190
|
+
requireUsEgress: options.requireUsEgress,
|
|
28148
28191
|
viewport: isMobile ? { width: 390, height: 844 } : { width: 1280, height: 800 },
|
|
28149
28192
|
locale: `${options.hl}-${options.gl.toUpperCase()}`,
|
|
28150
28193
|
userAgent: isMobile ? MOBILE_USER_AGENT2 : DESKTOP_USER_AGENT2,
|
|
@@ -28645,7 +28688,8 @@ async function harvest(rawOptions) {
|
|
|
28645
28688
|
};
|
|
28646
28689
|
const requestedDebug = typeof raw.debug === "boolean" ? raw.debug : false;
|
|
28647
28690
|
const needsLocationEvidence = proxyMode === "location" && Boolean(proxyOpts.location);
|
|
28648
|
-
const
|
|
28691
|
+
const requestedMaxAttempts = raw.maxAttempts;
|
|
28692
|
+
const maxAttempts = typeof requestedMaxAttempts === "number" && Number.isInteger(requestedMaxAttempts) && requestedMaxAttempts >= 1 ? requestedMaxAttempts : maxAttemptsForProxyMode(proxyMode) + 1;
|
|
28649
28693
|
const serializer = new OutputSerializer();
|
|
28650
28694
|
let lastError = null;
|
|
28651
28695
|
let effectiveProxyMode = proxyMode;
|
|
@@ -29723,7 +29767,7 @@ var PACKAGE_VERSION;
|
|
|
29723
29767
|
var init_version = __esm({
|
|
29724
29768
|
"src/version.ts"() {
|
|
29725
29769
|
"use strict";
|
|
29726
|
-
PACKAGE_VERSION = "0.
|
|
29770
|
+
PACKAGE_VERSION = "0.32.0";
|
|
29727
29771
|
}
|
|
29728
29772
|
});
|
|
29729
29773
|
|
|
@@ -30355,9 +30399,9 @@ var init_mcp_tool_schemas = __esm({
|
|
|
30355
30399
|
gl: import_zod35.z.string().length(2).default("us").describe("Google country code inferred from location."),
|
|
30356
30400
|
hl: import_zod35.z.string().length(2).default("en").describe("Language inferred from user request."),
|
|
30357
30401
|
maxResults: import_zod35.z.number().int().min(1).max(50).default(10).describe("Number of candidates to return. Default 10, maximum 50."),
|
|
30358
|
-
includeServices: import_zod35.z.boolean().default(false).describe("Open each returned
|
|
30359
|
-
proxyMode: import_zod35.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Leave unset for the default
|
|
30360
|
-
proxyZip: import_zod35.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override when proxyMode is location."),
|
|
30402
|
+
includeServices: import_zod35.z.boolean().default(false).describe("Open each returned business profile to include its configured services and areas served when available. Adds a page visit per business; does not collect review cards."),
|
|
30403
|
+
proxyMode: import_zod35.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Leave unset for the default route (stealth browser on the managed ISP proxy, retried on a fresh session when Google blocks). Localization comes from the city in the query plus gl/hl. location forces an explicit residential proxy \u2014 not recommended for Google."),
|
|
30404
|
+
proxyZip: import_zod35.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override, only used when proxyMode is location."),
|
|
30361
30405
|
debug: import_zod35.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics.")
|
|
30362
30406
|
};
|
|
30363
30407
|
DirectoryWorkflowInputSchema = {
|
|
@@ -30371,7 +30415,7 @@ var init_mcp_tool_schemas = __esm({
|
|
|
30371
30415
|
includeZipGroups: import_zod35.z.boolean().default(true).describe("Attach ZIP groups from a configured US ZIPS CSV when available (MCP_SCRAPER_USZIPS_CSV_PATH or usZipsCsvPath)."),
|
|
30372
30416
|
usZipsCsvPath: import_zod35.z.string().optional().describe("Local/test-only path to a US ZIPS CSV (state_abbr, zipcode, county, city columns). Deployed APIs should use MCP_SCRAPER_USZIPS_CSV_PATH instead. For ZIP enrichment, set MCP_SCRAPER_USZIPS_CSV_PATH on the server, or pass this in local/test mode."),
|
|
30373
30417
|
saveCsv: import_zod35.z.boolean().default(true).describe("Save a directory-ready CSV of results to the MCP Scraper output directory and return its path."),
|
|
30374
|
-
proxyMode: import_zod35.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy behavior per city search. Leave unset for the
|
|
30418
|
+
proxyMode: import_zod35.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy behavior per city search. Leave unset for the default route (stealth browser on the managed ISP proxy, retried fresh on a Google block). location forces an explicit residential proxy \u2014 not recommended for Google."),
|
|
30375
30419
|
proxyZip: import_zod35.z.string().regex(/^\d{5}$/).optional().describe("Optional ZIP override for proxy targeting; normally omitted."),
|
|
30376
30420
|
debug: import_zod35.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics.")
|
|
30377
30421
|
};
|
|
@@ -32191,7 +32235,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
32191
32235
|
}, async (input) => formatMapsPlaceIntel(await executor.mapsPlaceIntel(input), input));
|
|
32192
32236
|
server.registerTool("maps_search", {
|
|
32193
32237
|
title: "Google Maps Business Search",
|
|
32194
|
-
description: "Search Google local results for multiple businesses by category, niche, or local market \u2014 leads, prospects, competitors, or beyond the 3-pack.
|
|
32238
|
+
description: "Search Google local results for multiple businesses by category, niche, or local market \u2014 leads, prospects, competitors, or beyond the 3-pack. Reaches the local-results list from the organic page and paginates it, returning up to 50 candidates (default 10) with names, place URLs, CIDs, and ratings. Leave proxyMode unset. Set includeServices to also open each business profile for its services and areas served; review cards are never collected by this tool.",
|
|
32195
32239
|
inputSchema: MapsSearchInputSchema,
|
|
32196
32240
|
outputSchema: recordOutputSchema("maps_search", MapsSearchOutputSchema),
|
|
32197
32241
|
annotations: liveWebToolAnnotations("Google Maps Business Search")
|