mcp-scraper 0.21.3 → 0.21.4

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 (56) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/api-server.cjs +728 -181
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +3 -3
  5. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  6. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  7. package/dist/bin/mcp-scraper-cli.js +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs +1 -1
  9. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  10. package/dist/bin/mcp-scraper-install.js +1 -1
  11. package/dist/bin/mcp-stdio-server.cjs +97 -32
  12. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  13. package/dist/bin/mcp-stdio-server.js +5 -5
  14. package/dist/bin/paa-harvest.cjs +21 -0
  15. package/dist/bin/paa-harvest.cjs.map +1 -1
  16. package/dist/bin/paa-harvest.js +3 -3
  17. package/dist/{chunk-JJ5RMEGX.js → chunk-E5J4HJBO.js} +2 -2
  18. package/dist/{chunk-H7APH5UG.js → chunk-ICT7DDHL.js} +366 -50
  19. package/dist/chunk-ICT7DDHL.js.map +1 -0
  20. package/dist/{chunk-OBNKOYOE.js → chunk-JHI373VB.js} +7 -5
  21. package/dist/chunk-JHI373VB.js.map +1 -0
  22. package/dist/{chunk-5IU5ZFVO.js → chunk-MTSBI7ZH.js} +23 -6
  23. package/dist/chunk-MTSBI7ZH.js.map +1 -0
  24. package/dist/{chunk-7LO6KLKD.js → chunk-N4JHXPW6.js} +89 -27
  25. package/dist/chunk-N4JHXPW6.js.map +1 -0
  26. package/dist/{chunk-XDRHBGJR.js → chunk-PNBYYAEK.js} +3 -3
  27. package/dist/{chunk-2P5VFIJM.js → chunk-SDHPQTFM.js} +2 -2
  28. package/dist/chunk-WQI7JIIN.js +7 -0
  29. package/dist/chunk-WQI7JIIN.js.map +1 -0
  30. package/dist/{db-Z35S224E.js → db-WRILVOFY.js} +4 -2
  31. package/dist/{extract-bundle-4RO3YU4Q.js → extract-bundle-NFFVMUMT.js} +2 -2
  32. package/dist/index.cjs +21 -0
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.js +3 -3
  35. package/dist/{server-PBBRSLBP.js → server-OLOOFN5W.js} +246 -100
  36. package/dist/server-OLOOFN5W.js.map +1 -0
  37. package/dist/{site-extract-repository-FDH6ZXKT.js → site-extract-repository-2QQSL2QC.js} +4 -4
  38. package/dist/{worker-BXJQ7J4N.js → worker-RV2TYXE3.js} +9 -8
  39. package/dist/worker-RV2TYXE3.js.map +1 -0
  40. package/docs/mcp-tool-manifest.generated.json +90 -11
  41. package/docs/specs/oauth-mcp-spec.md +2 -2
  42. package/package.json +1 -1
  43. package/dist/chunk-5IU5ZFVO.js.map +0 -1
  44. package/dist/chunk-7LO6KLKD.js.map +0 -1
  45. package/dist/chunk-H7APH5UG.js.map +0 -1
  46. package/dist/chunk-OBNKOYOE.js.map +0 -1
  47. package/dist/chunk-VYUWWIVR.js +0 -7
  48. package/dist/chunk-VYUWWIVR.js.map +0 -1
  49. package/dist/server-PBBRSLBP.js.map +0 -1
  50. package/dist/worker-BXJQ7J4N.js.map +0 -1
  51. /package/dist/{chunk-JJ5RMEGX.js.map → chunk-E5J4HJBO.js.map} +0 -0
  52. /package/dist/{chunk-XDRHBGJR.js.map → chunk-PNBYYAEK.js.map} +0 -0
  53. /package/dist/{chunk-2P5VFIJM.js.map → chunk-SDHPQTFM.js.map} +0 -0
  54. /package/dist/{db-Z35S224E.js.map → db-WRILVOFY.js.map} +0 -0
  55. /package/dist/{extract-bundle-4RO3YU4Q.js.map → extract-bundle-NFFVMUMT.js.map} +0 -0
  56. /package/dist/{site-extract-repository-FDH6ZXKT.js.map → site-extract-repository-2QQSL2QC.js.map} +0 -0
