mcp-scraper 0.1.9 → 0.2.1

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.
Files changed (44) hide show
  1. package/README.md +74 -8
  2. package/dist/bin/api-server.cjs +5615 -3733
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +2 -2
  5. package/dist/bin/browser-agent-stdio-server.cjs +391 -0
  6. package/dist/bin/browser-agent-stdio-server.cjs.map +1 -0
  7. package/dist/bin/browser-agent-stdio-server.d.cts +1 -0
  8. package/dist/bin/browser-agent-stdio-server.d.ts +1 -0
  9. package/dist/bin/browser-agent-stdio-server.js +390 -0
  10. package/dist/bin/browser-agent-stdio-server.js.map +1 -0
  11. package/dist/bin/mcp-stdio-server.cjs +170 -12
  12. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  13. package/dist/bin/mcp-stdio-server.js +3 -2
  14. package/dist/bin/mcp-stdio-server.js.map +1 -1
  15. package/dist/bin/paa-harvest.cjs +223 -74
  16. package/dist/bin/paa-harvest.cjs.map +1 -1
  17. package/dist/bin/paa-harvest.js +2 -2
  18. package/dist/{chunk-ZK456YXN.js → chunk-IQOCZGJJ.js} +58 -4
  19. package/dist/chunk-IQOCZGJJ.js.map +1 -0
  20. package/dist/{chunk-ZMOWIBMK.js → chunk-M2S27J6Z.js} +9 -2
  21. package/dist/{chunk-ZMOWIBMK.js.map → chunk-M2S27J6Z.js.map} +1 -1
  22. package/dist/{chunk-TM22BLWP.js → chunk-MY3S7EX7.js} +221 -76
  23. package/dist/chunk-MY3S7EX7.js.map +1 -0
  24. package/dist/{chunk-JNC32DMS.js → chunk-OR7DLLH2.js} +175 -16
  25. package/dist/chunk-OR7DLLH2.js.map +1 -0
  26. package/dist/chunk-XR65SANX.js +7 -0
  27. package/dist/chunk-XR65SANX.js.map +1 -0
  28. package/dist/index.cjs +223 -74
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +1 -0
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.js +2 -2
  33. package/dist/{server-MTXAJG5J.js → server-CJMX2QUM.js} +1655 -194
  34. package/dist/server-CJMX2QUM.js.map +1 -0
  35. package/dist/{worker-AUCXFHEL.js → worker-NAKGTIF5.js} +4 -4
  36. package/docs/specs/api-forge-spec.md +234 -0
  37. package/docs/specs/deferred-work-spec.md +74 -0
  38. package/docs/specs/oauth-mcp-spec.md +213 -0
  39. package/package.json +3 -2
  40. package/dist/chunk-JNC32DMS.js.map +0 -1
  41. package/dist/chunk-TM22BLWP.js.map +0 -1
  42. package/dist/chunk-ZK456YXN.js.map +0 -1
  43. package/dist/server-MTXAJG5J.js.map +0 -1
  44. /package/dist/{worker-AUCXFHEL.js.map → worker-NAKGTIF5.js.map} +0 -0
@@ -1,6 +1,9 @@
1
+ import {
2
+ PACKAGE_VERSION
3
+ } from "./chunk-XR65SANX.js";
1
4
  import {
2
5
  sanitizeVendorName
3
- } from "./chunk-ZMOWIBMK.js";
6
+ } from "./chunk-M2S27J6Z.js";
4
7
 
5
8
  // src/harvest-timeout.ts
6
9
  var VERCEL_FUNCTION_MAX_MS = 3e5;
@@ -21,9 +24,6 @@ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mc
21
24
  import { readdirSync, readFileSync, statSync } from "fs";
22
25
  import { basename, join as join2 } from "path";
23
26
 
24
- // src/version.ts
25
- var PACKAGE_VERSION = "0.1.9";
26
-
27
27
  // src/mcp/mcp-response-formatter.ts
28
28
  import { mkdirSync, writeFileSync } from "fs";
29
29
  import { homedir } from "os";
