mcp-scraper 0.2.19 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/bin/api-server.cjs +1020 -348
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- package/dist/bin/browser-agent-stdio-server.cjs +314 -55
- package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
- package/dist/bin/browser-agent-stdio-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +321 -167
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +3 -2
- package/dist/bin/mcp-scraper-cli.js.map +1 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs +1128 -530
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.js +5 -4
- package/dist/bin/mcp-scraper-combined-stdio-server.js.map +1 -1
- package/dist/bin/mcp-scraper-install.cjs +3 -3
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +726 -387
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +3 -2
- package/dist/bin/mcp-stdio-server.js.map +1 -1
- package/dist/bin/paa-harvest.cjs +5 -3
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +2 -1
- package/dist/bin/paa-harvest.js.map +1 -1
- package/dist/{chunk-TL7YTFLH.js → chunk-DBQDG7EH.js} +99 -27
- package/dist/chunk-DBQDG7EH.js.map +1 -0
- package/dist/{chunk-F44RBOJ5.js → chunk-H2R232HK.js} +216 -108
- package/dist/chunk-H2R232HK.js.map +1 -0
- package/dist/{chunk-76P4DKWR.js → chunk-L4OWOUGR.js} +315 -56
- package/dist/chunk-L4OWOUGR.js.map +1 -0
- package/dist/chunk-LFATOGDF.js +111 -0
- package/dist/chunk-LFATOGDF.js.map +1 -0
- package/dist/{chunk-MCBOZ2JF.js → chunk-MLNCKQ2B.js} +3 -3
- package/dist/chunk-MLNCKQ2B.js.map +1 -0
- package/dist/{chunk-4UEVYYDW.js → chunk-O5J7E4VX.js} +394 -57
- package/dist/chunk-O5J7E4VX.js.map +1 -0
- package/dist/{chunk-BSYPATSM.js → chunk-SXTXMFEQ.js} +2 -2
- package/dist/{chunk-CQTAKXBN.js → chunk-XGUDTDZ2.js} +7 -104
- package/dist/chunk-XGUDTDZ2.js.map +1 -0
- package/dist/chunk-XKUDVN2E.js +7 -0
- package/dist/chunk-XKUDVN2E.js.map +1 -0
- package/dist/{db-P5X6UQ3E.js → db-BE4JVB3V.js} +8 -2
- package/dist/index.cjs +5 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/{server-CRUSZA2Q.js → server-7NE45K2A.js} +211 -37
- package/dist/server-7NE45K2A.js.map +1 -0
- package/dist/{worker-OZSWIS3F.js → worker-MIYG2B2I.js} +5 -4
- package/dist/{worker-OZSWIS3F.js.map → worker-MIYG2B2I.js.map} +1 -1
- package/docs/adr/0001-in-page-graphql-interception-for-anti-bot-scraping.md +2 -2
- package/docs/mcp-tool-craft-lint.generated.md +51 -0
- package/docs/mcp-tool-manifest.generated.json +1406 -0
- package/docs/specs/local-competitive-audit-spec.md +1 -2
- package/docs/specs/mcp-tool-definition-quality-audit-spec.md +1602 -0
- package/package.json +4 -1
- package/dist/chunk-4UEVYYDW.js.map +0 -1
- package/dist/chunk-76P4DKWR.js.map +0 -1
- package/dist/chunk-CQTAKXBN.js.map +0 -1
- package/dist/chunk-F44RBOJ5.js.map +0 -1
- package/dist/chunk-MCBOZ2JF.js.map +0 -1
- package/dist/chunk-RU2HZD55.js +0 -7
- package/dist/chunk-RU2HZD55.js.map +0 -1
- package/dist/chunk-TL7YTFLH.js.map +0 -1
- package/dist/server-CRUSZA2Q.js.map +0 -1
- /package/dist/{chunk-BSYPATSM.js.map → chunk-SXTXMFEQ.js.map} +0 -0
- /package/dist/{db-P5X6UQ3E.js.map → db-BE4JVB3V.js.map} +0 -0
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
sanitizeVendorName
|
|
3
3
|
} from "./chunk-M2S27J6Z.js";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_MAPS_PROXY_MODE,
|
|
6
|
+
DEFAULT_PROXY_MODE
|
|
7
|
+
} from "./chunk-LFATOGDF.js";
|
|
4
8
|
import {
|
|
5
9
|
PACKAGE_VERSION
|
|
6
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-XKUDVN2E.js";
|
|
7
11
|
|
|
8
12
|
// src/harvest-timeout.ts
|
|
9
13
|
var VERCEL_FUNCTION_MAX_MS = 3e5;
|
|
@@ -642,13 +646,25 @@ ${videoRows}`,
|
|
|
642
646
|
}
|
|
643
647
|
};
|
|
644
648
|
}
|
|
649
|
+
function structuredTranscriptChunks(chunks) {
|
|
650
|
+
return chunks.map((c) => ({
|
|
651
|
+
startSec: Number.isFinite(c.timestamp?.[0]) ? c.timestamp[0] : 0,
|
|
652
|
+
endSec: Number.isFinite(c.timestamp?.[1]) ? c.timestamp[1] : 0,
|
|
653
|
+
text: c.text
|
|
654
|
+
}));
|
|
655
|
+
}
|
|
656
|
+
function wordCount(text) {
|
|
657
|
+
return text.trim() ? text.trim().split(/\s+/).length : 0;
|
|
658
|
+
}
|
|
645
659
|
function formatYoutubeTranscribe(raw, input) {
|
|
646
660
|
const parsed = parseData(raw);
|
|
647
661
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
648
662
|
const d = parsed.data;
|
|
649
663
|
const text = d.text ?? "";
|
|
650
664
|
const chunks = d.chunks ?? [];
|
|
665
|
+
const videoId = d.videoId ?? input.videoId ?? null;
|
|
651
666
|
const durSec = d.durationMs ? (d.durationMs / 1e3).toFixed(0) : "\u2014";
|
|
667
|
+
const words = wordCount(text);
|
|
652
668
|
const chunkRows = chunks.slice(0, 50).map((c) => {
|
|
653
669
|
const sec = Number.isFinite(c.timestamp[0]) ? Math.floor(c.timestamp[0]) : 0;
|
|
654
670
|
const mm = String(Math.floor(sec / 60)).padStart(2, "0");
|
|
@@ -656,8 +672,8 @@ function formatYoutubeTranscribe(raw, input) {
|
|
|
656
672
|
return `| ${mm}:${ss} | ${cell(truncate(c.text, 120))} |`;
|
|
657
673
|
}).join("\n");
|
|
658
674
|
const full = [
|
|
659
|
-
`# YouTube Transcript: \`${input.
|
|
660
|
-
`**Duration:** ${durSec}s \xB7 **${
|
|
675
|
+
`# YouTube Transcript: \`${videoId ?? input.url ?? "video"}\``,
|
|
676
|
+
`**Duration:** ${durSec}s \xB7 **${words} words**`,
|
|
661
677
|
`
|
|
662
678
|
## Full Transcript
|
|
663
679
|
${text}`,
|
|
@@ -670,7 +686,22 @@ ${chunkRows}` : "",
|
|
|
670
686
|
---
|
|
671
687
|
\u{1F4A1} Harvest more from this channel: use \`youtube_harvest\` with \`mode: "channel"\``
|
|
672
688
|
].filter(Boolean).join("\n");
|
|
673
|
-
return
|
|
689
|
+
return {
|
|
690
|
+
...oneBlock(full),
|
|
691
|
+
structuredContent: {
|
|
692
|
+
videoId,
|
|
693
|
+
url: videoId ? `https://www.youtube.com/watch?v=${videoId}` : input.url ?? null,
|
|
694
|
+
wordCount: words,
|
|
695
|
+
chunkCount: chunks.length,
|
|
696
|
+
durationMs: typeof d.durationMs === "number" ? d.durationMs : null,
|
|
697
|
+
transcriptText: text,
|
|
698
|
+
chunks: structuredTranscriptChunks(chunks),
|
|
699
|
+
resolvedInputs: {
|
|
700
|
+
videoId,
|
|
701
|
+
url: input.url ?? null
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
};
|
|
674
705
|
}
|
|
675
706
|
function formatFacebookPageIntel(raw, input) {
|
|
676
707
|
const parsed = parseData(raw);
|
|
@@ -680,12 +711,13 @@ function formatFacebookPageIntel(raw, input) {
|
|
|
680
711
|
const ads = d.ads ?? [];
|
|
681
712
|
const s = d.summary ?? { totalAds: 0, activeCount: 0, videoCount: 0, imageCount: 0 };
|
|
682
713
|
const adBlocks = ads.map((ad, i) => [
|
|
683
|
-
`### Ad ${i + 1}${ad.libraryId ? ` \xB7 \`${ad.libraryId}\`` : ""} \u2014 ${ad.status ?? "\u2014"} \xB7 ${ad.creativeType ?? "\u2014"} \xB7 ${ad.startDate ?? "\u2014"}`,
|
|
714
|
+
`### Ad ${i + 1}${ad.libraryId ? ` \xB7 \`${ad.libraryId}\`` : ""} \u2014 ${ad.status ?? "\u2014"} \xB7 ${ad.creativeType ?? "\u2014"} \xB7 ${ad.startDate ?? ad.started ?? "\u2014"}`,
|
|
684
715
|
ad.headline ? `**Headline:** ${ad.headline}` : "",
|
|
685
716
|
ad.primaryText ? `**Copy:** ${truncate(ad.primaryText, 200)}` : "",
|
|
686
717
|
ad.cta ? `**CTA:** ${ad.cta}` : "",
|
|
687
|
-
ad.
|
|
688
|
-
ad.
|
|
718
|
+
ad.landingUrl ? `**Landing URL:** ${ad.landingUrl}` : "",
|
|
719
|
+
ad.videoUrl ?? ad.videoSrc ? `**Video URL:** \`${ad.videoUrl ?? ad.videoSrc}\`` : "",
|
|
720
|
+
ad.variations ?? ad.clusterCount ? `**Variations:** ${ad.variations ?? ad.clusterCount}` : ""
|
|
689
721
|
].filter(Boolean).join("\n")).join("\n\n---\n\n");
|
|
690
722
|
const full = [
|
|
691
723
|
`# Facebook Ad Intel: ${advertiser}`,
|
|
@@ -711,11 +743,16 @@ ${adBlocks}`,
|
|
|
711
743
|
libraryId: ad.libraryId ?? null,
|
|
712
744
|
status: ad.status ?? null,
|
|
713
745
|
creativeType: ad.creativeType ?? null,
|
|
746
|
+
primaryText: ad.primaryText ?? null,
|
|
714
747
|
headline: ad.headline ?? null,
|
|
715
748
|
cta: ad.cta ?? null,
|
|
716
|
-
startDate: ad.startDate ?? null,
|
|
717
|
-
|
|
718
|
-
|
|
749
|
+
startDate: ad.startDate ?? ad.started ?? null,
|
|
750
|
+
landingUrl: ad.landingUrl ?? null,
|
|
751
|
+
domain: ad.domain ?? null,
|
|
752
|
+
videoUrl: ad.videoUrl ?? ad.videoSrc ?? null,
|
|
753
|
+
imageUrl: ad.imageUrl ?? ad.imageSrc ?? null,
|
|
754
|
+
videoPoster: ad.videoPoster ?? null,
|
|
755
|
+
variations: typeof ad.variations === "number" ? ad.variations : typeof ad.clusterCount === "number" ? ad.clusterCount : null
|
|
719
756
|
}))
|
|
720
757
|
}
|
|
721
758
|
};
|
|
@@ -749,8 +786,11 @@ ${rows}`,
|
|
|
749
786
|
advertiserCount: advertisers.length,
|
|
750
787
|
advertisers: advertisers.map((a) => ({
|
|
751
788
|
name: a.pageName ?? a.name ?? null,
|
|
789
|
+
pageId: a.pageId ?? null,
|
|
790
|
+
pageUrl: a.pageUrl ?? null,
|
|
752
791
|
adCount: typeof a.adCount === "number" ? a.adCount : null,
|
|
753
|
-
libraryId: a.sampleLibraryId ?? a.libraryId ?? null
|
|
792
|
+
libraryId: a.sampleLibraryId ?? a.libraryId ?? null,
|
|
793
|
+
sampleLibraryId: a.sampleLibraryId ?? null
|
|
754
794
|
}))
|
|
755
795
|
}
|
|
756
796
|
};
|
|
@@ -848,6 +888,34 @@ function formatWorkflowSuggest(input) {
|
|
|
848
888
|
}
|
|
849
889
|
};
|
|
850
890
|
}
|
|
891
|
+
function workflowStepLines(data) {
|
|
892
|
+
const step = data.step;
|
|
893
|
+
const nextStep = data.nextStep;
|
|
894
|
+
const done = data.done === true;
|
|
895
|
+
const lines = [];
|
|
896
|
+
if (step) {
|
|
897
|
+
const index = Number(step.index ?? 0);
|
|
898
|
+
const totalSteps = step.totalSteps != null ? Number(step.totalSteps) : void 0;
|
|
899
|
+
const stepLabel = totalSteps ? `${index + 1}/${totalSteps}` : `${index + 1}`;
|
|
900
|
+
lines.push(`
|
|
901
|
+
## Step ${stepLabel}: ${step.title ?? step.id ?? ""}`);
|
|
902
|
+
const output = step.output;
|
|
903
|
+
if (output && Object.keys(output).length) {
|
|
904
|
+
lines.push(Object.entries(output).map(([k, v]) => `- ${k}: ${typeof v === "object" ? JSON.stringify(v) : String(v)}`).join("\n"));
|
|
905
|
+
}
|
|
906
|
+
const warnings = Array.isArray(step.warnings) ? step.warnings : [];
|
|
907
|
+
if (warnings.length) lines.push(`
|
|
908
|
+
**Warnings:**
|
|
909
|
+
${warnings.map((w) => `- ${w}`).join("\n")}`);
|
|
910
|
+
}
|
|
911
|
+
if (done) {
|
|
912
|
+
lines.push("\n**Done.** All steps complete.");
|
|
913
|
+
} else if (nextStep && typeof nextStep === "object") {
|
|
914
|
+
lines.push(`
|
|
915
|
+
**Next step:** \`${nextStep.id ?? nextStep.index}\` \u2014 call \`workflow_step\` with this run id to continue.`);
|
|
916
|
+
}
|
|
917
|
+
return lines;
|
|
918
|
+
}
|
|
851
919
|
function formatWorkflowRun(raw, input) {
|
|
852
920
|
const parsed = parseData(raw);
|
|
853
921
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
@@ -861,6 +929,7 @@ function formatWorkflowRun(raw, input) {
|
|
|
861
929
|
`**Run ID:** \`${runId || "unknown"}\``,
|
|
862
930
|
`**Status:** ${status}`,
|
|
863
931
|
summary?.title ? `**Title:** ${summary.title}` : "",
|
|
932
|
+
...workflowStepLines(parsed.data),
|
|
864
933
|
summary?.summary ? `
|
|
865
934
|
## Summary
|
|
866
935
|
${summary.summary}` : "",
|
|
@@ -876,6 +945,42 @@ ${workflowArtifactRows(artifacts)}` : "",
|
|
|
876
945
|
input: input.input ?? {},
|
|
877
946
|
run,
|
|
878
947
|
summary,
|
|
948
|
+
step: parsed.data.step,
|
|
949
|
+
nextStep: parsed.data.nextStep ?? null,
|
|
950
|
+
done: parsed.data.done === true,
|
|
951
|
+
artifacts
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
function formatWorkflowStep(raw, input) {
|
|
956
|
+
const parsed = parseData(raw);
|
|
957
|
+
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
958
|
+
const run = parsed.data.run;
|
|
959
|
+
const summary = parsed.data.summary;
|
|
960
|
+
const artifacts = workflowArtifactsFrom(run);
|
|
961
|
+
const done = parsed.data.done === true;
|
|
962
|
+
const full = [
|
|
963
|
+
`# Workflow Step`,
|
|
964
|
+
`**Run ID:** \`${input.runId}\``,
|
|
965
|
+
`**Status:** ${run?.status ?? (done ? "done" : "running")}`,
|
|
966
|
+
...workflowStepLines(parsed.data),
|
|
967
|
+
done && summary?.summary ? `
|
|
968
|
+
## Summary
|
|
969
|
+
${summary.summary}` : "",
|
|
970
|
+
artifacts.length ? `
|
|
971
|
+
## Artifacts
|
|
972
|
+
${workflowArtifactRows(artifacts)}` : "",
|
|
973
|
+
done && artifacts.length ? "\nUse `workflow_artifact_read` with the run id and artifact id to pull CSV, JSON, Markdown, or report content into context." : ""
|
|
974
|
+
].filter(Boolean).join("\n");
|
|
975
|
+
return {
|
|
976
|
+
...oneBlock(full),
|
|
977
|
+
structuredContent: {
|
|
978
|
+
runId: input.runId,
|
|
979
|
+
run,
|
|
980
|
+
summary: summary ?? null,
|
|
981
|
+
step: parsed.data.step,
|
|
982
|
+
nextStep: parsed.data.nextStep ?? null,
|
|
983
|
+
done,
|
|
879
984
|
artifacts
|
|
880
985
|
}
|
|
881
986
|
};
|
|
@@ -1280,6 +1385,7 @@ function formatFacebookAdTranscribe(raw, input) {
|
|
|
1280
1385
|
const text = d.text ?? "";
|
|
1281
1386
|
const chunks = d.chunks ?? [];
|
|
1282
1387
|
const durSec = d.durationMs ? (d.durationMs / 1e3).toFixed(0) : "\u2014";
|
|
1388
|
+
const words = wordCount(text);
|
|
1283
1389
|
const chunkRows = chunks.slice(0, 50).map((c) => {
|
|
1284
1390
|
const sec = Number.isFinite(c.timestamp[0]) ? Math.floor(c.timestamp[0]) : 0;
|
|
1285
1391
|
const mm = String(Math.floor(sec / 60)).padStart(2, "0");
|
|
@@ -1288,7 +1394,7 @@ function formatFacebookAdTranscribe(raw, input) {
|
|
|
1288
1394
|
}).join("\n");
|
|
1289
1395
|
const full = [
|
|
1290
1396
|
`# Facebook Ad Transcript`,
|
|
1291
|
-
`**Duration:** ${durSec}s \xB7 **${
|
|
1397
|
+
`**Duration:** ${durSec}s \xB7 **${words} words**`,
|
|
1292
1398
|
`
|
|
1293
1399
|
## Full Transcript
|
|
1294
1400
|
${text}`,
|
|
@@ -1301,7 +1407,20 @@ ${chunkRows}` : "",
|
|
|
1301
1407
|
---
|
|
1302
1408
|
\u{1F4A1} Get more ads from this advertiser: use \`facebook_page_intel\`. For public Facebook reel/post URLs, use \`facebook_video_transcribe\`.`
|
|
1303
1409
|
].filter(Boolean).join("\n");
|
|
1304
|
-
return
|
|
1410
|
+
return {
|
|
1411
|
+
...oneBlock(full),
|
|
1412
|
+
structuredContent: {
|
|
1413
|
+
videoUrl: input.videoUrl,
|
|
1414
|
+
wordCount: words,
|
|
1415
|
+
chunkCount: chunks.length,
|
|
1416
|
+
durationMs: typeof d.durationMs === "number" ? d.durationMs : null,
|
|
1417
|
+
transcriptText: text,
|
|
1418
|
+
chunks: structuredTranscriptChunks(chunks),
|
|
1419
|
+
resolvedInputs: {
|
|
1420
|
+
videoUrl: input.videoUrl
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1305
1424
|
}
|
|
1306
1425
|
function formatFacebookVideoTranscribe(raw, input) {
|
|
1307
1426
|
const parsed = parseData(raw);
|
|
@@ -1309,7 +1428,7 @@ function formatFacebookVideoTranscribe(raw, input) {
|
|
|
1309
1428
|
const d = parsed.data;
|
|
1310
1429
|
const text = d.text ?? "";
|
|
1311
1430
|
const chunks = d.chunks ?? [];
|
|
1312
|
-
const
|
|
1431
|
+
const wordCount2 = text.trim() ? text.trim().split(/\s+/).length : 0;
|
|
1313
1432
|
const durSec = d.durationMs ? (d.durationMs / 1e3).toFixed(0) : "\u2014";
|
|
1314
1433
|
const videoDuration = typeof d.videoDurationSec === "number" ? `${Math.round(d.videoDurationSec)}s` : "\u2014";
|
|
1315
1434
|
const label = d.videoId ? `Facebook Organic Video \`${d.videoId}\`` : "Facebook Organic Video";
|
|
@@ -1322,7 +1441,7 @@ function formatFacebookVideoTranscribe(raw, input) {
|
|
|
1322
1441
|
const full = [
|
|
1323
1442
|
`# ${label} Transcript`,
|
|
1324
1443
|
d.ownerName ? `**Owner:** ${d.ownerName}` : "",
|
|
1325
|
-
`**Video duration:** ${videoDuration} \xB7 **Transcribed in:** ${durSec}s \xB7 **${
|
|
1444
|
+
`**Video duration:** ${videoDuration} \xB7 **Transcribed in:** ${durSec}s \xB7 **${wordCount2} words**`,
|
|
1326
1445
|
d.pageUrl ? `**Page URL:** ${d.pageUrl}` : `**Page URL:** ${input.url}`,
|
|
1327
1446
|
d.videoUrl ? `**Extracted MP4:** \`${d.videoUrl}\`` : "",
|
|
1328
1447
|
`
|
|
@@ -1348,7 +1467,7 @@ ${chunkRows}` : "",
|
|
|
1348
1467
|
bitrate: typeof d.bitrate === "number" ? d.bitrate : null,
|
|
1349
1468
|
videoDurationSec: typeof d.videoDurationSec === "number" ? d.videoDurationSec : null,
|
|
1350
1469
|
videoUrl: d.videoUrl ?? "",
|
|
1351
|
-
wordCount,
|
|
1470
|
+
wordCount: wordCount2,
|
|
1352
1471
|
chunkCount: chunks.length,
|
|
1353
1472
|
transcriptText: text,
|
|
1354
1473
|
chunks: chunks.map((c) => ({
|
|
@@ -1359,6 +1478,96 @@ ${chunkRows}` : "",
|
|
|
1359
1478
|
}
|
|
1360
1479
|
};
|
|
1361
1480
|
}
|
|
1481
|
+
function formatCaptureSerpSnapshot(raw, input) {
|
|
1482
|
+
const parsed = parseData(raw);
|
|
1483
|
+
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
1484
|
+
const d = parsed.data;
|
|
1485
|
+
const providerPayload = d;
|
|
1486
|
+
const organic = Array.isArray(d.organicResults) ? d.organicResults : Array.isArray(d.organic) ? d.organic : [];
|
|
1487
|
+
const localPack = Array.isArray(d.localPack) ? d.localPack : [];
|
|
1488
|
+
const artifacts = Array.isArray(d.artifacts) ? d.artifacts : [];
|
|
1489
|
+
const status = String(d.status ?? d.captureStatus ?? "captured");
|
|
1490
|
+
const query = typeof d.query === "string" ? d.query : typeof input.query === "string" ? input.query : null;
|
|
1491
|
+
const location = typeof d.location === "string" ? d.location : typeof input.location === "string" ? input.location : null;
|
|
1492
|
+
const capturedAt = typeof d.capturedAt === "string" ? d.capturedAt : typeof d.extractedAt === "string" ? d.extractedAt : null;
|
|
1493
|
+
const resultCount = organic.length + localPack.length;
|
|
1494
|
+
const full = [
|
|
1495
|
+
`# SERP Intelligence Snapshot: ${query ?? "query"}`,
|
|
1496
|
+
`**Status:** ${status}`,
|
|
1497
|
+
location ? `**Location:** ${location}` : "",
|
|
1498
|
+
`**Result count:** ${resultCount}`,
|
|
1499
|
+
artifacts.length ? `**Artifacts:** ${artifacts.length}` : "",
|
|
1500
|
+
"",
|
|
1501
|
+
"Use `capture_serp_page_snapshots` when you need page-level evidence for ranking URLs."
|
|
1502
|
+
].filter(Boolean).join("\n");
|
|
1503
|
+
return {
|
|
1504
|
+
...oneBlock(full),
|
|
1505
|
+
structuredContent: {
|
|
1506
|
+
schemaVersion: "serp-intelligence.capture.v1",
|
|
1507
|
+
status,
|
|
1508
|
+
query,
|
|
1509
|
+
location,
|
|
1510
|
+
capturedAt,
|
|
1511
|
+
resultCount,
|
|
1512
|
+
snapshotId: typeof d.snapshotId === "string" ? d.snapshotId : typeof d.snapshot_id === "string" ? d.snapshot_id : typeof d.id === "string" ? d.id : null,
|
|
1513
|
+
resolvedInputs: {
|
|
1514
|
+
query: input.query ?? null,
|
|
1515
|
+
location: input.location ?? null,
|
|
1516
|
+
gl: input.gl ?? null,
|
|
1517
|
+
hl: input.hl ?? null,
|
|
1518
|
+
device: input.device ?? null,
|
|
1519
|
+
proxyMode: input.proxyMode ?? null,
|
|
1520
|
+
proxyZip: input.proxyZip ?? null,
|
|
1521
|
+
pages: input.pages ?? null
|
|
1522
|
+
},
|
|
1523
|
+
artifacts,
|
|
1524
|
+
diagnostics: d.diagnostics && typeof d.diagnostics === "object" ? d.diagnostics : null,
|
|
1525
|
+
providerPayload
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
function formatCaptureSerpPageSnapshots(raw, input) {
|
|
1530
|
+
const parsed = parseData(raw);
|
|
1531
|
+
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
1532
|
+
const d = parsed.data;
|
|
1533
|
+
const captures = Array.isArray(d.captures) ? d.captures : Array.isArray(d.results) ? d.results : [];
|
|
1534
|
+
const failedCount = captures.filter((c) => c.status === "failed" || c.error).length;
|
|
1535
|
+
const status = String(d.status ?? (failedCount ? "partial" : "captured"));
|
|
1536
|
+
const rows = captures.slice(0, 25).map((capture, i) => {
|
|
1537
|
+
const url = typeof capture.url === "string" ? capture.url : "";
|
|
1538
|
+
const sourceKind = typeof capture.sourceKind === "string" ? capture.sourceKind : typeof capture.source_kind === "string" ? capture.source_kind : "configured_target";
|
|
1539
|
+
return `| ${i + 1} | ${cell(url)} | ${cell(sourceKind)} | ${cell(String(capture.status ?? (capture.error ? "failed" : "ok")))} |`;
|
|
1540
|
+
}).join("\n");
|
|
1541
|
+
const full = [
|
|
1542
|
+
"# SERP Intelligence Page Snapshots",
|
|
1543
|
+
`**Status:** ${status}`,
|
|
1544
|
+
`**Captured:** ${captures.length}`,
|
|
1545
|
+
`**Failed:** ${failedCount}`,
|
|
1546
|
+
captures.length ? `
|
|
1547
|
+
| # | URL | Source | Status |
|
|
1548
|
+
|---|-----|--------|--------|
|
|
1549
|
+
${rows}` : ""
|
|
1550
|
+
].filter(Boolean).join("\n");
|
|
1551
|
+
return {
|
|
1552
|
+
...oneBlock(full),
|
|
1553
|
+
structuredContent: {
|
|
1554
|
+
schemaVersion: "serp-intelligence.page-snapshots.v1",
|
|
1555
|
+
status,
|
|
1556
|
+
count: captures.length,
|
|
1557
|
+
failedCount,
|
|
1558
|
+
captures,
|
|
1559
|
+
resolvedInputs: {
|
|
1560
|
+
urls: input.urls ?? null,
|
|
1561
|
+
targets: input.targets ?? null,
|
|
1562
|
+
maxConcurrency: input.maxConcurrency ?? null,
|
|
1563
|
+
timeoutMs: input.timeoutMs ?? null,
|
|
1564
|
+
debug: input.debug ?? null
|
|
1565
|
+
},
|
|
1566
|
+
diagnostics: d.diagnostics && typeof d.diagnostics === "object" ? d.diagnostics : null,
|
|
1567
|
+
providerPayload: d
|
|
1568
|
+
}
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1362
1571
|
|
|
1363
1572
|
// src/mcp/mcp-tool-schemas.ts
|
|
1364
1573
|
import { z } from "zod";
|
|
@@ -1369,8 +1578,8 @@ var HarvestPaaInputSchema = {
|
|
|
1369
1578
|
gl: z.string().length(2).default("us").describe("Google country code inferred from location or user language. Examples: United States us, United Kingdom gb, Japan jp, Canada ca, Australia au."),
|
|
1370
1579
|
hl: z.string().default("en").describe("Google interface/content language inferred from the user request. Use en unless the user asks for another language or locale."),
|
|
1371
1580
|
device: z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
|
|
1372
|
-
proxyMode: z.enum(["location", "configured", "none"]).default(
|
|
1373
|
-
proxyZip: z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use when the user gives a specific ZIP or
|
|
1581
|
+
proxyMode: 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."),
|
|
1582
|
+
proxyZip: 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."),
|
|
1374
1583
|
debug: z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior.")
|
|
1375
1584
|
};
|
|
1376
1585
|
var ExtractUrlInputSchema = {
|
|
@@ -1394,22 +1603,23 @@ var YoutubeHarvestInputSchema = {
|
|
|
1394
1603
|
mode: z.enum(["search", "channel"]).describe("Use search for topic/keyword requests. Use channel when the user provides @handle, channel ID, or channel URL."),
|
|
1395
1604
|
query: z.string().optional().describe("Required when mode is search. The YouTube search topic in the user\u2019s words."),
|
|
1396
1605
|
channelHandle: z.string().optional().describe("YouTube channel handle, channel ID, or URL. Examples: @mkbhd, UC..., https://youtube.com/@mkbhd."),
|
|
1397
|
-
maxVideos: z.number().int().min(1).max(500).default(50).describe("Number of videos to return. Default 50.
|
|
1606
|
+
maxVideos: 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.")
|
|
1398
1607
|
};
|
|
1399
1608
|
var YoutubeTranscribeInputSchema = {
|
|
1400
|
-
videoId: z.string().min(1).describe("YouTube video ID, e.g. dQw4w9WgXcQ")
|
|
1609
|
+
videoId: 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."),
|
|
1610
|
+
url: 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.")
|
|
1401
1611
|
};
|
|
1402
1612
|
var FacebookPageIntelInputSchema = {
|
|
1403
|
-
pageId: z.string().optional(),
|
|
1404
|
-
libraryId: z.string().optional(),
|
|
1613
|
+
pageId: 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."),
|
|
1614
|
+
libraryId: 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."),
|
|
1405
1615
|
query: z.string().optional().describe("Advertiser or brand name when pageId/libraryId is not known. One of pageId, libraryId, or query is required."),
|
|
1406
|
-
maxAds: z.number().int().min(1).max(200).default(50),
|
|
1407
|
-
country: z.string().length(2).default("US")
|
|
1616
|
+
maxAds: 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."),
|
|
1617
|
+
country: 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.")
|
|
1408
1618
|
};
|
|
1409
1619
|
var FacebookAdSearchInputSchema = {
|
|
1410
1620
|
query: z.string().min(1).describe("Advertiser, brand, competitor, niche, or keyword to search in Facebook Ad Library."),
|
|
1411
|
-
country: z.string().length(2).default("US"),
|
|
1412
|
-
maxResults: z.number().int().min(1).max(20).default(10)
|
|
1621
|
+
country: z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US. Examples: US, CA, GB, AU."),
|
|
1622
|
+
maxResults: 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.")
|
|
1413
1623
|
};
|
|
1414
1624
|
var FacebookAdTranscribeInputSchema = {
|
|
1415
1625
|
videoUrl: 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.")
|
|
@@ -1423,8 +1633,8 @@ var MapsPlaceIntelInputSchema = {
|
|
|
1423
1633
|
location: 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.'),
|
|
1424
1634
|
gl: z.string().length(2).default("us").describe("Google country code inferred from location."),
|
|
1425
1635
|
hl: z.string().length(2).default("en").describe("Language inferred from user request."),
|
|
1426
|
-
includeReviews: z.boolean().default(false).describe("Whether to fetch individual review cards"),
|
|
1427
|
-
maxReviews: z.number().int().min(1).max(500).default(50).describe("Max review cards to return
|
|
1636
|
+
includeReviews: 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."),
|
|
1637
|
+
maxReviews: 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.")
|
|
1428
1638
|
};
|
|
1429
1639
|
var MapsSearchInputSchema = {
|
|
1430
1640
|
query: 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.'),
|
|
@@ -1432,7 +1642,7 @@ var MapsSearchInputSchema = {
|
|
|
1432
1642
|
gl: z.string().length(2).default("us").describe("Google country code inferred from location."),
|
|
1433
1643
|
hl: z.string().length(2).default("en").describe("Language inferred from user request."),
|
|
1434
1644
|
maxResults: z.number().int().min(1).max(50).default(10).describe("Number of Google Maps business/profile candidates to return. Default 10. Maximum 50. Use 10 unless the user asks for more."),
|
|
1435
|
-
proxyMode: z.enum(["location", "configured", "none"]).default(
|
|
1645
|
+
proxyMode: 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."),
|
|
1436
1646
|
proxyZip: z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use when the user gives a specific ZIP or city-center ZIP."),
|
|
1437
1647
|
debug: z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics when debugging Maps localization, CAPTCHA, or proxy behavior.")
|
|
1438
1648
|
};
|
|
@@ -1447,8 +1657,8 @@ var DirectoryWorkflowInputSchema = {
|
|
|
1447
1657
|
includeZipGroups: z.boolean().default(true).describe("Attach ZIP groups from a configured US ZIPS CSV when available. Set MCP_SCRAPER_USZIPS_CSV_PATH on the API server or pass usZipsCsvPath in local/test mode."),
|
|
1448
1658
|
usZipsCsvPath: z.string().optional().describe("Local/test-only path to a US ZIPS CSV with state_abbr, zipcode, county, city columns, such as Lead Magician tools/analytics/data/uszips.csv. Deployed APIs should use MCP_SCRAPER_USZIPS_CSV_PATH instead."),
|
|
1449
1659
|
saveCsv: z.boolean().default(true).describe("Save a directory-ready CSV to the MCP Scraper output directory and return its path. CSV rows include source_location, result_position, business_name, review_stars, review_count, category, address, phone, hours_status, website_url, directions_url, place_url, CID fields, population, and ZIP groups."),
|
|
1450
|
-
proxyMode: z.enum(["location", "configured", "none"]).default(
|
|
1451
|
-
proxyZip: z.string().regex(/^\d{5}$/).optional().describe("Optional ZIP override for proxy targeting. Normally omit it so each city can use its
|
|
1660
|
+
proxyMode: 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."),
|
|
1661
|
+
proxyZip: 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."),
|
|
1452
1662
|
debug: z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics in each Maps browser session when supported.")
|
|
1453
1663
|
};
|
|
1454
1664
|
var RankTrackerModeSchema = z.enum(["maps", "organic", "ai_overview", "paa"]);
|
|
@@ -1762,8 +1972,11 @@ var FacebookAdSearchOutputSchema = {
|
|
|
1762
1972
|
advertiserCount: z.number().int().min(0),
|
|
1763
1973
|
advertisers: z.array(z.object({
|
|
1764
1974
|
name: NullableString,
|
|
1975
|
+
pageId: NullableString,
|
|
1976
|
+
pageUrl: NullableString,
|
|
1765
1977
|
adCount: z.number().int().nullable(),
|
|
1766
|
-
libraryId: NullableString
|
|
1978
|
+
libraryId: NullableString,
|
|
1979
|
+
sampleLibraryId: NullableString
|
|
1767
1980
|
}))
|
|
1768
1981
|
};
|
|
1769
1982
|
var FacebookPageIntelOutputSchema = {
|
|
@@ -1776,10 +1989,15 @@ var FacebookPageIntelOutputSchema = {
|
|
|
1776
1989
|
libraryId: NullableString,
|
|
1777
1990
|
status: NullableString,
|
|
1778
1991
|
creativeType: NullableString,
|
|
1992
|
+
primaryText: NullableString,
|
|
1779
1993
|
headline: NullableString,
|
|
1780
1994
|
cta: NullableString,
|
|
1781
1995
|
startDate: NullableString,
|
|
1996
|
+
landingUrl: NullableString,
|
|
1997
|
+
domain: NullableString,
|
|
1782
1998
|
videoUrl: NullableString,
|
|
1999
|
+
imageUrl: NullableString,
|
|
2000
|
+
videoPoster: NullableString,
|
|
1783
2001
|
variations: z.number().int().nullable()
|
|
1784
2002
|
}))
|
|
1785
2003
|
};
|
|
@@ -1801,6 +2019,58 @@ var FacebookVideoTranscribeOutputSchema = {
|
|
|
1801
2019
|
text: z.string()
|
|
1802
2020
|
}))
|
|
1803
2021
|
};
|
|
2022
|
+
var TranscriptChunkOutput = z.object({
|
|
2023
|
+
startSec: z.number(),
|
|
2024
|
+
endSec: z.number(),
|
|
2025
|
+
text: z.string()
|
|
2026
|
+
});
|
|
2027
|
+
var YoutubeTranscribeOutputSchema = {
|
|
2028
|
+
videoId: NullableString,
|
|
2029
|
+
url: NullableString,
|
|
2030
|
+
wordCount: z.number().int().min(0),
|
|
2031
|
+
chunkCount: z.number().int().min(0),
|
|
2032
|
+
durationMs: z.number().nullable(),
|
|
2033
|
+
transcriptText: z.string(),
|
|
2034
|
+
chunks: z.array(TranscriptChunkOutput),
|
|
2035
|
+
resolvedInputs: z.object({
|
|
2036
|
+
videoId: NullableString,
|
|
2037
|
+
url: NullableString
|
|
2038
|
+
})
|
|
2039
|
+
};
|
|
2040
|
+
var FacebookAdTranscribeOutputSchema = {
|
|
2041
|
+
videoUrl: z.string().url(),
|
|
2042
|
+
wordCount: z.number().int().min(0),
|
|
2043
|
+
chunkCount: z.number().int().min(0),
|
|
2044
|
+
durationMs: z.number().nullable(),
|
|
2045
|
+
transcriptText: z.string(),
|
|
2046
|
+
chunks: z.array(TranscriptChunkOutput),
|
|
2047
|
+
resolvedInputs: z.object({
|
|
2048
|
+
videoUrl: z.string().url()
|
|
2049
|
+
})
|
|
2050
|
+
};
|
|
2051
|
+
var CaptureSerpSnapshotOutputSchema = {
|
|
2052
|
+
schemaVersion: z.literal("serp-intelligence.capture.v1"),
|
|
2053
|
+
status: z.string(),
|
|
2054
|
+
query: NullableString,
|
|
2055
|
+
location: NullableString,
|
|
2056
|
+
capturedAt: NullableString,
|
|
2057
|
+
resultCount: z.number().int().min(0).nullable(),
|
|
2058
|
+
snapshotId: NullableString,
|
|
2059
|
+
resolvedInputs: z.record(z.unknown()),
|
|
2060
|
+
artifacts: z.array(z.record(z.unknown())),
|
|
2061
|
+
diagnostics: z.record(z.unknown()).nullable(),
|
|
2062
|
+
providerPayload: z.record(z.unknown())
|
|
2063
|
+
};
|
|
2064
|
+
var CaptureSerpPageSnapshotsOutputSchema = {
|
|
2065
|
+
schemaVersion: z.literal("serp-intelligence.page-snapshots.v1"),
|
|
2066
|
+
status: z.string(),
|
|
2067
|
+
count: z.number().int().min(0),
|
|
2068
|
+
failedCount: z.number().int().min(0),
|
|
2069
|
+
captures: z.array(z.record(z.unknown())),
|
|
2070
|
+
resolvedInputs: z.record(z.unknown()),
|
|
2071
|
+
diagnostics: z.record(z.unknown()).nullable(),
|
|
2072
|
+
providerPayload: z.record(z.unknown())
|
|
2073
|
+
};
|
|
1804
2074
|
var CreditsInfoInputSchema = {
|
|
1805
2075
|
item: z.string().optional().describe('Optional tool, action, or feature to look up, e.g. "maps reviews", "extract_url", "YouTube transcription", or "concurrency"'),
|
|
1806
2076
|
includeLedger: z.boolean().default(false).describe("Whether to include recent credit ledger entries")
|
|
@@ -1828,16 +2098,19 @@ var WorkflowSuggestInputSchema = {
|
|
|
1828
2098
|
maxSuggestions: z.number().int().min(1).max(8).default(3).describe("Number of matching workflow recipes to return.")
|
|
1829
2099
|
};
|
|
1830
2100
|
var WorkflowRunInputSchema = {
|
|
1831
|
-
workflowId: WorkflowIdSchema.describe("Workflow to run. Use workflow_list or workflow_suggest first when unsure."),
|
|
2101
|
+
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."),
|
|
1832
2102
|
input: z.record(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?}."),
|
|
1833
2103
|
webhookUrl: z.string().url().optional().describe("Optional HTTPS webhook to receive the completed hosted workflow run event.")
|
|
1834
2104
|
};
|
|
2105
|
+
var WorkflowStepInputSchema = {
|
|
2106
|
+
runId: 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).")
|
|
2107
|
+
};
|
|
1835
2108
|
var WorkflowStatusInputSchema = {
|
|
1836
|
-
runId: z.string().min(1).describe("Workflow run id returned by workflow_run.")
|
|
2109
|
+
runId: 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.")
|
|
1837
2110
|
};
|
|
1838
2111
|
var WorkflowArtifactReadInputSchema = {
|
|
1839
|
-
runId: z.string().min(1).describe("Workflow run id returned by workflow_run or workflow_status."),
|
|
1840
|
-
artifactId: z.string().min(1).describe("Artifact id from the run artifact list."),
|
|
2112
|
+
runId: 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."),
|
|
2113
|
+
artifactId: 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."),
|
|
1841
2114
|
maxBytes: 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.")
|
|
1842
2115
|
};
|
|
1843
2116
|
var WorkflowRecipeOutput = z.object({
|
|
@@ -1870,6 +2143,18 @@ var WorkflowRunOutputSchema = {
|
|
|
1870
2143
|
input: z.record(z.unknown()),
|
|
1871
2144
|
run: z.record(z.unknown()).optional(),
|
|
1872
2145
|
summary: z.record(z.unknown()).optional(),
|
|
2146
|
+
step: z.record(z.unknown()).optional(),
|
|
2147
|
+
nextStep: z.record(z.unknown()).nullable().optional(),
|
|
2148
|
+
done: z.boolean().optional(),
|
|
2149
|
+
artifacts: z.array(WorkflowArtifactOutput)
|
|
2150
|
+
};
|
|
2151
|
+
var WorkflowStepOutputSchema = {
|
|
2152
|
+
runId: z.string(),
|
|
2153
|
+
run: z.record(z.unknown()).optional(),
|
|
2154
|
+
summary: z.record(z.unknown()).nullable().optional(),
|
|
2155
|
+
step: z.record(z.unknown()).optional(),
|
|
2156
|
+
nextStep: z.record(z.unknown()).nullable().optional(),
|
|
2157
|
+
done: z.boolean(),
|
|
1873
2158
|
artifacts: z.array(WorkflowArtifactOutput)
|
|
1874
2159
|
};
|
|
1875
2160
|
var WorkflowStatusOutputSchema = {
|
|
@@ -1890,8 +2175,8 @@ var SearchSerpInputSchema = {
|
|
|
1890
2175
|
gl: z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
|
|
1891
2176
|
hl: z.string().default("en").describe("Google interface/content language inferred from user request."),
|
|
1892
2177
|
device: z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
|
|
1893
|
-
proxyMode: z.enum(["location", "configured", "none"]).default(
|
|
1894
|
-
proxyZip: z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use when the user gives a specific ZIP or
|
|
2178
|
+
proxyMode: 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."),
|
|
2179
|
+
proxyZip: 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."),
|
|
1895
2180
|
debug: z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior."),
|
|
1896
2181
|
pages: z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132)")
|
|
1897
2182
|
};
|
|
@@ -1901,8 +2186,8 @@ var CaptureSerpSnapshotInputSchema = {
|
|
|
1901
2186
|
gl: z.string().length(2).default("us").describe("Google country code inferred from the requested market, e.g. us, gb, ca, au."),
|
|
1902
2187
|
hl: z.string().default("en").describe("Google interface/content language inferred from the user request."),
|
|
1903
2188
|
device: z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only when the user asks for mobile rankings or mobile SERP evidence."),
|
|
1904
|
-
proxyMode: z.enum(["location", "configured", "none"]).default(
|
|
1905
|
-
proxyZip: z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting when a precise city-center or ZIP proxy is needed.
|
|
2189
|
+
proxyMode: 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."),
|
|
2190
|
+
proxyZip: 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."),
|
|
1906
2191
|
pages: z.number().int().min(1).max(2).default(1).describe("Number of Google result pages to capture. Use 1 normally and 2 only when the user needs deeper ranking evidence."),
|
|
1907
2192
|
debug: z.boolean().default(false).describe("Include sanitized browser, proxy, and location diagnostics. Use true when debugging localization, CAPTCHA, proxy selection, or capture reliability."),
|
|
1908
2193
|
includePageSnapshots: z.boolean().default(false).describe("Also capture ranking-page snapshots for selected SERP URLs through the same product capture path."),
|
|
@@ -2313,70 +2598,72 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
2313
2598
|
if (savesReports) registerSavedReportResources(server);
|
|
2314
2599
|
server.registerTool("harvest_paa", {
|
|
2315
2600
|
title: "Google PAA + SERP Harvest",
|
|
2316
|
-
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").
|
|
2601
|
+
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.'),
|
|
2317
2602
|
inputSchema: HarvestPaaInputSchema,
|
|
2318
2603
|
outputSchema: HarvestPaaOutputSchema,
|
|
2319
2604
|
annotations: liveWebToolAnnotations("Google PAA + SERP Harvest")
|
|
2320
2605
|
}, async (input) => formatHarvestPaa(await executor.harvestPaa(input), input));
|
|
2321
2606
|
server.registerTool("search_serp", {
|
|
2322
2607
|
title: "Google SERP Lookup",
|
|
2323
|
-
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.
|
|
2608
|
+
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."),
|
|
2324
2609
|
inputSchema: SearchSerpInputSchema,
|
|
2325
2610
|
outputSchema: SearchSerpOutputSchema,
|
|
2326
2611
|
annotations: liveWebToolAnnotations("Google SERP Lookup")
|
|
2327
2612
|
}, async (input) => formatSearchSerp(await executor.searchSerp(input), input));
|
|
2328
2613
|
server.registerTool("extract_url", {
|
|
2329
2614
|
title: "Single URL Extract",
|
|
2330
|
-
description: withReportNote("Extract structured data from one public URL
|
|
2615
|
+
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."),
|
|
2331
2616
|
inputSchema: ExtractUrlInputSchema,
|
|
2332
2617
|
outputSchema: ExtractUrlOutputSchema,
|
|
2333
2618
|
annotations: liveWebToolAnnotations("Single URL Extract")
|
|
2334
2619
|
}, async (input) => formatExtractUrl(await executor.extractUrl(input), input));
|
|
2335
2620
|
server.registerTool("map_site_urls", {
|
|
2336
2621
|
title: "Site URL Map",
|
|
2337
|
-
description: withReportNote("Map/crawl a public website
|
|
2622
|
+
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."),
|
|
2338
2623
|
inputSchema: MapSiteUrlsInputSchema,
|
|
2339
2624
|
outputSchema: MapSiteUrlsOutputSchema,
|
|
2340
2625
|
annotations: liveWebToolAnnotations("Site URL Map")
|
|
2341
2626
|
}, async (input) => formatMapSiteUrls(await executor.mapSiteUrls(input), input));
|
|
2342
2627
|
server.registerTool("extract_site", {
|
|
2343
2628
|
title: "Multi-Page Site Extract",
|
|
2344
|
-
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
|
|
2629
|
+
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."),
|
|
2345
2630
|
inputSchema: ExtractSiteInputSchema,
|
|
2346
2631
|
outputSchema: ExtractSiteOutputSchema,
|
|
2347
2632
|
annotations: liveWebToolAnnotations("Multi-Page Site Extract")
|
|
2348
2633
|
}, async (input) => formatExtractSite(await executor.extractSite(input), input));
|
|
2349
2634
|
server.registerTool("youtube_harvest", {
|
|
2350
2635
|
title: "YouTube Video Harvest",
|
|
2351
|
-
description: withReportNote('Harvest YouTube video metadata by
|
|
2636
|
+
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.'),
|
|
2352
2637
|
inputSchema: YoutubeHarvestInputSchema,
|
|
2353
2638
|
outputSchema: YoutubeHarvestOutputSchema,
|
|
2354
2639
|
annotations: liveWebToolAnnotations("YouTube Video Harvest")
|
|
2355
2640
|
}, async (input) => formatYoutubeHarvest(await executor.youtubeHarvest(input), input));
|
|
2356
2641
|
server.registerTool("youtube_transcribe", {
|
|
2357
2642
|
title: "YouTube Transcription",
|
|
2358
|
-
description: withReportNote("Fetch and transcribe captions from a YouTube video. Returns full transcript, timestamped chunks,
|
|
2643
|
+
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."),
|
|
2359
2644
|
inputSchema: YoutubeTranscribeInputSchema,
|
|
2645
|
+
outputSchema: YoutubeTranscribeOutputSchema,
|
|
2360
2646
|
annotations: liveWebToolAnnotations("YouTube Transcription")
|
|
2361
2647
|
}, async (input) => formatYoutubeTranscribe(await executor.youtubeTranscribe(input), input));
|
|
2362
2648
|
server.registerTool("facebook_page_intel", {
|
|
2363
2649
|
title: "Facebook Advertiser Ad Intel",
|
|
2364
|
-
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
|
|
2650
|
+
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."),
|
|
2365
2651
|
inputSchema: FacebookPageIntelInputSchema,
|
|
2366
2652
|
outputSchema: FacebookPageIntelOutputSchema,
|
|
2367
2653
|
annotations: liveWebToolAnnotations("Facebook Advertiser Ad Intel")
|
|
2368
2654
|
}, async (input) => formatFacebookPageIntel(await executor.facebookPageIntel(input), input));
|
|
2369
2655
|
server.registerTool("facebook_ad_search", {
|
|
2370
2656
|
title: "Facebook Ad Library Search",
|
|
2371
|
-
description: withReportNote("Search Facebook Ad Library by brand,
|
|
2657
|
+
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."),
|
|
2372
2658
|
inputSchema: FacebookAdSearchInputSchema,
|
|
2373
2659
|
outputSchema: FacebookAdSearchOutputSchema,
|
|
2374
2660
|
annotations: liveWebToolAnnotations("Facebook Ad Library Search")
|
|
2375
2661
|
}, async (input) => formatFacebookAdSearch(await executor.facebookAdSearch(input), input));
|
|
2376
2662
|
server.registerTool("facebook_ad_transcribe", {
|
|
2377
2663
|
title: "Facebook Ad Transcription",
|
|
2378
|
-
description: "Transcribe audio from a Facebook ad video CDN URL. Returns full transcript
|
|
2664
|
+
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.",
|
|
2379
2665
|
inputSchema: FacebookAdTranscribeInputSchema,
|
|
2666
|
+
outputSchema: FacebookAdTranscribeOutputSchema,
|
|
2380
2667
|
annotations: liveWebToolAnnotations("Facebook Ad Transcription")
|
|
2381
2668
|
}, async (input) => formatFacebookAdTranscribe(await executor.facebookAdTranscribe(input), input));
|
|
2382
2669
|
server.registerTool("facebook_video_transcribe", {
|
|
@@ -2395,14 +2682,14 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
2395
2682
|
}, async (input) => formatMapsPlaceIntel(await executor.mapsPlaceIntel(input), input));
|
|
2396
2683
|
server.registerTool("maps_search", {
|
|
2397
2684
|
title: "Google Maps Business Search",
|
|
2398
|
-
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."
|
|
2685
|
+
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.'),
|
|
2399
2686
|
inputSchema: MapsSearchInputSchema,
|
|
2400
2687
|
outputSchema: MapsSearchOutputSchema,
|
|
2401
2688
|
annotations: liveWebToolAnnotations("Google Maps Business Search")
|
|
2402
2689
|
}, async (input) => formatMapsSearch(await executor.mapsSearch(input), input));
|
|
2403
2690
|
server.registerTool("directory_workflow", {
|
|
2404
2691
|
title: "Directory Workflow: Markets + Maps",
|
|
2405
|
-
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.
|
|
2692
|
+
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.'),
|
|
2406
2693
|
inputSchema: DirectoryWorkflowInputSchema,
|
|
2407
2694
|
outputSchema: DirectoryWorkflowOutputSchema,
|
|
2408
2695
|
annotations: liveWebToolAnnotations("Directory Workflow: Markets + Maps")
|
|
@@ -2423,21 +2710,28 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
2423
2710
|
}, async (input) => formatWorkflowSuggest(input));
|
|
2424
2711
|
server.registerTool("workflow_run", {
|
|
2425
2712
|
title: "Run Workflow",
|
|
2426
|
-
description: withReportNote("
|
|
2713
|
+
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."),
|
|
2427
2714
|
inputSchema: WorkflowRunInputSchema,
|
|
2428
2715
|
outputSchema: WorkflowRunOutputSchema,
|
|
2429
2716
|
annotations: liveWebToolAnnotations("Run Workflow")
|
|
2430
2717
|
}, async (input) => formatWorkflowRun(await executor.workflowRun(input), input));
|
|
2718
|
+
server.registerTool("workflow_step", {
|
|
2719
|
+
title: "Advance Workflow Step",
|
|
2720
|
+
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."),
|
|
2721
|
+
inputSchema: WorkflowStepInputSchema,
|
|
2722
|
+
outputSchema: WorkflowStepOutputSchema,
|
|
2723
|
+
annotations: liveWebToolAnnotations("Advance Workflow Step")
|
|
2724
|
+
}, async (input) => formatWorkflowStep(await executor.workflowStep(input), input));
|
|
2431
2725
|
server.registerTool("workflow_status", {
|
|
2432
2726
|
title: "Workflow Status",
|
|
2433
|
-
description: "Fetch a hosted workflow run by id and list its current status and artifacts. Use
|
|
2727
|
+
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.",
|
|
2434
2728
|
inputSchema: WorkflowStatusInputSchema,
|
|
2435
2729
|
outputSchema: WorkflowStatusOutputSchema,
|
|
2436
2730
|
annotations: liveWebToolAnnotations("Workflow Status")
|
|
2437
2731
|
}, async (input) => formatWorkflowStatus(await executor.workflowStatus(input), input));
|
|
2438
2732
|
server.registerTool("workflow_artifact_read", {
|
|
2439
2733
|
title: "Read Workflow Artifact",
|
|
2440
|
-
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.",
|
|
2734
|
+
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.",
|
|
2441
2735
|
inputSchema: WorkflowArtifactReadInputSchema,
|
|
2442
2736
|
outputSchema: WorkflowArtifactReadOutputSchema,
|
|
2443
2737
|
annotations: liveWebToolAnnotations("Read Workflow Artifact")
|
|
@@ -2465,6 +2759,27 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
2465
2759
|
}
|
|
2466
2760
|
|
|
2467
2761
|
// src/mcp/http-mcp-tool-executor.ts
|
|
2762
|
+
function youtubeVideoIdFromUrl(url) {
|
|
2763
|
+
if (!url) return null;
|
|
2764
|
+
try {
|
|
2765
|
+
const parsed = new URL(url);
|
|
2766
|
+
const host = parsed.hostname.replace(/^www\./, "").replace(/^m\./, "");
|
|
2767
|
+
if (host === "youtu.be") {
|
|
2768
|
+
const id = parsed.pathname.split("/").filter(Boolean)[0];
|
|
2769
|
+
return id || null;
|
|
2770
|
+
}
|
|
2771
|
+
if (host === "youtube.com" || host === "music.youtube.com") {
|
|
2772
|
+
const watchId = parsed.searchParams.get("v");
|
|
2773
|
+
if (watchId) return watchId;
|
|
2774
|
+
const parts = parsed.pathname.split("/").filter(Boolean);
|
|
2775
|
+
const markerIndex = parts.findIndex((part) => ["shorts", "embed", "live"].includes(part));
|
|
2776
|
+
if (markerIndex >= 0 && parts[markerIndex + 1]) return parts[markerIndex + 1];
|
|
2777
|
+
}
|
|
2778
|
+
} catch {
|
|
2779
|
+
return null;
|
|
2780
|
+
}
|
|
2781
|
+
return null;
|
|
2782
|
+
}
|
|
2468
2783
|
var HttpMcpToolExecutor = class {
|
|
2469
2784
|
baseUrl;
|
|
2470
2785
|
apiKey;
|
|
@@ -2590,7 +2905,21 @@ var HttpMcpToolExecutor = class {
|
|
|
2590
2905
|
return this.call("/youtube/harvest", input);
|
|
2591
2906
|
}
|
|
2592
2907
|
youtubeTranscribe(input) {
|
|
2593
|
-
|
|
2908
|
+
const videoId = input.videoId?.trim() || youtubeVideoIdFromUrl(input.url);
|
|
2909
|
+
if (!videoId) {
|
|
2910
|
+
return Promise.resolve({
|
|
2911
|
+
content: [{
|
|
2912
|
+
type: "text",
|
|
2913
|
+
text: JSON.stringify({
|
|
2914
|
+
error_code: "youtube_video_id_required",
|
|
2915
|
+
error: "Pass videoId from youtube_harvest or a YouTube url that contains a video id.",
|
|
2916
|
+
retryable: false
|
|
2917
|
+
})
|
|
2918
|
+
}],
|
|
2919
|
+
isError: true
|
|
2920
|
+
});
|
|
2921
|
+
}
|
|
2922
|
+
return this.call("/youtube/transcribe", { videoId });
|
|
2594
2923
|
}
|
|
2595
2924
|
facebookPageIntel(input) {
|
|
2596
2925
|
return this.call("/facebook/page-intel", input);
|
|
@@ -2627,6 +2956,10 @@ var HttpMcpToolExecutor = class {
|
|
|
2627
2956
|
webhookUrl: input.webhookUrl
|
|
2628
2957
|
}, Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 9e5);
|
|
2629
2958
|
}
|
|
2959
|
+
workflowStep(input) {
|
|
2960
|
+
const timeoutMs = this.httpTimeoutOverrideMs ?? Number(process.env.MCP_SCRAPER_WORKFLOW_TIMEOUT_MS ?? 9e5);
|
|
2961
|
+
return this.call(`/workflows/runs/${encodeURIComponent(input.runId)}/step`, {}, Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 9e5);
|
|
2962
|
+
}
|
|
2630
2963
|
workflowStatus(input) {
|
|
2631
2964
|
return this.getJson(`/workflows/runs/${encodeURIComponent(input.runId)}`);
|
|
2632
2965
|
}
|
|
@@ -2651,6 +2984,10 @@ export {
|
|
|
2651
2984
|
harvestTimeoutBudget,
|
|
2652
2985
|
configureReportSaving,
|
|
2653
2986
|
outputBaseDir,
|
|
2987
|
+
formatCaptureSerpSnapshot,
|
|
2988
|
+
formatCaptureSerpPageSnapshots,
|
|
2989
|
+
CaptureSerpSnapshotOutputSchema,
|
|
2990
|
+
CaptureSerpPageSnapshotsOutputSchema,
|
|
2654
2991
|
CaptureSerpSnapshotInputSchema,
|
|
2655
2992
|
CaptureSerpPageSnapshotsInputSchema,
|
|
2656
2993
|
liveWebToolAnnotations,
|
|
@@ -2658,4 +2995,4 @@ export {
|
|
|
2658
2995
|
registerPaaExtractorMcpTools,
|
|
2659
2996
|
HttpMcpToolExecutor
|
|
2660
2997
|
};
|
|
2661
|
-
//# sourceMappingURL=chunk-
|
|
2998
|
+
//# sourceMappingURL=chunk-O5J7E4VX.js.map
|