mcp-scraper 0.2.7 → 0.2.8

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 (42) hide show
  1. package/README.md +6 -4
  2. package/dist/bin/api-server.cjs +364 -162
  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 +1 -1
  6. package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
  7. package/dist/bin/browser-agent-stdio-server.js +2 -2
  8. package/dist/bin/mcp-scraper-combined-stdio-server.cjs +68 -13
  9. package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
  10. package/dist/bin/mcp-scraper-combined-stdio-server.js +3 -3
  11. package/dist/bin/mcp-scraper-install.cjs +2 -1
  12. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  13. package/dist/bin/mcp-scraper-install.js +2 -1
  14. package/dist/bin/mcp-scraper-install.js.map +1 -1
  15. package/dist/bin/mcp-stdio-server.cjs +68 -13
  16. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  17. package/dist/bin/mcp-stdio-server.js +2 -2
  18. package/dist/bin/paa-harvest.cjs +42 -6
  19. package/dist/bin/paa-harvest.cjs.map +1 -1
  20. package/dist/bin/paa-harvest.js +1 -1
  21. package/dist/{chunk-3TF6UT2P.js → chunk-5HMOPP76.js} +2 -2
  22. package/dist/{chunk-7SI6XIR3.js → chunk-6NEXSNSA.js} +69 -14
  23. package/dist/chunk-6NEXSNSA.js.map +1 -0
  24. package/dist/{chunk-MY3S7EX7.js → chunk-CQTAKXBN.js} +43 -7
  25. package/dist/chunk-CQTAKXBN.js.map +1 -0
  26. package/dist/chunk-I26QN7WQ.js +7 -0
  27. package/dist/chunk-I26QN7WQ.js.map +1 -0
  28. package/dist/index.cjs +42 -6
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.js +1 -1
  31. package/dist/{server-RIKRBDOI.js → server-BTTDFPSQ.js} +238 -133
  32. package/dist/server-BTTDFPSQ.js.map +1 -0
  33. package/dist/{worker-NAKGTIF5.js → worker-5O44YBF4.js} +2 -2
  34. package/docs/mcp-tool-quality-spec.md +3 -1
  35. package/package.json +1 -1
  36. package/dist/chunk-7SI6XIR3.js.map +0 -1
  37. package/dist/chunk-IZE5UW7Y.js +0 -7
  38. package/dist/chunk-IZE5UW7Y.js.map +0 -1
  39. package/dist/chunk-MY3S7EX7.js.map +0 -1
  40. package/dist/server-RIKRBDOI.js.map +0 -1
  41. /package/dist/{chunk-3TF6UT2P.js.map → chunk-5HMOPP76.js.map} +0 -0
  42. /package/dist/{worker-NAKGTIF5.js.map → worker-5O44YBF4.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  harvest
3
- } from "./chunk-MY3S7EX7.js";
3
+ } from "./chunk-CQTAKXBN.js";
4
4
  import "./chunk-M2S27J6Z.js";
5
5
 
6
6
  // src/video/VideoGenerator.ts
@@ -31,7 +31,7 @@ import {
31
31
  deleteKernelProxyId,
32
32
  harvest,
33
33
  resolveKernelProxyId
34
- } from "./chunk-MY3S7EX7.js";
34
+ } from "./chunk-CQTAKXBN.js";
35
35
  import {
36
36
  CaptureSerpPageSnapshotsInputSchema,
37
37
  CaptureSerpSnapshotInputSchema,
@@ -41,13 +41,13 @@ import {
41
41
  harvestTimeoutBudget,
42
42
  liveWebToolAnnotations,
43
43
  outputBaseDir
44
- } from "./chunk-7SI6XIR3.js";
44
+ } from "./chunk-6NEXSNSA.js";
45
45
  import {
46
46
  CaptchaError,
47
47
  RECAPTCHA_INSTRUCTIONS,
48
48
  sanitizeVendorName
49
49
  } from "./chunk-M2S27J6Z.js";
50
- import "./chunk-IZE5UW7Y.js";
50
+ import "./chunk-I26QN7WQ.js";
51
51
  import {
52
52
  SiteAuditJobRowSchema,
53
53
  cancelJob,
@@ -9893,24 +9893,205 @@ var MapsSearchExtractor = class {
9893
9893
  }
9894
9894
  };