@@ -688,6 +688,11 @@ function formatMapsSearch(raw, input) {
688
688
  if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
689
689
  const d = parsed.data;
690
690
  const results = d.results ?? [];
691
+ const normalizedResults = results.map((result) => ({
692
+ ...result,
693
+ phone: result.phone ?? null,
694
+ hoursStatus: result.hoursStatus ?? null
695
+ }));
691
696
  const searchQuery = d.searchQuery ?? [input.query, input.location].filter(Boolean).join(" ");
692
697
  const requestedMax = d.requestedMaxResults ?? input.maxResults ?? 10;
693
698
  const durationMs = d.durationMs;
@@ -727,7 +732,79 @@ ${rows}`,
727
732
  extractedAt: d.extractedAt,
728
733
  requestedMaxResults: requestedMax,
729
734
  resultCount: results.length,
730
- results,
735
+ results: normalizedResults,
736
+ durationMs: durationMs ?? 0
737
+ }
738
+ };
739
+ }
740
+ function formatDirectoryWorkflow(raw, input) {
741
+ const parsed = parseData(raw);
742
+ if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
743
+ const d = parsed.data;
744
+ const cities = (d.cities ?? []).map((city) => ({
745
+ ...city,
746
+ results: city.results.map((result) => ({
747
+ ...result,
748
+ phone: result.phone ?? null,
749
+ hoursStatus: result.hoursStatus ?? null
750
+ }))
751
+ }));
752
+ const warnings = d.warnings ?? [];
753
+ const csvPath = d.csvPath ?? null;
754
+ const totalResultCount = d.totalResultCount ?? cities.reduce((sum, city) => sum + city.resultCount, 0);
755
+ const durationMs = d.durationMs;
756
+ const marketRows = cities.map((city) => {
757
+ const zips = city.zips?.length ? city.zips.slice(0, 8).join(" ") + (city.zips.length > 8 ? ` +${city.zips.length - 8}` : "") : "\u2014";
758
+ return `| ${cell(city.city)} | ${city.population.toLocaleString()} | ${city.zips?.length ?? 0} | ${city.resultCount} | ${city.status} | ${cell(zips)} |`;
759
+ }).join("\n");
760
+ const businessRows = cities.flatMap((city) => city.results.slice(0, 3).map((result) => ({ city, result }))).map(({ city, result }) => {
761
+ const rating = [result.rating, result.reviewCount ? `(${result.reviewCount})` : null].filter(Boolean).join(" ");
762
+ return `| ${cell(city.city)} | ${result.position} | ${cell(result.name)} | ${cell(result.category)} | ${cell(rating)} | ${result.websiteUrl ? `[site](${result.websiteUrl})` : "\u2014"} | [maps](${result.placeUrl}) |`;
763
+ }).join("\n");
764
+ const warningText = warnings.length ? `
765
+ ## Warnings
766
+ ${warnings.map((w) => `- ${w}`).join("\n")}` : "";
767
+ const csvText = csvPath ? `
768
+ **CSV:** \`${csvPath}\`` : "";
769
+ const full = [
770
+ `# Directory Workflow: ${input.query}`,
771
+ `**Markets:** ${cities.length} \xB7 **Maps results:** ${totalResultCount} \xB7 **State:** ${d.state ?? input.state ?? "US"} \xB7 **Population threshold:** ${d.minPopulation ?? input.minPopulation ?? 1e5}`,
772
+ csvText,
773
+ `
774
+ ## Markets
775
+ | City | Population | ZIPs | Maps Results | Status | ZIP Sample |
776
+ |---|---:|---:|---:|---|---|
777
+ ${marketRows}`,
778
+ businessRows ? `
779
+ ## Top Candidates By City
780
+ | City | # | Name | Category | Rating | Website | Maps |
781
+ |---|---:|---|---|---|---|---|
782
+ ${businessRows}` : null,
783
+ warningText,
784
+ `
785
+ ## Sources
786
+ - Population: ${d.censusSourceUrl ?? "Census Population Estimates Program"}
787
+ - ZIP groups: ${d.usZipsSourcePath ?? "not configured"}`,
788
+ durationMs != null ? `
789
+ *Completed in ${(durationMs / 1e3).toFixed(1)}s*` : null
790
+ ].filter(Boolean).join("\n");
791
+ return {
792
+ ...oneBlock(full),
793
+ structuredContent: {
794
+ query: d.query,
795
+ state: d.state,
796
+ minPopulation: d.minPopulation,
797
+ populationYear: d.populationYear,
798
+ maxResultsPerCity: d.maxResultsPerCity,
799
+ concurrency: d.concurrency,
800
+ censusSourceUrl: d.censusSourceUrl,
801
+ usZipsSourcePath: d.usZipsSourcePath ?? null,
802
+ warnings,
803
+ extractedAt: d.extractedAt,
804
+ selectedCityCount: d.selectedCityCount,
805
+ totalResultCount,
806
+ csvPath,
807
+ cities,
731
808
  durationMs: durationMs ?? 0
732
809
  }
733
810
  };
@@ -893,8 +970,8 @@ var HarvestPaaInputSchema = {
893
970
  gl: z.string().length(2).default("us").describe("Google country code inferred from location or user language. Examples: United States us, United Kingdom gb, Japan jp, Canada ca, Australia au."),
894
971
  hl: z.string().default("en").describe("Google interface/content language inferred from the user request. Use en unless the user asks for another language or locale."),
895
972
  device: z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
896
- proxyMode: z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode. Use location by default so city/state searches create or reuse a matching residential proxy. Use configured for the static configured proxy. Use none only for direct-network debugging."),
897
- proxyZip: z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use only when the user gives a specific ZIP or city-center proxy targeting needs to be forced."),
973
+ proxyMode: z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode. Use location by default for US city/state SERPs; it creates a fresh residential proxy ID per attempt and retries CAPTCHA, proxy tunnel failure, and wrong-location evidence before returning. Use configured only for the static configured proxy. Use none only for direct-network debugging."),
974
+ proxyZip: z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use when the user gives a specific ZIP or when city-center targeting needs to be forced. With proxyMode location this ZIP is used for each fresh proxy attempt."),
898
975
  debug: z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior.")
899
976
  };
900
977
  var ExtractUrlInputSchema = {
@@ -951,7 +1028,25 @@ var MapsSearchInputSchema = {
951
1028
  location: z.string().optional().describe('City, region, country, or service area for the Maps search, e.g. "Denver, CO". Infer from the user request when present.'),
952
1029
  gl: z.string().length(2).default("us").describe("Google country code inferred from location."),
953
1030
  hl: z.string().length(2).default("en").describe("Language inferred from user request."),
954
- maxResults: z.number().int().min(1).max(50).default(10).describe("Number of Google Maps business/profile candidates to return. Default 10. Maximum 50. Use 10 unless the user asks for more.")
1031
+ maxResults: z.number().int().min(1).max(50).default(10).describe("Number of Google Maps business/profile candidates to return. Default 10. Maximum 50. Use 10 unless the user asks for more."),
1032
+ proxyMode: z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode. Use location by default for US city/state Maps searches; it creates a fresh residential proxy ID when the browser service is available. Use configured for the server proxy ID, and none only for local direct-network debugging."),
1033
+ proxyZip: z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use when the user gives a specific ZIP or city-center ZIP."),
1034
+ debug: z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics when debugging Maps localization, CAPTCHA, or proxy behavior.")
1035
+ };
1036
+ var DirectoryWorkflowInputSchema = {
1037
+ query: z.string().min(1).describe("Business category, niche, or keyword to search on Google Maps for every selected market, e.g. roofers, dentists, med spas. Do not include the city here."),
1038
+ state: z.string().min(2).default("TN").describe("US state abbreviation or state name used to select Census places, e.g. TN or Tennessee."),
1039
+ minPopulation: z.number().int().min(0).default(1e5).describe('Minimum Census place population for market selection. Use 100000 for "cities above 100k population".'),
1040
+ populationYear: z.number().int().min(2020).max(2025).default(2025).describe("Census population estimate year from the 2020-2025 Population Estimates Program city/place dataset."),
1041
+ maxCities: z.number().int().min(1).max(100).default(25).describe("Maximum number of markets to process after sorting by population descending."),
1042
+ maxResultsPerCity: z.number().int().min(1).max(50).default(50).describe("Google Maps business/profile candidates to collect for each city. Maximum 50."),
1043
+ concurrency: z.number().int().min(1).max(5).default(5).describe("How many city Maps searches to run in parallel. Use 5 for broad directory batches unless debugging."),
1044
+ includeZipGroups: z.boolean().default(true).describe("Attach ZIP groups from a configured US ZIPS CSV when available. Set MCP_SCRAPER_USZIPS_CSV_PATH on the API server or pass usZipsCsvPath in local/test mode."),
1045
+ usZipsCsvPath: z.string().optional().describe("Local/test-only path to a US ZIPS CSV with state_abbr, zipcode, county, city columns, such as Lead Magician tools/analytics/data/uszips.csv. Deployed APIs should use MCP_SCRAPER_USZIPS_CSV_PATH instead."),
1046
+ saveCsv: z.boolean().default(true).describe("Save a directory-ready CSV to the MCP Scraper output directory and return its path. CSV rows include source_location, result_position, business_name, review_stars, category, address, phone, hours_status, website_url, directions_url, place_url, CID fields, population, and ZIP groups."),
1047
+ proxyMode: z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode for every city Maps search. Use location by default for US city/state batches; it creates fresh residential proxy IDs when the browser service is available. Use configured for the server proxy ID, and none only for local direct-network debugging."),
1048
+ proxyZip: z.string().regex(/^\d{5}$/).optional().describe("Optional ZIP override for proxy targeting. Normally omit it so each city can use its Lead Magician ZIP group or city/state location."),
1049
+ debug: z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics in each Maps browser session when supported.")
955
1050
  };
956
1051
  var NullableString = z.string().nullable();
957
1052
  var MapsSearchOutputSchema = {
@@ -972,12 +1067,62 @@ var MapsSearchOutputSchema = {
972
1067
  reviewCount: NullableString,
973
1068
  category: NullableString,
974
1069
  address: NullableString,
1070
+ phone: NullableString,
1071
+ hoursStatus: NullableString,
975
1072
  websiteUrl: NullableString,
976
1073
  directionsUrl: NullableString,
977
1074
  metadata: z.array(z.string())
978
1075
  })),
979
1076
  durationMs: z.number().int().min(0)
980
1077
  };
