mcp-scraper 0.2.19 → 0.2.21

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 (77) hide show
  1. package/README.md +36 -5
  2. package/dist/bin/api-server.cjs +1072 -349
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +3 -3
  5. package/dist/bin/browser-agent-stdio-server.cjs +1274 -75
  6. package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
  7. package/dist/bin/browser-agent-stdio-server.js +4 -2
  8. package/dist/bin/browser-agent-stdio-server.js.map +1 -1
  9. package/dist/bin/mcp-scraper-cli.cjs +659 -210
  10. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  11. package/dist/bin/mcp-scraper-cli.js +108 -9
  12. package/dist/bin/mcp-scraper-cli.js.map +1 -1
  13. package/dist/bin/mcp-scraper-combined-stdio-server.cjs +2104 -566
  14. package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
  15. package/dist/bin/mcp-scraper-combined-stdio-server.js +7 -4
  16. package/dist/bin/mcp-scraper-combined-stdio-server.js.map +1 -1
  17. package/dist/bin/mcp-scraper-install.cjs +3 -3
  18. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  19. package/dist/bin/mcp-scraper-install.js +2 -2
  20. package/dist/bin/mcp-stdio-server.cjs +726 -387
  21. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  22. package/dist/bin/mcp-stdio-server.js +3 -2
  23. package/dist/bin/mcp-stdio-server.js.map +1 -1
  24. package/dist/bin/paa-harvest.cjs +5 -3
  25. package/dist/bin/paa-harvest.cjs.map +1 -1
  26. package/dist/bin/paa-harvest.js +5 -2
  27. package/dist/bin/paa-harvest.js.map +1 -1
  28. package/dist/{chunk-TL7YTFLH.js → chunk-DBQDG7EH.js} +99 -27
  29. package/dist/chunk-DBQDG7EH.js.map +1 -0
  30. package/dist/chunk-DUEW4EOO.js +214 -0
  31. package/dist/chunk-DUEW4EOO.js.map +1 -0
  32. package/dist/{chunk-F44RBOJ5.js → chunk-H2R232HK.js} +216 -108
  33. package/dist/chunk-H2R232HK.js.map +1 -0
  34. package/dist/chunk-HS6OKELW.js +1786 -0
  35. package/dist/chunk-HS6OKELW.js.map +1 -0
  36. package/dist/{chunk-CQTAKXBN.js → chunk-IPW4LFOT.js} +11 -116
  37. package/dist/chunk-IPW4LFOT.js.map +1 -0
  38. package/dist/chunk-LFATOGDF.js +111 -0
  39. package/dist/chunk-LFATOGDF.js.map +1 -0
  40. package/dist/{chunk-MCBOZ2JF.js → chunk-MLNCKQ2B.js} +3 -3
  41. package/dist/chunk-MLNCKQ2B.js.map +1 -0
  42. package/dist/{chunk-4UEVYYDW.js → chunk-O5J7E4VX.js} +394 -57
  43. package/dist/chunk-O5J7E4VX.js.map +1 -0
  44. package/dist/{chunk-BSYPATSM.js → chunk-SXTXMFEQ.js} +2 -2
  45. package/dist/chunk-WN7PBKMV.js +28 -0
  46. package/dist/chunk-WN7PBKMV.js.map +1 -0
  47. package/dist/chunk-XKUDVN2E.js +7 -0
  48. package/dist/chunk-XKUDVN2E.js.map +1 -0
  49. package/dist/{db-P5X6UQ3E.js → db-BE4JVB3V.js} +8 -2
  50. package/dist/index.cjs +5 -3
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.d.cts +2 -2
  53. package/dist/index.d.ts +2 -2
  54. package/dist/index.js +3 -1
  55. package/dist/index.js.map +1 -1
  56. package/dist/{server-CRUSZA2Q.js → server-24XIQ6W6.js} +248 -40
  57. package/dist/server-24XIQ6W6.js.map +1 -0
  58. package/dist/{worker-OZSWIS3F.js → worker-SLQ375UG.js} +8 -5
  59. package/dist/{worker-OZSWIS3F.js.map → worker-SLQ375UG.js.map} +1 -1
  60. package/docs/adr/0001-in-page-graphql-interception-for-anti-bot-scraping.md +2 -2
  61. package/docs/mcp-tool-craft-lint.generated.md +51 -0
  62. package/docs/mcp-tool-manifest.generated.json +1534 -0
  63. package/docs/specs/local-competitive-audit-spec.md +1 -2
  64. package/docs/specs/mcp-tool-definition-quality-audit-spec.md +1602 -0
  65. package/package.json +4 -1
  66. package/dist/chunk-4UEVYYDW.js.map +0 -1
  67. package/dist/chunk-76P4DKWR.js +0 -793
  68. package/dist/chunk-76P4DKWR.js.map +0 -1
  69. package/dist/chunk-CQTAKXBN.js.map +0 -1
  70. package/dist/chunk-F44RBOJ5.js.map +0 -1
  71. package/dist/chunk-MCBOZ2JF.js.map +0 -1
  72. package/dist/chunk-RU2HZD55.js +0 -7
  73. package/dist/chunk-RU2HZD55.js.map +0 -1
  74. package/dist/chunk-TL7YTFLH.js.map +0 -1
  75. package/dist/server-CRUSZA2Q.js.map +0 -1
  76. /package/dist/{chunk-BSYPATSM.js.map → chunk-SXTXMFEQ.js.map} +0 -0
  77. /package/dist/{db-P5X6UQ3E.js.map → db-BE4JVB3V.js.map} +0 -0
@@ -22,6 +22,27 @@ function harvestTimeoutBudget(maxQuestions, serpOnly = false) {
22
22
  }
23
23
 
24
24
  // src/mcp/http-mcp-tool-executor.ts
25
+ function youtubeVideoIdFromUrl(url) {
26
+ if (!url) return null;
27
+ try {
28
+ const parsed = new URL(url);
29
+ const host = parsed.hostname.replace(/^www\./, "").replace(/^m\./, "");
30
+ if (host === "youtu.be") {
31
+ const id = parsed.pathname.split("/").filter(Boolean)[0];
32
+ return id || null;
33
+ }
34
+ if (host === "youtube.com" || host === "music.youtube.com") {
35
+ const watchId = parsed.searchParams.get("v");
36
+ if (watchId) return watchId;
37
+ const parts = parsed.pathname.split("/").filter(Boolean);
38
+ const markerIndex = parts.findIndex((part) => ["shorts", "embed", "live"].includes(part));
39
+ if (markerIndex >= 0 && parts[markerIndex + 1]) return parts[markerIndex + 1];
40
+ }
41
+ } catch {
42
+ return null;
43
+ }
44
+ return null;
45
+ }
25
46
  var HttpMcpToolExecutor = class {
26
47
  baseUrl;
27
48
  apiKey;
@@ -147,7 +168,21 @@ var HttpMcpToolExecutor = class {
147
168
  return this.call("/youtube/harvest", input);
148
169
  }
149
170
  youtubeTranscribe(input) {
150
- return this.call("/youtube/transcribe", input);
171
+ const videoId = input.videoId?.trim() || youtubeVideoIdFromUrl(input.url);
172
+ if (!videoId) {
173
+ return Promise.resolve({
174
+ content: [{
175
+ type: "text",
176
+ text: JSON.stringify({
177
+ error_code: "youtube_video_id_required",
178
+ error: "Pass videoId from youtube_harvest or a YouTube url that contains a video id.",
179
+ retryable: false
180
+ })
181
+ }],
182
+ isError: true
183
+ });
184
+ }
185
+ return this.call("/youtube/transcribe", { videoId });
151
186
  }
152
187
  facebookPageIntel(input) {
153
188
  return this.call("/facebook/page-intel", input);
@@ -184,6 +219,10 @@ var HttpMcpToolExecutor = class {
184
219
  webhookUrl: input.webhookUrl
185
220
  }, Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 9e5);
186
221
  }
222
+ workflowStep(input) {
223
+ const timeoutMs = this.httpTimeoutOverrideMs ?? Number(process.env.MCP_SCRAPER_WORKFLOW_TIMEOUT_MS ?? 9e5);
224
+ return this.call(`/workflows/runs/${encodeURIComponent(input.runId)}/step`, {}, Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 9e5);
225
+ }
187
226
  workflowStatus(input) {
188
227
  return this.getJson(`/workflows/runs/${encodeURIComponent(input.runId)}`);
189
228
  }
@@ -210,7 +249,7 @@ var import_node_fs2 = require("fs");
210
249
  var import_node_path2 = require("path");
211
250
 
212
251
  // src/version.ts
213
- var PACKAGE_VERSION = "0.2.19";
252
+ var PACKAGE_VERSION = "0.2.20";
214
253
 
215
254
  // src/mcp/mcp-response-formatter.ts
216
255
  var import_node_fs = require("fs");
@@ -832,13 +871,25 @@ ${videoRows}`,
832
871
  }
833
872
  };
834
873
  }
874
+ function structuredTranscriptChunks(chunks) {
875
+ return chunks.map((c) => ({
876
+ startSec: Number.isFinite(c.timestamp?.[0]) ? c.timestamp[0] : 0,
877
+ endSec: Number.isFinite(c.timestamp?.[1]) ? c.timestamp[1] : 0,
878
+ text: c.text
879
+ }));
880
+ }
881
+ function wordCount(text) {
882
+ return text.trim() ? text.trim().split(/\s+/).length : 0;
883
+ }
835
884
  function formatYoutubeTranscribe(raw, input) {
836
885
  const parsed = parseData(raw);
837
886
  if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
838
887
  const d = parsed.data;
839
888
  const text = d.text ?? "";
840
889
  const chunks = d.chunks ?? [];
890
+ const videoId = d.videoId ?? input.videoId ?? null;
841
891
  const durSec = d.durationMs ? (d.durationMs / 1e3).toFixed(0) : "\u2014";
892
+ const words = wordCount(text);
842
893
  const chunkRows = chunks.slice(0, 50).map((c) => {
843
894
  const sec = Number.isFinite(c.timestamp[0]) ? Math.floor(c.timestamp[0]) : 0;
844
895
  const mm = String(Math.floor(sec / 60)).padStart(2, "0");
@@ -846,8 +897,8 @@ function formatYoutubeTranscribe(raw, input) {
846
897
  return `| ${mm}:${ss} | ${cell(truncate(c.text, 120))} |`;
847
898
  }).join("\n");
848
899
  const full = [
849
- `# YouTube Transcript: \`${input.videoId}\``,
850
- `**Duration:** ${durSec}s \xB7 **${text.split(" ").length} words**`,
900
+ `# YouTube Transcript: \`${videoId ?? input.url ?? "video"}\``,
901
+ `**Duration:** ${durSec}s \xB7 **${words} words**`,
851
902
  `
852
903
  ## Full Transcript
853
904
  ${text}`,
@@ -860,7 +911,22 @@ ${chunkRows}` : "",
860
911
  ---
861
912
  \u{1F4A1} Harvest more from this channel: use \`youtube_harvest\` with \`mode: "channel"\``
862
913
  ].filter(Boolean).join("\n");
863
- return oneBlock(full);
914
+ return {
915
+ ...oneBlock(full),
916
+ structuredContent: {
917
+ videoId,
918
+ url: videoId ? `https://www.youtube.com/watch?v=${videoId}` : input.url ?? null,
919
+ wordCount: words,
920
+ chunkCount: chunks.length,
921
+ durationMs: typeof d.durationMs === "number" ? d.durationMs : null,
922
+ transcriptText: text,
923
+ chunks: structuredTranscriptChunks(chunks),
924
+ resolvedInputs: {
925
+ videoId,
926
+ url: input.url ?? null
927
+ }
928
+ }
929
+ };
864
930
  }
865
931
  function formatFacebookPageIntel(raw, input) {
866
932
  const parsed = parseData(raw);
@@ -870,12 +936,13 @@ function formatFacebookPageIntel(raw, input) {
870
936
  const ads = d.ads ?? [];
871
937
  const s = d.summary ?? { totalAds: 0, activeCount: 0, videoCount: 0, imageCount: 0 };
872
938
  const adBlocks = ads.map((ad, i) => [
873
- `### Ad ${i + 1}${ad.libraryId ? ` \xB7 \`${ad.libraryId}\`` : ""} \u2014 ${ad.status ?? "\u2014"} \xB7 ${ad.creativeType ?? "\u2014"} \xB7 ${ad.startDate ?? "\u2014"}`,
939
+ `### Ad ${i + 1}${ad.libraryId ? ` \xB7 \`${ad.libraryId}\`` : ""} \u2014 ${ad.status ?? "\u2014"} \xB7 ${ad.creativeType ?? "\u2014"} \xB7 ${ad.startDate ?? ad.started ?? "\u2014"}`,
874
940
  ad.headline ? `**Headline:** ${ad.headline}` : "",
875
941
  ad.primaryText ? `**Copy:** ${truncate(ad.primaryText, 200)}` : "",
876
942
  ad.cta ? `**CTA:** ${ad.cta}` : "",
877
- ad.videoUrl ? `**Video URL:** \`${ad.videoUrl}\`` : "",
878
- ad.variations ? `**Variations:** ${ad.variations}` : ""
943
+ ad.landingUrl ? `**Landing URL:** ${ad.landingUrl}` : "",
944
+ ad.videoUrl ?? ad.videoSrc ? `**Video URL:** \`${ad.videoUrl ?? ad.videoSrc}\`` : "",
945
+ ad.variations ?? ad.clusterCount ? `**Variations:** ${ad.variations ?? ad.clusterCount}` : ""
879
946
  ].filter(Boolean).join("\n")).join("\n\n---\n\n");
880
947
  const full = [
881
948
  `# Facebook Ad Intel: ${advertiser}`,
@@ -901,11 +968,16 @@ ${adBlocks}`,
901
968
  libraryId: ad.libraryId ?? null,
902
969
  status: ad.status ?? null,
903
970
  creativeType: ad.creativeType ?? null,
971
+ primaryText: ad.primaryText ?? null,
904
972
  headline: ad.headline ?? null,
905
973
  cta: ad.cta ?? null,
906
- startDate: ad.startDate ?? null,
907
- videoUrl: ad.videoUrl ?? null,
908
- variations: typeof ad.variations === "number" ? ad.variations : null
974
+ startDate: ad.startDate ?? ad.started ?? null,
975
+ landingUrl: ad.landingUrl ?? null,
976
+ domain: ad.domain ?? null,
977
+ videoUrl: ad.videoUrl ?? ad.videoSrc ?? null,
978
+ imageUrl: ad.imageUrl ?? ad.imageSrc ?? null,
979
+ videoPoster: ad.videoPoster ?? null,
980
+ variations: typeof ad.variations === "number" ? ad.variations : typeof ad.clusterCount === "number" ? ad.clusterCount : null
909
981
  }))
910
982
  }
911
983
  };
@@ -939,8 +1011,11 @@ ${rows}`,
939
1011
  advertiserCount: advertisers.length,
940
1012
  advertisers: advertisers.map((a) => ({
941
1013
  name: a.pageName ?? a.name ?? null,
1014
+ pageId: a.pageId ?? null,
1015
+ pageUrl: a.pageUrl ?? null,
942
1016
  adCount: typeof a.adCount === "number" ? a.adCount : null,
943
- libraryId: a.sampleLibraryId ?? a.libraryId ?? null
1017
+ libraryId: a.sampleLibraryId ?? a.libraryId ?? null,
1018
+ sampleLibraryId: a.sampleLibraryId ?? null
944
1019
  }))
945
1020
  }
946
1021
  };
@@ -1038,6 +1113,34 @@ function formatWorkflowSuggest(input) {
1038
1113
  }
1039
1114
  };
1040
1115
  }