9895
9895
 
9896
- // src/api/maps-routes.ts
9897
- function mapsErrorResponse(c, msg, errorCode) {
9898
- const blocked = msg.includes("CAPTCHA") || msg.includes("blocked");
9899
- return c.json({
9900
- error: sanitizeVendorName(msg),
9901
- error_code: blocked ? "captcha_or_blocked" : errorCode,
9902
- retryable: blocked
9903
- }, blocked ? 503 : 500);
9896
+ // src/maps-search-rotation.ts
9897
+ var MAPS_SEARCH_MAX_ATTEMPTS = 3;
9898
+ var MAPS_SEARCH_LOCATION_PROXY_MAX_ATTEMPTS = 5;
9899
+ var MapsSearchRotationError = class extends Error {
9900
+ constructor(message, attempts, retryable, errorCode = "maps_search_failed") {
9901
+ super(message);
9902
+ this.attempts = attempts;
9903
+ this.retryable = retryable;
9904
+ this.errorCode = errorCode;
9905
+ }
9906
+ attempts;
9907
+ retryable;
9908
+ errorCode;
9909
+ name = "MapsSearchRotationError";
9910
+ };
9911
+ function mapsSearchMaxAttemptsForProxyMode(proxyMode) {
9912
+ return proxyMode === "location" ? MAPS_SEARCH_LOCATION_PROXY_MAX_ATTEMPTS : MAPS_SEARCH_MAX_ATTEMPTS;
9913
+ }
9914
+ function mapsSearchErrorMessage(err) {
9915
+ return sanitizeVendorName(err instanceof Error ? err.message : String(err));
9916
+ }
9917
+ function looksLikeProxyTunnelFailure(message) {
9918
+ return /ERR_TUNNEL_CONNECTION_FAILED|ERR_PROXY_CONNECTION_FAILED|ERR_SOCKS_CONNECTION_FAILED|tunnel connection failed|proxy connection failed|transport error: proxy/i.test(message);
9919
+ }
9920
+ function looksLikeProxyUnavailable(message) {
9921
+ return /proxy unavailable|proxy_unavailable|connection_test_failed|did not return a proxy id|configured fallback/i.test(message);
9922
+ }
9923
+ function looksLikeBrowserClosed(message) {
9924
+ return /Target page, context or browser has been closed|browser has been closed|context has been closed|page has been closed|browser not found|CDP session closed|Connection closed|WebSocket.*closed|Protocol error.*closed|Browser closed|disconnected/i.test(message);
9925
+ }
9926
+ function mapsSearchFailureOutcome(err) {
9927
+ if (err instanceof CaptchaError) return "captcha";
9928
+ const message = mapsSearchErrorMessage(err);
9929
+ if (/timeout|timed out|Timeout \d+ms exceeded|deadline/i.test(message)) return "timeout";
9930
+ if (looksLikeProxyTunnelFailure(message)) return "proxy_tunnel_failed";
9931
+ if (looksLikeProxyUnavailable(message)) return "proxy_unavailable";
9932
+ if (looksLikeBrowserClosed(message)) return "browser_closed";
9933
+ return "unknown_error";
9904
9934
  }
9905
- async function cleanupDisposableProxy(kernelApiKey, proxyId) {
9935
+ function retryableMapsSearchError(err, proxyMode) {
9936
+ if (err instanceof CaptchaError) return true;
9937
+ const message = mapsSearchErrorMessage(err);
9938
+ if (/timeout|timed out|Timeout \d+ms exceeded|deadline/i.test(message)) return true;
9939
+ if (looksLikeBrowserClosed(message)) return true;
9940
+ return proxyMode === "location" && (looksLikeProxyTunnelFailure(message) || looksLikeProxyUnavailable(message));
9941
+ }
9942
+ function suffix(value) {
9943
+ return value ? value.slice(-8) : null;
9944
+ }
9945
+ function attemptFromDebug(input) {
9946
+ const network = input.debug.networkLocation;
9947
+ const browserSessionId = input.debug.kernel?.sessionId ?? null;
9948
+ const target = input.resolution?.target ?? null;
9949
+ return {
9950
+ attemptNumber: input.attemptIndex + 1,
9951
+ maxAttempts: input.maxAttempts,
9952
+ status: input.status,
9953
+ outcome: input.outcome,
9954
+ willRetry: input.willRetry,
9955
+ durationMs: input.durationMs,
9956
+ resultCount: input.resultCount,
9957
+ error: input.error,
9958
+ proxyMode: input.proxyMode,
9959
+ proxyResolutionSource: input.resolution?.source ?? null,
9960
+ proxyIdSuffix: input.resolution?.proxyIdSuffix ?? null,
9961
+ proxyTargetLevel: target?.level ?? null,
9962
+ proxyTargetLocation: target?.canonicalLocation ?? null,
9963
+ proxyTargetZip: target?.zip ?? null,
9964
+ browserSessionIdSuffix: suffix(browserSessionId),
9965
+ observedIp: network?.ip ?? null,
9966
+ observedCity: network?.city ?? null,
9967
+ observedRegion: network?.region ?? null
9968
+ };
9969
+ }
9970
+ async function cleanupDisposableProxy(kernelApiKey, proxyId, eventName) {
9906
9971
  if (!kernelApiKey || !proxyId) return;
9907
- await deleteKernelProxyId(kernelApiKey, proxyId).catch((err) => {
9972
+ try {
9973
+ await deleteKernelProxyId(kernelApiKey, proxyId);
9974
+ } catch (err) {
9908
9975
  console.warn(JSON.stringify({
9909
- event: "maps_search_proxy_delete_failed",
9910
- proxy_id_suffix: proxyId.slice(-6),
9911
- message: err instanceof Error ? err.message : String(err)
9976
+ event: eventName,
9977
+ proxy_id_suffix: suffix(proxyId),
9978
+ message: mapsSearchErrorMessage(err)
9912
9979
  }));
9913
- });
9980
+ }
9981
+ }
9982
+ async function runMapsSearchWithRotation(options) {
9983
+ const attempts = [];
9984
+ const maxAttempts = mapsSearchMaxAttemptsForProxyMode(options.proxyMode);
9985
+ const started = Date.now();
9986
+ let lastError = null;
9987
+ for (let attemptIndex = 0; attemptIndex < maxAttempts; attemptIndex += 1) {
9988
+ const attemptStarted = Date.now();
9989
+ const driver = new BrowserDriver();
9990
+ const extractor = new MapsSearchExtractor(driver);
9991
+ const proxyZip = options.proxyZipForAttempt?.(attemptIndex) ?? options.proxyZip;
9992
+ let resolution = null;
9993
+ let disposableProxyId;
9994
+ try {
9995
+ resolution = await resolveKernelProxyId({
9996
+ kernelApiKey: options.kernelApiKey,
9997
+ proxyMode: options.proxyMode,
9998
+ configuredKernelProxyId: options.kernelProxyId ?? options.configuredKernelProxyId,
9999
+ location: options.location,
10000
+ proxyZip,
10001
+ gl: options.gl,
10002
+ attemptIndex,
10003
+ fresh: options.proxyMode === "location"
10004
+ });
10005
+ disposableProxyId = resolution.disposableProxyId;
10006
+ const result = await extractor.extract({
10007
+ ...options,
10008
+ proxyZip,
10009
+ kernelProxyId: options.proxyMode === "none" ? void 0 : resolution.kernelProxyId,
10010
+ kernelProxyResolution: resolution.resolution
10011
+ });
10012
+ const attempt = attemptFromDebug({
10013
+ attemptIndex,
10014
+ maxAttempts,
10015
+ status: "ok",
10016
+ outcome: "success",
10017
+ willRetry: false,
10018
+ durationMs: Date.now() - attemptStarted,
10019
+ resultCount: result.resultCount,
10020
+ error: null,
10021
+ proxyMode: options.proxyMode,
10022
+ resolution: resolution.resolution,
10023
+ debug: driver.getDebugSnapshot()
10024
+ });
10025
+ attempts.push(attempt);
10026
+ return {
10027
+ ...result,
10028
+ attempts: attempts.slice(),
10029
+ durationMs: Date.now() - started
10030
+ };
10031
+ } catch (err) {
10032
+ lastError = err;
10033
+ const retryable = retryableMapsSearchError(err, options.proxyMode);
10034
+ const willRetry = attemptIndex < maxAttempts - 1 && retryable;
10035
+ const attempt = attemptFromDebug({
10036
+ attemptIndex,
10037
+ maxAttempts,
10038
+ status: "failed",
10039
+ outcome: mapsSearchFailureOutcome(err),
10040
+ willRetry,
10041
+ durationMs: Date.now() - attemptStarted,
10042
+ resultCount: 0,
10043
+ error: mapsSearchErrorMessage(err),
10044
+ proxyMode: options.proxyMode,
10045
+ resolution: resolution?.resolution ?? null,
10046
+ debug: driver.getDebugSnapshot()
10047
+ });
10048
+ attempts.push(attempt);
10049
+ console.warn(JSON.stringify({
10050
+ event: options.failureEventName ?? "maps_search_attempt_failed",
10051
+ ...options.logContext,
10052
+ attempt_number: attempt.attemptNumber,
10053
+ max_attempts: attempt.maxAttempts,
10054
+ outcome: attempt.outcome,
10055
+ will_retry: attempt.willRetry,
10056
+ proxy_resolution_source: attempt.proxyResolutionSource,
10057
+ proxy_id_suffix: attempt.proxyIdSuffix,
10058
+ browser_session_id_suffix: attempt.browserSessionIdSuffix,
10059
+ observed_city: attempt.observedCity,
10060
+ observed_region: attempt.observedRegion,
10061
+ message: attempt.error
10062
+ }));
10063
+ if (!willRetry) {
10064
+ throw new MapsSearchRotationError(
10065
+ mapsSearchErrorMessage(err),
10066
+ attempts,
10067
+ retryable,
10068
+ err instanceof CaptchaError ? "captcha_or_blocked" : "maps_search_failed"
10069
+ );
10070
+ }
10071
+ } finally {
10072
+ await cleanupDisposableProxy(options.kernelApiKey, disposableProxyId, "maps_search_proxy_delete_failed");
10073
+ await driver.close();
10074
+ }
10075
+ }
10076
+ throw new MapsSearchRotationError(
10077
+ lastError ? mapsSearchErrorMessage(lastError) : "Maps search failed",
10078
+ attempts,
10079
+ true
10080
+ );
10081
+ }
10082
+
10083
+ // src/api/maps-routes.ts
10084
+ function mapsErrorResponse(c, err, errorCode) {
10085
+ const msg = err instanceof Error ? err.message : String(err);
10086
+ const rotationError = err instanceof MapsSearchRotationError ? err : null;
10087
+ const blocked = msg.includes("CAPTCHA") || msg.includes("blocked");
10088
+ const retryable = rotationError?.retryable ?? blocked;
10089
+ return c.json({
10090
+ error: sanitizeVendorName(msg),
10091
+ error_code: rotationError?.errorCode ?? (blocked ? "captcha_or_blocked" : errorCode),
10092
+ retryable,
10093
+ attempts: rotationError?.attempts ?? void 0
10094
+ }, retryable ? 503 : 500);
9914
10095
  }
9915
10096
  var mapsApp = new Hono5();
9916
10097
  mapsApp.post("/search", createApiKeyAuth(), async (c) => {
@@ -9930,24 +10111,10 @@ mapsApp.post("/search", createApiKeyAuth(), async (c) => {
9930
10111
  [parsed.data.query, parsed.data.location].filter(Boolean).join(" ")
9931
10112
  );
9932
10113
  if (!ok) return c.json(insufficientBalanceResponse(balance_mc, MC_COSTS.maps_search), 402);
9933
- const driver = new BrowserDriver();
9934
- const extractor = new MapsSearchExtractor(driver);
9935
- let disposableProxyId;
9936
10114
  try {
9937
- const resolution = await resolveKernelProxyId({
9938
- kernelApiKey: parsed.data.kernelApiKey,
9939
- proxyMode: parsed.data.proxyMode,
9940
- configuredKernelProxyId: browserServiceProxyId(),
9941
- location: parsed.data.location,
9942
- proxyZip: parsed.data.proxyZip,
9943
- gl: parsed.data.gl,
9944
- fresh: parsed.data.proxyMode === "location"
9945
- });
9946
- disposableProxyId = resolution.disposableProxyId;
9947
- const result = await extractor.extract({
10115
+ const result = await runMapsSearchWithRotation({
9948
10116
  ...parsed.data,
9949
- kernelProxyId: parsed.data.proxyMode === "none" ? void 0 : resolution.kernelProxyId,
9950
- kernelProxyResolution: resolution.resolution
10117
+ configuredKernelProxyId: browserServiceProxyId()
9951
10118
  });
9952
10119
  await logRequestEvent({
9953
10120
  userId: user.id,
@@ -9968,12 +10135,10 @@ mapsApp.post("/search", createApiKeyAuth(), async (c) => {
9968
10135
  status: "failed",
9969
10136
  query: [parsed.data.query, parsed.data.location].filter(Boolean).join(" "),
9970
10137
  location: parsed.data.location,
9971
- error: msg
10138
+ error: msg,
10139
+ result: err instanceof MapsSearchRotationError ? { attempts: err.attempts } : void 0
9972
10140
  });
9973
- return mapsErrorResponse(c, msg, "maps_search_failed");
9974
- } finally {
9975
- await cleanupDisposableProxy(parsed.data.kernelApiKey, disposableProxyId);
9976
- await driver.close();
10141
+ return mapsErrorResponse(c, err, "maps_search_failed");
9977
10142
  }
9978
10143
  });
9979
10144
  mapsApp.post("/place", createApiKeyAuth(), async (c) => {
@@ -10278,8 +10443,6 @@ async function resolveDirectoryMarkets(options) {
10278
10443
  }
10279
10444
 
10280
10445
  // src/directory/directory-workflow.ts
10281
- var DIRECTORY_MAX_ATTEMPTS = 3;
10282
- var DIRECTORY_LOCATION_PROXY_MAX_ATTEMPTS = 5;
10283
10446
  var DirectoryWorkflowOptionsSchema = z15.object({
10284
10447
  query: z15.string().min(1),
10285
10448
  state: z15.string().min(2).default("TN"),
@@ -10299,36 +10462,9 @@ var DirectoryWorkflowOptionsSchema = z15.object({
10299
10462
  headless: z15.boolean().default(true),
10300
10463
  kernelApiKey: z15.string().optional()
10301
10464
  });
10302
- async function cleanupDisposableProxy2(kernelApiKey, proxyId) {
10303
- if (!kernelApiKey || !proxyId) return;
10304
- try {
10305
- await deleteKernelProxyId(kernelApiKey, proxyId);
10306
- } catch (err) {
10307
- console.warn(JSON.stringify({
10308
- event: "directory_workflow_proxy_delete_failed",
10309
- proxy_id_suffix: proxyId.slice(-6),
10310
- message: err instanceof Error ? err.message : String(err)
10311
- }));
10312
- }
10313
- }
10314
- function maxAttemptsForProxyMode(proxyMode) {
10315
- return proxyMode === "location" ? DIRECTORY_LOCATION_PROXY_MAX_ATTEMPTS : DIRECTORY_MAX_ATTEMPTS;
10316
- }
10317
10465
  function errorMessage(err) {
10318
10466
  return err instanceof Error ? err.message : String(err);
10319
10467
  }
10320
- function looksLikeProxyTunnelFailure(message) {
10321
- return /ERR_TUNNEL_CONNECTION_FAILED|ERR_PROXY_CONNECTION_FAILED|ERR_SOCKS_CONNECTION_FAILED|tunnel connection failed|proxy connection failed|transport error: proxy/i.test(message);
10322
- }
10323
- function looksLikeProxyUnavailable(message) {
10324
- return /proxy unavailable|proxy_unavailable|connection_test_failed|did not return a proxy id|configured fallback/i.test(message);
10325
- }
10326
- function retryableCitySearchError(err, proxyMode) {
10327
- if (err instanceof CaptchaError) return true;
10328
- const message = errorMessage(err);
10329
- if (/timeout|timed out|Timeout \d+ms exceeded|deadline/i.test(message)) return true;
10330
- return proxyMode === "location" && (looksLikeProxyTunnelFailure(message) || looksLikeProxyUnavailable(message));
10331
- }
10332
10468
  function proxyZipForAttempt(options, market, attemptIndex) {
10333
10469
  if (options.proxyZip) return options.proxyZip;
10334
10470
  if (!market.zips.length) return void 0;
@@ -10347,25 +10483,10 @@ async function mapLimit(items, limit, fn) {
10347
10483
  await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()));
10348
10484
  return out;
10349
10485
  }
10350
- async function searchCityAttempt(options, market, attemptIndex) {
10351
- const driver = new BrowserDriver();
10352
- const extractor = new MapsSearchExtractor(driver);
10353
- const start = Date.now();
10354
- let disposableProxyId;
10486
+ async function searchCity(options, market) {
10487
+ const started = Date.now();
10355
10488
  try {
10356
- const proxyZip = proxyZipForAttempt(options, market, attemptIndex);
10357
- const resolution = await resolveKernelProxyId({
10358
- kernelApiKey: options.kernelApiKey,
10359
- proxyMode: options.proxyMode,
10360
- configuredKernelProxyId: browserServiceProxyId(),
10361
- location: market.location,
10362
- proxyZip,
10363
- gl: options.gl,
10364
- attemptIndex,
10365
- fresh: options.proxyMode === "location"
10366
- });
10367
- disposableProxyId = resolution.disposableProxyId;
10368
- const result = await extractor.extract({
10489
+ const result = await runMapsSearchWithRotation({
10369
10490
  query: options.query,
10370
10491
  location: market.location,
10371
10492
  gl: options.gl,
@@ -10373,11 +10494,16 @@ async function searchCityAttempt(options, market, attemptIndex) {
10373
10494
  maxResults: options.maxResultsPerCity,
10374
10495
  headless: options.headless,
10375
10496
  kernelApiKey: options.kernelApiKey,
10376
- kernelProxyId: options.proxyMode === "none" ? void 0 : resolution.kernelProxyId,
10377
- kernelProxyResolution: resolution.resolution,
10378
10497
  proxyMode: options.proxyMode,
10379
- proxyZip,
10380
- debug: options.debug
10498
+ proxyZip: options.proxyZip,
10499
+ debug: options.debug,
10500
+ configuredKernelProxyId: browserServiceProxyId(),
10501
+ proxyZipForAttempt: (attemptIndex) => proxyZipForAttempt(options, market, attemptIndex),
10502
+ failureEventName: "directory_workflow_city_attempt_failed",
10503
+ logContext: {
10504
+ city: market.city,
10505
+ state: market.state
10506
+ }
10381
10507
  });
10382
10508
  return {
10383
10509
  city: market.city,
@@ -10393,50 +10519,29 @@ async function searchCityAttempt(options, market, attemptIndex) {
10393
10519
  error: null,
10394
10520
  resultCount: result.resultCount,
10395
10521
  durationMs: result.durationMs,
10522
+ attempts: result.attempts ?? [],
10396
10523
  results: result.results
10397
10524
  };
10398
- } finally {
10399
- await cleanupDisposableProxy2(options.kernelApiKey, disposableProxyId);
10400
- }
10401
- }
10402
- async function searchCity(options, market) {
10403
- const started = Date.now();
10404
- const maxAttempts = maxAttemptsForProxyMode(options.proxyMode);
10405
- let lastError = null;
10406
- for (let attemptIndex = 0; attemptIndex < maxAttempts; attemptIndex += 1) {
10407
- try {
10408
- return await searchCityAttempt(options, market, attemptIndex);
10409
- } catch (err) {
10410
- lastError = err;
10411
- const willRetry = attemptIndex < maxAttempts - 1 && retryableCitySearchError(err, options.proxyMode);
10412
- console.warn(JSON.stringify({
10413
- event: "directory_workflow_city_attempt_failed",
10414
- city: market.city,
10415
- state: market.state,
10416
- attempt_number: attemptIndex + 1,
10417
- max_attempts: maxAttempts,
10418
- will_retry: willRetry,
10419
- message: errorMessage(err)
10420
- }));
10421
- if (!willRetry) break;
10422
- }
10525
+ } catch (err) {
10526
+ const attempts = err instanceof MapsSearchRotationError ? err.attempts : [];
10527
+ return {
10528
+ city: market.city,
10529
+ state: market.state,
10530
+ location: market.location,
10531
+ cityKey: market.cityKey,
10532
+ censusName: market.censusName,
10533
+ population: market.population,
10534
+ populationYear: market.populationYear,
10535
+ zips: market.zips,
10536
+ counties: market.counties,
10537
+ status: "failed",
10538
+ error: errorMessage(err),
10539
+ resultCount: 0,
10540
+ durationMs: Date.now() - started,
10541
+ attempts,
10542
+ results: []
10543
+ };
10423
10544
  }
10424
- return {
10425
- city: market.city,
10426
- state: market.state,
10427
- location: market.location,
10428
- cityKey: market.cityKey,
10429
- censusName: market.censusName,
10430
- population: market.population,
10431
- populationYear: market.populationYear,
10432
- zips: market.zips,
10433
- counties: market.counties,
10434
- status: "failed",
10435
- error: lastError ? errorMessage(lastError) : "City Maps search failed",
10436
- resultCount: 0,
10437
- durationMs: Date.now() - started,
10438
- results: []
10439
- };
10440
10545
  }
10441
10546
  function csvRowsFor(result) {
10442
10547
  const rows = [];
@@ -11552,7 +11657,7 @@ var mcpApp = new Hono8();
11552
11657
  function registerSerpIntelligenceCaptureTools(server, executor) {
11553
11658
  server.registerTool("capture_serp_snapshot", {
11554
11659
  title: "SERP Intelligence Snapshot",
11555
- description: "Capture a structured SERP Intelligence Google snapshot through POST /serp-intelligence/capture, the same product capture path used by Phoenix. Split query from location, infer gl/hl, use proxyMode location for localized US residential evidence; location mode creates fresh proxy IDs across retries and rejects wrong-location evidence before returning. Use configured only for the static residential proxy, and none only for direct-network debugging. Set debug true when investigating location evidence, proxy behavior, CAPTCHA, or capture reliability.",
11660
+ description: "Capture a structured SERP Intelligence Google snapshot through POST /serp-intelligence/capture, the same product capture path used by Phoenix. Split query from location, infer gl/hl, use proxyMode location for localized US residential evidence; location mode creates fresh proxy IDs across retries, briefly waits for browser-service automatic CAPTCHA/challenge solving when Google blocks a page, and rotates to a new proxy/session if the challenge does not clear or location evidence is wrong. Use configured only for the static residential proxy, and none only for direct-network debugging. Set debug true when investigating location evidence, proxy behavior, CAPTCHA, or capture reliability.",
11556
11661
  inputSchema: CaptureSerpSnapshotInputSchema,
11557
11662
  annotations: liveWebToolAnnotations("SERP Intelligence Snapshot")
11558
11663
  }, async (input) => executor.captureSerpSnapshot(input));
@@ -13594,7 +13699,7 @@ app.get("/cron/tick", async (c) => {
13594
13699
  if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
13595
13700
  return c.json({ error: "Unauthorized" }, 401);
13596
13701
  }
13597
- const { drainQueue } = await import("./worker-NAKGTIF5.js");
13702
+ const { drainQueue } = await import("./worker-5O44YBF4.js");
13598
13703
  const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
13599
13704
  const [results, sweepResult] = await Promise.all([
13600
13705
  drainQueue(budget),
@@ -13720,4 +13825,4 @@ app.get("/blog/:slug/", (c) => {
13720
13825
  export {
13721
13826
  app
13722
13827
  };
13723
- //# sourceMappingURL=server-RIKRBDOI.js.map
13828
+ //# sourceMappingURL=server-BTTDFPSQ.js.map