1078
+ var DirectoryMapsBusinessOutput = z.object({
1079
+ position: z.number().int().min(1),
1080
+ name: z.string(),
1081
+ placeUrl: z.string().url(),
1082
+ cid: NullableString,
1083
+ cidDecimal: NullableString,
1084
+ rating: NullableString,
1085
+ reviewCount: NullableString,
1086
+ category: NullableString,
1087
+ address: NullableString,
1088
+ phone: NullableString,
1089
+ hoursStatus: NullableString,
1090
+ websiteUrl: NullableString,
1091
+ directionsUrl: NullableString,
1092
+ metadata: z.array(z.string())
1093
+ });
1094
+ var DirectoryWorkflowOutputSchema = {
1095
+ query: z.string(),
1096
+ state: z.string(),
1097
+ minPopulation: z.number().int().min(0),
1098
+ populationYear: z.number().int().min(2020).max(2025),
1099
+ maxResultsPerCity: z.number().int().min(1).max(50),
1100
+ concurrency: z.number().int().min(1).max(5),
1101
+ censusSourceUrl: z.string().url(),
1102
+ usZipsSourcePath: NullableString,
1103
+ warnings: z.array(z.string()),
1104
+ extractedAt: z.string(),
1105
+ selectedCityCount: z.number().int().min(0),
1106
+ totalResultCount: z.number().int().min(0),
1107
+ csvPath: NullableString,
1108
+ cities: z.array(z.object({
1109
+ city: z.string(),
1110
+ state: z.string(),
1111
+ location: z.string(),
1112
+ cityKey: z.string(),
1113
+ censusName: z.string(),
1114
+ population: z.number().int().min(0),
1115
+ populationYear: z.number().int().min(2020).max(2025),
1116
+ zips: z.array(z.string()),
1117
+ counties: z.array(z.string()),
1118
+ status: z.enum(["ok", "empty", "failed"]),
1119
+ error: NullableString,
1120
+ resultCount: z.number().int().min(0),
1121
+ durationMs: z.number().int().min(0),
1122
+ results: z.array(DirectoryMapsBusinessOutput)
1123
+ })),
1124
+ durationMs: z.number().int().min(0)
1125
+ };
981
1126
  var OrganicResultOutput = z.object({
982
1127
  position: z.number().int(),
983
1128
  title: z.string(),
@@ -1157,8 +1302,8 @@ var SearchSerpInputSchema = {
1157
1302
  gl: z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
1158
1303
  hl: z.string().default("en").describe("Google interface/content language inferred from user request."),
1159
1304
  device: z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
1160
- proxyMode: z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode. Use location by default so city/state searches create or reuse a matching residential proxy. Use configured for the static configured proxy. Use none only for direct-network debugging."),
1161
- proxyZip: z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use only when the user gives a specific ZIP or city-center proxy targeting needs to be forced."),
1305
+ proxyMode: z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode. Use location by default for US city/state SERPs; it creates a fresh residential proxy ID per attempt and retries CAPTCHA, proxy tunnel failure, and wrong-location evidence before returning. Use configured only for the static configured proxy. Use none only for direct-network debugging."),
1306
+ proxyZip: z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use when the user gives a specific ZIP or when city-center targeting needs to be forced. With proxyMode location this ZIP is used for each fresh proxy attempt."),
1162
1307
  debug: z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior."),
1163
1308
  pages: z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132)")
1164
1309
  };
@@ -1168,8 +1313,8 @@ var CaptureSerpSnapshotInputSchema = {
1168
1313
  gl: z.string().length(2).default("us").describe("Google country code inferred from the requested market, e.g. us, gb, ca, au."),
1169
1314
  hl: z.string().default("en").describe("Google interface/content language inferred from the user request."),
1170
1315
  device: z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only when the user asks for mobile rankings or mobile SERP evidence."),
1171
- proxyMode: z.enum(["location", "configured", "none"]).default("location").describe("Proxy behavior for capture. Use location for localized residential proxy targeting, configured for the static residential proxy, and none only for direct-network debugging."),
1172
- proxyZip: z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting when a precise city-center or ZIP proxy is needed."),
1316
+ proxyMode: z.enum(["location", "configured", "none"]).default("location").describe("Proxy behavior for capture. Use location for localized US residential evidence; it creates a fresh proxy ID per attempt and retries CAPTCHA, proxy tunnel failure, and wrong-location evidence before returning. Use configured only for the static residential proxy, and none only for direct-network debugging."),
1317
+ proxyZip: z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting when a precise city-center or ZIP proxy is needed. With proxyMode location this ZIP is used for each fresh proxy attempt."),
1173
1318
  pages: z.number().int().min(1).max(2).default(1).describe("Number of Google result pages to capture. Use 1 normally and 2 only when the user needs deeper ranking evidence."),
1174
1319
  debug: z.boolean().default(false).describe("Include sanitized browser, proxy, and location diagnostics. Use true when debugging localization, CAPTCHA, proxy selection, or capture reliability."),
1175
1320
  includePageSnapshots: z.boolean().default(false).describe("Also capture ranking-page snapshots for selected SERP URLs through the same product capture path."),
@@ -1244,14 +1389,14 @@ function buildPaaExtractorMcpServer(executor, options = {}) {
1244
1389
  if (savesReports) registerSavedReportResources(server);
1245
1390
  server.registerTool("harvest_paa", {
1246
1391
  title: "Google PAA + SERP Harvest",
1247
- description: withReportNote('Best default tool for Google search research. Extracts People Also Ask questions plus answers/source URLs, organic SERP, local pack when present, entity IDs (CID/GCID/KG MID), and AI Overview. Infer the user language: split topic from location (e.g. "best hvac company in Denver CO" => query "best hvac company", location "Denver, CO", gl "us", hl "en"). Use maxQuestions 30 normally, 100-200 for "full", "deep", "all", or comprehensive research. Deep harvests above 100 questions can run for several minutes with no interim progress \u2014 warn the user before starting one and keep maxQuestions at or below 100 unless they explicitly want a deep harvest. Credits are charged by extracted question; unused request hold is refunded.'),
1392
+ description: withReportNote('Best default tool for Google search research. Extracts People Also Ask questions plus answers/source URLs, organic SERP, local pack when present, entity IDs (CID/GCID/KG MID), and AI Overview. Infer the user language: split topic from location (e.g. "best hvac company in Denver CO" => query "best hvac company", location "Denver, CO", gl "us", hl "en"). For US local SERPs, leave proxyMode as location so the service uses fresh residential proxy IDs across retries and rejects wrong-location evidence instead of returning a bad market. Use maxQuestions 30 normally, 100-200 for "full", "deep", "all", or comprehensive research. Deep harvests above 100 questions can run for several minutes with no interim progress \u2014 warn the user before starting one and keep maxQuestions at or below 100 unless they explicitly want a deep harvest. Credits are charged by extracted question; unused request hold is refunded.'),
1248
1393
  inputSchema: HarvestPaaInputSchema,
1249
1394
  outputSchema: HarvestPaaOutputSchema,
1250
1395
  annotations: liveWebToolAnnotations("Google PAA + SERP Harvest")
1251
1396
  }, async (input) => formatHarvestPaa(await executor.harvestPaa(input), input));
1252
1397
  server.registerTool("search_serp", {
1253
1398
  title: "Google SERP Lookup",
1254
- description: withReportNote("Fast Google SERP lookup without PAA expansion. Use when the user asks for rankings, organic results, local pack, quick SERP, or positions. Split topic from location and infer gl/hl from the user request."),
1399
+ description: withReportNote("Fast Google SERP lookup without PAA expansion. Use when the user asks for rankings, organic results, local pack, quick SERP, or positions. Split topic from location and infer gl/hl from the user request. For US city/state rankings, keep proxyMode as location and pass proxyZip when a city-center ZIP is known; location mode uses fresh residential proxy IDs and retries CAPTCHA, proxy tunnel failures, and wrong-location evidence before returning."),
1255
1400
  inputSchema: SearchSerpInputSchema,
1256
1401
  outputSchema: SearchSerpOutputSchema,
1257
1402
  annotations: liveWebToolAnnotations("Google SERP Lookup")
@@ -1319,11 +1464,18 @@ function buildPaaExtractorMcpServer(executor, options = {}) {
1319
1464
  }, async (input) => formatMapsPlaceIntel(await executor.mapsPlaceIntel(input), input));
1320
1465
  server.registerTool("maps_search", {
1321
1466
  title: "Google Maps Business Search",
1322
- description: withReportNote('Search Google Maps for multiple businesses/profiles by category, niche, keyword, or local market. Use this when the user asks for several Google Business Profiles, GMBs, GBPs, leads, prospects, competitors, or "more than the 3-pack." Returns up to 50 candidates with names, place URLs, CIDs when available, ratings, review counts, and profile metadata. Default maxResults is 10; maximum is 50. Use maps_place_intel afterward only when a selected business needs full details and reviews.'),
1467
+ description: withReportNote('Search Google Maps for multiple businesses/profiles by category, niche, keyword, or local market. Use this when the user asks for several Google Business Profiles, GMBs, GBPs, leads, prospects, competitors, or "more than the 3-pack." For US city/state Maps searches, keep proxyMode as location so the browser service can create a fresh residential proxy ID for that market; pass proxyZip only when a specific ZIP or city-center ZIP is known. Returns up to 50 candidates with names, place URLs, CIDs when available, ratings, review counts, and profile metadata. Default maxResults is 10; maximum is 50. Use maps_place_intel afterward only when a selected business needs full details and reviews.'),
1323
1468
  inputSchema: MapsSearchInputSchema,
1324
1469
  outputSchema: MapsSearchOutputSchema,
1325
1470
  annotations: liveWebToolAnnotations("Google Maps Business Search")
1326
1471
  }, async (input) => formatMapsSearch(await executor.mapsSearch(input), input));
1472
+ server.registerTool("directory_workflow", {
1473
+ title: "Directory Workflow: Markets + Maps",
1474
+ description: withReportNote('Build directory/prospecting datasets by selecting US city markets from the free Census Population Estimates city/place dataset, optionally joining configured US ZIPS/Lead Magician ZIP groups, then running Google Maps business searches for each city in parallel. Use this when the user wants "all cities over 100k population in a state", "build a directory CSV", "find markets then get Maps data", or similar location-database + Maps workflows. Set minPopulation, state, query, maxResultsPerCity, and concurrency. Use concurrency up to 5 for parallel city sessions. Keep proxyMode as location so each city can use a fresh residential proxy ID when the browser service is available; retryable city failures use fresh proxies across attempts. Saved CSV rows include source_location, result_position, business_name, review_stars, category, address, phone, hours_status, website_url, directions_url, place_url, cid, cid_decimal, city population, and ZIP groups. This workflow captures star ratings from Maps list cards, not profile review counts; use maps_place_intel only when a selected profile needs deeper review details. For local Lead Magician ZIP enrichment, set MCP_SCRAPER_USZIPS_CSV_PATH on the API server or pass usZipsCsvPath only in local/test mode.'),
1475
+ inputSchema: DirectoryWorkflowInputSchema,
1476
+ outputSchema: DirectoryWorkflowOutputSchema,
1477
+ annotations: liveWebToolAnnotations("Directory Workflow: Markets + Maps")
1478
+ }, async (input) => formatDirectoryWorkflow(await executor.directoryWorkflow(input), input));
1327
1479
  server.registerTool("credits_info", {
1328
1480
  title: "MCP Scraper Credits & Costs",
1329
1481
  description: "Answer questions about MCP Scraper credits: current credit balance, what a specific tool/action costs, the full cost table, and optionally recent credit ledger entries. Does not expose payment methods or credit card information.",
@@ -1432,6 +1584,12 @@ var HttpMcpToolExecutor = class {
1432
1584
  mapsSearch(input) {
1433
1585
  return this.call("/maps/search", input);
1434
1586
  }
1587
+ directoryWorkflow(input) {
1588
+ const cityCount = typeof input.maxCities === "number" ? input.maxCities : 25;
1589
+ const concurrency = typeof input.concurrency === "number" && input.concurrency > 0 ? input.concurrency : 5;
1590
+ const timeoutMs = this.httpTimeoutOverrideMs ?? Math.min(9e5, Math.max(18e4, Math.ceil(cityCount / concurrency) * 12e4));
1591
+ return this.call("/directory/run", input, timeoutMs);
1592
+ }
1435
1593
  creditsInfo(input) {
1436
1594
  return this.call("/billing/credits", input);
1437
1595
  }
@@ -1446,10 +1604,11 @@ var HttpMcpToolExecutor = class {
1446
1604
  export {
1447
1605
  harvestTimeoutBudget,
1448
1606
  configureReportSaving,
1607
+ outputBaseDir,
1449
1608
  CaptureSerpSnapshotInputSchema,
1450
1609
  CaptureSerpPageSnapshotsInputSchema,
1451
1610
  liveWebToolAnnotations,
1452
1611
  buildPaaExtractorMcpServer,
1453
1612
  HttpMcpToolExecutor
1454
1613
  };
1455
- //# sourceMappingURL=chunk-JNC32DMS.js.map
1614
+ //# sourceMappingURL=chunk-OR7DLLH2.js.map