@@ -6,7 +6,7 @@ import {
6
6
  createHarvestAttemptRecorder,
7
7
  harvestProblemResponse,
8
8
  serializeHarvestProblem
9
- } from "./chunk-2P5VFIJM.js";
9
+ } from "./chunk-SDHPQTFM.js";
10
10
  import {
11
11
  BrowserDriver,
12
12
  MapsSelectors,
@@ -14,7 +14,7 @@ import {
14
14
  deleteKernelProxyId,
15
15
  harvest,
16
16
  resolveKernelProxyId
17
- } from "./chunk-JJ5RMEGX.js";
17
+ } from "./chunk-E5J4HJBO.js";
18
18
  import {
19
19
  HttpMcpToolExecutor,
20
20
  MemoryMcpToolExecutor,
@@ -35,7 +35,7 @@ import {
35
35
  sanitizeAttempts,
36
36
  sanitizeHarvestResult,
37
37
  transcribeMediaUrl
38
- } from "./chunk-7LO6KLKD.js";
38
+ } from "./chunk-N4JHXPW6.js";
39
39
  import {
40
40
  auditImages,
41
41
  buildLinkReport,
@@ -49,11 +49,10 @@ import {
49
49
  browserServiceProxyId,
50
50
  currentCostContext,
51
51
  kernelCostUsd,
52
- migrateCostTelemetry,
53
52
  recordKernelSession,
54
53
  recordVendorUsage,
55
54
  runWithCostContext
56
- } from "./chunk-5IU5ZFVO.js";
55
+ } from "./chunk-MTSBI7ZH.js";
57
56
  import {
58
57
  csvRecords,
59
58
  listWorkflowDefinitions,
@@ -77,7 +76,7 @@ import {
77
76
  RawMapsOverviewSchema,
78
77
  RawMapsReviewStatsSchema
79
78
  } from "./chunk-XGIPATLV.js";
80
- import "./chunk-VYUWWIVR.js";
79
+ import "./chunk-WQI7JIIN.js";
81
80
  import {
82
81
  completeExtractJob,
83
82
  countSuccessfulPages,
@@ -88,12 +87,11 @@ import {
88
87
  saveExtractPages,
89
88
  setExtractJobTotal,
90
89
  settleExtractJob
91
- } from "./chunk-XDRHBGJR.js";
90
+ } from "./chunk-PNBYYAEK.js";
92
91
  import {
93
92
  BROWSER_OPEN_MIN_BALANCE_MC,
94
93
  CONCURRENCY_PRICE_ID,
95
94
  CREDIT_COST_CATALOG,
96
- FREE_SIGNUP_MC,
97
95
  LedgerOperation,
98
96
  MC_COSTS,
99
97
  MC_PER_CREDIT,
@@ -117,8 +115,9 @@ import {
117
115
  concurrencySlotBillingInfo,
118
116
  getPublicRatesPayload,
119
117
  insufficientBalanceResponse,
118
+ serpActualCostMc,
120
119
  vendorCostUsdToMc
121
- } from "./chunk-OBNKOYOE.js";
120
+ } from "./chunk-JHI373VB.js";
122
121
  import {
123
122
  CaptchaError,
124
123
  RECAPTCHA_INSTRUCTIONS,
@@ -200,9 +199,10 @@ import {
200
199
  setStripeCustomerId,
201
200
  setSubscriptionTier,
202
201
  setUserMemoryCreds,
202
+ settleDebitMcIdempotent,
203
203
  upsertPageSnapshot,
204
204
  verifyPassword
205
- } from "./chunk-H7APH5UG.js";
205
+ } from "./chunk-ICT7DDHL.js";
206
206
 
207
207
  // src/blog/registry.ts
208
208
  var posts = [
@@ -7360,7 +7360,7 @@ var siteExtractFn = inngest.createFunction(
7360
7360
  return buildSeoAudit(pages, job.startUrl);
7361
7361
  }) : null;
7362
7362
  const artifacts = await step.run("finalize", async () => {
7363
- const { assembleExtractArtifacts } = await import("./extract-bundle-4RO3YU4Q.js");
7363
+ const { assembleExtractArtifacts } = await import("./extract-bundle-NFFVMUMT.js");
7364
7364
  const stored = await assembleExtractArtifacts(job, { branding, imageAudit, seoAudit });
7365
7365
  await completeExtractJob(jobId, stored);
7366
7366
  return stored;
@@ -11004,6 +11004,15 @@ function buildPageIntelUrl(body, country) {
11004
11004
  if (body.pageId?.trim()) return `https://www.facebook.com/ads/library/?active_status=all&ad_type=all&country=${country}&is_targeted_country=false&media_type=all&search_type=page&view_all_page_id=${body.pageId.trim()}`;
11005
11005
  return `https://www.facebook.com/ads/library/?active_status=all&ad_type=all&country=${country}&q=${encodeURIComponent(body.query.trim())}&search_type=keyword_unordered`;
11006
11006
  }
11007
+ function pageIntelRequestContext(body) {
11008
+ const country = body.country?.trim().toUpperCase() ?? "US";
11009
+ return {
11010
+ country,
11011
+ maxAds: Math.min(200, Math.max(1, body.maxAds ?? 50)),
11012
+ listingUrl: buildPageIntelUrl(body, country),
11013
+ subject: body.pageId ?? body.query ?? body.libraryId ?? ""
11014
+ };
11015
+ }
11007
11016
  function normalizeAdvertiserName(value) {
11008
11017
  return value.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, " ").trim().replace(/\s+/g, " ");
11009
11018
  }
@@ -11117,16 +11126,14 @@ facebookAdApp.post("/page-intel", createApiKeyAuth(), async (c) => {
11117
11126
  const parsed = FacebookPageIntelBodySchema.safeParse(raw);
11118
11127
  if (!parsed.success) return c.json(invalidRequest(parsed.error.issues[0]?.message ?? "Invalid request"), 400);
11119
11128
  const body = parsed.data;
11120
- const maxAds = Math.min(200, Math.max(1, body.maxAds ?? 50));
11121
- const country = body.country?.trim().toUpperCase() ?? "US";
11122
- const listingUrl = buildPageIntelUrl(body, country);
11129
+ const { country, maxAds, listingUrl, subject } = pageIntelRequestContext(body);
11123
11130
  const fbUser = c.get("user");
11124
11131
  const gate = await acquireConcurrencyGate(fbUser, "facebook_page_intel", { reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"), metadata: { pageId: body.pageId, query: body.query, libraryId: body.libraryId, country } });
11125
11132
  if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
11126
11133
  const driver = new BrowserDriver();
11127
11134
  let refunded = false, debited = false;
11128
11135
  try {
11129
- const { ok: fbOk, balance_mc: fbBal } = await debitMc(fbUser.id, MC_COSTS.fb_ad, LedgerOperation.FB_AD, body.pageId ?? body.query ?? body.libraryId ?? "");
11136
+ const { ok: fbOk, balance_mc: fbBal } = await debitMc(fbUser.id, MC_COSTS.fb_ad, LedgerOperation.FB_AD, subject);
11130
11137
  if (!fbOk) return c.json(insufficientBalanceResponse(fbBal, MC_COSTS.fb_ad), 402);
11131
11138
  debited = true;
11132
11139
  await driver.launch(await kernelLaunchOptsResidential());
@@ -11136,17 +11143,17 @@ facebookAdApp.post("/page-intel", createApiKeyAuth(), async (c) => {
11136
11143
  if (result.ads.length === 0 && await detectSoftBlock(driver)) {
11137
11144
  await creditMc(fbUser.id, MC_COSTS.fb_ad, LedgerOperation.FB_AD_REFUND, "soft-block empty result");
11138
11145
  refunded = true;
11139
- await logRequestEvent({ userId: fbUser.id, source: "facebook_page_intel", status: "failed", query: body.pageId ?? body.query ?? body.libraryId ?? "", error: "soft-block: empty result refunded" });
11146
+ await logRequestEvent({ userId: fbUser.id, source: "facebook_page_intel", status: "failed", query: subject, error: "soft-block: empty result refunded" });
11140
11147
  return c.json({ error: "soft-block: no ads returned (refunded)" }, 503);
11141
11148
  }
11142
11149
  const match = assessPageIntelMatch(body, result);
11143
11150
  const output = { ...result, ...match };
11144
- await logRequestEvent({ userId: fbUser.id, source: "facebook_page_intel", status: "done", query: body.pageId ?? body.query ?? body.libraryId ?? "", resultCount: result.ads.length, result: output });
11151
+ await logRequestEvent({ userId: fbUser.id, source: "facebook_page_intel", status: "done", query: subject, resultCount: result.ads.length, result: output });
11145
11152
  return c.json(output);
11146
11153
  } catch (err) {
11147
11154
  const msg = err instanceof Error ? err.message : String(err);
11148
11155
  if (debited && !refunded) await creditMc(fbUser.id, MC_COSTS.fb_ad, LedgerOperation.FB_AD_REFUND, "failed call");
11149
- await logRequestEvent({ userId: fbUser.id, source: "facebook_page_intel", status: "failed", query: body.pageId ?? body.query ?? body.libraryId ?? "", error: msg });
11156
+ await logRequestEvent({ userId: fbUser.id, source: "facebook_page_intel", status: "failed", query: subject, error: msg });
11150
11157
  if (msg.toLowerCase().includes("blocked") || msg.toLowerCase().includes("captcha")) {
11151
11158
  return c.json({ error: msg }, 503);
11152
11159
  }
@@ -15084,6 +15091,7 @@ workflowApp.post("/cron/dispatch", async (c) => {
15084
15091
 
15085
15092
  // src/api/serp-intelligence-routes.ts
15086
15093
  import { Hono as Hono16 } from "hono";
15094
+ import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
15087
15095
 
15088
15096
  // src/serp-intelligence/page-snapshot-extractor.ts
15089
15097
  import { createHash as createHash3 } from "crypto";
@@ -15477,12 +15485,22 @@ var SerpIntelligencePageSnapshotRequestSchema = z24.object({
15477
15485
  sourcePosition: z24.number().int().min(1).optional()
15478
15486
  }).strict();
15479
15487
  var SerpIntelligencePageSnapshotsBodySchema = z24.object({
15480
- urls: z24.array(SerpIntelligencePublicHttpUrlSchema).min(1).max(25),
15488
+ urls: z24.array(SerpIntelligencePublicHttpUrlSchema).min(1).max(25).optional(),
15481
15489
  targets: z24.array(SerpIntelligencePageSnapshotRequestSchema).min(1).max(25).optional(),
15482
15490
  maxConcurrency: z24.number().int().min(1).max(5).default(2),
15483
15491
  timeoutMs: z24.number().int().min(1e3).max(6e4).default(15e3),
15484
15492
  debug: z24.boolean().default(false)
15485
- }).strict();
15493
+ }).strict().superRefine((body, ctx) => {
15494
+ const hasUrls = Boolean(body.urls?.length);
15495
+ const hasTargets = Boolean(body.targets?.length);
15496
+ if (hasUrls === hasTargets) {
15497
+ ctx.addIssue({
15498
+ code: z24.ZodIssueCode.custom,
15499
+ message: "provide exactly one of urls or targets",
15500
+ path: hasUrls ? ["targets"] : ["urls"]
15501
+ });
15502
+ }
15503
+ });
15486
15504
  var SerpIntelligenceAICitationSchema = z24.object({
15487
15505
  text: z24.string(),
15488
15506
  href: z24.string()
@@ -15769,14 +15787,24 @@ async function captureSerpIntelligenceSnapshot(rawInput, runtimeOptions = {}) {
15769
15787
  });
15770
15788
  const pageSnapshotLimit = normalizePageSnapshotLimit(parsedInput);
15771
15789
  const pageSnapshotTargets = collectPageSnapshotTargets(harvestResult, pageSnapshotLimit);
15772
- const pageSnapshotArtifacts = pageSnapshotTargets.length > 0 ? (await capturePageSnapshotsFn(pageSnapshotTargets, {
15773
- kernelApiKey: runtimeOptions.kernelApiKey ?? browserServiceApiKey(),
15774
- timeoutMs: runtimeOptions.pageSnapshotTimeoutMs,
15775
- maxConcurrency: runtimeOptions.pageSnapshotMaxConcurrency,
15776
- debug,
15777
- includeContent: runtimeOptions.includePageSnapshotContent,
15778
- maxContentChars: runtimeOptions.maxPageSnapshotContentChars
15779
- })).pageSnapshotArtifacts.map(stripPageSnapshotArtifact) : [];
15790
+ const pageSnapshotArtifacts = pageSnapshotTargets.length > 0 ? (await runWithCostContext(
15791
+ {
15792
+ ...currentCostContext() ?? { op: "serp" },
15793
+ subOp: "page_snapshot",
15794
+ // Page snapshot launches have their own fixed product rate. They
15795
+ // must not overwrite the headless/headful mode used to settle the
15796
+ // preceding SERP harvest.
15797
+ headlessSentOut: void 0
15798
+ },
15799
+ () => capturePageSnapshotsFn(pageSnapshotTargets, {
15800
+ kernelApiKey: runtimeOptions.kernelApiKey ?? browserServiceApiKey(),
15801
+ timeoutMs: runtimeOptions.pageSnapshotTimeoutMs,
15802
+ maxConcurrency: runtimeOptions.pageSnapshotMaxConcurrency,
15803
+ debug,
15804
+ includeContent: runtimeOptions.includePageSnapshotContent,
15805
+ maxContentChars: runtimeOptions.maxPageSnapshotContentChars
15806
+ })
15807
+ )).pageSnapshotArtifacts.map(stripPageSnapshotArtifact) : [];
15780
15808
  return normalizeHarvestResultForSerpIntelligence({
15781
15809
  harvestResult,
15782
15810
  attempts,
@@ -15793,6 +15821,7 @@ var SERP_INTELLIGENCE_RATE_LIMIT = 60;
15793
15821
  var SERP_INTELLIGENCE_RATE_WINDOW_SECONDS = 60;
15794
15822
  var POST_CAPTURE_ROUTE_LABEL = "POST /capture";
15795
15823
  var POST_PAGE_SNAPSHOTS_ROUTE_LABEL = "POST /page-snapshots";
15824
+ var SERP_CAPTURE_BILLING_SOURCE = "serp_intelligence_capture";
15796
15825
  var serpIntelligenceApp = new Hono16();
15797
15826
  serpIntelligenceApp.use("*", createApiKeyAuth());
15798
15827
  function structuredError(input) {
@@ -15832,18 +15861,46 @@ async function enforceSerpIntelligenceRateLimit(c, userId) {
15832
15861
  );
15833
15862
  }
15834
15863
  function pageSnapshotTargetsFromBody(body) {
15835
- return body.targets?.length ? body.targets : body.urls.map((url) => ({
15864
+ return body.targets?.length ? body.targets : (body.urls ?? []).map((url) => ({
15836
15865
  url,
15837
15866
  sourceKind: "configured_target",
15838
15867
  sourcePosition: null
15839
15868
  }));
15840
15869
  }
15870
+ async function logRequestEventBestEffort(input) {
15871
+ try {
15872
+ await logRequestEvent(input);
15873
+ } catch (error) {
15874
+ console.warn("[serp-intelligence] request audit log failed:", error instanceof Error ? error.message : String(error));
15875
+ }
15876
+ }
15877
+ function captureBillingKeys(userId, suppliedKey, body) {
15878
+ const responseKey = suppliedKey?.trim() || randomUUID3();
15879
+ const requestFingerprint = createHash4("sha256").update(JSON.stringify(body)).digest("hex");
15880
+ const keyDigest = createHash4("sha256").update(String(userId)).update("\0").update(responseKey).digest("hex");
15881
+ return {
15882
+ responseKey,
15883
+ debitKey: `serp-capture:${userId}:${keyDigest}`,
15884
+ debitDescription: `${body.query} [request:${requestFingerprint}]`
15885
+ };
15886
+ }
15841
15887
  serpIntelligenceApp.post("/capture", async (c) => {
15842
15888
  void POST_CAPTURE_ROUTE_LABEL;
15843
15889
  const user = c.get("user");
15844
15890
  const raw = await c.req.json().catch(() => ({}));
15845
15891
  const parsed = SerpIntelligenceCaptureBodySchema.safeParse(raw);
15846
15892
  if (!parsed.success) return c.json(formatZodError(parsed.error), 400);
15893
+ const suppliedIdempotencyKey = c.req.header("idempotency-key") ?? c.req.header("x-idempotency-key");
15894
+ if (suppliedIdempotencyKey && suppliedIdempotencyKey.trim().length > 500) {
15895
+ return c.json(structuredError({
15896
+ error_code: "invalid_request",
15897
+ error_type: "validation_error",
15898
+ message: "Idempotency-Key must be 500 characters or fewer.",
15899
+ retryable: false
15900
+ }), 400);
15901
+ }
15902
+ const billingKeys = captureBillingKeys(user.id, suppliedIdempotencyKey, parsed.data);
15903
+ c.header("Idempotency-Key", billingKeys.responseKey);
15847
15904
  const limited = await enforceSerpIntelligenceRateLimit(c, user.id);
15848
15905
  if (limited) return limited;
15849
15906
  const gate = await acquireConcurrencyGate(user, "serp_intelligence_capture", {
@@ -15851,51 +15908,130 @@ serpIntelligenceApp.post("/capture", async (c) => {
15851
15908
  metadata: { query: parsed.data.query, location: parsed.data.location }
15852
15909
  });
15853
15910
  if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
15854
- const cost = MC_COSTS.serp;
15855
- let debited = false;
15911
+ const pageSnapshotHold = parsed.data.includePageSnapshots ? parsed.data.pageSnapshotLimit * MC_COSTS.page_scrape : 0;
15912
+ const cost = MC_COSTS.serp_headful + pageSnapshotHold;
15913
+ const headlessSentOut = { value: null };
15856
15914
  try {
15857
- const { ok, balance_mc } = await debitMc(
15915
+ const { ok, balance_mc } = await debitMcIdempotent(
15858
15916
  user.id,
15859
15917
  cost,
15860
15918
  LedgerOperation.SERP,
15861
- parsed.data.query
15919
+ billingKeys.debitDescription,
15920
+ billingKeys.debitKey
15862
15921
  );
15863
- if (!ok) return c.json(insufficientBalanceResponse(balance_mc, cost), 402);
15864
- debited = true;
15865
- const result = await captureSerpIntelligenceSnapshot(parsed.data, {
15866
- kernelApiKey: browserServiceApiKey(),
15867
- kernelProxyId: browserServiceProxyId(),
15868
- signal: c.req.raw.signal,
15869
- billing: { creditsUsed: cost / MC_PER_CREDIT }
15922
+ if (!ok) {
15923
+ await releaseConcurrencyGate(gate.lockId);
15924
+ return c.json(insufficientBalanceResponse(balance_mc, cost), 402);
15925
+ }
15926
+ } catch (error) {
15927
+ const idempotencyConflict = error instanceof Error && error.message === "idempotent debit key conflicts with another debit";
15928
+ const body = structuredError({
15929
+ error_code: idempotencyConflict ? "idempotency_conflict" : "capture_failed",
15930
+ error_type: idempotencyConflict ? "validation_error" : "capture_error",
15931
+ message: error instanceof Error ? error.message : String(error),
15932
+ retryable: !idempotencyConflict
15870
15933
  });
15871
- await logRequestEvent({
15934
+ await logRequestEventBestEffort({
15872
15935
  userId: user.id,
15873
- source: "serp_intelligence_capture",
15874
- status: "done",
15936
+ source: SERP_CAPTURE_BILLING_SOURCE,
15937
+ status: "failed",
15875
15938
  query: parsed.data.query,
15876
15939
  location: parsed.data.location,
15877
- resultCount: result.harvestResult.organicResults.length,
15878
- result
15940
+ error: body.message
15879
15941
  });
15880
- return c.json(result);
15942
+ await releaseConcurrencyGate(gate.lockId);
15943
+ return c.json(body, idempotencyConflict ? 409 : 500);
15944
+ }
15945
+ let result;
15946
+ try {
15947
+ const parentCostContext = currentCostContext();
15948
+ result = await runWithCostContext(
15949
+ {
15950
+ ...parentCostContext ?? {},
15951
+ op: "serp",
15952
+ userId: Number(user.id),
15953
+ headlessSentOut
15954
+ },
15955
+ () => captureSerpIntelligenceSnapshot(parsed.data, {
15956
+ kernelApiKey: browserServiceApiKey(),
15957
+ kernelProxyId: browserServiceProxyId(),
15958
+ signal: c.req.raw.signal
15959
+ })
15960
+ );
15881
15961
  } catch (error) {
15882
- if (debited) await creditMc(user.id, cost, LedgerOperation.REFUND, "failed serp intelligence capture");
15962
+ try {
15963
+ await settleDebitMcIdempotent(
15964
+ user.id,
15965
+ billingKeys.debitKey,
15966
+ 0,
15967
+ LedgerOperation.SERP_REFUND,
15968
+ "failed serp intelligence capture",
15969
+ SERP_CAPTURE_BILLING_SOURCE
15970
+ );
15971
+ } catch (settlementError) {
15972
+ console.warn(
15973
+ "[serp-intelligence] failed capture refund remains recoverable:",
15974
+ settlementError instanceof Error ? settlementError.message : String(settlementError)
15975
+ );
15976
+ }
15883
15977
  const body = error instanceof SerpIntelligenceCaptureError ? error.toJSON() : structuredError({
15884
15978
  error_code: "capture_failed",
15885
15979
  error_type: "capture_error",
15886
15980
  message: error instanceof Error ? error.message : String(error),
15887
15981
  retryable: true
15888
15982
  });
15889
- await logRequestEvent({
15983
+ await logRequestEventBestEffort({
15890
15984
  userId: user.id,
15891
- source: "serp_intelligence_capture",
15985
+ source: SERP_CAPTURE_BILLING_SOURCE,
15892
15986
  status: "failed",
15893
15987
  query: parsed.data.query,
15894
15988
  location: parsed.data.location,
15895
15989
  error: "message" in body && typeof body.message === "string" ? body.message : "SERP Intelligence capture failed"
15896
15990
  });
15897
15991
  const status = error instanceof SerpIntelligenceCaptureError ? error.httpStatus : 500;
15992
+ await releaseConcurrencyGate(gate.lockId);
15898
15993
  return c.json(body, status);
15994
+ }
15995
+ try {
15996
+ const requestedActualCost = serpActualCostMc(headlessSentOut.value) + result.pageSnapshotArtifacts.length * MC_COSTS.page_scrape;
15997
+ const settlement = await settleDebitMcIdempotent(
15998
+ user.id,
15999
+ billingKeys.debitKey,
16000
+ requestedActualCost,
16001
+ LedgerOperation.SERP_REFUND,
16002
+ "SERP capture pricing settle",
16003
+ SERP_CAPTURE_BILLING_SOURCE
16004
+ );
16005
+ const settledResult = {
16006
+ ...result,
16007
+ billing: { creditsUsed: settlement.final_amount_mc / MC_PER_CREDIT }
16008
+ };
16009
+ await logRequestEventBestEffort({
16010
+ userId: user.id,
16011
+ source: SERP_CAPTURE_BILLING_SOURCE,
16012
+ status: "done",
16013
+ query: parsed.data.query,
16014
+ location: parsed.data.location,
16015
+ resultCount: settledResult.harvestResult.organicResults.length,
16016
+ result: settledResult
16017
+ });
16018
+ return c.json(settledResult);
16019
+ } catch (error) {
16020
+ const body = structuredError({
16021
+ error_code: "capture_failed",
16022
+ error_type: "capture_error",
16023
+ message: error instanceof Error ? error.message : String(error),
16024
+ retryable: true
16025
+ });
16026
+ await logRequestEventBestEffort({
16027
+ userId: user.id,
16028
+ source: SERP_CAPTURE_BILLING_SOURCE,
16029
+ status: "failed",
16030
+ query: parsed.data.query,
16031
+ location: parsed.data.location,
16032
+ error: body.message
16033
+ });
16034
+ return c.json(body, 500);
15899
16035
  } finally {
15900
16036
  await releaseConcurrencyGate(gate.lockId);
15901
16037
  }
@@ -15931,7 +16067,7 @@ serpIntelligenceApp.post("/page-snapshots", async (c) => {
15931
16067
  maxConcurrency: parsed.data.maxConcurrency,
15932
16068
  debug: parsed.data.debug
15933
16069
  });
15934
- await logRequestEvent({
16070
+ await logRequestEventBestEffort({
15935
16071
  userId: user.id,
15936
16072
  source: "serp_intelligence_page_snapshots",
15937
16073
  status: "done",
@@ -15948,7 +16084,7 @@ serpIntelligenceApp.post("/page-snapshots", async (c) => {
15948
16084
  message: error instanceof Error ? error.message : String(error),
15949
16085
  retryable: true
15950
16086
  });
15951
- await logRequestEvent({
16087
+ await logRequestEventBestEffort({
15952
16088
  userId: user.id,
15953
16089
  source: "serp_intelligence_page_snapshots",
15954
16090
  status: "failed",
@@ -16593,15 +16729,42 @@ mcpApp.all("/", async (c) => {
16593
16729
  });
16594
16730
 
16595
16731
  // src/api/browser-agent-routes.ts
16596
- import { randomUUID as randomUUID4 } from "crypto";
16732
+ import { randomUUID as randomUUID5 } from "crypto";
16597
16733
  import { Hono as Hono18 } from "hono";
16598
16734
 
16599
16735
  // src/api/browser-agent-db.ts
16600
- import { randomUUID as randomUUID3 } from "crypto";
16736
+ import { randomUUID as randomUUID4 } from "crypto";
16601
16737
  var _ready = false;
16738
+ var _migrationPromise = null;
16602
16739
  async function migrateBrowserAgent() {
16603
16740
  if (_ready) return;
16741
+ if (_migrationPromise) return _migrationPromise;
16742
+ _migrationPromise = runBrowserAgentMigration().finally(() => {
16743
+ _migrationPromise = null;
16744
+ });
16745
+ return _migrationPromise;
16746
+ }
16747
+ async function runBrowserAgentMigration() {
16604
16748
  const db = getDb();
16749
+ const existing = await db.execute(`
16750
+ SELECT
16751
+ (SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name IN (
16752
+ 'browser_agent_sessions',
16753
+ 'browser_agent_actions',
16754
+ 'browser_agent_replays',
16755
+ 'browser_auth_connections',
16756
+ 'browser_agent_extensions'
16757
+ )) = 5
16758
+ AND (SELECT COUNT(*) FROM pragma_table_info('browser_agent_sessions') WHERE name = 'concurrency_lock_id') = 1
16759
+ AND (SELECT COUNT(*) FROM pragma_table_info('browser_auth_connections') WHERE name IN (
16760
+ 'account_email', 'note', 'updated_at', 'status', 'browser_agent_session_id'
16761
+ )) = 5
16762
+ AS ready
16763
+ `);
16764
+ if (Number(existing.rows[0]?.ready ?? 0) === 1) {
16765
+ _ready = true;
16766
+ return;
16767
+ }
16605
16768
  await db.execute(`
16606
16769
  CREATE TABLE IF NOT EXISTS browser_agent_sessions (
16607
16770
  id TEXT PRIMARY KEY,
@@ -16701,7 +16864,7 @@ async function migrateBrowserAgent() {
16701
16864
  }
16702
16865
  async function createExtensionRow(input) {
16703
16866
  const db = getDb();
16704
- const id = `bext_${randomUUID3().replace(/-/g, "").slice(0, 20)}`;
16867
+ const id = `bext_${randomUUID4().replace(/-/g, "").slice(0, 20)}`;
16705
16868
  await db.execute({
16706
16869
  sql: `INSERT INTO browser_agent_extensions (id, user_id, name, backend_id, backend_name, source, source_url, size_bytes)
16707
16870
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -16736,7 +16899,7 @@ async function deleteExtensionRow(userId, name) {
16736
16899
  }
16737
16900
  async function createAuthConnectionRow(input) {
16738
16901
  const db = getDb();
16739
- const connectionId = `authc_${randomUUID3().replace(/-/g, "").slice(0, 20)}`;
16902
+ const connectionId = `authc_${randomUUID4().replace(/-/g, "").slice(0, 20)}`;
16740
16903
  await db.execute({
16741
16904
  sql: `INSERT INTO browser_auth_connections (connection_id, domain, profile, account_email, note, status, browser_agent_session_id)
16742
16905
  VALUES (?, ?, ?, ?, ?, 'NEEDS_AUTH', ?)`,
@@ -16770,7 +16933,7 @@ async function listAuthConnectionsByProfile(profile) {
16770
16933
  }
16771
16934
  async function createSessionRow(input) {
16772
16935
  const db = getDb();
16773
- const id = `bas_${randomUUID3().replace(/-/g, "").slice(0, 20)}`;
16936
+ const id = `bas_${randomUUID4().replace(/-/g, "").slice(0, 20)}`;
16774
16937
  await db.execute({
16775
16938
  sql: `INSERT INTO browser_agent_sessions (id, runtime_session_id, live_view_url, cdp_ws_url, status, label, user_id, concurrency_lock_id, last_action_at)
16776
16939
  VALUES (?, ?, ?, ?, 'open', ?, ?, ?, datetime('now'))`,
@@ -16839,7 +17002,7 @@ async function recordAction(input) {
16839
17002
  sql: `INSERT INTO browser_agent_actions (id, session_id, type, params_json, ok, error)
16840
17003
  VALUES (?, ?, ?, ?, ?, ?)`,
16841
17004
  args: [
16842
- `baa_${randomUUID3().replace(/-/g, "").slice(0, 20)}`,
17005
+ `baa_${randomUUID4().replace(/-/g, "").slice(0, 20)}`,
16843
17006
  input.sessionId,
16844
17007
  input.type,
16845
17008
  input.params == null ? null : JSON.stringify(input.params),
@@ -18527,7 +18690,7 @@ function buildBrowserAgentRoutes() {
18527
18690
  }
18528
18691
  const existing = await getExtensionRow(user.id, name);
18529
18692
  if (existing) return c.json({ error: `an extension named "${name}" already exists \u2014 delete it first or pick another name` }, 409);
18530
- const backendName = `u${user.id}_${randomUUID4().replace(/-/g, "")}`;
18693
+ const backendName = `u${user.id}_${randomUUID5().replace(/-/g, "")}`;
18531
18694
  try {
18532
18695
  const imported = await importExtensionFromStore(storeUrl, backendName);
18533
18696
  const row = await createExtensionRow({
@@ -18957,7 +19120,7 @@ async function resolveUser(customerId, emailFallback) {
18957
19120
  // src/api/oauth-routes.ts
18958
19121
  import { Hono as Hono20 } from "hono";
18959
19122
  import { getCookie, setCookie } from "hono/cookie";
18960
- import { createHash as createHash4, randomBytes as randomBytes2, randomUUID as randomUUID5 } from "crypto";
19123
+ import { createHash as createHash5, randomBytes as randomBytes2, randomUUID as randomUUID6 } from "crypto";
18961
19124
  import { importPKCS8, exportJWK, SignJWT } from "jose";
18962
19125
  var ISSUER = "https://mcpscraper.dev";
18963
19126
  var RESOURCE = () => process.env.MCP_MEMORY_RESOURCE_URL ?? "https://memory.mcpscraper.dev/mcp";
@@ -18990,7 +19153,7 @@ async function getKeys() {
18990
19153
  const privateKey = await importPKCS8(pem, "RS256", { extractable: true });
18991
19154
  const full = await exportJWK(privateKey);
18992
19155
  const publicJwk = { kty: full.kty, n: full.n, e: full.e };
18993
- const kid = createHash4("sha256").update(JSON.stringify({ e: publicJwk.e, kty: publicJwk.kty, n: publicJwk.n })).digest("base64url").slice(0, 16);
19156
+ const kid = createHash5("sha256").update(JSON.stringify({ e: publicJwk.e, kty: publicJwk.kty, n: publicJwk.n })).digest("base64url").slice(0, 16);
18994
19157
  publicJwk.kid = kid;
18995
19158
  publicJwk.alg = "RS256";
18996
19159
  publicJwk.use = "sig";
@@ -19300,12 +19463,12 @@ oauthApp.post("/oauth/authorize", async (c) => {
19300
19463
  });
19301
19464
  function pkceMatches(verifier, challenge) {
19302
19465
  if (!verifier) return false;
19303
- const computed = createHash4("sha256").update(verifier).digest("base64url");
19466
+ const computed = createHash5("sha256").update(verifier).digest("base64url");
19304
19467
  return computed === challenge;
19305
19468
  }
19306
19469
  async function mintAccessToken(identity, scope, plan, audience) {
19307
19470
  const { privateKey, kid } = await getKeys();
19308
- return new SignJWT({ scope, plan }).setProtectedHeader({ alg: "RS256", kid }).setIssuer(ISSUER).setSubject(identity).setAudience(audience).setIssuedAt().setJti(randomUUID5()).setExpirationTime(`${ACCESS_TTL_SECONDS}s`).sign(privateKey);
19471
+ return new SignJWT({ scope, plan }).setProtectedHeader({ alg: "RS256", kid }).setIssuer(ISSUER).setSubject(identity).setAudience(audience).setIssuedAt().setJti(randomUUID6()).setExpirationTime(`${ACCESS_TTL_SECONDS}s`).sign(privateKey);
19309
19472
  }
19310
19473
  function tokenErrorResponse(c, error, description, status) {
19311
19474
  return c.json({ error, error_description: description }, status);
@@ -20651,13 +20814,13 @@ import { getCookie as getCookie2, setCookie as setCookie2, deleteCookie } from "
20651
20814
  import Stripe2 from "stripe";
20652
20815
 
20653
20816
  // src/api/page-diff.ts
20654
- import { createHash as createHash5 } from "crypto";
20817
+ import { createHash as createHash6 } from "crypto";
20655
20818
  import { diffLines } from "diff";
20656
20819
  var MAX_SNAPSHOT_CONTENT_CHARS = 25e4;
20657
20820
  var MAX_DIFF_HUNKS = 200;
20658
20821
  var MAX_DIFF_LINES_PER_RESPONSE = 2e3;
20659
20822
  function sha256Hex(value) {
20660
- return createHash5("sha256").update(value).digest("hex");
20823
+ return createHash6("sha256").update(value).digest("hex");
20661
20824
  }
20662
20825
  function truncateForStorage(value, maxChars = MAX_SNAPSHOT_CONTENT_CHARS) {
20663
20826
  if (value.length <= maxChars) return { value, truncated: false };
@@ -20764,7 +20927,7 @@ async function persistScrapeBody(user, opts) {
20764
20927
  }
20765
20928
 
20766
20929
  // src/api/connection-memory-import.ts
20767
- import { createHash as createHash6 } from "crypto";
20930
+ import { createHash as createHash7 } from "crypto";
20768
20931
  var CONNECTION_MEMORY_IMPORT_MAX_ARGS_BYTES = 64 * 1024;
20769
20932
  var CONNECTION_MEMORY_IMPORT_MAX_RESULT_BYTES = 1e6;
20770
20933
  var CONNECTION_MEMORY_IMPORT_MAX_STRING_CHARS = 256e3;
@@ -20794,7 +20957,7 @@ function canonicalJson(value) {
20794
20957
  return JSON.stringify(value);
20795
20958
  }
20796
20959
  function sha2562(value) {
20797
- return createHash6("sha256").update(value).digest("hex");
20960
+ return createHash7("sha256").update(value).digest("hex");
20798
20961
  }
20799
20962
  function sensitiveKey(key) {
20800
20963
  const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
@@ -21063,7 +21226,7 @@ async function cleanupExpiredScrapeBlobs(maxAgeMs = SCRAPE_BLOB_TTL_MS) {
21063
21226
  }
21064
21227
 
21065
21228
  // src/api/connected-data-export.ts
21066
- import { randomUUID as randomUUID6 } from "crypto";
21229
+ import { randomUUID as randomUUID7 } from "crypto";
21067
21230
  var CONNECTED_DATA_INLINE_BUDGET_BYTES = Number(
21068
21231
  process.env.MCP_SCRAPER_CONNECTED_DATA_INLINE_BUDGET_BYTES ?? 5e4
21069
21232
  );
@@ -21188,7 +21351,7 @@ function finitePositive(value, fallback) {
21188
21351
  return Number.isFinite(value) && value > 0 ? value : fallback;
21189
21352
  }
21190
21353
  async function collectConnectedDataExport(args) {
21191
- const exportId = randomUUID6();
21354
+ const exportId = randomUUID7();
21192
21355
  const now = args.now ?? Date.now;
21193
21356
  const startedAt = now();
21194
21357
  const budgetMs = finitePositive(CONNECTED_DATA_EXPORT_BUDGET_MS, 24e4);
@@ -21300,12 +21463,6 @@ ${lines.length ? `${lines.join("\n")}
21300
21463
  }
21301
21464
 
21302
21465
  // src/api/credit-operations.ts
21303
- async function grantSignupCredit(userId) {
21304
- if (!FREE_SIGNUP_MC || FREE_SIGNUP_MC <= 0) return;
21305
- const already = await ledgerExistsForOperation(userId, LedgerOperation.SIGNUP_GRANT);
21306
- if (already) return;
21307
- await creditMc(userId, FREE_SIGNUP_MC, LedgerOperation.SIGNUP_GRANT, "Welcome credits");
21308
- }
21309
21466
  async function applyMonthlyFreeRefresh(user) {
21310
21467
  return user;
21311
21468
  }
@@ -21517,7 +21674,7 @@ async function dbUsage(identity, plan) {
21517
21674
  }
21518
21675
 
21519
21676
  // src/api/nango-control.ts
21520
- import { createHash as createHash7 } from "crypto";
21677
+ import { createHash as createHash8 } from "crypto";
21521
21678
  var DEFAULT_NANGO_CONTROL_URL = "https://mcp-scraper-scheduler.vercel.app";
21522
21679
  var DISABLED_NANGO_TOOLS = {
21523
21680
  "google-mail": /* @__PURE__ */ new Set(["list-filters"]),
@@ -22116,7 +22273,7 @@ function canonicalJson2(value) {
22116
22273
  return JSON.stringify(value);
22117
22274
  }
22118
22275
  function projectedToolSchemaHash(tool) {
22119
- return createHash7("sha256").update(canonicalJson2(tool)).digest("hex");
22276
+ return createHash8("sha256").update(canonicalJson2(tool)).digest("hex");
22120
22277
  }
22121
22278
  async function describeNangoTool(identity, connectionId, tool, fresh) {
22122
22279
  const body = await controlRequest("/api/internal/nango/connections/actions/describe", {
@@ -22717,7 +22874,6 @@ app.use("*", (c, next) => {
22717
22874
  const forceHeadless = c.req.header("x-kernel-headless") === "true";
22718
22875
  return runWithCostContext({ op, probeRunId, forceHeadless }, () => next());
22719
22876
  });
22720
- void migrateCostTelemetry();
22721
22877
  app.post("/auth/register", requireAllowedOrigin, async (c) => {
22722
22878
  const { email, password } = await c.req.json();
22723
22879
  const normalizedEmail = email?.trim().toLowerCase();
@@ -22748,7 +22904,6 @@ app.post("/auth/register", requireAllowedOrigin, async (c) => {
22748
22904
  }
22749
22905
  }
22750
22906
  const newKey = await rotateApiKey(user.id);
22751
- await grantSignupCredit(user.id);
22752
22907
  void (async () => {
22753
22908
  try {
22754
22909
  const full = await getUserById(user.id);
@@ -23997,7 +24152,7 @@ app.post("/harvest/sync", auth2, async (c) => {
23997
24152
  if (diff > 0) await creditMc(user.id, diff, LedgerOperation.PAA_REFUND, "overestimate refund");
23998
24153
  else if (diff < 0) await debitMc(user.id, -diff, LedgerOperation.PAA, options.query);
23999
24154
  } else {
24000
- const actualCost = headlessSentOut.value === true ? MC_COSTS.serp_headless : MC_COSTS.serp_headful;
24155
+ const actualCost = serpActualCostMc(headlessSentOut.value);
24001
24156
  const diff = syncCost - actualCost;
24002
24157
  if (diff > 0) await creditMc(user.id, diff, LedgerOperation.SERP_REFUND, "headless-mode pricing settle");
24003
24158
  else if (diff < 0) await debitMc(user.id, -diff, LedgerOperation.SERP, options.query);
@@ -24076,22 +24231,13 @@ app.delete("/admin/users/:id", adminAuth, async (c) => {
24076
24231
  });
24077
24232
  app.post("/admin/backfill-signup-credits", adminAuth, async (c) => {
24078
24233
  const users = await listUsers();
24079
- let processed = 0;
24080
- let credited = 0;
24081
- let skipped = 0;
24082
- const users_credited = [];
24083
- for (const user of users) {
24084
- processed++;
24085
- const alreadyHasGrant = await ledgerExistsForOperation(user.id, LedgerOperation.SIGNUP_GRANT);
24086
- if (alreadyHasGrant) {
24087
- skipped++;
24088
- } else {
24089
- await grantSignupCredit(user.id);
24090
- credited++;
24091
- users_credited.push({ id: user.id, email: user.email });
24092
- }
24093
- }
24094
- return c.json({ processed, credited, skipped, users_credited });
24234
+ return c.json({
24235
+ processed: users.length,
24236
+ credited: 0,
24237
+ skipped: users.length,
24238
+ users_credited: [],
24239
+ retired: true
24240
+ });
24095
24241
  });
24096
24242
  app.post("/extract-url", auth2, async (c) => {
24097
24243
  const raw = await c.req.json().catch(() => ({}));
@@ -24672,7 +24818,7 @@ app.get("/cron/tick", async (c) => {
24672
24818
  if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
24673
24819
  return c.json({ error: "Unauthorized" }, 401);
24674
24820
  }
24675
- const { drainQueue } = await import("./worker-BXJQ7J4N.js");
24821
+ const { drainQueue } = await import("./worker-RV2TYXE3.js");
24676
24822
  const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
24677
24823
  const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
24678
24824
  const [results, sweepResult, reapResult, expiredResult, blobCleanup, connectedDataArtifactCleanup] = await Promise.all([
@@ -24700,15 +24846,15 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
24700
24846
  return c.json({ error: "Unauthorized" }, 401);
24701
24847
  }
24702
24848
  const jobId = c.req.param("id");
24703
- const { getExtractJob: getExtractJob2, completeExtractJob: completeExtractJob2 } = await import("./site-extract-repository-FDH6ZXKT.js");
24704
- const { assembleExtractArtifacts } = await import("./extract-bundle-4RO3YU4Q.js");
24849
+ const { getExtractJob: getExtractJob2, completeExtractJob: completeExtractJob2 } = await import("./site-extract-repository-2QQSL2QC.js");
24850
+ const { assembleExtractArtifacts } = await import("./extract-bundle-NFFVMUMT.js");
24705
24851
  const job = await getExtractJob2(jobId);
24706
24852
  if (!job) return c.json({ error: "job not found" }, 404);
24707
24853
  const stored = await assembleExtractArtifacts(job, {});
24708
24854
  await completeExtractJob2(jobId, stored);
24709
24855
  let settlement = "already_settled_or_refunded";
24710
24856
  if (job.billedMc == null && job.userId != null) {
24711
- const { settleExtractJob: settleExtractJob2, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-FDH6ZXKT.js");
24857
+ const { settleExtractJob: settleExtractJob2, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-2QQSL2QC.js");
24712
24858
  const heldMc = Number(job.options.heldMc ?? 0);
24713
24859
  const successful = await countSuccessfulPages2(jobId);
24714
24860
  const usedMc = Math.min(successful * MC_COSTS.page_scrape, heldMc);
@@ -24874,4 +25020,4 @@ app.get("/blog/:slug/", (c) => {
24874
25020
  export {
24875
25021
  app
24876
25022
  };
24877
- //# sourceMappingURL=server-PBBRSLBP.js.map
25023
+ //# sourceMappingURL=server-OLOOFN5W.js.map