mcp-scraper 0.18.0 → 0.19.0
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 +2 -2
- package/dist/bin/api-server.cjs +448 -74
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-install.cjs +2 -2
- 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 +2137 -1778
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +5 -4
- package/dist/bin/mcp-stdio-server.js.map +1 -1
- package/dist/bin/paa-harvest.js +2 -2
- package/dist/{chunk-RMPPYKUV.js → chunk-2HDMYW4B.js} +7 -218
- package/dist/chunk-2HDMYW4B.js.map +1 -0
- package/dist/chunk-D7ZT27HY.js +244 -0
- package/dist/chunk-D7ZT27HY.js.map +1 -0
- package/dist/{chunk-FMEDPBIV.js → chunk-O2S5TOCG.js} +383 -22
- package/dist/chunk-O2S5TOCG.js.map +1 -0
- package/dist/chunk-OQHYDW4Q.js +7 -0
- package/dist/chunk-OQHYDW4Q.js.map +1 -0
- package/dist/{chunk-BX5RCOG5.js → chunk-Q44DN6T2.js} +2 -2
- package/dist/{chunk-BX5RCOG5.js.map → chunk-Q44DN6T2.js.map} +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/{server-MQDCAR6I.js → server-VWXDE64Y.js} +69 -59
- package/dist/server-VWXDE64Y.js.map +1 -0
- package/dist/{worker-JQTS437L.js → worker-AM2DHUWG.js} +6 -6
- package/docs/mcp-tool-manifest.generated.json +401 -10
- package/docs/specs/meta-ad-creative-media-resolution-spec.md +31 -0
- package/package.json +1 -1
- package/dist/chunk-FMEDPBIV.js.map +0 -1
- package/dist/chunk-HPV4VOQX.js +0 -27
- package/dist/chunk-HPV4VOQX.js.map +0 -1
- package/dist/chunk-RLWCHLV3.js +0 -7
- package/dist/chunk-RLWCHLV3.js.map +0 -1
- package/dist/chunk-RMPPYKUV.js.map +0 -1
- package/dist/server-MQDCAR6I.js.map +0 -1
- /package/dist/{worker-JQTS437L.js.map → worker-AM2DHUWG.js.map} +0 -0
|
@@ -9,15 +9,17 @@ import {
|
|
|
9
9
|
} from "./chunk-R7EETU7Z.js";
|
|
10
10
|
import {
|
|
11
11
|
browserServiceProfileName,
|
|
12
|
-
browserServiceProfileSaveChanges
|
|
13
|
-
|
|
12
|
+
browserServiceProfileSaveChanges,
|
|
13
|
+
recordVendorUsage,
|
|
14
|
+
vendorCostUsd
|
|
15
|
+
} from "./chunk-D7ZT27HY.js";
|
|
14
16
|
import {
|
|
15
17
|
DEFAULT_MAPS_PROXY_MODE,
|
|
16
18
|
DEFAULT_PROXY_MODE
|
|
17
19
|
} from "./chunk-XGIPATLV.js";
|
|
18
20
|
import {
|
|
19
21
|
PACKAGE_VERSION
|
|
20
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-OQHYDW4Q.js";
|
|
21
23
|
import {
|
|
22
24
|
MC_PER_CREDIT
|
|
23
25
|
} from "./chunk-HL33CGJF.js";
|
|
@@ -317,10 +319,14 @@ seam is noted so you can chain them.
|
|
|
317
319
|
|
|
318
320
|
## Facebook
|
|
319
321
|
- Find advertisers -> **facebook_ad_search** (returns \`advertisers[]\` with pageId, libraryId).
|
|
320
|
-
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
-
|
|
322
|
+
- Public Ad Library scan -> **facebook_page_intel**. Prefer pageId/libraryId. Query mode is broad discovery,
|
|
323
|
+
can mix advertisers, and must be checked through matchConfidence/warnings. Paused non-political ads may
|
|
324
|
+
be absent even when the connected ad account still has them.
|
|
325
|
+
- Connected ad-account creative -> **meta_ad_creative_media** (takes connectionId + adId). This is the
|
|
326
|
+
preferred paused/dark-ad path and returns actual image content plus exact video follow-up arguments.
|
|
327
|
+
- Direct Graph/Ad Library CDN video -> **facebook_ad_transcribe**. Use transient source URLs immediately.
|
|
328
|
+
- Public reel/video/post -> **facebook_video_transcribe**. Use the public candidate returned by
|
|
329
|
+
\`meta_ad_creative_media\` when Graph did not provide a direct source; dark posts may not resolve publicly.
|
|
324
330
|
|
|
325
331
|
## Google Ads (Ads Transparency Center)
|
|
326
332
|
- Find advertisers by domain or brand -> **google_ads_search** (returns \`advertisers[]\` with advertiserId).
|
|
@@ -362,8 +368,8 @@ Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when
|
|
|
362
368
|
recording) locates a DOM target and returns a ready-to-use timed annotation -> **browser_replay_stop**
|
|
363
369
|
ends it -> feed collected annotations to **browser_replay_annotate**, or just
|
|
364
370
|
**browser_replay_download** the plain MP4. **browser_list_replays** recovers replay ids.
|
|
365
|
-
- Video breakdown is async: **video_frame_analysis** takes a
|
|
366
|
-
|
|
371
|
+
- Video breakdown is async: **video_frame_analysis** takes a supported public video page or direct media URL
|
|
372
|
+
(for connected Meta ads, use the exact sourceUrl/public candidate returned by \`meta_ad_creative_media\`) and
|
|
367
373
|
returns a \`runId\` immediately -> poll **video_frame_analysis_status** with that \`runId\` until \`status\`
|
|
368
374
|
is \`done\`.
|
|
369
375
|
|
|
@@ -372,6 +378,8 @@ Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when
|
|
|
372
378
|
sites. It returns a saved folder/artifact plus a summary, not the full content inline.
|
|
373
379
|
- Browser sessions and media transcription cost credits and take longer \u2014 prefer the cheapest tool that
|
|
374
380
|
answers the question (plain search/extract before browser agents).
|
|
381
|
+
- Use the hosted browser as a controlled resolver for validated public Facebook post/reel redirects only
|
|
382
|
+
when connected Graph media did not provide a playable source. It is not a bypass for URL/SSRF restrictions.
|
|
375
383
|
- Large results are saved to disk or an artifact and returned as a summary plus a path or artifactId;
|
|
376
384
|
read it back for full detail rather than expecting the whole payload inline.
|
|
377
385
|
|
|
@@ -704,6 +712,75 @@ function summaryEnvelope(executiveSummary, offloaded) {
|
|
|
704
712
|
].join("\n");
|
|
705
713
|
}
|
|
706
714
|
|
|
715
|
+
// src/services/media-transcription.ts
|
|
716
|
+
import { fal } from "@fal-ai/client";
|
|
717
|
+
function transcriptWordCount(text) {
|
|
718
|
+
return text.trim() ? text.trim().split(/\s+/).length : 0;
|
|
719
|
+
}
|
|
720
|
+
function assessTranscriptSignal(text, chunks, durationHintSec) {
|
|
721
|
+
const words = transcriptWordCount(text);
|
|
722
|
+
const chunkDurationSec = chunks.reduce((max, chunk) => {
|
|
723
|
+
const end = Number(chunk.timestamp?.[1]);
|
|
724
|
+
return Number.isFinite(end) && end > max ? end : max;
|
|
725
|
+
}, 0);
|
|
726
|
+
const mediaDurationSec = typeof durationHintSec === "number" && Number.isFinite(durationHintSec) && durationHintSec > 0 ? durationHintSec : chunkDurationSec > 0 ? chunkDurationSec : null;
|
|
727
|
+
const wordsPerMinute = mediaDurationSec && mediaDurationSec > 0 ? Math.round(words / mediaDurationSec * 600) / 10 : null;
|
|
728
|
+
const status = words === 0 ? "empty" : words < 5 ? "low_speech_signal" : "speech_detected";
|
|
729
|
+
const retryRecommended = words <= 2;
|
|
730
|
+
const warnings = [];
|
|
731
|
+
if (status === "empty") {
|
|
732
|
+
warnings.push("No speech was transcribed. This does not distinguish silent media from an inaccessible or expired playback URL.");
|
|
733
|
+
} else if (status === "low_speech_signal") {
|
|
734
|
+
warnings.push("Very little speech was transcribed. Review the media or retry from a durable public post/reel URL before treating this as a complete transcript.");
|
|
735
|
+
}
|
|
736
|
+
if (retryRecommended) {
|
|
737
|
+
warnings.push("For a connected Meta ad, resolve the creative effectiveObjectStoryId and retry with facebook_video_transcribe when a public post URL is available.");
|
|
738
|
+
}
|
|
739
|
+
return {
|
|
740
|
+
status,
|
|
741
|
+
speechDetected: status === "speech_detected",
|
|
742
|
+
confidence: status === "speech_detected" ? "medium" : "low",
|
|
743
|
+
basis: "transcript_word_count_and_timing",
|
|
744
|
+
mediaDurationSec,
|
|
745
|
+
wordsPerMinute,
|
|
746
|
+
retryRecommended,
|
|
747
|
+
warnings
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
function transcriptMarkdown(title, text, chunks, durationMs) {
|
|
751
|
+
const fmtTs = (s) => `${Math.floor(s / 60)}:${String(Math.floor(s % 60)).padStart(2, "0")}`;
|
|
752
|
+
const lines = [title, "", `*Transcribed in ${(durationMs / 1e3).toFixed(1)}s*`, "", "## Full Text", "", text, ""];
|
|
753
|
+
if (chunks.length) {
|
|
754
|
+
lines.push("## Timestamped Segments", "");
|
|
755
|
+
for (const ch of chunks) {
|
|
756
|
+
lines.push(`**[${fmtTs(ch.timestamp[0])} -> ${fmtTs(ch.timestamp[1])}]** ${ch.text.trim()}`, "");
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return lines.join("\n");
|
|
760
|
+
}
|
|
761
|
+
async function transcribeMediaUrl(mediaUrl, markdownTitle = "# Media Transcript") {
|
|
762
|
+
const startMs = Date.now();
|
|
763
|
+
const result = await fal.subscribe("fal-ai/wizper", {
|
|
764
|
+
input: { audio_url: mediaUrl, task: "transcribe", language: "en" },
|
|
765
|
+
logs: false,
|
|
766
|
+
pollInterval: 3e3
|
|
767
|
+
});
|
|
768
|
+
const data = result.data;
|
|
769
|
+
const text = data.text ?? "";
|
|
770
|
+
const chunks = data.chunks ?? [];
|
|
771
|
+
const durationMs = Date.now() - startMs;
|
|
772
|
+
void recordVendorUsage({ vendor: "fal_wizper", model: "fal-ai/wizper", units: durationMs / 1e3, unitType: "compute_sec" });
|
|
773
|
+
const costUsd = vendorCostUsd("fal_wizper", durationMs / 1e3);
|
|
774
|
+
return {
|
|
775
|
+
text,
|
|
776
|
+
chunks,
|
|
777
|
+
durationMs,
|
|
778
|
+
costUsd,
|
|
779
|
+
markdown: transcriptMarkdown(markdownTitle, text, chunks, durationMs),
|
|
780
|
+
transcriptSignal: assessTranscriptSignal(text, chunks)
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
707
784
|
// src/mcp/mcp-response-formatter.ts
|
|
708
785
|
var INLINE_BUDGET_BYTES = Number(process.env.MCP_SCRAPER_INLINE_BUDGET ?? 5e4);
|
|
709
786
|
var STRUCTURED_ARRAY_CAP = 25;
|
|
@@ -1671,6 +1748,7 @@ function formatFacebookPageIntel(raw, input) {
|
|
|
1671
1748
|
const advertiser = d.advertiserName ?? input.query ?? input.pageId ?? input.libraryId ?? "Advertiser";
|
|
1672
1749
|
const ads = d.ads ?? [];
|
|
1673
1750
|
const s = d.summary ?? { totalAds: 0, activeCount: 0, videoCount: 0, imageCount: 0 };
|
|
1751
|
+
const warnings = d.warnings ?? [];
|
|
1674
1752
|
const adBlocks = ads.map((ad, i) => [
|
|
1675
1753
|
`### Ad ${i + 1}${ad.libraryId ? ` \xB7 \`${ad.libraryId}\`` : ""} \u2014 ${ad.status ?? "\u2014"} \xB7 ${ad.creativeType ?? "\u2014"} \xB7 ${ad.startDate ?? ad.started ?? "\u2014"}`,
|
|
1676
1754
|
ad.headline ? `**Headline:** ${ad.headline}` : "",
|
|
@@ -1683,19 +1761,28 @@ function formatFacebookPageIntel(raw, input) {
|
|
|
1683
1761
|
const full = [
|
|
1684
1762
|
`# Facebook Ad Intel: ${advertiser}`,
|
|
1685
1763
|
`**${s.totalAds} ads** \xB7 ${s.activeCount} active \xB7 ${s.videoCount} video \xB7 ${s.imageCount} image`,
|
|
1764
|
+
d.matchConfidence ? `**Match confidence:** ${d.matchConfidence}${d.matchReason ? ` \u2014 ${d.matchReason}` : ""}` : "",
|
|
1765
|
+
warnings.length ? `
|
|
1766
|
+
> \u26A0\uFE0F ${warnings.join("\n> \u26A0\uFE0F ")}` : "",
|
|
1686
1767
|
`
|
|
1687
1768
|
${adBlocks}`,
|
|
1688
1769
|
`
|
|
1689
1770
|
---
|
|
1690
1771
|
\u{1F4A1} **Tips**
|
|
1691
|
-
-
|
|
1692
|
-
-
|
|
1693
|
-
-
|
|
1772
|
+
- Connected, paused, or dark ad: use \`meta_ad_creative_media\` with its adId
|
|
1773
|
+
- Ad Library direct video URL: use \`facebook_ad_transcribe\` immediately
|
|
1774
|
+
- Public Facebook post/reel URL: use \`facebook_video_transcribe\`
|
|
1775
|
+
- Find the exact advertiser pageId first: use \`facebook_ad_search\``
|
|
1694
1776
|
].filter(Boolean).join("\n");
|
|
1695
1777
|
return {
|
|
1696
1778
|
...oneBlock(full),
|
|
1697
1779
|
structuredContent: {
|
|
1698
1780
|
advertiserName: d.advertiserName ?? null,
|
|
1781
|
+
inputMode: d.inputMode ?? (input.pageId ? "pageId" : input.libraryId ? "libraryId" : "query"),
|
|
1782
|
+
matchConfidence: d.matchConfidence ?? (input.query ? "low" : "high"),
|
|
1783
|
+
matchReason: d.matchReason ?? (input.query ? "Keyword search result was not confidence-scored by the server." : "Exact identifier filter."),
|
|
1784
|
+
warnings,
|
|
1785
|
+
matchedAdvertisers: d.matchedAdvertisers ?? [],
|
|
1699
1786
|
totalAds: s.totalAds ?? 0,
|
|
1700
1787
|
activeCount: s.activeCount ?? 0,
|
|
1701
1788
|
videoCount: s.videoCount ?? 0,
|
|
@@ -2624,6 +2711,7 @@ function formatFacebookAdTranscribe(raw, input) {
|
|
|
2624
2711
|
const chunks = d.chunks ?? [];
|
|
2625
2712
|
const durSec = d.durationMs ? (d.durationMs / 1e3).toFixed(0) : "\u2014";
|
|
2626
2713
|
const words = wordCount(text);
|
|
2714
|
+
const signal = d.transcriptSignal ?? assessTranscriptSignal(text, chunks);
|
|
2627
2715
|
const chunkRows = chunks.slice(0, 50).map((c) => {
|
|
2628
2716
|
const sec = Number.isFinite(c.timestamp[0]) ? Math.floor(c.timestamp[0]) : 0;
|
|
2629
2717
|
const mm = String(Math.floor(sec / 60)).padStart(2, "0");
|
|
@@ -2632,7 +2720,9 @@ function formatFacebookAdTranscribe(raw, input) {
|
|
|
2632
2720
|
}).join("\n");
|
|
2633
2721
|
const full = [
|
|
2634
2722
|
`# Facebook Ad Transcript`,
|
|
2635
|
-
`**
|
|
2723
|
+
`**Transcription elapsed:** ${durSec}s \xB7 **${words} words** \xB7 **Signal:** ${signal.status}`,
|
|
2724
|
+
signal.warnings.length ? `
|
|
2725
|
+
> \u26A0\uFE0F ${signal.warnings.join("\n> \u26A0\uFE0F ")}` : "",
|
|
2636
2726
|
`
|
|
2637
2727
|
## Full Transcript
|
|
2638
2728
|
${text}`,
|
|
@@ -2654,6 +2744,7 @@ ${chunkRows}` : "",
|
|
|
2654
2744
|
durationMs: typeof d.durationMs === "number" ? d.durationMs : null,
|
|
2655
2745
|
transcriptText: text,
|
|
2656
2746
|
chunks: structuredTranscriptChunks(chunks),
|
|
2747
|
+
transcriptSignal: signal,
|
|
2657
2748
|
resolvedInputs: {
|
|
2658
2749
|
videoUrl: input.videoUrl
|
|
2659
2750
|
}
|
|
@@ -2670,6 +2761,7 @@ function formatFacebookVideoTranscribe(raw, input) {
|
|
|
2670
2761
|
const durSec = d.durationMs ? (d.durationMs / 1e3).toFixed(0) : "\u2014";
|
|
2671
2762
|
const videoDuration = typeof d.videoDurationSec === "number" ? `${Math.round(d.videoDurationSec)}s` : "\u2014";
|
|
2672
2763
|
const label = d.videoId ? `Facebook Organic Video \`${d.videoId}\`` : "Facebook Organic Video";
|
|
2764
|
+
const signal = d.transcriptSignal ?? assessTranscriptSignal(text, chunks, d.videoDurationSec);
|
|
2673
2765
|
const chunkRows = chunks.slice(0, 50).map((c) => {
|
|
2674
2766
|
const sec = Number.isFinite(c.timestamp[0]) ? Math.floor(c.timestamp[0]) : 0;
|
|
2675
2767
|
const mm = String(Math.floor(sec / 60)).padStart(2, "0");
|
|
@@ -2680,6 +2772,9 @@ function formatFacebookVideoTranscribe(raw, input) {
|
|
|
2680
2772
|
`# ${label} Transcript`,
|
|
2681
2773
|
d.ownerName ? `**Owner:** ${d.ownerName}` : "",
|
|
2682
2774
|
`**Video duration:** ${videoDuration} \xB7 **Transcribed in:** ${durSec}s \xB7 **${wordCount2} words**`,
|
|
2775
|
+
`**Transcript signal:** ${signal.status} (${signal.confidence} heuristic confidence)`,
|
|
2776
|
+
signal.warnings.length ? `
|
|
2777
|
+
> \u26A0\uFE0F ${signal.warnings.join("\n> \u26A0\uFE0F ")}` : "",
|
|
2683
2778
|
d.pageUrl ? `**Page URL:** ${d.pageUrl}` : `**Page URL:** ${input.url}`,
|
|
2684
2779
|
d.videoUrl ? `**Extracted MP4:** \`${d.videoUrl}\`` : "",
|
|
2685
2780
|
`
|
|
@@ -2712,7 +2807,8 @@ ${chunkRows}` : "",
|
|
|
2712
2807
|
startSec: Number.isFinite(c.timestamp[0]) ? c.timestamp[0] : 0,
|
|
2713
2808
|
endSec: Number.isFinite(c.timestamp[1]) ? c.timestamp[1] : 0,
|
|
2714
2809
|
text: c.text
|
|
2715
|
-
}))
|
|
2810
|
+
})),
|
|
2811
|
+
transcriptSignal: signal
|
|
2716
2812
|
}
|
|
2717
2813
|
};
|
|
2718
2814
|
}
|
|
@@ -3022,6 +3118,208 @@ function recordOutputSchema(name, schema) {
|
|
|
3022
3118
|
return ADVERTISE_OUTPUT_SCHEMAS ? schema : void 0;
|
|
3023
3119
|
}
|
|
3024
3120
|
|
|
3121
|
+
// src/mcp/meta-ad-creative-media.ts
|
|
3122
|
+
var META_IMAGE_HOSTS = ["facebook.com", "fbcdn.net", "fbsbx.com", "cdninstagram.com"];
|
|
3123
|
+
var MAX_IMAGE_BYTES = 15e5;
|
|
3124
|
+
var MAX_TOTAL_IMAGE_BYTES = 25e5;
|
|
3125
|
+
var MAX_REDIRECTS = 3;
|
|
3126
|
+
function asRecord(value) {
|
|
3127
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
3128
|
+
}
|
|
3129
|
+
function parseToolPayload(result) {
|
|
3130
|
+
const structured = asRecord(result.structuredContent);
|
|
3131
|
+
if (structured) return structured;
|
|
3132
|
+
for (const block of result.content ?? []) {
|
|
3133
|
+
if (block.type !== "text") continue;
|
|
3134
|
+
try {
|
|
3135
|
+
const parsed = JSON.parse(block.text);
|
|
3136
|
+
const row = asRecord(parsed);
|
|
3137
|
+
if (row) return row;
|
|
3138
|
+
} catch {
|
|
3139
|
+
continue;
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
return null;
|
|
3143
|
+
}
|
|
3144
|
+
function allowedMetaImageUrl(raw) {
|
|
3145
|
+
try {
|
|
3146
|
+
const url = new URL(raw);
|
|
3147
|
+
if (url.protocol !== "https:") return null;
|
|
3148
|
+
const host = url.hostname.toLowerCase().replace(/\.$/, "");
|
|
3149
|
+
if (!META_IMAGE_HOSTS.some((suffix) => host === suffix || host.endsWith(`.${suffix}`))) return null;
|
|
3150
|
+
return url;
|
|
3151
|
+
} catch {
|
|
3152
|
+
return null;
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
function detectImageMime(bytes) {
|
|
3156
|
+
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "image/jpeg";
|
|
3157
|
+
if (bytes.length >= 8 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71 && bytes[4] === 13 && bytes[5] === 10 && bytes[6] === 26 && bytes[7] === 10) return "image/png";
|
|
3158
|
+
if (bytes.length >= 12 && String.fromCharCode(...bytes.slice(0, 4)) === "RIFF" && String.fromCharCode(...bytes.slice(8, 12)) === "WEBP") return "image/webp";
|
|
3159
|
+
if (bytes.length >= 6) {
|
|
3160
|
+
const signature = String.fromCharCode(...bytes.slice(0, 6));
|
|
3161
|
+
if (signature === "GIF87a" || signature === "GIF89a") return "image/gif";
|
|
3162
|
+
}
|
|
3163
|
+
return null;
|
|
3164
|
+
}
|
|
3165
|
+
async function readBoundedBody(response, maxBytes) {
|
|
3166
|
+
const contentLength = Number(response.headers.get("content-length") ?? "");
|
|
3167
|
+
if (Number.isFinite(contentLength) && contentLength > maxBytes) throw new Error("image_too_large");
|
|
3168
|
+
if (!response.body) {
|
|
3169
|
+
const bytes2 = new Uint8Array(await response.arrayBuffer());
|
|
3170
|
+
if (bytes2.byteLength > maxBytes) throw new Error("image_too_large");
|
|
3171
|
+
return bytes2;
|
|
3172
|
+
}
|
|
3173
|
+
const reader = response.body.getReader();
|
|
3174
|
+
const chunks = [];
|
|
3175
|
+
let total = 0;
|
|
3176
|
+
try {
|
|
3177
|
+
while (true) {
|
|
3178
|
+
const { done, value } = await reader.read();
|
|
3179
|
+
if (done) break;
|
|
3180
|
+
if (!value) continue;
|
|
3181
|
+
total += value.byteLength;
|
|
3182
|
+
if (total > maxBytes) throw new Error("image_too_large");
|
|
3183
|
+
chunks.push(value);
|
|
3184
|
+
}
|
|
3185
|
+
} finally {
|
|
3186
|
+
reader.releaseLock();
|
|
3187
|
+
}
|
|
3188
|
+
const bytes = new Uint8Array(total);
|
|
3189
|
+
let offset = 0;
|
|
3190
|
+
for (const chunk of chunks) {
|
|
3191
|
+
bytes.set(chunk, offset);
|
|
3192
|
+
offset += chunk.byteLength;
|
|
3193
|
+
}
|
|
3194
|
+
return bytes;
|
|
3195
|
+
}
|
|
3196
|
+
async function fetchMetaImage(rawUrl, maxBytes) {
|
|
3197
|
+
let url = allowedMetaImageUrl(rawUrl);
|
|
3198
|
+
if (!url) throw new Error("image_host_not_allowed");
|
|
3199
|
+
for (let redirect = 0; redirect <= MAX_REDIRECTS; redirect++) {
|
|
3200
|
+
const response = await fetch(url, {
|
|
3201
|
+
redirect: "manual",
|
|
3202
|
+
headers: { Accept: "image/jpeg,image/png,image/webp,image/gif;q=0.8" },
|
|
3203
|
+
signal: AbortSignal.timeout(12e3)
|
|
3204
|
+
});
|
|
3205
|
+
if ([301, 302, 303, 307, 308].includes(response.status)) {
|
|
3206
|
+
const location = response.headers.get("location");
|
|
3207
|
+
if (!location || redirect === MAX_REDIRECTS) throw new Error("image_redirect_rejected");
|
|
3208
|
+
url = allowedMetaImageUrl(new URL(location, url).href);
|
|
3209
|
+
if (!url) throw new Error("image_redirect_host_not_allowed");
|
|
3210
|
+
continue;
|
|
3211
|
+
}
|
|
3212
|
+
if (!response.ok) throw new Error(`image_fetch_${response.status}`);
|
|
3213
|
+
const bytes = await readBoundedBody(response, maxBytes);
|
|
3214
|
+
const mimeType = detectImageMime(bytes);
|
|
3215
|
+
if (!mimeType) throw new Error("image_content_invalid");
|
|
3216
|
+
return { bytes, mimeType, finalUrl: url.href };
|
|
3217
|
+
}
|
|
3218
|
+
throw new Error("image_redirect_rejected");
|
|
3219
|
+
}
|
|
3220
|
+
function firstString(row, keys) {
|
|
3221
|
+
for (const key of keys) {
|
|
3222
|
+
const value = row[key];
|
|
3223
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
3224
|
+
}
|
|
3225
|
+
return null;
|
|
3226
|
+
}
|
|
3227
|
+
function displayUrl(raw) {
|
|
3228
|
+
try {
|
|
3229
|
+
const url = new URL(raw);
|
|
3230
|
+
return `${url.origin}${url.pathname}`;
|
|
3231
|
+
} catch {
|
|
3232
|
+
return "[invalid media URL]";
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
async function buildMetaAdCreativeMediaResult(executor, input) {
|
|
3236
|
+
const providerResult = await executor.readServiceConnection({
|
|
3237
|
+
connectionId: input.connectionId,
|
|
3238
|
+
tool: "resolve-ad-creative-media",
|
|
3239
|
+
args: { adId: input.adId }
|
|
3240
|
+
});
|
|
3241
|
+
if (providerResult.isError) return providerResult;
|
|
3242
|
+
const payload = parseToolPayload(providerResult);
|
|
3243
|
+
const resolved = asRecord(payload?.["result"]) ?? payload;
|
|
3244
|
+
if (!resolved || typeof resolved["creativeId"] !== "string") {
|
|
3245
|
+
return {
|
|
3246
|
+
content: [{ type: "text", text: "Meta returned an invalid creative-media result. Confirm that resolve-ad-creative-media is deployed and this connection can read the ad." }],
|
|
3247
|
+
isError: true
|
|
3248
|
+
};
|
|
3249
|
+
}
|
|
3250
|
+
const warnings = Array.isArray(resolved["warnings"]) ? resolved["warnings"].filter((value) => typeof value === "string") : [];
|
|
3251
|
+
const limitations = Array.isArray(resolved["limitations"]) ? resolved["limitations"].filter((value) => typeof value === "string") : [];
|
|
3252
|
+
const images = Array.isArray(resolved["images"]) ? resolved["images"].map(asRecord).filter((value) => value !== null) : [];
|
|
3253
|
+
const videos = Array.isArray(resolved["videos"]) ? resolved["videos"].map(asRecord).filter((value) => value !== null) : [];
|
|
3254
|
+
const nextActions = Array.isArray(resolved["nextActions"]) ? resolved["nextActions"].map(asRecord).filter((value) => value !== null) : [];
|
|
3255
|
+
const content = [];
|
|
3256
|
+
const inlineImages = [];
|
|
3257
|
+
if (input.imageMode === "inline_preview") {
|
|
3258
|
+
let remaining = MAX_TOTAL_IMAGE_BYTES;
|
|
3259
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3260
|
+
for (const image of images) {
|
|
3261
|
+
if (inlineImages.length >= input.maxInlineImages || remaining <= 0) break;
|
|
3262
|
+
const sourceUrl = firstString(image, ["url", "thumbnailUrl", "permalinkUrl"]);
|
|
3263
|
+
if (!sourceUrl || seen.has(sourceUrl)) continue;
|
|
3264
|
+
seen.add(sourceUrl);
|
|
3265
|
+
try {
|
|
3266
|
+
const fetched = await fetchMetaImage(sourceUrl, Math.min(MAX_IMAGE_BYTES, remaining));
|
|
3267
|
+
const contentIndex = content.length + 1;
|
|
3268
|
+
content.push({ type: "image", data: Buffer.from(fetched.bytes).toString("base64"), mimeType: fetched.mimeType });
|
|
3269
|
+
inlineImages.push({ sourceUrl: fetched.finalUrl, mimeType: fetched.mimeType, bytes: fetched.bytes.byteLength, contentIndex });
|
|
3270
|
+
remaining -= fetched.bytes.byteLength;
|
|
3271
|
+
} catch (error) {
|
|
3272
|
+
const code = error instanceof Error ? error.message : "image_fetch_failed";
|
|
3273
|
+
warnings.push(`Creative image preview could not be attached (${code}) for ${displayUrl(sourceUrl)}.`);
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
const mediaType = typeof resolved["mediaType"] === "string" ? resolved["mediaType"] : "unknown";
|
|
3278
|
+
const publicPostCandidateUrl = typeof resolved["publicPostCandidateUrl"] === "string" ? resolved["publicPostCandidateUrl"] : null;
|
|
3279
|
+
const summary = [
|
|
3280
|
+
"# Meta Ad Creative Media",
|
|
3281
|
+
`**Ad:** ${typeof resolved["adName"] === "string" ? resolved["adName"] : input.adId} (\`${input.adId}\`)`,
|
|
3282
|
+
`**Creative:** ${typeof resolved["creativeName"] === "string" ? resolved["creativeName"] : resolved["creativeId"]} (\`${resolved["creativeId"]}\`)`,
|
|
3283
|
+
`**Media:** ${mediaType} \xB7 ${images.length} image descriptor(s) \xB7 ${videos.length} video descriptor(s) \xB7 ${inlineImages.length} inline visual preview(s)`,
|
|
3284
|
+
publicPostCandidateUrl ? `**Public post candidate:** ${publicPostCandidateUrl} (unverified; dark/paused ads may not be public)` : "",
|
|
3285
|
+
nextActions.length ? `
|
|
3286
|
+
## Recommended next calls
|
|
3287
|
+
${nextActions.map((action) => `- \`${String(action["tool"] ?? "")}\`: ${String(action["reason"] ?? "")}`).join("\n")}` : "",
|
|
3288
|
+
warnings.length ? `
|
|
3289
|
+
## Warnings
|
|
3290
|
+
${warnings.map((warning) => `- ${warning}`).join("\n")}` : "",
|
|
3291
|
+
limitations.length ? `
|
|
3292
|
+
## Limitations
|
|
3293
|
+
${limitations.map((limitation) => `- ${limitation}`).join("\n")}` : ""
|
|
3294
|
+
].filter(Boolean).join("\n");
|
|
3295
|
+
content.unshift({ type: "text", text: summary });
|
|
3296
|
+
return {
|
|
3297
|
+
content,
|
|
3298
|
+
structuredContent: {
|
|
3299
|
+
ok: true,
|
|
3300
|
+
adId: String(resolved["adId"] ?? input.adId),
|
|
3301
|
+
adName: typeof resolved["adName"] === "string" ? resolved["adName"] : null,
|
|
3302
|
+
adAccountId: typeof resolved["adAccountId"] === "string" ? resolved["adAccountId"] : null,
|
|
3303
|
+
creativeId: String(resolved["creativeId"]),
|
|
3304
|
+
creativeName: typeof resolved["creativeName"] === "string" ? resolved["creativeName"] : null,
|
|
3305
|
+
effectiveObjectStoryId: typeof resolved["effectiveObjectStoryId"] === "string" ? resolved["effectiveObjectStoryId"] : null,
|
|
3306
|
+
sourceFacebookPostId: typeof resolved["sourceFacebookPostId"] === "string" ? resolved["sourceFacebookPostId"] : null,
|
|
3307
|
+
objectStoryId: typeof resolved["objectStoryId"] === "string" ? resolved["objectStoryId"] : null,
|
|
3308
|
+
pageId: typeof resolved["pageId"] === "string" ? resolved["pageId"] : null,
|
|
3309
|
+
postId: typeof resolved["postId"] === "string" ? resolved["postId"] : null,
|
|
3310
|
+
publicPostCandidateUrl,
|
|
3311
|
+
publicPostStatus: "unverified",
|
|
3312
|
+
mediaType,
|
|
3313
|
+
images,
|
|
3314
|
+
videos,
|
|
3315
|
+
inlineImages,
|
|
3316
|
+
nextActions,
|
|
3317
|
+
warnings,
|
|
3318
|
+
limitations
|
|
3319
|
+
}
|
|
3320
|
+
};
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3025
3323
|
// src/mcp/mcp-tool-schemas.ts
|
|
3026
3324
|
import { z } from "zod";
|
|
3027
3325
|
var HarvestPaaInputSchema = {
|
|
@@ -3081,7 +3379,7 @@ var YoutubeTranscribeInputSchema = {
|
|
|
3081
3379
|
var FacebookPageIntelInputSchema = {
|
|
3082
3380
|
pageId: z.string().optional().describe("Facebook advertiser/page ID. Use only a value returned by facebook_ad_search or copied from Ad Library."),
|
|
3083
3381
|
libraryId: z.string().optional().describe("Facebook Ad Library archive ID. Use a value returned by facebook_ad_search, or a libraryId/adArchiveId visible in Ad Library."),
|
|
3084
|
-
query: z.string().optional().describe("
|
|
3382
|
+
query: z.string().optional().describe("Broad Ad Library keyword discovery when pageId/libraryId is not known. Results can mix unrelated advertisers; inspect matchConfidence, matchedAdvertisers, and warnings before analysis. One of pageId, libraryId, or query is required."),
|
|
3085
3383
|
maxAds: z.number().int().min(1).max(200).default(50).describe("Maximum ads to inspect. Default 50, maximum 200."),
|
|
3086
3384
|
country: z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US.")
|
|
3087
3385
|
};
|
|
@@ -3105,10 +3403,10 @@ var VideoFrameAnalysisStatusInputSchema = {
|
|
|
3105
3403
|
runId: z.string().min(1).describe("The runId returned by video_frame_analysis.")
|
|
3106
3404
|
};
|
|
3107
3405
|
var FacebookAdTranscribeInputSchema = {
|
|
3108
|
-
videoUrl: z.string().url().describe("Direct Facebook CDN video URL from facebook_page_intel. Do not pass a public post/reel/share URL
|
|
3406
|
+
videoUrl: z.string().url().describe("Direct Meta/Facebook CDN video URL from facebook_page_intel or meta_ad_creative_media. Use transient sources immediately. Do not pass a public post/reel/share URL\u2014use facebook_video_transcribe for those.")
|
|
3109
3407
|
};
|
|
3110
3408
|
var FacebookVideoTranscribeInputSchema = {
|
|
3111
|
-
url: z.string().url().describe("
|
|
3409
|
+
url: z.string().url().describe("Public Facebook reel/video/watch/post/share URL from facebook.com, m.facebook.com, or fb.watch. For connected account ads, get the correct public candidate from meta_ad_creative_media instead of guessing URL structure."),
|
|
3112
3410
|
quality: 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.")
|
|
3113
3411
|
};
|
|
3114
3412
|
var GoogleAdsSearchInputSchema = {
|
|
@@ -3663,6 +3961,11 @@ var RedditThreadOutputSchema = {
|
|
|
3663
3961
|
};
|
|
3664
3962
|
var FacebookPageIntelOutputSchema = {
|
|
3665
3963
|
advertiserName: NullableString,
|
|
3964
|
+
inputMode: z.enum(["pageId", "libraryId", "query"]),
|
|
3965
|
+
matchConfidence: z.enum(["high", "medium", "low"]),
|
|
3966
|
+
matchReason: z.string(),
|
|
3967
|
+
warnings: z.array(z.string()),
|
|
3968
|
+
matchedAdvertisers: z.array(z.object({ name: z.string(), adCount: z.number().int().min(0) })),
|
|
3666
3969
|
totalAds: z.number().int().min(0),
|
|
3667
3970
|
activeCount: z.number().int().min(0),
|
|
3668
3971
|
videoCount: z.number().int().min(0),
|
|
@@ -3717,6 +4020,16 @@ var GoogleAdsPageIntelOutputSchema = {
|
|
|
3717
4020
|
variations: z.number().int().nullable()
|
|
3718
4021
|
}))
|
|
3719
4022
|
};
|
|
4023
|
+
var TranscriptSignalOutput = z.object({
|
|
4024
|
+
status: z.enum(["speech_detected", "low_speech_signal", "empty"]),
|
|
4025
|
+
speechDetected: z.boolean(),
|
|
4026
|
+
confidence: z.enum(["medium", "low"]),
|
|
4027
|
+
basis: z.literal("transcript_word_count_and_timing"),
|
|
4028
|
+
mediaDurationSec: z.number().nullable(),
|
|
4029
|
+
wordsPerMinute: z.number().nullable(),
|
|
4030
|
+
retryRecommended: z.boolean(),
|
|
4031
|
+
warnings: z.array(z.string())
|
|
4032
|
+
});
|
|
3720
4033
|
var FacebookVideoTranscribeOutputSchema = {
|
|
3721
4034
|
sourceUrl: z.string().url(),
|
|
3722
4035
|
pageUrl: z.string().url(),
|
|
@@ -3733,7 +4046,8 @@ var FacebookVideoTranscribeOutputSchema = {
|
|
|
3733
4046
|
startSec: z.number(),
|
|
3734
4047
|
endSec: z.number(),
|
|
3735
4048
|
text: z.string()
|
|
3736
|
-
}))
|
|
4049
|
+
})),
|
|
4050
|
+
transcriptSignal: TranscriptSignalOutput
|
|
3737
4051
|
};
|
|
3738
4052
|
var TranscriptChunkOutput = z.object({
|
|
3739
4053
|
startSec: z.number(),
|
|
@@ -3855,6 +4169,7 @@ var FacebookAdTranscribeOutputSchema = {
|
|
|
3855
4169
|
durationMs: z.number().nullable(),
|
|
3856
4170
|
transcriptText: z.string(),
|
|
3857
4171
|
chunks: z.array(TranscriptChunkOutput),
|
|
4172
|
+
transcriptSignal: TranscriptSignalOutput,
|
|
3858
4173
|
resolvedInputs: z.object({
|
|
3859
4174
|
videoUrl: z.string().url()
|
|
3860
4175
|
})
|
|
@@ -4087,6 +4402,43 @@ var ReadServiceConnectionOutputSchema = {
|
|
|
4087
4402
|
result: z.unknown().optional(),
|
|
4088
4403
|
error: NullableString
|
|
4089
4404
|
};
|
|
4405
|
+
var MetaAdCreativeMediaInputSchema = {
|
|
4406
|
+
connectionId: z.string().min(1).describe("Tenant-owned Meta Marketing connectionId from list_service_connections."),
|
|
4407
|
+
adId: z.string().regex(/^\d{5,30}$/).describe("Meta ad ID from the connected ad account. This is not an Ad Library archive ID."),
|
|
4408
|
+
imageMode: z.enum(["inline_preview", "resource_only", "none"]).default("inline_preview").describe("inline_preview returns bounded MCP image content that a vision-capable client can inspect. resource_only returns descriptors/URLs only. none skips image delivery."),
|
|
4409
|
+
maxInlineImages: z.number().int().min(1).max(4).default(2).describe("Maximum creative image/thumbnail previews to attach as MCP image blocks. Default 2; maximum 4.")
|
|
4410
|
+
};
|
|
4411
|
+
var MetaAdCreativeMediaOutputSchema = {
|
|
4412
|
+
ok: z.boolean(),
|
|
4413
|
+
adId: z.string(),
|
|
4414
|
+
adName: NullableString,
|
|
4415
|
+
adAccountId: NullableString,
|
|
4416
|
+
creativeId: z.string(),
|
|
4417
|
+
creativeName: NullableString,
|
|
4418
|
+
effectiveObjectStoryId: NullableString,
|
|
4419
|
+
sourceFacebookPostId: NullableString,
|
|
4420
|
+
objectStoryId: NullableString,
|
|
4421
|
+
pageId: NullableString,
|
|
4422
|
+
postId: NullableString,
|
|
4423
|
+
publicPostCandidateUrl: NullableString,
|
|
4424
|
+
publicPostStatus: z.literal("unverified"),
|
|
4425
|
+
mediaType: z.enum(["image", "video", "carousel", "mixed", "unknown"]),
|
|
4426
|
+
images: z.array(z.record(z.string(), z.unknown())),
|
|
4427
|
+
videos: z.array(z.record(z.string(), z.unknown())),
|
|
4428
|
+
inlineImages: z.array(z.object({
|
|
4429
|
+
sourceUrl: z.string().url(),
|
|
4430
|
+
mimeType: z.string(),
|
|
4431
|
+
bytes: z.number().int().min(0),
|
|
4432
|
+
contentIndex: z.number().int().min(1)
|
|
4433
|
+
})),
|
|
4434
|
+
nextActions: z.array(z.object({
|
|
4435
|
+
tool: z.enum(["facebook_ad_transcribe", "facebook_video_transcribe", "video_frame_analysis"]),
|
|
4436
|
+
args: z.record(z.string(), z.unknown()),
|
|
4437
|
+
reason: z.string()
|
|
4438
|
+
})),
|
|
4439
|
+
warnings: z.array(z.string()),
|
|
4440
|
+
limitations: z.array(z.string())
|
|
4441
|
+
};
|
|
4090
4442
|
var ImportServiceConnectionToMemoryInputSchema = {
|
|
4091
4443
|
connectionId: z.string().min(1).max(200).describe("A tenant-owned connectionId from list_service_connections."),
|
|
4092
4444
|
providerConfigKey: z.string().min(1).max(200).describe("The exact providerConfigKey returned with that connection. It is matched together with connectionId against the authenticated caller."),
|
|
@@ -4764,7 +5116,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
4764
5116
|
}, async (input) => formatYoutubeTranscribe(await executor.youtubeTranscribe(input), input));
|
|
4765
5117
|
server.registerTool("facebook_page_intel", {
|
|
4766
5118
|
title: "Facebook Advertiser Ad Intel",
|
|
4767
|
-
description: "Harvest
|
|
5119
|
+
description: "Harvest public Ad Library creatives. Prefer exact pageId/libraryId; query is broad keyword discovery and can mix unrelated advertisers, so inspect matchConfidence/warnings before analysis. Paused or inactive non-political ads may be absent from Ad Library\u2014use meta_ad_creative_media with the connected ad account for those. Direct Ad Library videoUrl values go to facebook_ad_transcribe.",
|
|
4768
5120
|
inputSchema: FacebookPageIntelInputSchema,
|
|
4769
5121
|
outputSchema: recordOutputSchema("facebook_page_intel", FacebookPageIntelOutputSchema),
|
|
4770
5122
|
annotations: liveWebToolAnnotations("Facebook Advertiser Ad Intel")
|
|
@@ -4799,7 +5151,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
4799
5151
|
}, async (input) => executor.videoFrameAnalysisStatus(input));
|
|
4800
5152
|
server.registerTool("facebook_ad_transcribe", {
|
|
4801
5153
|
title: "Facebook Ad Transcription",
|
|
4802
|
-
description: "Transcribe
|
|
5154
|
+
description: "Transcribe a direct Meta/Facebook CDN video source returned by facebook_page_intel or meta_ad_creative_media. CDN sources can expire, so use them immediately. Do not pass public post/reel/share URLs; use facebook_video_transcribe for those. For a paused/account-owned ad, start with meta_ad_creative_media: it selects the direct Graph source when available and otherwise returns the effective organic-post candidate.",
|
|
4803
5155
|
inputSchema: FacebookAdTranscribeInputSchema,
|
|
4804
5156
|
outputSchema: recordOutputSchema("facebook_ad_transcribe", FacebookAdTranscribeOutputSchema),
|
|
4805
5157
|
annotations: liveWebToolAnnotations("Facebook Ad Transcription")
|
|
@@ -4827,7 +5179,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
4827
5179
|
}, async (input) => formatGoogleAdsTranscribe(await executor.googleAdsTranscribe(input), input));
|
|
4828
5180
|
server.registerTool("facebook_video_transcribe", {
|
|
4829
5181
|
title: "Facebook Organic Video Transcription",
|
|
4830
|
-
description: "Transcribe audio from
|
|
5182
|
+
description: "Transcribe audio from a public Facebook reel/video/post/share URL (including fb.watch). Renders the public page, selects the best progressive MP4, and returns transcript plus resolved metadata and a low-speech signal. For a connected paused ad, use meta_ad_creative_media first; pass its public post/permalink candidate here only when no direct Graph source is available. Dark/unpublished ads may not have a public route.",
|
|
4831
5183
|
inputSchema: FacebookVideoTranscribeInputSchema,
|
|
4832
5184
|
outputSchema: recordOutputSchema("facebook_video_transcribe", FacebookVideoTranscribeOutputSchema),
|
|
4833
5185
|
annotations: liveWebToolAnnotations("Facebook Organic Video Transcription")
|
|
@@ -5005,6 +5357,13 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
5005
5357
|
outputSchema: recordOutputSchema("read_service_connection", ReadServiceConnectionOutputSchema),
|
|
5006
5358
|
annotations: { title: "Read Connected Service", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
5007
5359
|
}, async (input) => executor.readServiceConnection(input));
|
|
5360
|
+
server.registerTool("meta_ad_creative_media", {
|
|
5361
|
+
title: "View Meta Ad Creative Media",
|
|
5362
|
+
description: "Preferred connected-account path for viewing a Meta ad creative, especially paused or dark ads that may be absent from Ad Library. Given a tenant-owned Meta connectionId and adId, resolves the ad, creative, effective story/post candidate, image assets, video assets, and transient Graph playback source. Bounded creative images are returned as actual MCP image content for vision-capable clients. For video, follow the returned exact nextActions with facebook_ad_transcribe when Graph returned a direct source, facebook_video_transcribe when only a public post/video candidate is available, or video_frame_analysis for visual breakdown. This tool is read-only and does not itself spend transcription/analysis credits.",
|
|
5363
|
+
inputSchema: MetaAdCreativeMediaInputSchema,
|
|
5364
|
+
outputSchema: recordOutputSchema("meta_ad_creative_media", MetaAdCreativeMediaOutputSchema),
|
|
5365
|
+
annotations: { title: "View Meta Ad Creative Media", readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
5366
|
+
}, async (input) => buildMetaAdCreativeMediaResult(executor, input));
|
|
5008
5367
|
server.registerTool("import_service_connection_to_memory", {
|
|
5009
5368
|
title: "Import Connected Service Snapshot to Memory",
|
|
5010
5369
|
description: "Run exactly one bounded, approved read on a tenant-owned connected service and upsert the redacted result into an existing ordinary Memory vault at a server-generated stable path. The saved document is embedded for RAG and marked as untrusted provider data, never instructions. This is a one-result snapshot: it does not paginate, bulk-import an account, continuously sync changes, propagate deletions, or create normalized tables. Use list_service_connections first and supply an exact current readTools entry; action and admin tools are rejected.",
|
|
@@ -9316,6 +9675,8 @@ export {
|
|
|
9316
9675
|
sanitizeAttempts,
|
|
9317
9676
|
sanitizeHarvestResult,
|
|
9318
9677
|
buildLinkGraph,
|
|
9678
|
+
assessTranscriptSignal,
|
|
9679
|
+
transcribeMediaUrl,
|
|
9319
9680
|
createConnectedDataArtifact,
|
|
9320
9681
|
renewConnectedDataArtifactDownload,
|
|
9321
9682
|
cleanupExpiredConnectedDataArtifacts,
|
|
@@ -9332,4 +9693,4 @@ export {
|
|
|
9332
9693
|
registerMemoryMcpTools,
|
|
9333
9694
|
MemoryMcpToolExecutor
|
|
9334
9695
|
};
|
|
9335
|
-
//# sourceMappingURL=chunk-
|
|
9696
|
+
//# sourceMappingURL=chunk-O2S5TOCG.js.map
|