1116
+ function workflowStepLines(data) {
1117
+ const step = data.step;
1118
+ const nextStep = data.nextStep;
1119
+ const done = data.done === true;
1120
+ const lines = [];
1121
+ if (step) {
1122
+ const index = Number(step.index ?? 0);
1123
+ const totalSteps = step.totalSteps != null ? Number(step.totalSteps) : void 0;
1124
+ const stepLabel = totalSteps ? `${index + 1}/${totalSteps}` : `${index + 1}`;
1125
+ lines.push(`
1126
+ ## Step ${stepLabel}: ${step.title ?? step.id ?? ""}`);
1127
+ const output = step.output;
1128
+ if (output && Object.keys(output).length) {
1129
+ lines.push(Object.entries(output).map(([k, v]) => `- ${k}: ${typeof v === "object" ? JSON.stringify(v) : String(v)}`).join("\n"));
1130
+ }
1131
+ const warnings = Array.isArray(step.warnings) ? step.warnings : [];
1132
+ if (warnings.length) lines.push(`
1133
+ **Warnings:**
1134
+ ${warnings.map((w) => `- ${w}`).join("\n")}`);
1135
+ }
1136
+ if (done) {
1137
+ lines.push("\n**Done.** All steps complete.");
1138
+ } else if (nextStep && typeof nextStep === "object") {
1139
+ lines.push(`
1140
+ **Next step:** \`${nextStep.id ?? nextStep.index}\` \u2014 call \`workflow_step\` with this run id to continue.`);
1141
+ }
1142
+ return lines;
1143
+ }
1041
1144
  function formatWorkflowRun(raw, input) {
1042
1145
  const parsed = parseData(raw);
1043
1146
  if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
@@ -1051,6 +1154,7 @@ function formatWorkflowRun(raw, input) {
1051
1154
  `**Run ID:** \`${runId || "unknown"}\``,
1052
1155
  `**Status:** ${status}`,
1053
1156
  summary?.title ? `**Title:** ${summary.title}` : "",
1157
+ ...workflowStepLines(parsed.data),
1054
1158
  summary?.summary ? `
1055
1159
  ## Summary
1056
1160
  ${summary.summary}` : "",
@@ -1066,6 +1170,42 @@ ${workflowArtifactRows(artifacts)}` : "",
1066
1170
  input: input.input ?? {},
1067
1171
  run,
1068
1172
  summary,
1173
+ step: parsed.data.step,
1174
+ nextStep: parsed.data.nextStep ?? null,
1175
+ done: parsed.data.done === true,
1176
+ artifacts
1177
+ }
1178
+ };
1179
+ }
1180
+ function formatWorkflowStep(raw, input) {
1181
+ const parsed = parseData(raw);
1182
+ if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
1183
+ const run = parsed.data.run;
1184
+ const summary = parsed.data.summary;
1185
+ const artifacts = workflowArtifactsFrom(run);
1186
+ const done = parsed.data.done === true;
1187
+ const full = [
1188
+ `# Workflow Step`,
1189
+ `**Run ID:** \`${input.runId}\``,
1190
+ `**Status:** ${run?.status ?? (done ? "done" : "running")}`,
1191
+ ...workflowStepLines(parsed.data),
1192
+ done && summary?.summary ? `
1193
+ ## Summary
1194
+ ${summary.summary}` : "",
1195
+ artifacts.length ? `
1196
+ ## Artifacts
1197
+ ${workflowArtifactRows(artifacts)}` : "",
1198
+ done && artifacts.length ? "\nUse `workflow_artifact_read` with the run id and artifact id to pull CSV, JSON, Markdown, or report content into context." : ""
1199
+ ].filter(Boolean).join("\n");
1200
+ return {
1201
+ ...oneBlock(full),
1202
+ structuredContent: {
1203
+ runId: input.runId,
1204
+ run,
1205
+ summary: summary ?? null,
1206
+ step: parsed.data.step,
1207
+ nextStep: parsed.data.nextStep ?? null,
1208
+ done,
1069
1209
  artifacts
1070
1210
  }
1071
1211
  };
@@ -1470,6 +1610,7 @@ function formatFacebookAdTranscribe(raw, input) {
1470
1610
  const text = d.text ?? "";
1471
1611
  const chunks = d.chunks ?? [];
1472
1612
  const durSec = d.durationMs ? (d.durationMs / 1e3).toFixed(0) : "\u2014";
1613
+ const words = wordCount(text);
1473
1614
  const chunkRows = chunks.slice(0, 50).map((c) => {
1474
1615
  const sec = Number.isFinite(c.timestamp[0]) ? Math.floor(c.timestamp[0]) : 0;
1475
1616
  const mm = String(Math.floor(sec / 60)).padStart(2, "0");
@@ -1478,7 +1619,7 @@ function formatFacebookAdTranscribe(raw, input) {
1478
1619
  }).join("\n");
1479
1620
  const full = [
1480
1621
  `# Facebook Ad Transcript`,
1481
- `**Duration:** ${durSec}s \xB7 **${text.split(" ").length} words**`,
1622
+ `**Duration:** ${durSec}s \xB7 **${words} words**`,
1482
1623
  `
1483
1624
  ## Full Transcript
1484
1625
  ${text}`,
@@ -1491,7 +1632,20 @@ ${chunkRows}` : "",
1491
1632
  ---
1492
1633
  \u{1F4A1} Get more ads from this advertiser: use \`facebook_page_intel\`. For public Facebook reel/post URLs, use \`facebook_video_transcribe\`.`
1493
1634
  ].filter(Boolean).join("\n");
1494
- return oneBlock(full);
1635
+ return {
1636
+ ...oneBlock(full),
1637
+ structuredContent: {
1638
+ videoUrl: input.videoUrl,
1639
+ wordCount: words,
1640
+ chunkCount: chunks.length,
1641
+ durationMs: typeof d.durationMs === "number" ? d.durationMs : null,
1642
+ transcriptText: text,
1643
+ chunks: structuredTranscriptChunks(chunks),
1644
+ resolvedInputs: {
1645
+ videoUrl: input.videoUrl
1646
+ }
1647
+ }
1648
+ };
1495
1649
  }
1496
1650
  function formatFacebookVideoTranscribe(raw, input) {
1497
1651
  const parsed = parseData(raw);
@@ -1499,7 +1653,7 @@ function formatFacebookVideoTranscribe(raw, input) {
1499
1653
  const d = parsed.data;
1500
1654
  const text = d.text ?? "";
1501
1655
  const chunks = d.chunks ?? [];
1502
- const wordCount = text.trim() ? text.trim().split(/\s+/).length : 0;
1656
+ const wordCount2 = text.trim() ? text.trim().split(/\s+/).length : 0;
1503
1657
  const durSec = d.durationMs ? (d.durationMs / 1e3).toFixed(0) : "\u2014";
1504
1658
  const videoDuration = typeof d.videoDurationSec === "number" ? `${Math.round(d.videoDurationSec)}s` : "\u2014";
1505
1659
  const label = d.videoId ? `Facebook Organic Video \`${d.videoId}\`` : "Facebook Organic Video";
@@ -1512,7 +1666,7 @@ function formatFacebookVideoTranscribe(raw, input) {
1512
1666
  const full = [
1513
1667
  `# ${label} Transcript`,
1514
1668
  d.ownerName ? `**Owner:** ${d.ownerName}` : "",
1515
- `**Video duration:** ${videoDuration} \xB7 **Transcribed in:** ${durSec}s \xB7 **${wordCount} words**`,
1669
+ `**Video duration:** ${videoDuration} \xB7 **Transcribed in:** ${durSec}s \xB7 **${wordCount2} words**`,
1516
1670
  d.pageUrl ? `**Page URL:** ${d.pageUrl}` : `**Page URL:** ${input.url}`,
1517
1671
  d.videoUrl ? `**Extracted MP4:** \`${d.videoUrl}\`` : "",
1518
1672
  `
@@ -1538,7 +1692,7 @@ ${chunkRows}` : "",
1538
1692
  bitrate: typeof d.bitrate === "number" ? d.bitrate : null,
1539
1693
  videoDurationSec: typeof d.videoDurationSec === "number" ? d.videoDurationSec : null,
1540
1694
  videoUrl: d.videoUrl ?? "",
1541
- wordCount,
1695
+ wordCount: wordCount2,
1542
1696
  chunkCount: chunks.length,
1543
1697
  transcriptText: text,
1544
1698
  chunks: chunks.map((c) => ({
@@ -1551,128 +1705,229 @@ ${chunkRows}` : "",
1551
1705
  }
1552
1706
 
1553
1707
  // src/mcp/mcp-tool-schemas.ts
1708
+ var import_zod2 = require("zod");
1709
+
1710
+ // src/schemas.ts
1554
1711
  var import_zod = require("zod");
1712
+ var DEFAULT_PROXY_MODE = "configured";
1713
+ var DEFAULT_MAPS_PROXY_MODE = "location";
1714
+ var HarvestOptionsSchema = import_zod.z.object({
1715
+ query: import_zod.z.string().min(1),
1716
+ location: import_zod.z.string().optional(),
1717
+ gl: import_zod.z.string().length(2).default("us"),
1718
+ hl: import_zod.z.string().length(2).default("en"),
1719
+ device: import_zod.z.enum(["desktop", "mobile"]).default("desktop"),
1720
+ proxyMode: import_zod.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE),
1721
+ proxyZip: import_zod.z.string().regex(/^\d{5}$/).optional(),
1722
+ debug: import_zod.z.boolean().default(false),
1723
+ depth: import_zod.z.number().int().min(1).max(30).default(3),
1724
+ maxQuestions: import_zod.z.number().int().min(1).max(1e3).default(100),
1725
+ headless: import_zod.z.boolean().default(false),
1726
+ profileDir: import_zod.z.string().optional(),
1727
+ proxy: import_zod.z.string().url().optional(),
1728
+ kernelApiKey: import_zod.z.string().optional(),
1729
+ kernelProxyId: import_zod.z.string().optional(),
1730
+ kernelProxyResolution: import_zod.z.unknown().optional(),
1731
+ outputDir: import_zod.z.string().default("./paa-output"),
1732
+ format: import_zod.z.enum(["json", "csv", "both"]).default("both"),
1733
+ serpOnly: import_zod.z.boolean().default(false),
1734
+ pages: import_zod.z.number().int().min(1).max(2).default(1)
1735
+ });
1736
+ var MapsPlaceOptionsSchema = import_zod.z.object({
1737
+ businessName: import_zod.z.string().min(1),
1738
+ location: import_zod.z.string().min(1),
1739
+ gl: import_zod.z.string().length(2).default("us"),
1740
+ hl: import_zod.z.string().length(2).default("en"),
1741
+ includeReviews: import_zod.z.boolean().default(false),
1742
+ maxReviews: import_zod.z.number().int().min(1).max(500).default(50),
1743
+ kernelApiKey: import_zod.z.string().optional(),
1744
+ kernelProxyId: import_zod.z.string().optional(),
1745
+ headless: import_zod.z.boolean().default(true)
1746
+ });
1747
+ var MapsSearchOptionsSchema = import_zod.z.object({
1748
+ query: import_zod.z.string().min(1),
1749
+ location: import_zod.z.string().optional(),
1750
+ gl: import_zod.z.string().length(2).default("us"),
1751
+ hl: import_zod.z.string().length(2).default("en"),
1752
+ maxResults: import_zod.z.number().int().min(1).max(50).default(10),
1753
+ proxyMode: import_zod.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
1754
+ proxyZip: import_zod.z.string().regex(/^\d{5}$/).optional(),
1755
+ debug: import_zod.z.boolean().default(false),
1756
+ kernelApiKey: import_zod.z.string().optional(),
1757
+ kernelProxyId: import_zod.z.string().optional(),
1758
+ kernelProxyResolution: import_zod.z.unknown().optional(),
1759
+ headless: import_zod.z.boolean().default(true)
1760
+ });
1761
+ var RawPAAItemSchema = import_zod.z.object({
1762
+ question: import_zod.z.string().min(1),
1763
+ answer: import_zod.z.string().optional(),
1764
+ sourceTitle: import_zod.z.string().optional(),
1765
+ sourceSite: import_zod.z.string().optional(),
1766
+ sourceCite: import_zod.z.string().optional()
1767
+ });
1768
+ var RawMapsOverviewSchema = import_zod.z.object({
1769
+ name: import_zod.z.string().nullable(),
1770
+ rating: import_zod.z.string().nullable(),
1771
+ reviewCount: import_zod.z.string().nullable(),
1772
+ category: import_zod.z.string().nullable(),
1773
+ address: import_zod.z.string().nullable(),
1774
+ hoursSummary: import_zod.z.string().nullable(),
1775
+ phone: import_zod.z.string().nullable(),
1776
+ phoneDisplay: import_zod.z.string().nullable(),
1777
+ website: import_zod.z.string().nullable(),
1778
+ plusCode: import_zod.z.string().nullable(),
1779
+ bookingUrl: import_zod.z.string().nullable()
1780
+ });
1781
+ var RawMapsHoursRowSchema = import_zod.z.object({
1782
+ day: import_zod.z.string(),
1783
+ hours: import_zod.z.string()
1784
+ });
1785
+ var RawMapsReviewStatsSchema = import_zod.z.object({
1786
+ reviewHistogram: import_zod.z.array(import_zod.z.object({
1787
+ stars: import_zod.z.number(),
1788
+ count: import_zod.z.string()
1789
+ })),
1790
+ reviewTopics: import_zod.z.array(import_zod.z.object({
1791
+ label: import_zod.z.string(),
1792
+ count: import_zod.z.string()
1793
+ }))
1794
+ });
1795
+ var RawMapsReviewCardSchema = import_zod.z.object({
1796
+ reviewId: import_zod.z.string(),
1797
+ author: import_zod.z.string().nullable(),
1798
+ stars: import_zod.z.string().nullable(),
1799
+ date: import_zod.z.string().nullable(),
1800
+ text: import_zod.z.string().nullable(),
1801
+ ownerResponse: import_zod.z.string().nullable()
1802
+ });
1803
+ var RawMapsAboutAttributeSchema = import_zod.z.object({
1804
+ section: import_zod.z.string(),
1805
+ attribute: import_zod.z.string()
1806
+ });
1807
+
1808
+ // src/mcp/mcp-tool-schemas.ts
1555
1809
  var HarvestPaaInputSchema = {
1556
- query: import_zod.z.string().min(1).describe('Core search topic only. If the user says "best hvac company in Denver CO", use query="best hvac company" and location="Denver, CO". Do not include the location in query when it can be separated.'),
1557
- location: import_zod.z.string().optional().describe('City, region, or country for geo-targeted results, inferred from the user request when present, e.g. "Denver, CO", "Tokyo, Japan", "London, UK".'),
1558
- maxQuestions: import_zod.z.number().int().min(1).max(200).default(30).describe("Number of PAA questions to extract. Default 30. Maximum 200. Use 10 for quick probes, 30 for normal research, 100-200 when the user asks for everything/full/deep research. Larger harvests get a longer server time budget (151-200 questions \u2192 up to 280s). Credits are charged by extracted question; unused request hold is refunded."),
1559
- gl: import_zod.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."),
1560
- hl: import_zod.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."),
1561
- device: import_zod.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
1562
- proxyMode: import_zod.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. If Google shows a CAPTCHA/challenge, browser-service sessions briefly wait for the automatic solver first, then retry with a fresh proxy/session if the challenge does not clear. Also retries proxy tunnel failure and wrong-location evidence before returning. Use configured only for the static configured proxy. Use none only for direct-network debugging."),
1563
- proxyZip: import_zod.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."),
1564
- debug: import_zod.z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior.")
1810
+ query: import_zod2.z.string().min(1).describe('Core search topic only. If the user says "best hvac company in Denver CO", use query="best hvac company" and location="Denver, CO". Do not include the location in query when it can be separated.'),
1811
+ location: import_zod2.z.string().optional().describe('City, region, or country for geo-targeted results, inferred from the user request when present, e.g. "Denver, CO", "Tokyo, Japan", "London, UK".'),
1812
+ maxQuestions: import_zod2.z.number().int().min(1).max(200).default(30).describe("Number of PAA questions to extract. Default 30. Maximum 200. Use 10 for quick probes, 30 for normal research, 100-200 when the user asks for everything/full/deep research. Larger harvests get a longer server time budget (151-200 questions \u2192 up to 280s). Credits are charged by extracted question; unused request hold is refunded."),
1813
+ gl: import_zod2.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."),
1814
+ hl: import_zod2.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."),
1815
+ device: import_zod2.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
1816
+ proxyMode: import_zod2.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe("Proxy targeting mode. Default configured uses the service proxy without city/ZIP targeting for the highest general success rate. Use location only when the user explicitly needs city/ZIP-targeted residential proxy evidence. Use none only for direct-network debugging."),
1817
+ proxyZip: import_zod2.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use only with proxyMode location when the user gives a specific ZIP or city-center targeting needs to be forced."),
1818
+ debug: import_zod2.z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior.")
1565
1819
  };
1566
1820
  var ExtractUrlInputSchema = {
1567
- url: import_zod.z.string().url().describe("Public http/https URL to extract. Use this when the user provides one specific page URL."),
1568
- screenshot: import_zod.z.boolean().default(false).describe("Also capture a full-page screenshot of the URL. Saved to ~/Downloads/mcp-scraper/screenshots/ and returned inline. Use when the user asks to see or capture the page visually."),
1569
- screenshotDevice: import_zod.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport for screenshot. desktop = 1440\xD7900. mobile = 390\xD7844. Default desktop."),
1570
- extractBranding: import_zod.z.boolean().default(false).describe("Extract brand colors, fonts, logo, and favicon using a rendered browser session. Returns colorScheme (light/dark), colors (primary/accent/background/text/heading as hex), fonts (heading/body family names), and assets (logo URL, favicon URL). Use when the user asks about brand colors, site theme, or brand assets."),
1571
- downloadMedia: import_zod.z.boolean().default(false).describe("Extract and download all page media (images, video, audio) to ~/Downloads/mcp-scraper/media/. Ad networks, tracking pixels, and noise URLs are filtered automatically. Use when the user asks to download or harvest assets from a page."),
1572
- mediaTypes: import_zod.z.array(import_zod.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download. Default all three."),
1573
- allowLocal: import_zod.z.boolean().default(false).describe("Allow localhost and private-network URLs. For local development only.")
1821
+ url: import_zod2.z.string().url().describe("Public http/https URL to extract. Use this when the user provides one specific page URL."),
1822
+ screenshot: import_zod2.z.boolean().default(false).describe("Also capture a full-page screenshot of the URL. Saved to ~/Downloads/mcp-scraper/screenshots/ and returned inline. Use when the user asks to see or capture the page visually."),
1823
+ screenshotDevice: import_zod2.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport for screenshot. desktop = 1440\xD7900. mobile = 390\xD7844. Default desktop."),
1824
+ extractBranding: import_zod2.z.boolean().default(false).describe("Extract brand colors, fonts, logo, and favicon using a rendered browser session. Returns colorScheme (light/dark), colors (primary/accent/background/text/heading as hex), fonts (heading/body family names), and assets (logo URL, favicon URL). Use when the user asks about brand colors, site theme, or brand assets."),
1825
+ downloadMedia: import_zod2.z.boolean().default(false).describe("Extract and download all page media (images, video, audio) to ~/Downloads/mcp-scraper/media/. Ad networks, tracking pixels, and noise URLs are filtered automatically. Use when the user asks to download or harvest assets from a page."),
1826
+ mediaTypes: import_zod2.z.array(import_zod2.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download. Default all three."),
1827
+ allowLocal: import_zod2.z.boolean().default(false).describe("Allow localhost and private-network URLs. For local development only.")
1574
1828
  };
1575
1829
  var MapSiteUrlsInputSchema = {
1576
- url: import_zod.z.string().url().describe("Public website URL or domain to crawl for internal URLs. Use before extract_site when the user asks to audit/map/crawl a site."),
1577
- maxUrls: import_zod.z.number().int().min(1).max(500).optional().describe("Maximum URLs to discover. Use 100 for normal maps, higher when the user asks for a full inventory.")
1830
+ url: import_zod2.z.string().url().describe("Public website URL or domain to crawl for internal URLs. Use before extract_site when the user asks to audit/map/crawl a site."),
1831
+ maxUrls: import_zod2.z.number().int().min(1).max(500).optional().describe("Maximum URLs to discover. Use 100 for normal maps, higher when the user asks for a full inventory.")
1578
1832
  };
1579
1833
  var ExtractSiteInputSchema = {
1580
- url: import_zod.z.string().url().describe("Public website URL or domain to extract across multiple pages. Use when the user asks for a site audit, website crawl, or full-site content/schema extraction."),
1581
- maxPages: import_zod.z.number().int().min(1).max(50).optional().describe("Maximum pages to extract. Use 50 when the user asks for full results or a complete crawl within MCP limits.")
1834
+ url: import_zod2.z.string().url().describe("Public website URL or domain to extract across multiple pages. Use when the user asks for a site audit, website crawl, or full-site content/schema extraction."),
1835
+ maxPages: import_zod2.z.number().int().min(1).max(50).optional().describe("Maximum pages to extract. Use 50 when the user asks for full results or a complete crawl within MCP limits.")
1582
1836
  };
1583
1837
  var YoutubeHarvestInputSchema = {
1584
- mode: import_zod.z.enum(["search", "channel"]).describe("Use search for topic/keyword requests. Use channel when the user provides @handle, channel ID, or channel URL."),
1585
- query: import_zod.z.string().optional().describe("Required when mode is search. The YouTube search topic in the user\u2019s words."),
1586
- channelHandle: import_zod.z.string().optional().describe("YouTube channel handle, channel ID, or URL. Examples: @mkbhd, UC..., https://youtube.com/@mkbhd."),
1587
- maxVideos: import_zod.z.number().int().min(1).max(500).default(50).describe("Number of videos to return. Default 50. Increase when user asks for full channel/history.")
1838
+ mode: import_zod2.z.enum(["search", "channel"]).describe("Use search for topic/keyword requests. Use channel when the user provides @handle, channel ID, or channel URL."),
1839
+ query: import_zod2.z.string().optional().describe("Required when mode is search. The YouTube search topic in the user\u2019s words."),
1840
+ channelHandle: import_zod2.z.string().optional().describe("YouTube channel handle, channel ID, or URL. Examples: @mkbhd, UC..., https://youtube.com/@mkbhd."),
1841
+ maxVideos: import_zod2.z.number().int().min(1).max(500).default(50).describe("Number of videos to return. Default 50, maximum 500. Use 10-25 for quick topic discovery, 50 for normal channel/search harvests, and larger values only when the user asks for full channel/history because large responses consume more context.")
1588
1842
  };
1589
1843
  var YoutubeTranscribeInputSchema = {
1590
- videoId: import_zod.z.string().min(1).describe("YouTube video ID, e.g. dQw4w9WgXcQ")
1844
+ videoId: import_zod2.z.string().min(1).optional().describe("YouTube video ID, e.g. dQw4w9WgXcQ. Use only an ID returned by youtube_harvest or visible in a YouTube URL; do not invent one."),
1845
+ url: import_zod2.z.string().url().optional().describe("Full YouTube URL, e.g. https://www.youtube.com/watch?v=dQw4w9WgXcQ or https://youtu.be/dQw4w9WgXcQ. Use this when the user pasted a URL instead of an ID. Provide videoId or url.")
1591
1846
  };
1592
1847
  var FacebookPageIntelInputSchema = {
1593
- pageId: import_zod.z.string().optional(),
1594
- libraryId: import_zod.z.string().optional(),
1595
- query: import_zod.z.string().optional().describe("Advertiser or brand name when pageId/libraryId is not known. One of pageId, libraryId, or query is required."),
1596
- maxAds: import_zod.z.number().int().min(1).max(200).default(50),
1597
- country: import_zod.z.string().length(2).default("US")
1848
+ pageId: import_zod2.z.string().optional().describe("Facebook advertiser/page ID. Use only a pageId returned by facebook_ad_search/facebook_page_intel or copied from Facebook Ad Library; do not construct one yourself."),
1849
+ libraryId: import_zod2.z.string().optional().describe("Facebook Ad Library archive ID for a known ad or advertiser sample. Use a libraryId returned by facebook_ad_search, or a libraryId/adArchiveId visible in Ad Library."),
1850
+ query: import_zod2.z.string().optional().describe("Advertiser or brand name when pageId/libraryId is not known. One of pageId, libraryId, or query is required."),
1851
+ maxAds: import_zod2.z.number().int().min(1).max(200).default(50).describe("Maximum ads to inspect. Default 50, maximum 200. Prefer 25-50 for focused advertiser scans; use 100-200 only when the user asks for a broad ad archive sweep."),
1852
+ country: import_zod2.z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US. Examples: US, CA, GB, AU. Infer from the user request when they name a country.")
1598
1853
  };
1599
1854
  var FacebookAdSearchInputSchema = {
1600
- query: import_zod.z.string().min(1).describe("Advertiser, brand, competitor, niche, or keyword to search in Facebook Ad Library."),
1601
- country: import_zod.z.string().length(2).default("US"),
1602
- maxResults: import_zod.z.number().int().min(1).max(20).default(10)
1855
+ query: import_zod2.z.string().min(1).describe("Advertiser, brand, competitor, niche, or keyword to search in Facebook Ad Library."),
1856
+ country: import_zod2.z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US. Examples: US, CA, GB, AU."),
1857
+ maxResults: import_zod2.z.number().int().min(1).max(20).default(10).describe("Maximum advertisers to return. Default 10, maximum 20. Prefer tighter search terms over maxing this out.")
1603
1858
  };
1604
1859
  var FacebookAdTranscribeInputSchema = {
1605
- videoUrl: import_zod.z.string().url().describe("Direct Facebook CDN video URL from a facebook_page_intel ad result. Do not pass a public Facebook reel/post/share URL here; use facebook_video_transcribe for organic Facebook URLs.")
1860
+ videoUrl: import_zod2.z.string().url().describe("Direct Facebook CDN video URL from a facebook_page_intel ad result. Do not pass a public Facebook reel/post/share URL here; use facebook_video_transcribe for organic Facebook URLs.")
1606
1861
  };
1607
1862
  var FacebookVideoTranscribeInputSchema = {
1608
- url: import_zod.z.string().url().describe("Organic Facebook reel, video, watch, post, or share URL from facebook.com, m.facebook.com, or fb.watch. The tool renders the page, extracts the best matching public Facebook CDN MP4 URL, then transcribes it. Use this when the user pastes a normal Facebook video page URL and asks for the transcript or downloadable MP4."),
1609
- quality: import_zod.z.enum(["best", "hd", "sd"]).default("best").describe("Preferred progressive MP4 quality. Use best by default; hd prefers the highest HD progressive URL; sd forces the SD URL.")
1863
+ url: import_zod2.z.string().url().describe("Organic Facebook reel, video, watch, post, or share URL from facebook.com, m.facebook.com, or fb.watch. The tool renders the page, extracts the best matching public Facebook CDN MP4 URL, then transcribes it. Use this when the user pastes a normal Facebook video page URL and asks for the transcript or downloadable MP4."),
1864
+ quality: import_zod2.z.enum(["best", "hd", "sd"]).default("best").describe("Preferred progressive MP4 quality. Use best by default; hd prefers the highest HD progressive URL; sd forces the SD URL.")
1610
1865
  };
1611
1866
  var MapsPlaceIntelInputSchema = {
1612
- businessName: import_zod.z.string().min(1).describe('Business name only. If user says "Elite Roofing Denver CO", use businessName="Elite Roofing" and location="Denver, CO".'),
1613
- location: import_zod.z.string().min(1).describe('City/region/country where the business should be searched, e.g. "Denver, CO". Infer from the user request when possible.'),
1614
- gl: import_zod.z.string().length(2).default("us").describe("Google country code inferred from location."),
1615
- hl: import_zod.z.string().length(2).default("en").describe("Language inferred from user request."),
1616
- includeReviews: import_zod.z.boolean().default(false).describe("Whether to fetch individual review cards"),
1617
- maxReviews: import_zod.z.number().int().min(1).max(500).default(50).describe("Max review cards to return (requires includeReviews: true)")
1867
+ businessName: import_zod2.z.string().min(1).describe('Business name only. If user says "Elite Roofing Denver CO", use businessName="Elite Roofing" and location="Denver, CO".'),
1868
+ location: import_zod2.z.string().min(1).describe('City/region/country where the business should be searched, e.g. "Denver, CO". Infer from the user request when possible.'),
1869
+ gl: import_zod2.z.string().length(2).default("us").describe("Google country code inferred from location."),
1870
+ hl: import_zod2.z.string().length(2).default("en").describe("Language inferred from user request."),
1871
+ includeReviews: import_zod2.z.boolean().default(false).describe("Whether to fetch individual review cards. Use true when the user asks for reviews, customer pain, complaints, praise, themes, or review evidence."),
1872
+ maxReviews: import_zod2.z.number().int().min(1).max(500).default(50).describe("Max review cards to return when includeReviews is true. Default 50, maximum 500. Use 50 for normal review analysis and larger values only for deep review mining.")
1618
1873
  };
1619
1874
  var MapsSearchInputSchema = {
1620
- query: import_zod.z.string().min(1).describe('Business category, niche, keyword, or search term. If the user says "roofers in Denver CO", use query="roofers" and location="Denver, CO". Do not put the location here when it can be separated.'),
1621
- location: import_zod.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.'),
1622
- gl: import_zod.z.string().length(2).default("us").describe("Google country code inferred from location."),
1623
- hl: import_zod.z.string().length(2).default("en").describe("Language inferred from user request."),
1624
- maxResults: import_zod.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."),
1625
- proxyMode: import_zod.z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode. Use location by default for US city/state Maps searches; retryable failures create a new residential proxy ID and new browser session for up to 5 attempts. Use configured for the server proxy ID, and none only for local direct-network debugging."),
1626
- proxyZip: import_zod.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."),
1627
- debug: import_zod.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics when debugging Maps localization, CAPTCHA, or proxy behavior.")
1875
+ query: import_zod2.z.string().min(1).describe('Business category, niche, keyword, or search term. If the user says "roofers in Denver CO", use query="roofers" and location="Denver, CO". Do not put the location here when it can be separated.'),
1876
+ location: import_zod2.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.'),
1877
+ gl: import_zod2.z.string().length(2).default("us").describe("Google country code inferred from location."),
1878
+ hl: import_zod2.z.string().length(2).default("en").describe("Language inferred from user request."),
1879
+ maxResults: import_zod2.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."),
1880
+ proxyMode: import_zod2.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy targeting mode. Maps defaults to location so local market searches get city/state residential proxy targeting and rotation. Use configured to force the service proxy without city/ZIP targeting, and none only for local direct-network debugging."),
1881
+ proxyZip: import_zod2.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."),
1882
+ debug: import_zod2.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics when debugging Maps localization, CAPTCHA, or proxy behavior.")
1628
1883
  };
1629
1884
  var DirectoryWorkflowInputSchema = {
1630
- query: import_zod.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."),
1631
- state: import_zod.z.string().min(2).default("TN").describe("US state abbreviation or state name used to select Census places, e.g. TN or Tennessee."),
1632
- minPopulation: import_zod.z.number().int().min(0).default(1e5).describe('Minimum Census place population for market selection. Use 100000 for "cities above 100k population".'),
1633
- populationYear: import_zod.z.number().int().min(2020).max(2025).default(2025).describe("Census population estimate year from the 2020-2025 Population Estimates Program city/place dataset."),
1634
- maxCities: import_zod.z.number().int().min(1).max(100).default(25).describe("Maximum number of markets to process after sorting by population descending."),
1635
- maxResultsPerCity: import_zod.z.number().int().min(1).max(50).default(50).describe("Google Maps business/profile candidates to collect for each city. Maximum 50."),
1636
- concurrency: import_zod.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."),
1637
- includeZipGroups: import_zod.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."),
1638
- usZipsCsvPath: import_zod.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."),
1639
- saveCsv: import_zod.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, review_count, category, address, phone, hours_status, website_url, directions_url, place_url, CID fields, population, and ZIP groups."),
1640
- proxyMode: import_zod.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; retryable failures create a new residential proxy ID and new browser session for up to 5 attempts per city. Use configured for the server proxy ID, and none only for local direct-network debugging."),
1641
- proxyZip: import_zod.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."),
1642
- debug: import_zod.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics in each Maps browser session when supported.")
1885
+ query: import_zod2.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."),
1886
+ state: import_zod2.z.string().min(2).default("TN").describe("US state abbreviation or state name used to select Census places, e.g. TN or Tennessee."),
1887
+ minPopulation: import_zod2.z.number().int().min(0).default(1e5).describe('Minimum Census place population for market selection. Use 100000 for "cities above 100k population".'),
1888
+ populationYear: import_zod2.z.number().int().min(2020).max(2025).default(2025).describe("Census population estimate year from the 2020-2025 Population Estimates Program city/place dataset."),
1889
+ maxCities: import_zod2.z.number().int().min(1).max(100).default(25).describe("Maximum number of markets to process after sorting by population descending."),
1890
+ maxResultsPerCity: import_zod2.z.number().int().min(1).max(50).default(50).describe("Google Maps business/profile candidates to collect for each city. Maximum 50."),
1891
+ concurrency: import_zod2.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."),
1892
+ includeZipGroups: import_zod2.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."),
1893
+ usZipsCsvPath: import_zod2.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."),
1894
+ saveCsv: import_zod2.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, review_count, category, address, phone, hours_status, website_url, directions_url, place_url, CID fields, population, and ZIP groups."),
1895
+ proxyMode: import_zod2.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy targeting mode for every city Maps search. Maps workflows default to location so each city can use city/state or ZIP-group residential proxy targeting. Use configured to force the service proxy without city/ZIP targeting, and none only for local direct-network debugging."),
1896
+ proxyZip: import_zod2.z.string().regex(/^\d{5}$/).optional().describe("Optional ZIP override for proxy targeting. Normally omit it so each city can use its ZIP group or city/state location."),
1897
+ debug: import_zod2.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics in each Maps browser session when supported.")
1643
1898
  };
1644
- var RankTrackerModeSchema = import_zod.z.enum(["maps", "organic", "ai_overview", "paa"]);
1899
+ var RankTrackerModeSchema = import_zod2.z.enum(["maps", "organic", "ai_overview", "paa"]);
1645
1900
  var RankTrackerBlueprintInputSchema = {
1646
- projectName: import_zod.z.string().min(1).optional().describe("Optional name for the rank tracker project, client, or campaign."),
1647
- targetDomain: import_zod.z.string().min(1).optional().describe("Primary domain to track in organic results, AI Overview citations, and PAA sources, e.g. example.com."),
1648
- targetBusinessName: import_zod.z.string().min(1).optional().describe("Primary Google Business Profile or brand/business name to match in Maps results. Required for reliable Maps rank tracking."),
1649
- trackingModes: import_zod.z.array(RankTrackerModeSchema).min(1).max(4).default(["maps", "organic", "ai_overview", "paa"]).describe("Rank tracker surfaces to build. maps uses directory_workflow/maps_search. organic uses search_serp. ai_overview uses search_serp/harvest_paa. paa uses harvest_paa source presence."),
1650
- keywords: import_zod.z.array(import_zod.z.string().min(1)).max(200).default([]).describe("Seed keywords or service queries to track. Leave empty when the downstream AI should create the keyword table from user input."),
1651
- locations: import_zod.z.array(import_zod.z.string().min(1)).max(100).default([]).describe("Markets, cities, ZIPs, or service areas to track. Use city/state strings like Denver, CO for localized SERP and Maps checks."),
1652
- competitors: import_zod.z.array(import_zod.z.string().min(1)).max(100).default([]).describe("Optional competitor domains or business names to persist as comparison targets."),
1653
- database: import_zod.z.enum(["postgres", "neon", "supabase", "sqlite", "mysql"]).default("postgres").describe("Database family the downstream AI should target when generating migrations."),
1654
- scheduleCadence: import_zod.z.enum(["daily", "weekly", "monthly", "custom"]).default("weekly").describe("Default recurring rank check cadence for the generated cron/heartbeat plan."),
1655
- customCron: import_zod.z.string().min(1).optional().describe("Cron expression to use when scheduleCadence is custom."),
1656
- timezone: import_zod.z.string().min(1).default("UTC").describe("IANA timezone for scheduled rank checks, e.g. America/Denver."),
1657
- includeCron: import_zod.z.boolean().default(true).describe("Include a cron or heartbeat worker plan. Keep true for production rank trackers."),
1658
- includeDashboard: import_zod.z.boolean().default(true).describe("Include dashboard/reporting requirements in the generated prompt."),
1659
- includeAlerts: import_zod.z.boolean().default(true).describe("Include alert rules for rank movement and SERP feature gains/losses."),
1660
- notes: import_zod.z.string().max(4e3).optional().describe("Extra product, client, stack, or hosting requirements to include in the implementation prompt.")
1901
+ projectName: import_zod2.z.string().min(1).optional().describe("Optional name for the rank tracker project, client, or campaign."),
1902
+ targetDomain: import_zod2.z.string().min(1).optional().describe("Primary domain to track in organic results, AI Overview citations, and PAA sources, e.g. example.com."),
1903
+ targetBusinessName: import_zod2.z.string().min(1).optional().describe("Primary Google Business Profile or brand/business name to match in Maps results. Required for reliable Maps rank tracking."),
1904
+ trackingModes: import_zod2.z.array(RankTrackerModeSchema).min(1).max(4).default(["maps", "organic", "ai_overview", "paa"]).describe("Rank tracker surfaces to build. maps uses directory_workflow/maps_search. organic uses search_serp. ai_overview uses search_serp/harvest_paa. paa uses harvest_paa source presence."),
1905
+ keywords: import_zod2.z.array(import_zod2.z.string().min(1)).max(200).default([]).describe("Seed keywords or service queries to track. Leave empty when the downstream AI should create the keyword table from user input."),
1906
+ locations: import_zod2.z.array(import_zod2.z.string().min(1)).max(100).default([]).describe("Markets, cities, ZIPs, or service areas to track. Use city/state strings like Denver, CO for localized SERP and Maps checks."),
1907
+ competitors: import_zod2.z.array(import_zod2.z.string().min(1)).max(100).default([]).describe("Optional competitor domains or business names to persist as comparison targets."),
1908
+ database: import_zod2.z.enum(["postgres", "neon", "supabase", "sqlite", "mysql"]).default("postgres").describe("Database family the downstream AI should target when generating migrations."),
1909
+ scheduleCadence: import_zod2.z.enum(["daily", "weekly", "monthly", "custom"]).default("weekly").describe("Default recurring rank check cadence for the generated cron/heartbeat plan."),
1910
+ customCron: import_zod2.z.string().min(1).optional().describe("Cron expression to use when scheduleCadence is custom."),
1911
+ timezone: import_zod2.z.string().min(1).default("UTC").describe("IANA timezone for scheduled rank checks, e.g. America/Denver."),
1912
+ includeCron: import_zod2.z.boolean().default(true).describe("Include a cron or heartbeat worker plan. Keep true for production rank trackers."),
1913
+ includeDashboard: import_zod2.z.boolean().default(true).describe("Include dashboard/reporting requirements in the generated prompt."),
1914
+ includeAlerts: import_zod2.z.boolean().default(true).describe("Include alert rules for rank movement and SERP feature gains/losses."),
1915
+ notes: import_zod2.z.string().max(4e3).optional().describe("Extra product, client, stack, or hosting requirements to include in the implementation prompt.")
1661
1916
  };
1662
- var NullableString = import_zod.z.string().nullable();
1663
- var MapsSearchAttemptOutput = import_zod.z.object({
1664
- attemptNumber: import_zod.z.number().int().min(1),
1665
- maxAttempts: import_zod.z.number().int().min(1),
1666
- status: import_zod.z.enum(["ok", "failed"]),
1667
- outcome: import_zod.z.string(),
1668
- willRetry: import_zod.z.boolean(),
1669
- durationMs: import_zod.z.number().int().min(0),
1670
- resultCount: import_zod.z.number().int().min(0),
1917
+ var NullableString = import_zod2.z.string().nullable();
1918
+ var MapsSearchAttemptOutput = import_zod2.z.object({
1919
+ attemptNumber: import_zod2.z.number().int().min(1),
1920
+ maxAttempts: import_zod2.z.number().int().min(1),
1921
+ status: import_zod2.z.enum(["ok", "failed"]),
1922
+ outcome: import_zod2.z.string(),
1923
+ willRetry: import_zod2.z.boolean(),
1924
+ durationMs: import_zod2.z.number().int().min(0),
1925
+ resultCount: import_zod2.z.number().int().min(0),
1671
1926
  error: NullableString,
1672
- proxyMode: import_zod.z.enum(["location", "configured", "none"]),
1673
- proxyResolutionSource: import_zod.z.enum(["disabled", "location_reused", "location_created", "configured_fallback", "unavailable"]).nullable(),
1927
+ proxyMode: import_zod2.z.enum(["location", "configured", "none"]),
1928
+ proxyResolutionSource: import_zod2.z.enum(["disabled", "location_reused", "location_created", "configured_fallback", "unavailable"]).nullable(),
1674
1929
  proxyIdSuffix: NullableString,
1675
- proxyTargetLevel: import_zod.z.enum(["zip", "city", "state"]).nullable(),
1930
+ proxyTargetLevel: import_zod2.z.enum(["zip", "city", "state"]).nullable(),
1676
1931
  proxyTargetLocation: NullableString,
1677
1932
  proxyTargetZip: NullableString,
1678
1933
  browserSessionIdSuffix: NullableString,
@@ -1681,17 +1936,17 @@ var MapsSearchAttemptOutput = import_zod.z.object({
1681
1936
  observedRegion: NullableString
1682
1937
  });
1683
1938
  var MapsSearchOutputSchema = {
1684
- query: import_zod.z.string(),
1685
- location: import_zod.z.string().nullable(),
1686
- searchQuery: import_zod.z.string(),
1687
- searchUrl: import_zod.z.string().url(),
1688
- extractedAt: import_zod.z.string(),
1689
- requestedMaxResults: import_zod.z.number().int().min(1).max(50),
1690
- resultCount: import_zod.z.number().int().min(0).max(50),
1691
- results: import_zod.z.array(import_zod.z.object({
1692
- position: import_zod.z.number().int().min(1),
1693
- name: import_zod.z.string(),
1694
- placeUrl: import_zod.z.string().url(),
1939
+ query: import_zod2.z.string(),
1940
+ location: import_zod2.z.string().nullable(),
1941
+ searchQuery: import_zod2.z.string(),
1942
+ searchUrl: import_zod2.z.string().url(),
1943
+ extractedAt: import_zod2.z.string(),
1944
+ requestedMaxResults: import_zod2.z.number().int().min(1).max(50),
1945
+ resultCount: import_zod2.z.number().int().min(0).max(50),
1946
+ results: import_zod2.z.array(import_zod2.z.object({
1947
+ position: import_zod2.z.number().int().min(1),
1948
+ name: import_zod2.z.string(),
1949
+ placeUrl: import_zod2.z.string().url(),
1695
1950
  cid: NullableString,
1696
1951
  cidDecimal: NullableString,
1697
1952
  rating: NullableString,
@@ -1702,15 +1957,15 @@ var MapsSearchOutputSchema = {
1702
1957
  hoursStatus: NullableString,
1703
1958
  websiteUrl: NullableString,
1704
1959
  directionsUrl: NullableString,
1705
- metadata: import_zod.z.array(import_zod.z.string())
1960
+ metadata: import_zod2.z.array(import_zod2.z.string())
1706
1961
  })),
1707
- attempts: import_zod.z.array(MapsSearchAttemptOutput),
1708
- durationMs: import_zod.z.number().int().min(0)
1962
+ attempts: import_zod2.z.array(MapsSearchAttemptOutput),
1963
+ durationMs: import_zod2.z.number().int().min(0)
1709
1964
  };
1710
- var DirectoryMapsBusinessOutput = import_zod.z.object({
1711
- position: import_zod.z.number().int().min(1),
1712
- name: import_zod.z.string(),
1713
- placeUrl: import_zod.z.string().url(),
1965
+ var DirectoryMapsBusinessOutput = import_zod2.z.object({
1966
+ position: import_zod2.z.number().int().min(1),
1967
+ name: import_zod2.z.string(),
1968
+ placeUrl: import_zod2.z.string().url(),
1714
1969
  cid: NullableString,
1715
1970
  cidDecimal: NullableString,
1716
1971
  rating: NullableString,
@@ -1721,113 +1976,113 @@ var DirectoryMapsBusinessOutput = import_zod.z.object({
1721
1976
  hoursStatus: NullableString,
1722
1977
  websiteUrl: NullableString,
1723
1978
  directionsUrl: NullableString,
1724
- metadata: import_zod.z.array(import_zod.z.string())
1979
+ metadata: import_zod2.z.array(import_zod2.z.string())
1725
1980
  });
1726
1981
  var DirectoryWorkflowOutputSchema = {
1727
- query: import_zod.z.string(),
1728
- state: import_zod.z.string(),
1729
- minPopulation: import_zod.z.number().int().min(0),
1730
- populationYear: import_zod.z.number().int().min(2020).max(2025),
1731
- maxResultsPerCity: import_zod.z.number().int().min(1).max(50),
1732
- concurrency: import_zod.z.number().int().min(1).max(5),
1733
- censusSourceUrl: import_zod.z.string().url(),
1982
+ query: import_zod2.z.string(),
1983
+ state: import_zod2.z.string(),
1984
+ minPopulation: import_zod2.z.number().int().min(0),
1985
+ populationYear: import_zod2.z.number().int().min(2020).max(2025),
1986
+ maxResultsPerCity: import_zod2.z.number().int().min(1).max(50),
1987
+ concurrency: import_zod2.z.number().int().min(1).max(5),
1988
+ censusSourceUrl: import_zod2.z.string().url(),
1734
1989
  usZipsSourcePath: NullableString,
1735
- warnings: import_zod.z.array(import_zod.z.string()),
1736
- extractedAt: import_zod.z.string(),
1737
- selectedCityCount: import_zod.z.number().int().min(0),
1738
- totalResultCount: import_zod.z.number().int().min(0),
1990
+ warnings: import_zod2.z.array(import_zod2.z.string()),
1991
+ extractedAt: import_zod2.z.string(),
1992
+ selectedCityCount: import_zod2.z.number().int().min(0),
1993
+ totalResultCount: import_zod2.z.number().int().min(0),
1739
1994
  csvPath: NullableString,
1740
- cities: import_zod.z.array(import_zod.z.object({
1741
- city: import_zod.z.string(),
1742
- state: import_zod.z.string(),
1743
- location: import_zod.z.string(),
1744
- cityKey: import_zod.z.string(),
1745
- censusName: import_zod.z.string(),
1746
- population: import_zod.z.number().int().min(0),
1747
- populationYear: import_zod.z.number().int().min(2020).max(2025),
1748
- zips: import_zod.z.array(import_zod.z.string()),
1749
- counties: import_zod.z.array(import_zod.z.string()),
1750
- status: import_zod.z.enum(["ok", "empty", "failed"]),
1995
+ cities: import_zod2.z.array(import_zod2.z.object({
1996
+ city: import_zod2.z.string(),
1997
+ state: import_zod2.z.string(),
1998
+ location: import_zod2.z.string(),
1999
+ cityKey: import_zod2.z.string(),
2000
+ censusName: import_zod2.z.string(),
2001
+ population: import_zod2.z.number().int().min(0),
2002
+ populationYear: import_zod2.z.number().int().min(2020).max(2025),
2003
+ zips: import_zod2.z.array(import_zod2.z.string()),
2004
+ counties: import_zod2.z.array(import_zod2.z.string()),
2005
+ status: import_zod2.z.enum(["ok", "empty", "failed"]),
1751
2006
  error: NullableString,
1752
- resultCount: import_zod.z.number().int().min(0),
1753
- durationMs: import_zod.z.number().int().min(0),
1754
- attempts: import_zod.z.array(MapsSearchAttemptOutput),
1755
- results: import_zod.z.array(DirectoryMapsBusinessOutput)
2007
+ resultCount: import_zod2.z.number().int().min(0),
2008
+ durationMs: import_zod2.z.number().int().min(0),
2009
+ attempts: import_zod2.z.array(MapsSearchAttemptOutput),
2010
+ results: import_zod2.z.array(DirectoryMapsBusinessOutput)
1756
2011
  })),
1757
- durationMs: import_zod.z.number().int().min(0)
2012
+ durationMs: import_zod2.z.number().int().min(0)
1758
2013
  };
1759
- var RankTrackerToolPlanOutput = import_zod.z.object({
1760
- tool: import_zod.z.string(),
1761
- purpose: import_zod.z.string()
2014
+ var RankTrackerToolPlanOutput = import_zod2.z.object({
2015
+ tool: import_zod2.z.string(),
2016
+ purpose: import_zod2.z.string()
1762
2017
  });
1763
- var RankTrackerTableOutput = import_zod.z.object({
1764
- name: import_zod.z.string(),
1765
- purpose: import_zod.z.string(),
1766
- keyColumns: import_zod.z.array(import_zod.z.string())
2018
+ var RankTrackerTableOutput = import_zod2.z.object({
2019
+ name: import_zod2.z.string(),
2020
+ purpose: import_zod2.z.string(),
2021
+ keyColumns: import_zod2.z.array(import_zod2.z.string())
1767
2022
  });
1768
- var RankTrackerCronJobOutput = import_zod.z.object({
1769
- name: import_zod.z.string(),
1770
- purpose: import_zod.z.string(),
1771
- modes: import_zod.z.array(RankTrackerModeSchema),
1772
- recommendedTools: import_zod.z.array(import_zod.z.string())
2023
+ var RankTrackerCronJobOutput = import_zod2.z.object({
2024
+ name: import_zod2.z.string(),
2025
+ purpose: import_zod2.z.string(),
2026
+ modes: import_zod2.z.array(RankTrackerModeSchema),
2027
+ recommendedTools: import_zod2.z.array(import_zod2.z.string())
1773
2028
  });
1774
2029
  var RankTrackerBlueprintOutputSchema = {
1775
- projectName: import_zod.z.string(),
2030
+ projectName: import_zod2.z.string(),
1776
2031
  targetDomain: NullableString,
1777
2032
  targetBusinessName: NullableString,
1778
- trackingModes: import_zod.z.array(RankTrackerModeSchema),
1779
- database: import_zod.z.string(),
1780
- recommendedTools: import_zod.z.array(RankTrackerToolPlanOutput),
1781
- tables: import_zod.z.array(RankTrackerTableOutput),
1782
- cron: import_zod.z.object({
1783
- enabled: import_zod.z.boolean(),
1784
- cadence: import_zod.z.string(),
1785
- expression: import_zod.z.string(),
1786
- timezone: import_zod.z.string(),
1787
- jobs: import_zod.z.array(RankTrackerCronJobOutput)
2033
+ trackingModes: import_zod2.z.array(RankTrackerModeSchema),
2034
+ database: import_zod2.z.string(),
2035
+ recommendedTools: import_zod2.z.array(RankTrackerToolPlanOutput),
2036
+ tables: import_zod2.z.array(RankTrackerTableOutput),
2037
+ cron: import_zod2.z.object({
2038
+ enabled: import_zod2.z.boolean(),
2039
+ cadence: import_zod2.z.string(),
2040
+ expression: import_zod2.z.string(),
2041
+ timezone: import_zod2.z.string(),
2042
+ jobs: import_zod2.z.array(RankTrackerCronJobOutput)
1788
2043
  }),
1789
- metrics: import_zod.z.array(import_zod.z.string()),
1790
- implementationPrompt: import_zod.z.string()
2044
+ metrics: import_zod2.z.array(import_zod2.z.string()),
2045
+ implementationPrompt: import_zod2.z.string()
1791
2046
  };
1792
- var OrganicResultOutput = import_zod.z.object({
1793
- position: import_zod.z.number().int(),
1794
- title: import_zod.z.string(),
1795
- url: import_zod.z.string(),
1796
- domain: import_zod.z.string(),
2047
+ var OrganicResultOutput = import_zod2.z.object({
2048
+ position: import_zod2.z.number().int(),
2049
+ title: import_zod2.z.string(),
2050
+ url: import_zod2.z.string(),
2051
+ domain: import_zod2.z.string(),
1797
2052
  snippet: NullableString
1798
2053
  });
1799
- var AiOverviewOutput = import_zod.z.object({
1800
- detected: import_zod.z.boolean(),
2054
+ var AiOverviewOutput = import_zod2.z.object({
2055
+ detected: import_zod2.z.boolean(),
1801
2056
  text: NullableString
1802
2057
  }).nullable();
1803
- var EntityIdsOutput = import_zod.z.object({
1804
- kgIds: import_zod.z.array(import_zod.z.string()),
1805
- cids: import_zod.z.array(import_zod.z.string()),
1806
- gcids: import_zod.z.array(import_zod.z.string())
2058
+ var EntityIdsOutput = import_zod2.z.object({
2059
+ kgIds: import_zod2.z.array(import_zod2.z.string()),
2060
+ cids: import_zod2.z.array(import_zod2.z.string()),
2061
+ gcids: import_zod2.z.array(import_zod2.z.string())
1807
2062
  }).nullable();
1808
2063
  var HarvestPaaOutputSchema = {
1809
- query: import_zod.z.string(),
2064
+ query: import_zod2.z.string(),
1810
2065
  location: NullableString,
1811
- questionCount: import_zod.z.number().int().min(0),
2066
+ questionCount: import_zod2.z.number().int().min(0),
1812
2067
  completionStatus: NullableString,
1813
- questions: import_zod.z.array(import_zod.z.object({
1814
- question: import_zod.z.string(),
2068
+ questions: import_zod2.z.array(import_zod2.z.object({
2069
+ question: import_zod2.z.string(),
1815
2070
  answer: NullableString,
1816
2071
  sourceTitle: NullableString,
1817
2072
  sourceSite: NullableString
1818
2073
  })),
1819
- organicResults: import_zod.z.array(OrganicResultOutput),
2074
+ organicResults: import_zod2.z.array(OrganicResultOutput),
1820
2075
  aiOverview: AiOverviewOutput,
1821
2076
  entityIds: EntityIdsOutput,
1822
- durationMs: import_zod.z.number().min(0).nullable()
2077
+ durationMs: import_zod2.z.number().min(0).nullable()
1823
2078
  };
1824
2079
  var SearchSerpOutputSchema = {
1825
- query: import_zod.z.string(),
2080
+ query: import_zod2.z.string(),
1826
2081
  location: NullableString,
1827
- organicResults: import_zod.z.array(OrganicResultOutput),
1828
- localPack: import_zod.z.array(import_zod.z.object({
1829
- position: import_zod.z.number().int(),
1830
- name: import_zod.z.string(),
2082
+ organicResults: import_zod2.z.array(OrganicResultOutput),
2083
+ localPack: import_zod2.z.array(import_zod2.z.object({
2084
+ position: import_zod2.z.number().int(),
2085
+ name: import_zod2.z.string(),
1831
2086
  rating: NullableString,
1832
2087
  reviewCount: NullableString,
1833
2088
  websiteUrl: NullableString
@@ -1836,31 +2091,31 @@ var SearchSerpOutputSchema = {
1836
2091
  entityIds: EntityIdsOutput
1837
2092
  };
1838
2093
  var ExtractUrlOutputSchema = {
1839
- url: import_zod.z.string(),
2094
+ url: import_zod2.z.string(),
1840
2095
  title: NullableString,
1841
- headings: import_zod.z.array(import_zod.z.object({
1842
- level: import_zod.z.number().int(),
1843
- text: import_zod.z.string()
2096
+ headings: import_zod2.z.array(import_zod2.z.object({
2097
+ level: import_zod2.z.number().int(),
2098
+ text: import_zod2.z.string()
1844
2099
  })),
1845
- schemaBlockCount: import_zod.z.number().int().min(0),
2100
+ schemaBlockCount: import_zod2.z.number().int().min(0),
1846
2101
  entityName: NullableString,
1847
- entityTypes: import_zod.z.array(import_zod.z.string()),
1848
- napScore: import_zod.z.number().nullable(),
1849
- missingSchemaFields: import_zod.z.array(import_zod.z.string()),
2102
+ entityTypes: import_zod2.z.array(import_zod2.z.string()),
2103
+ napScore: import_zod2.z.number().nullable(),
2104
+ missingSchemaFields: import_zod2.z.array(import_zod2.z.string()),
1850
2105
  screenshotSaved: NullableString
1851
2106
  };
1852
2107
  var ExtractSiteOutputSchema = {
1853
- url: import_zod.z.string(),
1854
- pageCount: import_zod.z.number().int().min(0),
1855
- pages: import_zod.z.array(import_zod.z.object({
1856
- url: import_zod.z.string(),
2108
+ url: import_zod2.z.string(),
2109
+ pageCount: import_zod2.z.number().int().min(0),
2110
+ pages: import_zod2.z.array(import_zod2.z.object({
2111
+ url: import_zod2.z.string(),
1857
2112
  title: NullableString,
1858
- schemaTypes: import_zod.z.array(import_zod.z.string())
2113
+ schemaTypes: import_zod2.z.array(import_zod2.z.string())
1859
2114
  })),
1860
- durationMs: import_zod.z.number().min(0)
2115
+ durationMs: import_zod2.z.number().min(0)
1861
2116
  };
1862
2117
  var MapsPlaceIntelOutputSchema = {
1863
- name: import_zod.z.string(),
2118
+ name: import_zod2.z.string(),
1864
2119
  rating: NullableString,
1865
2120
  reviewCount: NullableString,
1866
2121
  category: NullableString,
@@ -1872,75 +2127,75 @@ var MapsPlaceIntelOutputSchema = {
1872
2127
  kgmid: NullableString,
1873
2128
  cidDecimal: NullableString,
1874
2129
  cidUrl: NullableString,
1875
- lat: import_zod.z.number().nullable(),
1876
- lng: import_zod.z.number().nullable(),
1877
- reviewsStatus: import_zod.z.string(),
1878
- reviewsCollected: import_zod.z.number().int().min(0),
1879
- reviewTopics: import_zod.z.array(import_zod.z.object({
1880
- label: import_zod.z.string(),
1881
- count: import_zod.z.string()
2130
+ lat: import_zod2.z.number().nullable(),
2131
+ lng: import_zod2.z.number().nullable(),
2132
+ reviewsStatus: import_zod2.z.string(),
2133
+ reviewsCollected: import_zod2.z.number().int().min(0),
2134
+ reviewTopics: import_zod2.z.array(import_zod2.z.object({
2135
+ label: import_zod2.z.string(),
2136
+ count: import_zod2.z.string()
1882
2137
  }))
1883
2138
  };
1884
2139
  var CreditsInfoOutputSchema = {
1885
- balanceCredits: import_zod.z.number().nullable(),
1886
- matchedCost: import_zod.z.object({
1887
- label: import_zod.z.string(),
1888
- credits: import_zod.z.number(),
1889
- unit: import_zod.z.string(),
2140
+ balanceCredits: import_zod2.z.number().nullable(),
2141
+ matchedCost: import_zod2.z.object({
2142
+ label: import_zod2.z.string(),
2143
+ credits: import_zod2.z.number(),
2144
+ unit: import_zod2.z.string(),
1890
2145
  notes: NullableString
1891
2146
  }).nullable(),
1892
- costs: import_zod.z.array(import_zod.z.object({
1893
- key: import_zod.z.string(),
1894
- label: import_zod.z.string(),
1895
- credits: import_zod.z.number(),
1896
- unit: import_zod.z.string(),
2147
+ costs: import_zod2.z.array(import_zod2.z.object({
2148
+ key: import_zod2.z.string(),
2149
+ label: import_zod2.z.string(),
2150
+ credits: import_zod2.z.number(),
2151
+ unit: import_zod2.z.string(),
1897
2152
  notes: NullableString
1898
2153
  })),
1899
- ledger: import_zod.z.array(import_zod.z.object({
1900
- createdAt: import_zod.z.string(),
1901
- operation: import_zod.z.string(),
1902
- credits: import_zod.z.number(),
2154
+ ledger: import_zod2.z.array(import_zod2.z.object({
2155
+ createdAt: import_zod2.z.string(),
2156
+ operation: import_zod2.z.string(),
2157
+ credits: import_zod2.z.number(),
1903
2158
  description: NullableString
1904
2159
  })),
1905
- concurrency: import_zod.z.object({
1906
- currentExtraSlots: import_zod.z.number().int().min(0),
1907
- currentLimit: import_zod.z.number().int().min(1),
1908
- hasSubscription: import_zod.z.boolean(),
1909
- upgrade: import_zod.z.object({
1910
- product: import_zod.z.string(),
1911
- priceLabel: import_zod.z.string(),
1912
- unitAmountUsd: import_zod.z.number(),
1913
- currency: import_zod.z.string(),
1914
- interval: import_zod.z.string(),
1915
- billingUrl: import_zod.z.string().url(),
1916
- terminalCommand: import_zod.z.string(),
1917
- terminalCommandWithApiKeyEnv: import_zod.z.string()
2160
+ concurrency: import_zod2.z.object({
2161
+ currentExtraSlots: import_zod2.z.number().int().min(0),
2162
+ currentLimit: import_zod2.z.number().int().min(1),
2163
+ hasSubscription: import_zod2.z.boolean(),
2164
+ upgrade: import_zod2.z.object({
2165
+ product: import_zod2.z.string(),
2166
+ priceLabel: import_zod2.z.string(),
2167
+ unitAmountUsd: import_zod2.z.number(),
2168
+ currency: import_zod2.z.string(),
2169
+ interval: import_zod2.z.string(),
2170
+ billingUrl: import_zod2.z.string().url(),
2171
+ terminalCommand: import_zod2.z.string(),
2172
+ terminalCommandWithApiKeyEnv: import_zod2.z.string()
1918
2173
  })
1919
2174
  }).nullable()
1920
2175
  };
1921
2176
  var MapSiteUrlsOutputSchema = {
1922
- startUrl: import_zod.z.string(),
1923
- totalFound: import_zod.z.number().int().min(0),
1924
- truncated: import_zod.z.boolean(),
1925
- okCount: import_zod.z.number().int().min(0),
1926
- redirectCount: import_zod.z.number().int().min(0),
1927
- brokenCount: import_zod.z.number().int().min(0),
1928
- urls: import_zod.z.array(import_zod.z.object({
1929
- url: import_zod.z.string(),
1930
- status: import_zod.z.number().int().nullable()
2177
+ startUrl: import_zod2.z.string(),
2178
+ totalFound: import_zod2.z.number().int().min(0),
2179
+ truncated: import_zod2.z.boolean(),
2180
+ okCount: import_zod2.z.number().int().min(0),
2181
+ redirectCount: import_zod2.z.number().int().min(0),
2182
+ brokenCount: import_zod2.z.number().int().min(0),
2183
+ urls: import_zod2.z.array(import_zod2.z.object({
2184
+ url: import_zod2.z.string(),
2185
+ status: import_zod2.z.number().int().nullable()
1931
2186
  })),
1932
- durationMs: import_zod.z.number().min(0)
2187
+ durationMs: import_zod2.z.number().min(0)
1933
2188
  };
1934
2189
  var YoutubeHarvestOutputSchema = {
1935
- mode: import_zod.z.string(),
1936
- videoCount: import_zod.z.number().int().min(0),
1937
- channel: import_zod.z.object({
2190
+ mode: import_zod2.z.string(),
2191
+ videoCount: import_zod2.z.number().int().min(0),
2192
+ channel: import_zod2.z.object({
1938
2193
  title: NullableString,
1939
2194
  subscriberCount: NullableString
1940
2195
  }).nullable(),
1941
- videos: import_zod.z.array(import_zod.z.object({
1942
- videoId: import_zod.z.string(),
1943
- title: import_zod.z.string(),
2196
+ videos: import_zod2.z.array(import_zod2.z.object({
2197
+ videoId: import_zod2.z.string(),
2198
+ title: import_zod2.z.string(),
1944
2199
  channelName: NullableString,
1945
2200
  views: NullableString,
1946
2201
  duration: NullableString,
@@ -1948,54 +2203,114 @@ var YoutubeHarvestOutputSchema = {
1948
2203
  }))
1949
2204
  };
1950
2205
  var FacebookAdSearchOutputSchema = {
1951
- query: import_zod.z.string(),
1952
- advertiserCount: import_zod.z.number().int().min(0),
1953
- advertisers: import_zod.z.array(import_zod.z.object({
2206
+ query: import_zod2.z.string(),
2207
+ advertiserCount: import_zod2.z.number().int().min(0),
2208
+ advertisers: import_zod2.z.array(import_zod2.z.object({
1954
2209
  name: NullableString,
1955
- adCount: import_zod.z.number().int().nullable(),
1956
- libraryId: NullableString
2210
+ pageId: NullableString,
2211
+ pageUrl: NullableString,
2212
+ adCount: import_zod2.z.number().int().nullable(),
2213
+ libraryId: NullableString,
2214
+ sampleLibraryId: NullableString
1957
2215
  }))
1958
2216
  };
1959
2217
  var FacebookPageIntelOutputSchema = {
1960
2218
  advertiserName: NullableString,
1961
- totalAds: import_zod.z.number().int().min(0),
1962
- activeCount: import_zod.z.number().int().min(0),
1963
- videoCount: import_zod.z.number().int().min(0),
1964
- imageCount: import_zod.z.number().int().min(0),
1965
- ads: import_zod.z.array(import_zod.z.object({
2219
+ totalAds: import_zod2.z.number().int().min(0),
2220
+ activeCount: import_zod2.z.number().int().min(0),
2221
+ videoCount: import_zod2.z.number().int().min(0),
2222
+ imageCount: import_zod2.z.number().int().min(0),
2223
+ ads: import_zod2.z.array(import_zod2.z.object({
1966
2224
  libraryId: NullableString,
1967
2225
  status: NullableString,
1968
2226
  creativeType: NullableString,
2227
+ primaryText: NullableString,
1969
2228
  headline: NullableString,
1970
2229
  cta: NullableString,
1971
2230
  startDate: NullableString,
2231
+ landingUrl: NullableString,
2232
+ domain: NullableString,
1972
2233
  videoUrl: NullableString,
1973
- variations: import_zod.z.number().int().nullable()
2234
+ imageUrl: NullableString,
2235
+ videoPoster: NullableString,
2236
+ variations: import_zod2.z.number().int().nullable()
1974
2237
  }))
1975
2238
  };
1976
2239
  var FacebookVideoTranscribeOutputSchema = {
1977
- sourceUrl: import_zod.z.string().url(),
1978
- pageUrl: import_zod.z.string().url(),
2240
+ sourceUrl: import_zod2.z.string().url(),
2241
+ pageUrl: import_zod2.z.string().url(),
1979
2242
  videoId: NullableString,
1980
2243
  ownerName: NullableString,
1981
- selectedQuality: import_zod.z.string(),
1982
- bitrate: import_zod.z.number().int().nullable(),
1983
- videoDurationSec: import_zod.z.number().nullable(),
1984
- videoUrl: import_zod.z.string().url(),
1985
- wordCount: import_zod.z.number().int().min(0),
1986
- chunkCount: import_zod.z.number().int().min(0),
1987
- transcriptText: import_zod.z.string(),
1988
- chunks: import_zod.z.array(import_zod.z.object({
1989
- startSec: import_zod.z.number(),
1990
- endSec: import_zod.z.number(),
1991
- text: import_zod.z.string()
2244
+ selectedQuality: import_zod2.z.string(),
2245
+ bitrate: import_zod2.z.number().int().nullable(),
2246
+ videoDurationSec: import_zod2.z.number().nullable(),
2247
+ videoUrl: import_zod2.z.string().url(),
2248
+ wordCount: import_zod2.z.number().int().min(0),
2249
+ chunkCount: import_zod2.z.number().int().min(0),
2250
+ transcriptText: import_zod2.z.string(),
2251
+ chunks: import_zod2.z.array(import_zod2.z.object({
2252
+ startSec: import_zod2.z.number(),
2253
+ endSec: import_zod2.z.number(),
2254
+ text: import_zod2.z.string()
1992
2255
  }))
1993
2256
  };
2257
+ var TranscriptChunkOutput = import_zod2.z.object({
2258
+ startSec: import_zod2.z.number(),
2259
+ endSec: import_zod2.z.number(),
2260
+ text: import_zod2.z.string()
2261
+ });
2262
+ var YoutubeTranscribeOutputSchema = {
2263
+ videoId: NullableString,
2264
+ url: NullableString,
2265
+ wordCount: import_zod2.z.number().int().min(0),
2266
+ chunkCount: import_zod2.z.number().int().min(0),
2267
+ durationMs: import_zod2.z.number().nullable(),
2268
+ transcriptText: import_zod2.z.string(),
2269
+ chunks: import_zod2.z.array(TranscriptChunkOutput),
2270
+ resolvedInputs: import_zod2.z.object({
2271
+ videoId: NullableString,
2272
+ url: NullableString
2273
+ })
2274
+ };
2275
+ var FacebookAdTranscribeOutputSchema = {
2276
+ videoUrl: import_zod2.z.string().url(),
2277
+ wordCount: import_zod2.z.number().int().min(0),
2278
+ chunkCount: import_zod2.z.number().int().min(0),
2279
+ durationMs: import_zod2.z.number().nullable(),
2280
+ transcriptText: import_zod2.z.string(),
2281
+ chunks: import_zod2.z.array(TranscriptChunkOutput),
2282
+ resolvedInputs: import_zod2.z.object({
2283
+ videoUrl: import_zod2.z.string().url()
2284
+ })
2285
+ };
2286
+ var CaptureSerpSnapshotOutputSchema = {
2287
+ schemaVersion: import_zod2.z.literal("serp-intelligence.capture.v1"),
2288
+ status: import_zod2.z.string(),
2289
+ query: NullableString,
2290
+ location: NullableString,
2291
+ capturedAt: NullableString,
2292
+ resultCount: import_zod2.z.number().int().min(0).nullable(),
2293
+ snapshotId: NullableString,
2294
+ resolvedInputs: import_zod2.z.record(import_zod2.z.unknown()),
2295
+ artifacts: import_zod2.z.array(import_zod2.z.record(import_zod2.z.unknown())),
2296
+ diagnostics: import_zod2.z.record(import_zod2.z.unknown()).nullable(),
2297
+ providerPayload: import_zod2.z.record(import_zod2.z.unknown())
2298
+ };
2299
+ var CaptureSerpPageSnapshotsOutputSchema = {
2300
+ schemaVersion: import_zod2.z.literal("serp-intelligence.page-snapshots.v1"),
2301
+ status: import_zod2.z.string(),
2302
+ count: import_zod2.z.number().int().min(0),
2303
+ failedCount: import_zod2.z.number().int().min(0),
2304
+ captures: import_zod2.z.array(import_zod2.z.record(import_zod2.z.unknown())),
2305
+ resolvedInputs: import_zod2.z.record(import_zod2.z.unknown()),
2306
+ diagnostics: import_zod2.z.record(import_zod2.z.unknown()).nullable(),
2307
+ providerPayload: import_zod2.z.record(import_zod2.z.unknown())
2308
+ };
1994
2309
  var CreditsInfoInputSchema = {
1995
- item: import_zod.z.string().optional().describe('Optional tool, action, or feature to look up, e.g. "maps reviews", "extract_url", "YouTube transcription", or "concurrency"'),
1996
- includeLedger: import_zod.z.boolean().default(false).describe("Whether to include recent credit ledger entries")
2310
+ item: import_zod2.z.string().optional().describe('Optional tool, action, or feature to look up, e.g. "maps reviews", "extract_url", "YouTube transcription", or "concurrency"'),
2311
+ includeLedger: import_zod2.z.boolean().default(false).describe("Whether to include recent credit ledger entries")
1997
2312
  };
1998
- var WorkflowIdSchema = import_zod.z.enum([
2313
+ var WorkflowIdSchema = import_zod2.z.enum([
1999
2314
  "directory",
2000
2315
  "agent-packet",
2001
2316
  "local-competitive-audit",
@@ -2005,114 +2320,129 @@ var WorkflowIdSchema = import_zod.z.enum([
2005
2320
  "ai-overview-language"
2006
2321
  ]);
2007
2322
  var WorkflowListInputSchema = {
2008
- includeRecipes: import_zod.z.boolean().default(true).describe("Include high-level AI-facing recipes such as market analysis, ICP research, forum/review research, brand design briefings, CRO audits, positioning briefs, content gaps, and AI search visibility audits.")
2323
+ includeRecipes: import_zod2.z.boolean().default(true).describe("Include high-level AI-facing recipes such as market analysis, ICP research, forum/review research, brand design briefings, CRO audits, positioning briefs, content gaps, and AI search visibility audits.")
2009
2324
  };
2010
2325
  var WorkflowSuggestInputSchema = {
2011
- goal: import_zod.z.string().min(1).describe('The user goal or job to route, e.g. "market analysis for roofers in Tennessee", "ICP research for med spas", "CRO audit for this URL", or "brand design briefing".'),
2012
- query: import_zod.z.string().optional().describe("Business category, niche, or Maps query when known."),
2013
- keyword: import_zod.z.string().optional().describe("Search keyword, audience problem, or content topic when known."),
2014
- domain: import_zod.z.string().optional().describe("Target domain or brand domain when known."),
2015
- url: import_zod.z.string().url().optional().describe("Target URL when the workflow should inspect a specific page."),
2016
- location: import_zod.z.string().optional().describe("City/region/country for localized research, e.g. Denver, CO."),
2017
- state: import_zod.z.string().optional().describe("US state abbreviation or name for state-wide market research."),
2018
- maxSuggestions: import_zod.z.number().int().min(1).max(8).default(3).describe("Number of matching workflow recipes to return.")
2326
+ goal: import_zod2.z.string().min(1).describe('The user goal or job to route, e.g. "market analysis for roofers in Tennessee", "ICP research for med spas", "CRO audit for this URL", or "brand design briefing".'),
2327
+ query: import_zod2.z.string().optional().describe("Business category, niche, or Maps query when known."),
2328
+ keyword: import_zod2.z.string().optional().describe("Search keyword, audience problem, or content topic when known."),
2329
+ domain: import_zod2.z.string().optional().describe("Target domain or brand domain when known."),
2330
+ url: import_zod2.z.string().url().optional().describe("Target URL when the workflow should inspect a specific page."),
2331
+ location: import_zod2.z.string().optional().describe("City/region/country for localized research, e.g. Denver, CO."),
2332
+ state: import_zod2.z.string().optional().describe("US state abbreviation or name for state-wide market research."),
2333
+ maxSuggestions: import_zod2.z.number().int().min(1).max(8).default(3).describe("Number of matching workflow recipes to return.")
2019
2334
  };
2020
2335
  var WorkflowRunInputSchema = {
2021
- workflowId: WorkflowIdSchema.describe("Workflow to run. Use workflow_list or workflow_suggest first when unsure."),
2022
- input: import_zod.z.record(import_zod.z.unknown()).default({}).describe("Workflow-specific input object. Examples: agent-packet uses {keyword, domain?, location?, maxQuestions?}; local-competitive-audit uses {query, state, minPopulation?, maxCities?, maxResultsPerCity?, hydrateTop?, maxReviews?}; serp-comparison uses {keyword, domain?, url?, location?, extractTop?}."),
2023
- webhookUrl: import_zod.z.string().url().optional().describe("Optional HTTPS webhook to receive the completed hosted workflow run event.")
2336
+ workflowId: WorkflowIdSchema.describe("Workflow to run: directory, agent-packet, local-competitive-audit, map-comparison, serp-comparison, paa-expansion-brief, ai-overview-language. Use only these values; call workflow_list or workflow_suggest first when unsure."),
2337
+ input: import_zod2.z.record(import_zod2.z.unknown()).default({}).describe("Workflow-specific input object. Examples: agent-packet uses {keyword, domain?, location?, maxQuestions?}; local-competitive-audit uses {query, state, minPopulation?, maxCities?, maxResultsPerCity?, hydrateTop?, maxReviews?}; serp-comparison uses {keyword, domain?, url?, location?, extractTop?}."),
2338
+ webhookUrl: import_zod2.z.string().url().optional().describe("Optional HTTPS webhook to receive the completed hosted workflow run event.")
2339
+ };
2340
+ var WorkflowStepInputSchema = {
2341
+ runId: import_zod2.z.string().min(1).describe("Workflow run id returned by workflow_run, workflow_step, or workflow_status. Use only a returned runId; do not construct one yourself. Advances the run by exactly one step (one logical leg, e.g. one live harvest).")
2024
2342
  };
2025
2343
  var WorkflowStatusInputSchema = {
2026
- runId: import_zod.z.string().min(1).describe("Workflow run id returned by workflow_run.")
2344
+ runId: import_zod2.z.string().min(1).describe("Workflow run id returned by workflow_run, workflow_step, or workflow_status. Use only a returned runId; do not construct one yourself.")
2027
2345
  };
2028
2346
  var WorkflowArtifactReadInputSchema = {
2029
- runId: import_zod.z.string().min(1).describe("Workflow run id returned by workflow_run or workflow_status."),
2030
- artifactId: import_zod.z.string().min(1).describe("Artifact id from the run artifact list."),
2031
- maxBytes: import_zod.z.number().int().min(1e3).max(1e6).default(2e5).describe("Maximum bytes of artifact text to return inline. Use lower values for large CSV/JSON artifacts; call again with the downloadUrl if needed outside MCP.")
2347
+ runId: import_zod2.z.string().min(1).describe("Workflow run id returned by workflow_run, workflow_step, or workflow_status. Use only a returned runId; do not construct one yourself."),
2348
+ artifactId: import_zod2.z.string().min(1).describe("Artifact id from the run artifact list returned by workflow_run, workflow_step, or workflow_status. Use only a returned artifactId; do not construct one yourself."),
2349
+ maxBytes: import_zod2.z.number().int().min(1e3).max(1e6).default(2e5).describe("Maximum bytes of artifact text to return inline. Use lower values for large CSV/JSON artifacts; call again with the downloadUrl if needed outside MCP.")
2032
2350
  };
2033
- var WorkflowRecipeOutput = import_zod.z.object({
2034
- id: import_zod.z.string(),
2035
- title: import_zod.z.string(),
2036
- description: import_zod.z.string(),
2037
- primaryWorkflowId: import_zod.z.string().nullable(),
2038
- recommendedTools: import_zod.z.array(import_zod.z.string()),
2039
- requiredInputs: import_zod.z.array(import_zod.z.string()),
2040
- optionalInputs: import_zod.z.array(import_zod.z.string()),
2041
- produces: import_zod.z.array(import_zod.z.string()),
2042
- runHint: import_zod.z.string()
2351
+ var WorkflowRecipeOutput = import_zod2.z.object({
2352
+ id: import_zod2.z.string(),
2353
+ title: import_zod2.z.string(),
2354
+ description: import_zod2.z.string(),
2355
+ primaryWorkflowId: import_zod2.z.string().nullable(),
2356
+ recommendedTools: import_zod2.z.array(import_zod2.z.string()),
2357
+ requiredInputs: import_zod2.z.array(import_zod2.z.string()),
2358
+ optionalInputs: import_zod2.z.array(import_zod2.z.string()),
2359
+ produces: import_zod2.z.array(import_zod2.z.string()),
2360
+ runHint: import_zod2.z.string()
2043
2361
  });
2044
- var WorkflowDefinitionOutput = import_zod.z.object({
2045
- id: import_zod.z.string(),
2046
- title: import_zod.z.string(),
2047
- description: import_zod.z.string()
2362
+ var WorkflowDefinitionOutput = import_zod2.z.object({
2363
+ id: import_zod2.z.string(),
2364
+ title: import_zod2.z.string(),
2365
+ description: import_zod2.z.string()
2048
2366
  });
2049
- var WorkflowArtifactOutput = import_zod.z.record(import_zod.z.unknown());
2367
+ var WorkflowArtifactOutput = import_zod2.z.record(import_zod2.z.unknown());
2050
2368
  var WorkflowListOutputSchema = {
2051
- workflows: import_zod.z.array(WorkflowDefinitionOutput),
2052
- recipes: import_zod.z.array(WorkflowRecipeOutput)
2369
+ workflows: import_zod2.z.array(WorkflowDefinitionOutput),
2370
+ recipes: import_zod2.z.array(WorkflowRecipeOutput)
2053
2371
  };
2054
2372
  var WorkflowSuggestOutputSchema = {
2055
- goal: import_zod.z.string(),
2056
- suggestions: import_zod.z.array(WorkflowRecipeOutput)
2373
+ goal: import_zod2.z.string(),
2374
+ suggestions: import_zod2.z.array(WorkflowRecipeOutput)
2057
2375
  };
2058
2376
  var WorkflowRunOutputSchema = {
2059
- workflowId: import_zod.z.string(),
2060
- input: import_zod.z.record(import_zod.z.unknown()),
2061
- run: import_zod.z.record(import_zod.z.unknown()).optional(),
2062
- summary: import_zod.z.record(import_zod.z.unknown()).optional(),
2063
- artifacts: import_zod.z.array(WorkflowArtifactOutput)
2377
+ workflowId: import_zod2.z.string(),
2378
+ input: import_zod2.z.record(import_zod2.z.unknown()),
2379
+ run: import_zod2.z.record(import_zod2.z.unknown()).optional(),
2380
+ summary: import_zod2.z.record(import_zod2.z.unknown()).optional(),
2381
+ step: import_zod2.z.record(import_zod2.z.unknown()).optional(),
2382
+ nextStep: import_zod2.z.record(import_zod2.z.unknown()).nullable().optional(),
2383
+ done: import_zod2.z.boolean().optional(),
2384
+ artifacts: import_zod2.z.array(WorkflowArtifactOutput)
2385
+ };
2386
+ var WorkflowStepOutputSchema = {
2387
+ runId: import_zod2.z.string(),
2388
+ run: import_zod2.z.record(import_zod2.z.unknown()).optional(),
2389
+ summary: import_zod2.z.record(import_zod2.z.unknown()).nullable().optional(),
2390
+ step: import_zod2.z.record(import_zod2.z.unknown()).optional(),
2391
+ nextStep: import_zod2.z.record(import_zod2.z.unknown()).nullable().optional(),
2392
+ done: import_zod2.z.boolean(),
2393
+ artifacts: import_zod2.z.array(WorkflowArtifactOutput)
2064
2394
  };
2065
2395
  var WorkflowStatusOutputSchema = {
2066
- run: import_zod.z.record(import_zod.z.unknown()).optional(),
2067
- artifacts: import_zod.z.array(WorkflowArtifactOutput)
2396
+ run: import_zod2.z.record(import_zod2.z.unknown()).optional(),
2397
+ artifacts: import_zod2.z.array(WorkflowArtifactOutput)
2068
2398
  };
2069
2399
  var WorkflowArtifactReadOutputSchema = {
2070
- runId: import_zod.z.string(),
2071
- artifactId: import_zod.z.string(),
2072
- contentType: import_zod.z.string(),
2073
- bytes: import_zod.z.number().int().min(0),
2074
- truncated: import_zod.z.boolean(),
2075
- text: import_zod.z.string()
2400
+ runId: import_zod2.z.string(),
2401
+ artifactId: import_zod2.z.string(),
2402
+ contentType: import_zod2.z.string(),
2403
+ bytes: import_zod2.z.number().int().min(0),
2404
+ truncated: import_zod2.z.boolean(),
2405
+ text: import_zod2.z.string()
2076
2406
  };
2077
2407
  var SearchSerpInputSchema = {
2078
- query: import_zod.z.string().min(1).describe('Core search topic only. Separate location when possible. If user says "best dentist in Brooklyn NY serp", use query="best dentist" and location="Brooklyn, NY".'),
2079
- location: import_zod.z.string().optional().describe("City, region, or country for geo-targeted results, inferred from user request when present."),
2080
- gl: import_zod.z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
2081
- hl: import_zod.z.string().default("en").describe("Google interface/content language inferred from user request."),
2082
- device: import_zod.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
2083
- proxyMode: import_zod.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. If Google shows a CAPTCHA/challenge, browser-service sessions briefly wait for the automatic solver first, then retry with a fresh proxy/session if the challenge does not clear. Also retries proxy tunnel failure and wrong-location evidence before returning. Use configured only for the static configured proxy. Use none only for direct-network debugging."),
2084
- proxyZip: import_zod.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."),
2085
- debug: import_zod.z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior."),
2086
- pages: import_zod.z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132)")
2408
+ query: import_zod2.z.string().min(1).describe('Core search topic only. Separate location when possible. If user says "best dentist in Brooklyn NY serp", use query="best dentist" and location="Brooklyn, NY".'),
2409
+ location: import_zod2.z.string().optional().describe("City, region, or country for geo-targeted results, inferred from user request when present."),
2410
+ gl: import_zod2.z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
2411
+ hl: import_zod2.z.string().default("en").describe("Google interface/content language inferred from user request."),
2412
+ device: import_zod2.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
2413
+ proxyMode: import_zod2.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe("Proxy targeting mode. Default configured uses the service proxy without city/ZIP targeting for the highest general SERP success rate. Use location only when the user explicitly needs city/ZIP-targeted residential proxy evidence. Use none only for direct-network debugging."),
2414
+ proxyZip: import_zod2.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use only with proxyMode location when the user gives a specific ZIP or city-center targeting needs to be forced."),
2415
+ debug: import_zod2.z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior."),
2416
+ pages: import_zod2.z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132)")
2087
2417
  };
2088
2418
  var CaptureSerpSnapshotInputSchema = {
2089
- query: import_zod.z.string().min(1).describe("Core search query to capture as a structured SERP Intelligence snapshot. Separate the place into location when the user gives a city, region, country, or ZIP."),
2090
- location: import_zod.z.string().optional().describe("City, region, country, or service area used for localized Google results. MCP Scraper records location evidence; UULE alone is not proof of localization."),
2091
- gl: import_zod.z.string().length(2).default("us").describe("Google country code inferred from the requested market, e.g. us, gb, ca, au."),
2092
- hl: import_zod.z.string().default("en").describe("Google interface/content language inferred from the user request."),
2093
- device: import_zod.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only when the user asks for mobile rankings or mobile SERP evidence."),
2094
- proxyMode: import_zod.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. If Google shows a CAPTCHA/challenge, browser-service sessions briefly wait for the automatic solver first, then retry with a fresh proxy/session if the challenge does not clear. Also retries proxy tunnel failure and wrong-location evidence before returning. Use configured only for the static residential proxy, and none only for direct-network debugging."),
2095
- proxyZip: import_zod.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."),
2096
- pages: import_zod.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."),
2097
- debug: import_zod.z.boolean().default(false).describe("Include sanitized browser, proxy, and location diagnostics. Use true when debugging localization, CAPTCHA, proxy selection, or capture reliability."),
2098
- includePageSnapshots: import_zod.z.boolean().default(false).describe("Also capture ranking-page snapshots for selected SERP URLs through the same product capture path."),
2099
- pageSnapshotLimit: import_zod.z.number().int().min(0).max(10).default(0).describe("Maximum ranking-page snapshots to capture when includePageSnapshots is true. Use 0 when only SERP evidence is needed.")
2419
+ query: import_zod2.z.string().min(1).describe("Core search query to capture as a structured SERP Intelligence snapshot. Separate the place into location when the user gives a city, region, country, or ZIP."),
2420
+ location: import_zod2.z.string().optional().describe("City, region, country, or service area used for localized Google results. MCP Scraper records location evidence; UULE alone is not proof of localization."),
2421
+ gl: import_zod2.z.string().length(2).default("us").describe("Google country code inferred from the requested market, e.g. us, gb, ca, au."),
2422
+ hl: import_zod2.z.string().default("en").describe("Google interface/content language inferred from the user request."),
2423
+ device: import_zod2.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only when the user asks for mobile rankings or mobile SERP evidence."),
2424
+ proxyMode: import_zod2.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe("Proxy behavior for capture. Default configured uses the service proxy without city/ZIP targeting for the highest general capture success rate. Use location only when the user explicitly needs city/ZIP-targeted residential proxy evidence. Use none only for direct-network debugging."),
2425
+ proxyZip: import_zod2.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use only with proxyMode location when a precise city-center or ZIP proxy is needed."),
2426
+ pages: import_zod2.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."),
2427
+ debug: import_zod2.z.boolean().default(false).describe("Include sanitized browser, proxy, and location diagnostics. Use true when debugging localization, CAPTCHA, proxy selection, or capture reliability."),
2428
+ includePageSnapshots: import_zod2.z.boolean().default(false).describe("Also capture ranking-page snapshots for selected SERP URLs through the same product capture path."),
2429
+ pageSnapshotLimit: import_zod2.z.number().int().min(0).max(10).default(0).describe("Maximum ranking-page snapshots to capture when includePageSnapshots is true. Use 0 when only SERP evidence is needed.")
2100
2430
  };
2101
2431
  var ScreenshotInputSchema = {
2102
- url: import_zod.z.string().url().describe("URL to capture as a full-page screenshot. Use http or https. Pass allowLocal: true to capture localhost or private-network URLs during development."),
2103
- device: import_zod.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport profile. desktop = 1440\xD7900. mobile = 390\xD7844. Use desktop by default; use mobile when the user asks for a mobile view."),
2104
- allowLocal: import_zod.z.boolean().default(false).describe("Allow localhost and private-network URLs (127.x, 192.168.x, 10.x, etc.). For local development only \u2014 not for production use.")
2432
+ url: import_zod2.z.string().url().describe("URL to capture as a full-page screenshot. Use http or https. Pass allowLocal: true to capture localhost or private-network URLs during development."),
2433
+ device: import_zod2.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport profile. desktop = 1440\xD7900. mobile = 390\xD7844. Use desktop by default; use mobile when the user asks for a mobile view."),
2434
+ allowLocal: import_zod2.z.boolean().default(false).describe("Allow localhost and private-network URLs (127.x, 192.168.x, 10.x, etc.). For local development only \u2014 not for production use.")
2105
2435
  };
2106
2436
  var CaptureSerpPageSnapshotsInputSchema = {
2107
- urls: import_zod.z.array(import_zod.z.string().url()).min(1).max(25).describe("Public HTTP/HTTPS URLs to capture as SERP Intelligence page snapshots. Do not pass localhost, private IPs, file URLs, or internal admin URLs."),
2108
- targets: import_zod.z.array(import_zod.z.object({
2109
- url: import_zod.z.string().url().describe("Public HTTP/HTTPS URL to capture."),
2110
- sourceKind: import_zod.z.enum(["organic", "ai_citation", "local_pack_website", "configured_target", "site_subject"]).default("configured_target").describe("Why this page is being captured for SERP Intelligence evidence."),
2111
- sourcePosition: import_zod.z.number().int().min(1).optional().describe("Ranking or citation position when the page came from SERP evidence.")
2437
+ urls: import_zod2.z.array(import_zod2.z.string().url()).min(1).max(25).describe("Public HTTP/HTTPS URLs to capture as SERP Intelligence page snapshots. Do not pass localhost, private IPs, file URLs, or internal admin URLs."),
2438
+ targets: import_zod2.z.array(import_zod2.z.object({
2439
+ url: import_zod2.z.string().url().describe("Public HTTP/HTTPS URL to capture."),
2440
+ sourceKind: import_zod2.z.enum(["organic", "ai_citation", "local_pack_website", "configured_target", "site_subject"]).default("configured_target").describe("Why this page is being captured for SERP Intelligence evidence."),
2441
+ sourcePosition: import_zod2.z.number().int().min(1).optional().describe("Ranking or citation position when the page came from SERP evidence.")
2112
2442
  }).strict()).min(1).max(25).optional().describe("Structured page snapshot targets. Use this instead of urls when source kind or position should be preserved."),
2113
- maxConcurrency: import_zod.z.number().int().min(1).max(5).default(2).describe("Parallel page captures. Use 2 normally; higher values can increase proxy/browser pressure."),
2114
- timeoutMs: import_zod.z.number().int().min(1e3).max(6e4).default(15e3).describe("Per-page capture timeout in milliseconds. Increase for slow pages; timeout artifacts are returned as structured capture failures."),
2115
- debug: import_zod.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics for page snapshot debugging. Use true for capture, network, or proxy troubleshooting.")
2443
+ maxConcurrency: import_zod2.z.number().int().min(1).max(5).default(2).describe("Parallel page captures. Use 2 normally; higher values can increase proxy/browser pressure."),
2444
+ timeoutMs: import_zod2.z.number().int().min(1e3).max(6e4).default(15e3).describe("Per-page capture timeout in milliseconds. Increase for slow pages; timeout artifacts are returned as structured capture failures."),
2445
+ debug: import_zod2.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics for page snapshot debugging. Use true for capture, network, or proxy troubleshooting.")
2116
2446
  };
2117
2447
 
2118
2448
  // src/mcp/rank-tracker-blueprint.ts
@@ -2503,70 +2833,72 @@ function registerPaaExtractorMcpTools(server2, executor2, options = {}) {
2503
2833
  if (savesReports) registerSavedReportResources(server2);
2504
2834
  server2.registerTool("harvest_paa", {
2505
2835
  title: "Google PAA + SERP Harvest",
2506
- 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, 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 the market evidence is wrong. 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.'),
2836
+ 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"). Omit proxyMode for normal use; the service defaults to the configured browser-service proxy without city/ZIP targeting for the highest general success rate. Use proxyMode location only when the user explicitly needs city/ZIP-targeted residential proxy evidence. 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.'),
2507
2837
  inputSchema: HarvestPaaInputSchema,
2508
2838
  outputSchema: HarvestPaaOutputSchema,
2509
2839
  annotations: liveWebToolAnnotations("Google PAA + SERP Harvest")
2510
2840
  }, async (input) => formatHarvestPaa(await executor2.harvestPaa(input), input));
2511
2841
  server2.registerTool("search_serp", {
2512
2842
  title: "Google SERP Lookup",
2513
- 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, 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, the proxy tunnel fails, or location evidence is wrong."),
2843
+ 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. Omit proxyMode for normal use; the service defaults to the configured browser-service proxy without city/ZIP targeting. Use proxyMode location and proxyZip only when the user explicitly needs city/ZIP-targeted residential proxy evidence."),
2514
2844
  inputSchema: SearchSerpInputSchema,
2515
2845
  outputSchema: SearchSerpOutputSchema,
2516
2846
  annotations: liveWebToolAnnotations("Google SERP Lookup")
2517
2847
  }, async (input) => formatSearchSerp(await executor2.searchSerp(input), input));
2518
2848
  server2.registerTool("extract_url", {
2519
2849
  title: "Single URL Extract",
2520
- description: withReportNote("Extract structured data from one public URL: page content as Markdown, heading structure, JSON-LD schema, entity details, NAP score, metadata, and missing schema fields. Use when the user provides a single URL or asks to inspect/scrape one page."),
2850
+ description: withReportNote("Extract structured data from one public URL when the user provides one page, asks to inspect/scrape a page, or needs page content, schema, headings, metadata, screenshots, branding, or media assets. Returns structured page fields plus artifact handles for saved reports/screenshots/media when requested. Use map_site_urls before extracting a site inventory; use extract_site for multi-page crawling."),
2521
2851
  inputSchema: ExtractUrlInputSchema,
2522
2852
  outputSchema: ExtractUrlOutputSchema,
2523
2853
  annotations: liveWebToolAnnotations("Single URL Extract")
2524
2854
  }, async (input) => formatExtractUrl(await executor2.extractUrl(input), input));
2525
2855
  server2.registerTool("map_site_urls", {
2526
2856
  title: "Site URL Map",
2527
- description: withReportNote("Map/crawl a public website to build a URL inventory with HTTP status codes, broken links, redirects, and site scope. Use before extract_site for audits or when the user asks for a sitemap/URL inventory."),
2857
+ description: withReportNote("Map/crawl a public website when the user asks for a sitemap, URL inventory, broken-link scan, redirect scan, or crawl planning. Returns internal URLs with HTTP status and truncation metadata. Use this before extract_site when choosing pages for an audit; use extract_url for one known page."),
2528
2858
  inputSchema: MapSiteUrlsInputSchema,
2529
2859
  outputSchema: MapSiteUrlsOutputSchema,
2530
2860
  annotations: liveWebToolAnnotations("Site URL Map")
2531
2861
  }, async (input) => formatMapSiteUrls(await executor2.mapSiteUrls(input), input));
2532
2862
  server2.registerTool("extract_site", {
2533
2863
  title: "Multi-Page Site Extract",
2534
- description: withReportNote("Run multi-page extraction across a public website. Returns per-page titles, H1s, metadata, headings, schema/entity data, canonical URLs, and content. Use for website audits, competitor audits, and full-site extraction."),
2864
+ description: withReportNote("Run multi-page extraction across a public website when the user asks for a website audit, competitor audit, full-site content crawl, schema inventory, or page metadata review. Returns per-page titles, H1s, metadata, headings, schema/entity data, canonical URLs, and content. Use map_site_urls first when URL selection matters; use extract_url for one page."),
2535
2865
  inputSchema: ExtractSiteInputSchema,
2536
2866
  outputSchema: ExtractSiteOutputSchema,
2537
2867
  annotations: liveWebToolAnnotations("Multi-Page Site Extract")
2538
2868
  }, async (input) => formatExtractSite(await executor2.extractSite(input), input));
2539
2869
  server2.registerTool("youtube_harvest", {
2540
2870
  title: "YouTube Video Harvest",
2541
- description: withReportNote('Harvest YouTube video metadata by search query or channel handle/ID/URL. Use mode "search" for keyword/topic requests and mode "channel" for @handles, channel IDs, or channel URLs. Returns titles, views, dates, durations, URLs, thumbnails, and videoIds for follow-up transcription.'),
2871
+ description: withReportNote('Harvest YouTube video metadata when the user wants to find videos by topic, inspect a channel library, compare video angles, or get videoIds for later transcription. Use mode "search" for keyword/topic requests and mode "channel" for @handles, channel IDs, or channel URLs. Returns titles, views, durations, URLs, and videoIds for follow-up transcription. Use youtube_transcribe after selecting one video.'),
2542
2872
  inputSchema: YoutubeHarvestInputSchema,
2543
2873
  outputSchema: YoutubeHarvestOutputSchema,
2544
2874
  annotations: liveWebToolAnnotations("YouTube Video Harvest")
2545
2875
  }, async (input) => formatYoutubeHarvest(await executor2.youtubeHarvest(input), input));
2546
2876
  server2.registerTool("youtube_transcribe", {
2547
2877
  title: "YouTube Transcription",
2548
- description: withReportNote("Fetch and transcribe captions from a YouTube video. Returns full transcript, timestamped chunks, and word count. Pass a videoId from youtube_harvest results or infer it from a YouTube URL if the user provided one."),
2878
+ description: withReportNote("Fetch and transcribe captions from a YouTube video. Use this when the user asks what was said in a YouTube video, wants claims/offers/lessons extracted from a video, or provides a YouTube URL for transcript work. Returns full transcript, timestamped chunks, word count, and resolvedInputs. Pass videoId from youtube_harvest results, or pass url when the user pasted a YouTube URL. Use youtube_harvest first when you need to discover videos by topic/channel."),
2549
2879
  inputSchema: YoutubeTranscribeInputSchema,
2880
+ outputSchema: YoutubeTranscribeOutputSchema,
2550
2881
  annotations: liveWebToolAnnotations("YouTube Transcription")
2551
2882
  }, async (input) => formatYoutubeTranscribe(await executor2.youtubeTranscribe(input), input));
2552
2883
  server2.registerTool("facebook_page_intel", {
2553
2884
  title: "Facebook Advertiser Ad Intel",
2554
- description: withReportNote("Harvest ads from a Facebook advertiser. Returns ad copy, headlines, CTAs, creative type, status, landing URLs, and direct ad video URLs ready for facebook_ad_transcribe. Accepts pageId, libraryId, or a brand/advertiser name as query. Use after facebook_ad_search when possible. For normal public Facebook reels/posts/watch/share URLs, use facebook_video_transcribe instead."),
2885
+ description: withReportNote("Harvest ads from a Facebook advertiser when the user wants current ad copy, creative angles, CTAs, video URLs, or competitive ad intelligence for one brand/page. Returns ad copy, headlines, CTAs, creative type, status, landing URLs, and direct ad video URLs ready for facebook_ad_transcribe. Accepts pageId, libraryId, or a brand/advertiser name as query. Use facebook_ad_search first when the advertiser handle is unknown. For normal public Facebook reels/posts/watch/share URLs, use facebook_video_transcribe instead."),
2555
2886
  inputSchema: FacebookPageIntelInputSchema,
2556
2887
  outputSchema: FacebookPageIntelOutputSchema,
2557
2888
  annotations: liveWebToolAnnotations("Facebook Advertiser Ad Intel")
2558
2889
  }, async (input) => formatFacebookPageIntel(await executor2.facebookPageIntel(input), input));
2559
2890
  server2.registerTool("facebook_ad_search", {
2560
2891
  title: "Facebook Ad Library Search",
2561
- description: withReportNote("Search Facebook Ad Library by brand, advertiser, competitor, niche, or keyword. Returns advertisers with ad counts and library IDs. Use to discover competitors, then pass libraryId to facebook_page_intel."),
2892
+ description: withReportNote("Search Facebook Ad Library when the user wants to find advertisers by brand, competitor, niche, or keyword. Returns advertisers with ad counts and library IDs. Use this to discover competitor/page handles, then pass libraryId or pageId to facebook_page_intel for ad details."),
2562
2893
  inputSchema: FacebookAdSearchInputSchema,
2563
2894
  outputSchema: FacebookAdSearchOutputSchema,
2564
2895
  annotations: liveWebToolAnnotations("Facebook Ad Library Search")
2565
2896
  }, async (input) => formatFacebookAdSearch(await executor2.facebookAdSearch(input), input));
2566
2897
  server2.registerTool("facebook_ad_transcribe", {
2567
2898
  title: "Facebook Ad Transcription",
2568
- description: "Transcribe audio from a Facebook ad video CDN URL. Returns full transcript and timestamped chunks. Use only with the direct videoUrl value from facebook_page_intel results, not public Facebook post/reel/share URLs.",
2899
+ description: "Transcribe audio from a Facebook ad video CDN URL. Use this when facebook_page_intel returned a direct videoUrl and the user asks what the ad says, what claims it makes, or wants ad-message extraction. Returns full transcript, timestamped chunks, word count, and resolvedInputs. Use only with the direct videoUrl value from facebook_page_intel results; do not pass public Facebook post/reel/share URLs. Use facebook_video_transcribe for organic Facebook URLs, and facebook_page_intel first when you only have a brand/page/ad library handle.",
2569
2900
  inputSchema: FacebookAdTranscribeInputSchema,
2901
+ outputSchema: FacebookAdTranscribeOutputSchema,
2570
2902
  annotations: liveWebToolAnnotations("Facebook Ad Transcription")
2571
2903
  }, async (input) => formatFacebookAdTranscribe(await executor2.facebookAdTranscribe(input), input));
2572
2904
  server2.registerTool("facebook_video_transcribe", {
@@ -2585,14 +2917,14 @@ function registerPaaExtractorMcpTools(server2, executor2, options = {}) {
2585
2917
  }, async (input) => formatMapsPlaceIntel(await executor2.mapsPlaceIntel(input), input));
2586
2918
  server2.registerTool("maps_search", {
2587
2919
  title: "Google Maps Business Search",
2588
- 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 creates a fresh residential proxy ID and browser session for the market; retryable failures rotate to a new proxy and new browser session for up to 5 attempts. 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, profile metadata, and sanitized attempt telemetry. Default maxResults is 10; maximum is 50. Use maps_place_intel afterward only when a selected business needs full details and reviews.'),
2920
+ 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." Maps is the default location-targeted surface: omit proxyMode for normal Maps use so the service creates residential proxy evidence for the requested market and rotates on retryable failures. Pass proxyZip only when a specific ZIP or city-center ZIP is known. Use proxyMode configured only when you explicitly do not want city/ZIP proxy targeting. Returns up to 50 candidates with names, place URLs, CIDs when available, ratings, review counts, profile metadata, and sanitized attempt telemetry. Default maxResults is 10; maximum is 50. Use maps_place_intel afterward only when a selected business needs full details and reviews.'),
2589
2921
  inputSchema: MapsSearchInputSchema,
2590
2922
  outputSchema: MapsSearchOutputSchema,
2591
2923
  annotations: liveWebToolAnnotations("Google Maps Business Search")
2592
2924
  }, async (input) => formatMapsSearch(await executor2.mapsSearch(input), input));
2593
2925
  server2.registerTool("directory_workflow", {
2594
2926
  title: "Directory Workflow: Markets + Maps",
2595
- 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 search creates fresh residential proxy IDs and browser sessions; retryable city failures rotate to a new proxy and new browser session for up to 5 attempts. Saved CSV rows include source_location, result_position, business_name, review_stars, review_count, category, address, phone, hours_status, website_url, directions_url, place_url, cid, cid_decimal, city population, and ZIP groups. Structured city results include sanitized attempt telemetry. Use maps_place_intel only when a selected profile needs deeper review topics, profile review count confirmation, or review cards. For local Lead Magician ZIP enrichment, set MCP_SCRAPER_USZIPS_CSV_PATH on the API server or pass usZipsCsvPath only in local/test mode.'),
2927
+ 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. Maps workflows default to location-targeted proxying so each city can use city/state or ZIP-group residential proxy evidence; use proxyMode configured only when you explicitly do not want city/ZIP proxy targeting. Saved CSV rows include source_location, result_position, business_name, review_stars, review_count, category, address, phone, hours_status, website_url, directions_url, place_url, cid, cid_decimal, city population, and ZIP groups. Structured city results include sanitized attempt telemetry. Use maps_place_intel only when a selected profile needs deeper review topics, profile review count confirmation, or review cards. For local Lead Magician ZIP enrichment, set MCP_SCRAPER_USZIPS_CSV_PATH on the API server or pass usZipsCsvPath only in local/test mode.'),
2596
2928
  inputSchema: DirectoryWorkflowInputSchema,
2597
2929
  outputSchema: DirectoryWorkflowOutputSchema,
2598
2930
  annotations: liveWebToolAnnotations("Directory Workflow: Markets + Maps")
@@ -2613,21 +2945,28 @@ function registerPaaExtractorMcpTools(server2, executor2, options = {}) {
2613
2945
  }, async (input) => formatWorkflowSuggest(input));
2614
2946
  server2.registerTool("workflow_run", {
2615
2947
  title: "Run Workflow",
2616
- description: withReportNote("Run a higher-level MCP Scraper workflow and return the run id, summary, and artifacts. Use after workflow_suggest or workflow_list. Runnable workflow ids: directory, agent-packet, local-competitive-audit, map-comparison, serp-comparison, paa-expansion-brief, ai-overview-language. This is the main MCP tool for executing market analysis, ICP evidence packets, local competitive audits, Maps/SERP comparisons, content gap briefs, and AI Overview language guidance."),
2948
+ description: withReportNote("Start a higher-level MCP Scraper workflow. Use after workflow_suggest or workflow_list. Runnable workflow ids: directory, agent-packet, local-competitive-audit, map-comparison, serp-comparison, paa-expansion-brief, ai-overview-language. This is the main MCP tool for market analysis, ICP evidence packets, local competitive audits, Maps/SERP comparisons, content gap briefs, and AI Overview language guidance. Stepwise workflows (e.g. agent-packet) run ONE leg per call and return runId, the step output, and nextStep \u2014 when nextStep is present, call workflow_step with the runId to run the next leg, and keep calling it until done is true. This keeps each call short instead of one long blocking run, so report each step result to the user as it arrives."),
2617
2949
  inputSchema: WorkflowRunInputSchema,
2618
2950
  outputSchema: WorkflowRunOutputSchema,
2619
2951
  annotations: liveWebToolAnnotations("Run Workflow")
2620
2952
  }, async (input) => formatWorkflowRun(await executor2.workflowRun(input), input));
2953
+ server2.registerTool("workflow_step", {
2954
+ title: "Advance Workflow Step",
2955
+ description: withReportNote("Run the next leg of a stepwise MCP Scraper workflow started with workflow_run. Pass the runId. Each call executes exactly one logical step (typically one live harvest), persists that step's artifacts, and returns the step output plus nextStep. Keep calling workflow_step with the same runId until done is true, reporting each step result to the user as it lands. Use this instead of waiting on one long workflow call \u2014 it avoids client timeouts on long multi-step jobs."),
2956
+ inputSchema: WorkflowStepInputSchema,
2957
+ outputSchema: WorkflowStepOutputSchema,
2958
+ annotations: liveWebToolAnnotations("Advance Workflow Step")
2959
+ }, async (input) => formatWorkflowStep(await executor2.workflowStep(input), input));
2621
2960
  server2.registerTool("workflow_status", {
2622
2961
  title: "Workflow Status",
2623
- description: "Fetch a hosted workflow run by id and list its current status and artifacts. Use after workflow_run when the model needs to re-open a run, inspect artifact ids, or recover from a long workflow.",
2962
+ description: "Fetch a hosted workflow run by id and list its current status and artifacts. Use when a workflow may still be running, when the model needs to re-open a run, inspect artifact ids, recover from a long workflow, or decide whether to call workflow_step or workflow_artifact_read next. Use only a runId returned by workflow_run/workflow_step/workflow_status; do not construct one yourself.",
2624
2963
  inputSchema: WorkflowStatusInputSchema,
2625
2964
  outputSchema: WorkflowStatusOutputSchema,
2626
2965
  annotations: liveWebToolAnnotations("Workflow Status")
2627
2966
  }, async (input) => formatWorkflowStatus(await executor2.workflowStatus(input), input));
2628
2967
  server2.registerTool("workflow_artifact_read", {
2629
2968
  title: "Read Workflow Artifact",
2630
- description: "Read a workflow artifact back into MCP context by run id and artifact id. Use this before writing final deliverables so the answer is grounded in generated evidence.json, CSVs, Markdown briefs, reports, and task files instead of memory. Use maxBytes to limit large CSV/JSON artifacts.",
2969
+ description: "Read a workflow artifact back into MCP context by run id and artifact id. Use this before writing final deliverables so the answer is grounded in generated evidence.json, CSVs, Markdown briefs, reports, and task files instead of memory. Use workflow_status first when artifact ids are unknown. Use only artifactId values returned by workflow_run/workflow_step/workflow_status; do not construct one yourself. Use maxBytes to limit large CSV/JSON artifacts.",
2631
2970
  inputSchema: WorkflowArtifactReadInputSchema,
2632
2971
  outputSchema: WorkflowArtifactReadOutputSchema,
2633
2972
  annotations: liveWebToolAnnotations("Read Workflow Artifact")