mcp-scraper 0.18.0 → 0.20.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 +635 -109
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- 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 +2228 -1783
- 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.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +3 -3
- package/dist/{chunk-GL4BW4CP.js → chunk-22VEGGTW.js} +21 -9
- package/dist/{chunk-GL4BW4CP.js.map → chunk-22VEGGTW.js.map} +1 -1
- package/dist/{chunk-RUGJE5EB.js → chunk-EJK25QOW.js} +2 -2
- package/dist/chunk-JWIE5NCR.js +284 -0
- package/dist/chunk-JWIE5NCR.js.map +1 -0
- package/dist/{chunk-FMEDPBIV.js → chunk-KE7KE2Q2.js} +482 -35
- package/dist/chunk-KE7KE2Q2.js.map +1 -0
- package/dist/chunk-PZB3TJWK.js +7 -0
- package/dist/chunk-PZB3TJWK.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/chunk-UN7VMHZL.js +244 -0
- package/dist/chunk-UN7VMHZL.js.map +1 -0
- package/dist/{chunk-RMPPYKUV.js → chunk-XDFSLSSH.js} +7 -218
- package/dist/chunk-XDFSLSSH.js.map +1 -0
- package/dist/{chunk-HE2LQPJ2.js → chunk-ZRKFW5FB.js} +2 -2
- package/dist/{db-LIOTIWVN.js → db-YHZYG7D2.js} +2 -2
- package/dist/{extract-bundle-U4D5LW5W.js → extract-bundle-OSUPAHCE.js} +58 -5
- package/dist/extract-bundle-OSUPAHCE.js.map +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/{server-MQDCAR6I.js → server-VDWIPV7F.js} +100 -363
- package/dist/server-VDWIPV7F.js.map +1 -0
- package/dist/{site-extract-repository-NVSZH35Y.js → site-extract-repository-GWKGK46Z.js} +3 -3
- package/dist/{worker-JQTS437L.js → worker-O5PZTTPY.js} +8 -8
- package/docs/mcp-tool-manifest.generated.json +564 -30
- package/docs/specs/meta-ad-creative-media-resolution-spec.md +31 -0
- package/docs/specs/unified-credit-and-scheduled-execution-billing-spec.md +1007 -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/extract-bundle-U4D5LW5W.js.map +0 -1
- package/dist/server-MQDCAR6I.js.map +0 -1
- /package/dist/{chunk-RUGJE5EB.js.map → chunk-EJK25QOW.js.map} +0 -0
- /package/dist/{chunk-HE2LQPJ2.js.map → chunk-ZRKFW5FB.js.map} +0 -0
- /package/dist/{db-LIOTIWVN.js.map → db-YHZYG7D2.js.map} +0 -0
- /package/dist/{site-extract-repository-NVSZH35Y.js.map → site-extract-repository-GWKGK46Z.js.map} +0 -0
- /package/dist/{worker-JQTS437L.js.map → worker-O5PZTTPY.js.map} +0 -0
package/dist/bin/api-server.cjs
CHANGED
|
@@ -5271,18 +5271,30 @@ async function ledgerExistsForStripePI(stripePI) {
|
|
|
5271
5271
|
});
|
|
5272
5272
|
return res.rows.length > 0;
|
|
5273
5273
|
}
|
|
5274
|
-
async function reconcileBalanceMc(userId) {
|
|
5274
|
+
async function reconcileBalanceMc(userId, knownBalanceMc) {
|
|
5275
5275
|
const db = getDb();
|
|
5276
|
-
await ensureMigrated(Number(userId));
|
|
5277
5276
|
const res = await db.execute({
|
|
5278
|
-
sql: `SELECT
|
|
5277
|
+
sql: `SELECT
|
|
5278
|
+
COUNT(*) AS lot_count,
|
|
5279
|
+
COALESCE(SUM(CASE WHEN remaining_mc > 0 AND expires_at > datetime('now') THEN remaining_mc ELSE 0 END), 0) AS balance_mc
|
|
5280
|
+
FROM credit_lots WHERE user_id = ?`,
|
|
5279
5281
|
args: [userId]
|
|
5280
5282
|
});
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5283
|
+
let balanceMc = Number(res.rows[0]?.balance_mc ?? 0);
|
|
5284
|
+
if (Number(res.rows[0]?.lot_count ?? 0) === 0) {
|
|
5285
|
+
await ensureMigrated(Number(userId));
|
|
5286
|
+
const migrated = await db.execute({
|
|
5287
|
+
sql: `SELECT COALESCE(SUM(remaining_mc), 0) AS balance_mc FROM credit_lots WHERE user_id = ? AND remaining_mc > 0 AND expires_at > datetime('now')`,
|
|
5288
|
+
args: [userId]
|
|
5289
|
+
});
|
|
5290
|
+
balanceMc = Number(migrated.rows[0]?.balance_mc ?? 0);
|
|
5291
|
+
}
|
|
5292
|
+
if (knownBalanceMc !== balanceMc) {
|
|
5293
|
+
await db.execute({
|
|
5294
|
+
sql: "UPDATE users SET balance_mc = ? WHERE id = ? AND balance_mc != ?",
|
|
5295
|
+
args: [balanceMc, userId, balanceMc]
|
|
5296
|
+
});
|
|
5297
|
+
}
|
|
5286
5298
|
return balanceMc;
|
|
5287
5299
|
}
|
|
5288
5300
|
var import_http, import_node_crypto, import_zod, DB_URL, DB_TOKEN, _db, _rateLimitSchemaReady, CREDIT_LOT_TTL, SiteAuditJobRowSchema, SiteAuditPhaseLogRowSchema;
|
|
@@ -6372,8 +6384,8 @@ async function downloadAsset(url, destDir, filename) {
|
|
|
6372
6384
|
}
|
|
6373
6385
|
const writer = (0, import_node_fs.createWriteStream)(dest);
|
|
6374
6386
|
await (0, import_promises2.pipeline)(import_node_stream.Readable.fromWeb(res.body), writer);
|
|
6375
|
-
const { statSync:
|
|
6376
|
-
const sizeBytes =
|
|
6387
|
+
const { statSync: statSync3 } = await import("fs");
|
|
6388
|
+
const sizeBytes = statSync3(dest).size;
|
|
6377
6389
|
return { savedPath: dest, sizeBytes, mimeType };
|
|
6378
6390
|
}
|
|
6379
6391
|
async function harvestPageMedia(html, pageUrl, options = {}) {
|
|
@@ -10746,6 +10758,15 @@ function registrableDomain2(host) {
|
|
|
10746
10758
|
const parts = h.split(".");
|
|
10747
10759
|
return parts.length <= 2 ? h : parts.slice(-2).join(".");
|
|
10748
10760
|
}
|
|
10761
|
+
function safeImageFilename(url, index) {
|
|
10762
|
+
try {
|
|
10763
|
+
const u = new URL(url);
|
|
10764
|
+
const base = (0, import_node_path3.basename)(u.pathname).replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
|
|
10765
|
+
return base || `image-${index}`;
|
|
10766
|
+
} catch {
|
|
10767
|
+
return `image-${index}`;
|
|
10768
|
+
}
|
|
10769
|
+
}
|
|
10749
10770
|
function slugFactory() {
|
|
10750
10771
|
const counts = /* @__PURE__ */ new Map();
|
|
10751
10772
|
return (url) => {
|
|
@@ -10768,7 +10789,9 @@ async function* pageChunks(jobId) {
|
|
|
10768
10789
|
}
|
|
10769
10790
|
async function assembleExtractArtifacts(job, extras = {}) {
|
|
10770
10791
|
const dir = (0, import_node_path3.join)((0, import_node_os3.tmpdir)(), `extract-bundle-${job.id}-${Date.now()}`);
|
|
10792
|
+
const downloadImages = job.options.downloadImages === true;
|
|
10771
10793
|
(0, import_node_fs3.mkdirSync)((0, import_node_path3.join)(dir, "pages"), { recursive: true });
|
|
10794
|
+
if (downloadImages) (0, import_node_fs3.mkdirSync)((0, import_node_path3.join)(dir, "images"), { recursive: true });
|
|
10772
10795
|
try {
|
|
10773
10796
|
const metas = [];
|
|
10774
10797
|
const statusByUrl = /* @__PURE__ */ new Map();
|
|
@@ -10802,6 +10825,10 @@ async function assembleExtractArtifacts(job, extras = {}) {
|
|
|
10802
10825
|
} catch {
|
|
10803
10826
|
siteReg = "";
|
|
10804
10827
|
}
|
|
10828
|
+
const imageLimit = (0, import_p_limit3.default)(IMAGE_DOWNLOAD_CONCURRENCY);
|
|
10829
|
+
const imageDownloads = [];
|
|
10830
|
+
let imagesQueued = 0;
|
|
10831
|
+
let imagesFailed = 0;
|
|
10805
10832
|
for await (const chunk of pageChunks(job.id)) {
|
|
10806
10833
|
for (const p of chunk) {
|
|
10807
10834
|
if (p.bodyMarkdown) {
|
|
@@ -10819,6 +10846,20 @@ ${p.bodyMarkdown}
|
|
|
10819
10846
|
`
|
|
10820
10847
|
);
|
|
10821
10848
|
}
|
|
10849
|
+
if (downloadImages && p.imageLinks?.length && imagesQueued < MAX_IMAGES_PER_SITE) {
|
|
10850
|
+
const pageDir = (0, import_node_path3.join)(dir, "images", slug(p.url));
|
|
10851
|
+
(0, import_node_fs3.mkdirSync)(pageDir, { recursive: true });
|
|
10852
|
+
for (const [idx, imgUrl] of p.imageLinks.slice(0, MAX_IMAGES_PER_PAGE).entries()) {
|
|
10853
|
+
if (imagesQueued >= MAX_IMAGES_PER_SITE) break;
|
|
10854
|
+
imagesQueued++;
|
|
10855
|
+
const filename = safeImageFilename(imgUrl, idx);
|
|
10856
|
+
imageDownloads.push(
|
|
10857
|
+
imageLimit(() => downloadAsset(imgUrl, pageDir, filename)).then(() => void 0, () => {
|
|
10858
|
+
imagesFailed++;
|
|
10859
|
+
})
|
|
10860
|
+
);
|
|
10861
|
+
}
|
|
10862
|
+
}
|
|
10822
10863
|
const from = normalize2(p.url);
|
|
10823
10864
|
const oc = { int: 0, ext: 0 };
|
|
10824
10865
|
for (const l of p.outlinks ?? []) {
|
|
@@ -10866,6 +10907,7 @@ ${p.bodyMarkdown}
|
|
|
10866
10907
|
}
|
|
10867
10908
|
gz.end();
|
|
10868
10909
|
await (0, import_node_events.once)(linksOut, "finish");
|
|
10910
|
+
await Promise.all(imageDownloads);
|
|
10869
10911
|
const depth = /* @__PURE__ */ new Map();
|
|
10870
10912
|
const start = normalize2(job.startUrl);
|
|
10871
10913
|
depth.set(start, 0);
|
|
@@ -10957,6 +10999,15 @@ ${renderImageSection(extras.imageAudit)}`;
|
|
|
10957
10999
|
}
|
|
10958
11000
|
if (extras.seoAudit) (0, import_node_fs3.writeFileSync)((0, import_node_path3.join)(dir, "seo-audit.json"), JSON.stringify(extras.seoAudit, null, 2));
|
|
10959
11001
|
if (extras.branding) (0, import_node_fs3.writeFileSync)((0, import_node_path3.join)(dir, "branding.json"), JSON.stringify(extras.branding, null, 2));
|
|
11002
|
+
if (downloadImages) {
|
|
11003
|
+
(0, import_node_fs3.writeFileSync)((0, import_node_path3.join)(dir, "images-download-summary.json"), JSON.stringify({
|
|
11004
|
+
queued: imagesQueued,
|
|
11005
|
+
downloaded: imagesQueued - imagesFailed,
|
|
11006
|
+
failed: imagesFailed,
|
|
11007
|
+
perPageCap: MAX_IMAGES_PER_PAGE,
|
|
11008
|
+
perSiteCap: MAX_IMAGES_PER_SITE
|
|
11009
|
+
}, null, 2));
|
|
11010
|
+
}
|
|
10960
11011
|
const artifactFiles = [
|
|
10961
11012
|
{ name: "report.md", type: "text/markdown" },
|
|
10962
11013
|
{ name: "issues.json", type: "application/json" },
|
|
@@ -10973,13 +11024,20 @@ ${renderImageSection(extras.imageAudit)}`;
|
|
|
10973
11024
|
}
|
|
10974
11025
|
if (extras.seoAudit) artifactFiles.push({ name: "seo-audit.json", type: "application/json" });
|
|
10975
11026
|
if (extras.branding) artifactFiles.push({ name: "branding.json", type: "application/json" });
|
|
11027
|
+
if (downloadImages) artifactFiles.push({ name: "images-download-summary.json", type: "application/json" });
|
|
10976
11028
|
const zip = new import_yazl.ZipFile();
|
|
10977
11029
|
const zipOut = (0, import_node_fs3.createWriteStream)((0, import_node_path3.join)(dir, "bundle.zip"));
|
|
10978
11030
|
zip.outputStream.pipe(zipOut);
|
|
10979
11031
|
for (const f of artifactFiles) zip.addFile((0, import_node_path3.join)(dir, f.name), f.name);
|
|
10980
11032
|
if (pagesWithBody > 0) {
|
|
10981
|
-
const
|
|
10982
|
-
|
|
11033
|
+
for (const f of (0, import_node_fs3.readdirSync)((0, import_node_path3.join)(dir, "pages"))) zip.addFile((0, import_node_path3.join)(dir, "pages", f), `pages/${f}`);
|
|
11034
|
+
}
|
|
11035
|
+
if (downloadImages && imagesQueued > imagesFailed) {
|
|
11036
|
+
const imagesDir = (0, import_node_path3.join)(dir, "images");
|
|
11037
|
+
for (const rel of (0, import_node_fs3.readdirSync)(imagesDir, { recursive: true })) {
|
|
11038
|
+
const full = (0, import_node_path3.join)(imagesDir, rel);
|
|
11039
|
+
if ((0, import_node_fs3.statSync)(full).isFile()) zip.addFile(full, `images/${rel.split("\\").join("/")}`);
|
|
11040
|
+
}
|
|
10983
11041
|
}
|
|
10984
11042
|
zip.end();
|
|
10985
11043
|
await (0, import_node_events.once)(zipOut, "finish");
|
|
@@ -10993,7 +11051,7 @@ ${renderImageSection(extras.imageAudit)}`;
|
|
|
10993
11051
|
(0, import_node_fs3.rmSync)(dir, { recursive: true, force: true });
|
|
10994
11052
|
}
|
|
10995
11053
|
}
|
|
10996
|
-
var import_node_fs3, import_node_path3, import_node_os3, import_node_zlib, import_node_events, import_yazl, CHUNK;
|
|
11054
|
+
var import_node_fs3, import_node_path3, import_node_os3, import_node_zlib, import_node_events, import_yazl, import_p_limit3, CHUNK, MAX_IMAGES_PER_PAGE, MAX_IMAGES_PER_SITE, IMAGE_DOWNLOAD_CONCURRENCY;
|
|
10997
11055
|
var init_extract_bundle = __esm({
|
|
10998
11056
|
"src/api/extract-bundle.ts"() {
|
|
10999
11057
|
"use strict";
|
|
@@ -11003,12 +11061,17 @@ var init_extract_bundle = __esm({
|
|
|
11003
11061
|
import_node_zlib = require("zlib");
|
|
11004
11062
|
import_node_events = require("events");
|
|
11005
11063
|
import_yazl = require("yazl");
|
|
11064
|
+
import_p_limit3 = __toESM(require("p-limit"), 1);
|
|
11006
11065
|
init_db();
|
|
11007
11066
|
init_blob_store();
|
|
11008
11067
|
init_seo_issues();
|
|
11009
11068
|
init_seo_link_report();
|
|
11010
11069
|
init_image_audit();
|
|
11070
|
+
init_media_extractor();
|
|
11011
11071
|
CHUNK = 400;
|
|
11072
|
+
MAX_IMAGES_PER_PAGE = 20;
|
|
11073
|
+
MAX_IMAGES_PER_SITE = 500;
|
|
11074
|
+
IMAGE_DOWNLOAD_CONCURRENCY = 8;
|
|
11012
11075
|
}
|
|
11013
11076
|
});
|
|
11014
11077
|
|
|
@@ -13647,6 +13710,39 @@ var init_audio_resolver = __esm({
|
|
|
13647
13710
|
});
|
|
13648
13711
|
|
|
13649
13712
|
// src/services/media-transcription.ts
|
|
13713
|
+
function transcriptWordCount(text) {
|
|
13714
|
+
return text.trim() ? text.trim().split(/\s+/).length : 0;
|
|
13715
|
+
}
|
|
13716
|
+
function assessTranscriptSignal(text, chunks, durationHintSec) {
|
|
13717
|
+
const words = transcriptWordCount(text);
|
|
13718
|
+
const chunkDurationSec = chunks.reduce((max, chunk) => {
|
|
13719
|
+
const end = Number(chunk.timestamp?.[1]);
|
|
13720
|
+
return Number.isFinite(end) && end > max ? end : max;
|
|
13721
|
+
}, 0);
|
|
13722
|
+
const mediaDurationSec = typeof durationHintSec === "number" && Number.isFinite(durationHintSec) && durationHintSec > 0 ? durationHintSec : chunkDurationSec > 0 ? chunkDurationSec : null;
|
|
13723
|
+
const wordsPerMinute = mediaDurationSec && mediaDurationSec > 0 ? Math.round(words / mediaDurationSec * 600) / 10 : null;
|
|
13724
|
+
const status = words === 0 ? "empty" : words < 5 ? "low_speech_signal" : "speech_detected";
|
|
13725
|
+
const retryRecommended = words <= 2;
|
|
13726
|
+
const warnings = [];
|
|
13727
|
+
if (status === "empty") {
|
|
13728
|
+
warnings.push("No speech was transcribed. This does not distinguish silent media from an inaccessible or expired playback URL.");
|
|
13729
|
+
} else if (status === "low_speech_signal") {
|
|
13730
|
+
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.");
|
|
13731
|
+
}
|
|
13732
|
+
if (retryRecommended) {
|
|
13733
|
+
warnings.push("For a connected Meta ad, resolve the creative effectiveObjectStoryId and retry with facebook_video_transcribe when a public post URL is available.");
|
|
13734
|
+
}
|
|
13735
|
+
return {
|
|
13736
|
+
status,
|
|
13737
|
+
speechDetected: status === "speech_detected",
|
|
13738
|
+
confidence: status === "speech_detected" ? "medium" : "low",
|
|
13739
|
+
basis: "transcript_word_count_and_timing",
|
|
13740
|
+
mediaDurationSec,
|
|
13741
|
+
wordsPerMinute,
|
|
13742
|
+
retryRecommended,
|
|
13743
|
+
warnings
|
|
13744
|
+
};
|
|
13745
|
+
}
|
|
13650
13746
|
function transcriptMarkdown(title, text, chunks, durationMs) {
|
|
13651
13747
|
const fmtTs2 = (s) => `${Math.floor(s / 60)}:${String(Math.floor(s % 60)).padStart(2, "0")}`;
|
|
13652
13748
|
const lines = [title, "", `*Transcribed in ${(durationMs / 1e3).toFixed(1)}s*`, "", "## Full Text", "", text, ""];
|
|
@@ -13676,7 +13772,8 @@ async function transcribeMediaUrl(mediaUrl, markdownTitle = "# Media Transcript"
|
|
|
13676
13772
|
chunks,
|
|
13677
13773
|
durationMs,
|
|
13678
13774
|
costUsd,
|
|
13679
|
-
markdown: transcriptMarkdown(markdownTitle, text, chunks, durationMs)
|
|
13775
|
+
markdown: transcriptMarkdown(markdownTitle, text, chunks, durationMs),
|
|
13776
|
+
transcriptSignal: assessTranscriptSignal(text, chunks)
|
|
13680
13777
|
};
|
|
13681
13778
|
}
|
|
13682
13779
|
var import_client3;
|
|
@@ -14015,6 +14112,7 @@ var init_server_schemas = __esm({
|
|
|
14015
14112
|
rotateProxies: import_zod13.z.boolean().optional(),
|
|
14016
14113
|
rotateProxyEvery: import_zod13.z.number().int().min(1).max(100).optional(),
|
|
14017
14114
|
background: import_zod13.z.boolean().optional(),
|
|
14115
|
+
downloadImages: import_zod13.z.boolean().optional(),
|
|
14018
14116
|
formats: import_zod13.z.array(import_zod13.z.enum(["markdown", "links", "json", "images", "branding", "issues"])).optional()
|
|
14019
14117
|
});
|
|
14020
14118
|
YoutubeHarvestBodySchema = import_zod13.z.object({
|
|
@@ -14969,11 +15067,13 @@ var init_FacebookAdExtractor = __esm({
|
|
|
14969
15067
|
if (n) nameFreq.set(n, (nameFreq.get(n) ?? 0) + 1);
|
|
14970
15068
|
}
|
|
14971
15069
|
const advertiserName = nameFreq.size ? [...nameFreq.entries()].sort((a, b) => b[1] - a[1])[0][0] : null;
|
|
15070
|
+
const matchedAdvertisers = [...nameFreq.entries()].map(([name, adCount]) => ({ name, adCount })).sort((a, b) => b.adCount - a.adCount).slice(0, 20);
|
|
14972
15071
|
return {
|
|
14973
15072
|
listingUrl,
|
|
14974
15073
|
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14975
15074
|
advertiserPageId,
|
|
14976
15075
|
advertiserName,
|
|
15076
|
+
matchedAdvertisers,
|
|
14977
15077
|
summary: {
|
|
14978
15078
|
totalAds,
|
|
14979
15079
|
activeCount,
|
|
@@ -15496,6 +15596,45 @@ function buildPageIntelUrl(body, country) {
|
|
|
15496
15596
|
if (body.pageId?.trim()) return `https://www.facebook.com/ads/library/?active_status=all&ad_type=all&country=${country}&is_targeted_country=false&media_type=all&search_type=page&view_all_page_id=${body.pageId.trim()}`;
|
|
15497
15597
|
return `https://www.facebook.com/ads/library/?active_status=all&ad_type=all&country=${country}&q=${encodeURIComponent(body.query.trim())}&search_type=keyword_unordered`;
|
|
15498
15598
|
}
|
|
15599
|
+
function normalizeAdvertiserName(value) {
|
|
15600
|
+
return value.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, " ").trim().replace(/\s+/g, " ");
|
|
15601
|
+
}
|
|
15602
|
+
function advertiserTokenOverlap(query, candidate) {
|
|
15603
|
+
const queryTokens = new Set(normalizeAdvertiserName(query).split(" ").filter(Boolean));
|
|
15604
|
+
const candidateTokens = new Set(normalizeAdvertiserName(candidate).split(" ").filter(Boolean));
|
|
15605
|
+
if (queryTokens.size === 0 || candidateTokens.size === 0) return 0;
|
|
15606
|
+
let overlap = 0;
|
|
15607
|
+
for (const token of queryTokens) if (candidateTokens.has(token)) overlap++;
|
|
15608
|
+
return overlap / queryTokens.size;
|
|
15609
|
+
}
|
|
15610
|
+
function assessPageIntelMatch(body, result) {
|
|
15611
|
+
if (body.pageId) return { inputMode: "pageId", matchConfidence: "high", matchReason: "Exact Facebook page ID filter.", warnings: [] };
|
|
15612
|
+
if (body.libraryId) return { inputMode: "libraryId", matchConfidence: "high", matchReason: "Exact Ad Library archive ID filter.", warnings: [] };
|
|
15613
|
+
const query = body.query?.trim() ?? "";
|
|
15614
|
+
const normalizedQuery = normalizeAdvertiserName(query);
|
|
15615
|
+
const matches = result.matchedAdvertisers ?? [];
|
|
15616
|
+
const exact = matches.find((match) => normalizeAdvertiserName(match.name) === normalizedQuery);
|
|
15617
|
+
const top = matches[0];
|
|
15618
|
+
const topShare = top && result.summary.totalAds > 0 ? top.adCount / result.summary.totalAds : 0;
|
|
15619
|
+
const overlap = top ? advertiserTokenOverlap(query, top.name) : 0;
|
|
15620
|
+
const warnings = [];
|
|
15621
|
+
let matchConfidence;
|
|
15622
|
+
let matchReason;
|
|
15623
|
+
if (exact && exact.adCount / Math.max(1, result.summary.totalAds) >= 0.7) {
|
|
15624
|
+
matchConfidence = "high";
|
|
15625
|
+
matchReason = "The exact advertiser name accounts for most returned ads.";
|
|
15626
|
+
} else if (top && overlap >= 0.75 && topShare >= 0.5) {
|
|
15627
|
+
matchConfidence = "medium";
|
|
15628
|
+
matchReason = "The leading advertiser overlaps strongly with the query, but keyword search is not page-scoped.";
|
|
15629
|
+
} else {
|
|
15630
|
+
matchConfidence = "low";
|
|
15631
|
+
matchReason = "Keyword search did not identify one dominant exact advertiser match.";
|
|
15632
|
+
}
|
|
15633
|
+
if (matches.length > 1) warnings.push(`Keyword search mixed ${matches.length} advertiser names. Confirm the intended pageId before using these ads for performance or creative analysis.`);
|
|
15634
|
+
if (matchConfidence === "low") warnings.push("Low-specificity Ad Library match: treat these results as discovery candidates, not as verified ads from the requested brand.");
|
|
15635
|
+
warnings.push("Paused, cancelled, or otherwise inactive non-political ads may be absent from Ad Library. Use meta_ad_creative_media for connected ad-account creatives.");
|
|
15636
|
+
return { inputMode: "query", matchConfidence, matchReason, warnings };
|
|
15637
|
+
}
|
|
15499
15638
|
function configuredKernelProxy() {
|
|
15500
15639
|
return (process.env.BROWSER_SERVICE_PROXY_ID ?? process.env.KERNEL_PROXY_ID)?.trim() || void 0;
|
|
15501
15640
|
}
|
|
@@ -15643,8 +15782,10 @@ var init_facebook_ad_routes = __esm({
|
|
|
15643
15782
|
await logRequestEvent({ userId: fbUser.id, source: "facebook_page_intel", status: "failed", query: body.pageId ?? body.query ?? body.libraryId ?? "", error: "soft-block: empty result refunded" });
|
|
15644
15783
|
return c.json({ error: "soft-block: no ads returned (refunded)" }, 503);
|
|
15645
15784
|
}
|
|
15646
|
-
|
|
15647
|
-
|
|
15785
|
+
const match = assessPageIntelMatch(body, result);
|
|
15786
|
+
const output = { ...result, ...match };
|
|
15787
|
+
await logRequestEvent({ userId: fbUser.id, source: "facebook_page_intel", status: "done", query: body.pageId ?? body.query ?? body.libraryId ?? "", resultCount: result.ads.length, result: output });
|
|
15788
|
+
return c.json(output);
|
|
15648
15789
|
} catch (err) {
|
|
15649
15790
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15650
15791
|
if (debited && !refunded) await creditMc(fbUser.id, MC_COSTS.fb_ad, LedgerOperation.FB_AD_REFUND, "failed call");
|
|
@@ -15745,7 +15886,8 @@ var init_facebook_ad_routes = __esm({
|
|
|
15745
15886
|
text: transcript.text,
|
|
15746
15887
|
chunks: transcript.chunks,
|
|
15747
15888
|
durationMs: transcript.durationMs,
|
|
15748
|
-
markdown: transcript.markdown
|
|
15889
|
+
markdown: transcript.markdown,
|
|
15890
|
+
transcriptSignal: assessTranscriptSignal(transcript.text, transcript.chunks, video.durationSec)
|
|
15749
15891
|
};
|
|
15750
15892
|
await logRequestEvent({ userId: fbUser.id, source: "facebook_video_transcribe", status: "done", query: sourceUrl.href, resultCount: transcript.chunks.length, result });
|
|
15751
15893
|
return c.json(result);
|
|
@@ -19957,9 +20099,24 @@ function buildSeoExport(siteUrl, pages, branding) {
|
|
|
19957
20099
|
branding: branding ?? void 0
|
|
19958
20100
|
};
|
|
19959
20101
|
}
|
|
20102
|
+
function formatBackgroundJobStarted(toolLabel, data) {
|
|
20103
|
+
if (data.status !== "pending" || typeof data.jobId !== "string") return null;
|
|
20104
|
+
const jobId = data.jobId;
|
|
20105
|
+
const full = [
|
|
20106
|
+
`# ${toolLabel} \u2014 export started`,
|
|
20107
|
+
`**Job ID:** \`${jobId}\``,
|
|
20108
|
+
`
|
|
20109
|
+
Running in the background \u2014 this can take a while for large sites.`,
|
|
20110
|
+
`
|
|
20111
|
+
Poll \`check_site_export\` with this jobId to get the download link once it's ready.`
|
|
20112
|
+
].join("\n");
|
|
20113
|
+
return { content: [{ type: "text", text: full }], structuredContent: { jobId, status: "pending" } };
|
|
20114
|
+
}
|
|
19960
20115
|
async function formatExtractSite(raw, input, ctx) {
|
|
19961
20116
|
const parsed = parseData(raw);
|
|
19962
20117
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
20118
|
+
const started = formatBackgroundJobStarted("Multi-Page Site Content Crawl", parsed.data);
|
|
20119
|
+
if (started) return started;
|
|
19963
20120
|
const d = parsed.data;
|
|
19964
20121
|
const pages = d.pages ?? [];
|
|
19965
20122
|
const schemaTypesOf = (p) => p.kpo?.type ?? (Array.isArray(p.schema) && p.schema.length ? [`${p.schema.length} block(s)`] : []);
|
|
@@ -20077,6 +20234,8 @@ ${pageDetails}${tips}`;
|
|
|
20077
20234
|
async function formatAuditSite(raw, input, ctx) {
|
|
20078
20235
|
const parsed = parseData(raw);
|
|
20079
20236
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
20237
|
+
const started = formatBackgroundJobStarted("Technical SEO Audit", parsed.data);
|
|
20238
|
+
if (started) return started;
|
|
20080
20239
|
const d = parsed.data;
|
|
20081
20240
|
const pages = d.pages ?? [];
|
|
20082
20241
|
const schemaTypesOf = (p) => p.kpo?.type ?? (Array.isArray(p.schema) && p.schema.length ? [`${p.schema.length} block(s)`] : []);
|
|
@@ -20148,6 +20307,42 @@ ${imgLine}`
|
|
|
20148
20307
|
].join("\n");
|
|
20149
20308
|
return { content: [{ type: "text", text: full }], structuredContent };
|
|
20150
20309
|
}
|
|
20310
|
+
function formatCheckSiteExport(raw, input) {
|
|
20311
|
+
const parsed = parseData(raw);
|
|
20312
|
+
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
20313
|
+
const d = parsed.data;
|
|
20314
|
+
const bundle = (d.artifacts ?? []).find((a) => a.key.endsWith("bundle.zip")) ?? null;
|
|
20315
|
+
const progress = d.totalUrls ? `${d.doneUrls ?? 0}/${d.totalUrls} pages` : "starting";
|
|
20316
|
+
const body = d.status === "complete" && bundle ? `
|
|
20317
|
+
## \u2705 Ready
|
|
20318
|
+
**Download:** ${bundle.url}
|
|
20319
|
+
**Size:** ${(bundle.bytes / 1e6).toFixed(1)} MB` : d.status === "complete" ? `
|
|
20320
|
+
## \u26A0\uFE0F Complete, but no bundle was produced
|
|
20321
|
+
Check the job's artifacts \u2014 nothing matched \`bundle.zip\`.` : d.status === "failed" ? `
|
|
20322
|
+
## \u274C Failed
|
|
20323
|
+
${d.error ?? "no error message recorded"}` : `
|
|
20324
|
+
## \u23F3 Not ready yet
|
|
20325
|
+
Status: ${d.status} (${progress}). Poll again shortly.`;
|
|
20326
|
+
const full = [
|
|
20327
|
+
`# Site Export: ${d.startUrl ?? input.jobId}`,
|
|
20328
|
+
`**Job ID:** \`${d.jobId}\``,
|
|
20329
|
+
`**Status:** ${d.status}`,
|
|
20330
|
+
body
|
|
20331
|
+
].join("\n");
|
|
20332
|
+
return {
|
|
20333
|
+
...oneBlock(full),
|
|
20334
|
+
structuredContent: {
|
|
20335
|
+
jobId: d.jobId,
|
|
20336
|
+
status: d.status,
|
|
20337
|
+
startUrl: d.startUrl,
|
|
20338
|
+
totalUrls: d.totalUrls,
|
|
20339
|
+
doneUrls: d.doneUrls,
|
|
20340
|
+
bundleUrl: bundle?.url ?? null,
|
|
20341
|
+
bundleBytes: bundle?.bytes ?? null,
|
|
20342
|
+
error: d.error ?? null
|
|
20343
|
+
}
|
|
20344
|
+
};
|
|
20345
|
+
}
|
|
20151
20346
|
function formatYoutubeHarvest(raw, input) {
|
|
20152
20347
|
const parsed = parseData(raw);
|
|
20153
20348
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
@@ -20257,6 +20452,7 @@ function formatFacebookPageIntel(raw, input) {
|
|
|
20257
20452
|
const advertiser = d.advertiserName ?? input.query ?? input.pageId ?? input.libraryId ?? "Advertiser";
|
|
20258
20453
|
const ads = d.ads ?? [];
|
|
20259
20454
|
const s = d.summary ?? { totalAds: 0, activeCount: 0, videoCount: 0, imageCount: 0 };
|
|
20455
|
+
const warnings = d.warnings ?? [];
|
|
20260
20456
|
const adBlocks = ads.map((ad, i) => [
|
|
20261
20457
|
`### Ad ${i + 1}${ad.libraryId ? ` \xB7 \`${ad.libraryId}\`` : ""} \u2014 ${ad.status ?? "\u2014"} \xB7 ${ad.creativeType ?? "\u2014"} \xB7 ${ad.startDate ?? ad.started ?? "\u2014"}`,
|
|
20262
20458
|
ad.headline ? `**Headline:** ${ad.headline}` : "",
|
|
@@ -20269,19 +20465,28 @@ function formatFacebookPageIntel(raw, input) {
|
|
|
20269
20465
|
const full = [
|
|
20270
20466
|
`# Facebook Ad Intel: ${advertiser}`,
|
|
20271
20467
|
`**${s.totalAds} ads** \xB7 ${s.activeCount} active \xB7 ${s.videoCount} video \xB7 ${s.imageCount} image`,
|
|
20468
|
+
d.matchConfidence ? `**Match confidence:** ${d.matchConfidence}${d.matchReason ? ` \u2014 ${d.matchReason}` : ""}` : "",
|
|
20469
|
+
warnings.length ? `
|
|
20470
|
+
> \u26A0\uFE0F ${warnings.join("\n> \u26A0\uFE0F ")}` : "",
|
|
20272
20471
|
`
|
|
20273
20472
|
${adBlocks}`,
|
|
20274
20473
|
`
|
|
20275
20474
|
---
|
|
20276
20475
|
\u{1F4A1} **Tips**
|
|
20277
|
-
-
|
|
20278
|
-
-
|
|
20279
|
-
-
|
|
20476
|
+
- Connected, paused, or dark ad: use \`meta_ad_creative_media\` with its adId
|
|
20477
|
+
- Ad Library direct video URL: use \`facebook_ad_transcribe\` immediately
|
|
20478
|
+
- Public Facebook post/reel URL: use \`facebook_video_transcribe\`
|
|
20479
|
+
- Find the exact advertiser pageId first: use \`facebook_ad_search\``
|
|
20280
20480
|
].filter(Boolean).join("\n");
|
|
20281
20481
|
return {
|
|
20282
20482
|
...oneBlock(full),
|
|
20283
20483
|
structuredContent: {
|
|
20284
20484
|
advertiserName: d.advertiserName ?? null,
|
|
20485
|
+
inputMode: d.inputMode ?? (input.pageId ? "pageId" : input.libraryId ? "libraryId" : "query"),
|
|
20486
|
+
matchConfidence: d.matchConfidence ?? (input.query ? "low" : "high"),
|
|
20487
|
+
matchReason: d.matchReason ?? (input.query ? "Keyword search result was not confidence-scored by the server." : "Exact identifier filter."),
|
|
20488
|
+
warnings,
|
|
20489
|
+
matchedAdvertisers: d.matchedAdvertisers ?? [],
|
|
20285
20490
|
totalAds: s.totalAds ?? 0,
|
|
20286
20491
|
activeCount: s.activeCount ?? 0,
|
|
20287
20492
|
videoCount: s.videoCount ?? 0,
|
|
@@ -21210,6 +21415,7 @@ function formatFacebookAdTranscribe(raw, input) {
|
|
|
21210
21415
|
const chunks = d.chunks ?? [];
|
|
21211
21416
|
const durSec = d.durationMs ? (d.durationMs / 1e3).toFixed(0) : "\u2014";
|
|
21212
21417
|
const words = wordCount(text);
|
|
21418
|
+
const signal = d.transcriptSignal ?? assessTranscriptSignal(text, chunks);
|
|
21213
21419
|
const chunkRows = chunks.slice(0, 50).map((c) => {
|
|
21214
21420
|
const sec = Number.isFinite(c.timestamp[0]) ? Math.floor(c.timestamp[0]) : 0;
|
|
21215
21421
|
const mm = String(Math.floor(sec / 60)).padStart(2, "0");
|
|
@@ -21218,7 +21424,9 @@ function formatFacebookAdTranscribe(raw, input) {
|
|
|
21218
21424
|
}).join("\n");
|
|
21219
21425
|
const full = [
|
|
21220
21426
|
`# Facebook Ad Transcript`,
|
|
21221
|
-
`**
|
|
21427
|
+
`**Transcription elapsed:** ${durSec}s \xB7 **${words} words** \xB7 **Signal:** ${signal.status}`,
|
|
21428
|
+
signal.warnings.length ? `
|
|
21429
|
+
> \u26A0\uFE0F ${signal.warnings.join("\n> \u26A0\uFE0F ")}` : "",
|
|
21222
21430
|
`
|
|
21223
21431
|
## Full Transcript
|
|
21224
21432
|
${text}`,
|
|
@@ -21240,6 +21448,7 @@ ${chunkRows}` : "",
|
|
|
21240
21448
|
durationMs: typeof d.durationMs === "number" ? d.durationMs : null,
|
|
21241
21449
|
transcriptText: text,
|
|
21242
21450
|
chunks: structuredTranscriptChunks(chunks),
|
|
21451
|
+
transcriptSignal: signal,
|
|
21243
21452
|
resolvedInputs: {
|
|
21244
21453
|
videoUrl: input.videoUrl
|
|
21245
21454
|
}
|
|
@@ -21256,6 +21465,7 @@ function formatFacebookVideoTranscribe(raw, input) {
|
|
|
21256
21465
|
const durSec = d.durationMs ? (d.durationMs / 1e3).toFixed(0) : "\u2014";
|
|
21257
21466
|
const videoDuration = typeof d.videoDurationSec === "number" ? `${Math.round(d.videoDurationSec)}s` : "\u2014";
|
|
21258
21467
|
const label = d.videoId ? `Facebook Organic Video \`${d.videoId}\`` : "Facebook Organic Video";
|
|
21468
|
+
const signal = d.transcriptSignal ?? assessTranscriptSignal(text, chunks, d.videoDurationSec);
|
|
21259
21469
|
const chunkRows = chunks.slice(0, 50).map((c) => {
|
|
21260
21470
|
const sec = Number.isFinite(c.timestamp[0]) ? Math.floor(c.timestamp[0]) : 0;
|
|
21261
21471
|
const mm = String(Math.floor(sec / 60)).padStart(2, "0");
|
|
@@ -21266,6 +21476,9 @@ function formatFacebookVideoTranscribe(raw, input) {
|
|
|
21266
21476
|
`# ${label} Transcript`,
|
|
21267
21477
|
d.ownerName ? `**Owner:** ${d.ownerName}` : "",
|
|
21268
21478
|
`**Video duration:** ${videoDuration} \xB7 **Transcribed in:** ${durSec}s \xB7 **${wordCount2} words**`,
|
|
21479
|
+
`**Transcript signal:** ${signal.status} (${signal.confidence} heuristic confidence)`,
|
|
21480
|
+
signal.warnings.length ? `
|
|
21481
|
+
> \u26A0\uFE0F ${signal.warnings.join("\n> \u26A0\uFE0F ")}` : "",
|
|
21269
21482
|
d.pageUrl ? `**Page URL:** ${d.pageUrl}` : `**Page URL:** ${input.url}`,
|
|
21270
21483
|
d.videoUrl ? `**Extracted MP4:** \`${d.videoUrl}\`` : "",
|
|
21271
21484
|
`
|
|
@@ -21298,7 +21511,8 @@ ${chunkRows}` : "",
|
|
|
21298
21511
|
startSec: Number.isFinite(c.timestamp[0]) ? c.timestamp[0] : 0,
|
|
21299
21512
|
endSec: Number.isFinite(c.timestamp[1]) ? c.timestamp[1] : 0,
|
|
21300
21513
|
text: c.text
|
|
21301
|
-
}))
|
|
21514
|
+
})),
|
|
21515
|
+
transcriptSignal: signal
|
|
21302
21516
|
}
|
|
21303
21517
|
};
|
|
21304
21518
|
}
|
|
@@ -21614,6 +21828,7 @@ var init_mcp_response_formatter = __esm({
|
|
|
21614
21828
|
init_image_audit();
|
|
21615
21829
|
init_report_artifact_offload();
|
|
21616
21830
|
init_rates();
|
|
21831
|
+
init_media_transcription();
|
|
21617
21832
|
INLINE_BUDGET_BYTES = Number(process.env.MCP_SCRAPER_INLINE_BUDGET ?? 5e4);
|
|
21618
21833
|
STRUCTURED_ARRAY_CAP = 25;
|
|
21619
21834
|
reportSavingEnabled = true;
|
|
@@ -24945,7 +25160,7 @@ async function capturePageSnapshot(target, options = {}) {
|
|
|
24945
25160
|
async function capturePageSnapshots(targets, options = {}) {
|
|
24946
25161
|
const timeoutMs = normalizeTimeoutMs(options.timeoutMs);
|
|
24947
25162
|
const maxConcurrency = normalizeMaxConcurrency(options.maxConcurrency);
|
|
24948
|
-
const limit = (0,
|
|
25163
|
+
const limit = (0, import_p_limit4.default)(maxConcurrency);
|
|
24949
25164
|
const pageSnapshotArtifacts = await Promise.all(
|
|
24950
25165
|
targets.map((target) => limit(() => capturePageSnapshot(target, { ...options, timeoutMs })))
|
|
24951
25166
|
);
|
|
@@ -24970,12 +25185,12 @@ async function capturePageSnapshots(targets, options = {}) {
|
|
|
24970
25185
|
}
|
|
24971
25186
|
};
|
|
24972
25187
|
}
|
|
24973
|
-
var import_node_crypto10,
|
|
25188
|
+
var import_node_crypto10, import_p_limit4, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_CONCURRENCY, DEFAULT_MAX_CONTENT_CHARS;
|
|
24974
25189
|
var init_page_snapshot_extractor = __esm({
|
|
24975
25190
|
"src/serp-intelligence/page-snapshot-extractor.ts"() {
|
|
24976
25191
|
"use strict";
|
|
24977
25192
|
import_node_crypto10 = require("crypto");
|
|
24978
|
-
|
|
25193
|
+
import_p_limit4 = __toESM(require("p-limit"), 1);
|
|
24979
25194
|
init_kpo_extractor();
|
|
24980
25195
|
init_url_utils();
|
|
24981
25196
|
DEFAULT_TIMEOUT_MS = 15e3;
|
|
@@ -27642,7 +27857,7 @@ var PACKAGE_VERSION;
|
|
|
27642
27857
|
var init_version = __esm({
|
|
27643
27858
|
"src/version.ts"() {
|
|
27644
27859
|
"use strict";
|
|
27645
|
-
PACKAGE_VERSION = "0.
|
|
27860
|
+
PACKAGE_VERSION = "0.20.0";
|
|
27646
27861
|
}
|
|
27647
27862
|
});
|
|
27648
27863
|
|
|
@@ -27682,10 +27897,14 @@ seam is noted so you can chain them.
|
|
|
27682
27897
|
|
|
27683
27898
|
## Facebook
|
|
27684
27899
|
- Find advertisers -> **facebook_ad_search** (returns \`advertisers[]\` with pageId, libraryId).
|
|
27685
|
-
-
|
|
27686
|
-
|
|
27687
|
-
|
|
27688
|
-
-
|
|
27900
|
+
- Public Ad Library scan -> **facebook_page_intel**. Prefer pageId/libraryId. Query mode is broad discovery,
|
|
27901
|
+
can mix advertisers, and must be checked through matchConfidence/warnings. Paused non-political ads may
|
|
27902
|
+
be absent even when the connected ad account still has them.
|
|
27903
|
+
- Connected ad-account creative -> **meta_ad_creative_media** (takes connectionId + adId). This is the
|
|
27904
|
+
preferred paused/dark-ad path and returns actual image content plus exact video follow-up arguments.
|
|
27905
|
+
- Direct Graph/Ad Library CDN video -> **facebook_ad_transcribe**. Use transient source URLs immediately.
|
|
27906
|
+
- Public reel/video/post -> **facebook_video_transcribe**. Use the public candidate returned by
|
|
27907
|
+
\`meta_ad_creative_media\` when Graph did not provide a direct source; dark posts may not resolve publicly.
|
|
27689
27908
|
|
|
27690
27909
|
## Google Ads (Ads Transparency Center)
|
|
27691
27910
|
- Find advertisers by domain or brand -> **google_ads_search** (returns \`advertisers[]\` with advertiserId).
|
|
@@ -27727,8 +27946,8 @@ Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when
|
|
|
27727
27946
|
recording) locates a DOM target and returns a ready-to-use timed annotation -> **browser_replay_stop**
|
|
27728
27947
|
ends it -> feed collected annotations to **browser_replay_annotate**, or just
|
|
27729
27948
|
**browser_replay_download** the plain MP4. **browser_list_replays** recovers replay ids.
|
|
27730
|
-
- Video breakdown is async: **video_frame_analysis** takes a
|
|
27731
|
-
|
|
27949
|
+
- Video breakdown is async: **video_frame_analysis** takes a supported public video page or direct media URL
|
|
27950
|
+
(for connected Meta ads, use the exact sourceUrl/public candidate returned by \`meta_ad_creative_media\`) and
|
|
27732
27951
|
returns a \`runId\` immediately -> poll **video_frame_analysis_status** with that \`runId\` until \`status\`
|
|
27733
27952
|
is \`done\`.
|
|
27734
27953
|
|
|
@@ -27737,6 +27956,8 @@ Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when
|
|
|
27737
27956
|
sites. It returns a saved folder/artifact plus a summary, not the full content inline.
|
|
27738
27957
|
- Browser sessions and media transcription cost credits and take longer \u2014 prefer the cheapest tool that
|
|
27739
27958
|
answers the question (plain search/extract before browser agents).
|
|
27959
|
+
- Use the hosted browser as a controlled resolver for validated public Facebook post/reel redirects only
|
|
27960
|
+
when connected Graph media did not provide a playable source. It is not a bypass for URL/SSRF restrictions.
|
|
27740
27961
|
- Large results are saved to disk or an artifact and returned as a summary plus a path or artifactId;
|
|
27741
27962
|
read it back for full detail rather than expecting the whole payload inline.
|
|
27742
27963
|
|
|
@@ -27814,8 +28035,216 @@ var init_output_schema_registry = __esm({
|
|
|
27814
28035
|
}
|
|
27815
28036
|
});
|
|
27816
28037
|
|
|
28038
|
+
// src/mcp/meta-ad-creative-media.ts
|
|
28039
|
+
function asRecord(value) {
|
|
28040
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
28041
|
+
}
|
|
28042
|
+
function parseToolPayload(result) {
|
|
28043
|
+
const structured = asRecord(result.structuredContent);
|
|
28044
|
+
if (structured) return structured;
|
|
28045
|
+
for (const block of result.content ?? []) {
|
|
28046
|
+
if (block.type !== "text") continue;
|
|
28047
|
+
try {
|
|
28048
|
+
const parsed = JSON.parse(block.text);
|
|
28049
|
+
const row = asRecord(parsed);
|
|
28050
|
+
if (row) return row;
|
|
28051
|
+
} catch {
|
|
28052
|
+
continue;
|
|
28053
|
+
}
|
|
28054
|
+
}
|
|
28055
|
+
return null;
|
|
28056
|
+
}
|
|
28057
|
+
function allowedMetaImageUrl(raw) {
|
|
28058
|
+
try {
|
|
28059
|
+
const url = new URL(raw);
|
|
28060
|
+
if (url.protocol !== "https:") return null;
|
|
28061
|
+
const host = url.hostname.toLowerCase().replace(/\.$/, "");
|
|
28062
|
+
if (!META_IMAGE_HOSTS.some((suffix2) => host === suffix2 || host.endsWith(`.${suffix2}`))) return null;
|
|
28063
|
+
return url;
|
|
28064
|
+
} catch {
|
|
28065
|
+
return null;
|
|
28066
|
+
}
|
|
28067
|
+
}
|
|
28068
|
+
function detectImageMime(bytes) {
|
|
28069
|
+
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "image/jpeg";
|
|
28070
|
+
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";
|
|
28071
|
+
if (bytes.length >= 12 && String.fromCharCode(...bytes.slice(0, 4)) === "RIFF" && String.fromCharCode(...bytes.slice(8, 12)) === "WEBP") return "image/webp";
|
|
28072
|
+
if (bytes.length >= 6) {
|
|
28073
|
+
const signature = String.fromCharCode(...bytes.slice(0, 6));
|
|
28074
|
+
if (signature === "GIF87a" || signature === "GIF89a") return "image/gif";
|
|
28075
|
+
}
|
|
28076
|
+
return null;
|
|
28077
|
+
}
|
|
28078
|
+
async function readBoundedBody(response, maxBytes) {
|
|
28079
|
+
const contentLength = Number(response.headers.get("content-length") ?? "");
|
|
28080
|
+
if (Number.isFinite(contentLength) && contentLength > maxBytes) throw new Error("image_too_large");
|
|
28081
|
+
if (!response.body) {
|
|
28082
|
+
const bytes2 = new Uint8Array(await response.arrayBuffer());
|
|
28083
|
+
if (bytes2.byteLength > maxBytes) throw new Error("image_too_large");
|
|
28084
|
+
return bytes2;
|
|
28085
|
+
}
|
|
28086
|
+
const reader = response.body.getReader();
|
|
28087
|
+
const chunks = [];
|
|
28088
|
+
let total = 0;
|
|
28089
|
+
try {
|
|
28090
|
+
while (true) {
|
|
28091
|
+
const { done, value } = await reader.read();
|
|
28092
|
+
if (done) break;
|
|
28093
|
+
if (!value) continue;
|
|
28094
|
+
total += value.byteLength;
|
|
28095
|
+
if (total > maxBytes) throw new Error("image_too_large");
|
|
28096
|
+
chunks.push(value);
|
|
28097
|
+
}
|
|
28098
|
+
} finally {
|
|
28099
|
+
reader.releaseLock();
|
|
28100
|
+
}
|
|
28101
|
+
const bytes = new Uint8Array(total);
|
|
28102
|
+
let offset = 0;
|
|
28103
|
+
for (const chunk of chunks) {
|
|
28104
|
+
bytes.set(chunk, offset);
|
|
28105
|
+
offset += chunk.byteLength;
|
|
28106
|
+
}
|
|
28107
|
+
return bytes;
|
|
28108
|
+
}
|
|
28109
|
+
async function fetchMetaImage(rawUrl, maxBytes) {
|
|
28110
|
+
let url = allowedMetaImageUrl(rawUrl);
|
|
28111
|
+
if (!url) throw new Error("image_host_not_allowed");
|
|
28112
|
+
for (let redirect = 0; redirect <= MAX_REDIRECTS; redirect++) {
|
|
28113
|
+
const response = await fetch(url, {
|
|
28114
|
+
redirect: "manual",
|
|
28115
|
+
headers: { Accept: "image/jpeg,image/png,image/webp,image/gif;q=0.8" },
|
|
28116
|
+
signal: AbortSignal.timeout(12e3)
|
|
28117
|
+
});
|
|
28118
|
+
if ([301, 302, 303, 307, 308].includes(response.status)) {
|
|
28119
|
+
const location2 = response.headers.get("location");
|
|
28120
|
+
if (!location2 || redirect === MAX_REDIRECTS) throw new Error("image_redirect_rejected");
|
|
28121
|
+
url = allowedMetaImageUrl(new URL(location2, url).href);
|
|
28122
|
+
if (!url) throw new Error("image_redirect_host_not_allowed");
|
|
28123
|
+
continue;
|
|
28124
|
+
}
|
|
28125
|
+
if (!response.ok) throw new Error(`image_fetch_${response.status}`);
|
|
28126
|
+
const bytes = await readBoundedBody(response, maxBytes);
|
|
28127
|
+
const mimeType = detectImageMime(bytes);
|
|
28128
|
+
if (!mimeType) throw new Error("image_content_invalid");
|
|
28129
|
+
return { bytes, mimeType, finalUrl: url.href };
|
|
28130
|
+
}
|
|
28131
|
+
throw new Error("image_redirect_rejected");
|
|
28132
|
+
}
|
|
28133
|
+
function firstString(row, keys) {
|
|
28134
|
+
for (const key of keys) {
|
|
28135
|
+
const value = row[key];
|
|
28136
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
28137
|
+
}
|
|
28138
|
+
return null;
|
|
28139
|
+
}
|
|
28140
|
+
function displayUrl(raw) {
|
|
28141
|
+
try {
|
|
28142
|
+
const url = new URL(raw);
|
|
28143
|
+
return `${url.origin}${url.pathname}`;
|
|
28144
|
+
} catch {
|
|
28145
|
+
return "[invalid media URL]";
|
|
28146
|
+
}
|
|
28147
|
+
}
|
|
28148
|
+
async function buildMetaAdCreativeMediaResult(executor, input) {
|
|
28149
|
+
const providerResult = await executor.readServiceConnection({
|
|
28150
|
+
connectionId: input.connectionId,
|
|
28151
|
+
tool: "resolve-ad-creative-media",
|
|
28152
|
+
args: { adId: input.adId }
|
|
28153
|
+
});
|
|
28154
|
+
if (providerResult.isError) return providerResult;
|
|
28155
|
+
const payload = parseToolPayload(providerResult);
|
|
28156
|
+
const resolved = asRecord(payload?.["result"]) ?? payload;
|
|
28157
|
+
if (!resolved || typeof resolved["creativeId"] !== "string") {
|
|
28158
|
+
return {
|
|
28159
|
+
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." }],
|
|
28160
|
+
isError: true
|
|
28161
|
+
};
|
|
28162
|
+
}
|
|
28163
|
+
const warnings = Array.isArray(resolved["warnings"]) ? resolved["warnings"].filter((value) => typeof value === "string") : [];
|
|
28164
|
+
const limitations = Array.isArray(resolved["limitations"]) ? resolved["limitations"].filter((value) => typeof value === "string") : [];
|
|
28165
|
+
const images = Array.isArray(resolved["images"]) ? resolved["images"].map(asRecord).filter((value) => value !== null) : [];
|
|
28166
|
+
const videos = Array.isArray(resolved["videos"]) ? resolved["videos"].map(asRecord).filter((value) => value !== null) : [];
|
|
28167
|
+
const nextActions = Array.isArray(resolved["nextActions"]) ? resolved["nextActions"].map(asRecord).filter((value) => value !== null) : [];
|
|
28168
|
+
const content = [];
|
|
28169
|
+
const inlineImages = [];
|
|
28170
|
+
if (input.imageMode === "inline_preview") {
|
|
28171
|
+
let remaining = MAX_TOTAL_IMAGE_BYTES;
|
|
28172
|
+
const seen = /* @__PURE__ */ new Set();
|
|
28173
|
+
for (const image of images) {
|
|
28174
|
+
if (inlineImages.length >= input.maxInlineImages || remaining <= 0) break;
|
|
28175
|
+
const sourceUrl = firstString(image, ["url", "thumbnailUrl", "permalinkUrl"]);
|
|
28176
|
+
if (!sourceUrl || seen.has(sourceUrl)) continue;
|
|
28177
|
+
seen.add(sourceUrl);
|
|
28178
|
+
try {
|
|
28179
|
+
const fetched = await fetchMetaImage(sourceUrl, Math.min(MAX_IMAGE_BYTES, remaining));
|
|
28180
|
+
const contentIndex = content.length + 1;
|
|
28181
|
+
content.push({ type: "image", data: Buffer.from(fetched.bytes).toString("base64"), mimeType: fetched.mimeType });
|
|
28182
|
+
inlineImages.push({ sourceUrl: fetched.finalUrl, mimeType: fetched.mimeType, bytes: fetched.bytes.byteLength, contentIndex });
|
|
28183
|
+
remaining -= fetched.bytes.byteLength;
|
|
28184
|
+
} catch (error) {
|
|
28185
|
+
const code = error instanceof Error ? error.message : "image_fetch_failed";
|
|
28186
|
+
warnings.push(`Creative image preview could not be attached (${code}) for ${displayUrl(sourceUrl)}.`);
|
|
28187
|
+
}
|
|
28188
|
+
}
|
|
28189
|
+
}
|
|
28190
|
+
const mediaType = typeof resolved["mediaType"] === "string" ? resolved["mediaType"] : "unknown";
|
|
28191
|
+
const publicPostCandidateUrl = typeof resolved["publicPostCandidateUrl"] === "string" ? resolved["publicPostCandidateUrl"] : null;
|
|
28192
|
+
const summary = [
|
|
28193
|
+
"# Meta Ad Creative Media",
|
|
28194
|
+
`**Ad:** ${typeof resolved["adName"] === "string" ? resolved["adName"] : input.adId} (\`${input.adId}\`)`,
|
|
28195
|
+
`**Creative:** ${typeof resolved["creativeName"] === "string" ? resolved["creativeName"] : resolved["creativeId"]} (\`${resolved["creativeId"]}\`)`,
|
|
28196
|
+
`**Media:** ${mediaType} \xB7 ${images.length} image descriptor(s) \xB7 ${videos.length} video descriptor(s) \xB7 ${inlineImages.length} inline visual preview(s)`,
|
|
28197
|
+
publicPostCandidateUrl ? `**Public post candidate:** ${publicPostCandidateUrl} (unverified; dark/paused ads may not be public)` : "",
|
|
28198
|
+
nextActions.length ? `
|
|
28199
|
+
## Recommended next calls
|
|
28200
|
+
${nextActions.map((action) => `- \`${String(action["tool"] ?? "")}\`: ${String(action["reason"] ?? "")}`).join("\n")}` : "",
|
|
28201
|
+
warnings.length ? `
|
|
28202
|
+
## Warnings
|
|
28203
|
+
${warnings.map((warning) => `- ${warning}`).join("\n")}` : "",
|
|
28204
|
+
limitations.length ? `
|
|
28205
|
+
## Limitations
|
|
28206
|
+
${limitations.map((limitation) => `- ${limitation}`).join("\n")}` : ""
|
|
28207
|
+
].filter(Boolean).join("\n");
|
|
28208
|
+
content.unshift({ type: "text", text: summary });
|
|
28209
|
+
return {
|
|
28210
|
+
content,
|
|
28211
|
+
structuredContent: {
|
|
28212
|
+
ok: true,
|
|
28213
|
+
adId: String(resolved["adId"] ?? input.adId),
|
|
28214
|
+
adName: typeof resolved["adName"] === "string" ? resolved["adName"] : null,
|
|
28215
|
+
adAccountId: typeof resolved["adAccountId"] === "string" ? resolved["adAccountId"] : null,
|
|
28216
|
+
creativeId: String(resolved["creativeId"]),
|
|
28217
|
+
creativeName: typeof resolved["creativeName"] === "string" ? resolved["creativeName"] : null,
|
|
28218
|
+
effectiveObjectStoryId: typeof resolved["effectiveObjectStoryId"] === "string" ? resolved["effectiveObjectStoryId"] : null,
|
|
28219
|
+
sourceFacebookPostId: typeof resolved["sourceFacebookPostId"] === "string" ? resolved["sourceFacebookPostId"] : null,
|
|
28220
|
+
objectStoryId: typeof resolved["objectStoryId"] === "string" ? resolved["objectStoryId"] : null,
|
|
28221
|
+
pageId: typeof resolved["pageId"] === "string" ? resolved["pageId"] : null,
|
|
28222
|
+
postId: typeof resolved["postId"] === "string" ? resolved["postId"] : null,
|
|
28223
|
+
publicPostCandidateUrl,
|
|
28224
|
+
publicPostStatus: "unverified",
|
|
28225
|
+
mediaType,
|
|
28226
|
+
images,
|
|
28227
|
+
videos,
|
|
28228
|
+
inlineImages,
|
|
28229
|
+
nextActions,
|
|
28230
|
+
warnings,
|
|
28231
|
+
limitations
|
|
28232
|
+
}
|
|
28233
|
+
};
|
|
28234
|
+
}
|
|
28235
|
+
var META_IMAGE_HOSTS, MAX_IMAGE_BYTES, MAX_TOTAL_IMAGE_BYTES, MAX_REDIRECTS;
|
|
28236
|
+
var init_meta_ad_creative_media = __esm({
|
|
28237
|
+
"src/mcp/meta-ad-creative-media.ts"() {
|
|
28238
|
+
"use strict";
|
|
28239
|
+
META_IMAGE_HOSTS = ["facebook.com", "fbcdn.net", "fbsbx.com", "cdninstagram.com"];
|
|
28240
|
+
MAX_IMAGE_BYTES = 15e5;
|
|
28241
|
+
MAX_TOTAL_IMAGE_BYTES = 25e5;
|
|
28242
|
+
MAX_REDIRECTS = 3;
|
|
28243
|
+
}
|
|
28244
|
+
});
|
|
28245
|
+
|
|
27817
28246
|
// src/mcp/mcp-tool-schemas.ts
|
|
27818
|
-
var import_zod33, HarvestPaaInputSchema, ExtractUrlInputSchema, DiffPageInputSchema, MapSiteUrlsInputSchema, ExtractSiteInputSchema, AuditSiteInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, RedditThreadInputSchema, VideoFrameAnalysisInputSchema, VideoFrameAnalysisStatusInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, GoogleAdsSearchInputSchema, GoogleAdsPageIntelInputSchema, GoogleAdsTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, TrustpilotReviewsInputSchema, G2ReviewsInputSchema, ReviewCardSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, ArtifactPointerOutputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryWorkflowOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, DiffPageOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, MapsPlaceIntelOutputSchema, TrustpilotReviewsOutputSchema, G2ReviewsOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, VideoFrameAnalysisOutputSchema, VideoFrameAnalysisStatusOutputSchema, RedditThreadOutputSchema, FacebookPageIntelOutputSchema, GoogleAdsSearchOutputSchema, GoogleAdsPageIntelOutputSchema, FacebookVideoTranscribeOutputSchema, TranscriptChunkOutput, InstagramBrowserOutput, InstagramPaginationOutput, InstagramProfileContentOutputSchema, InstagramMediaTrackOutput, InstagramDownloadOutput, InstagramMediaDownloadOutputSchema, YoutubeTranscribeOutputSchema, FacebookAdTranscribeOutputSchema, GoogleAdsTranscribeOutputSchema, CaptureSerpSnapshotOutputSchema, CaptureSerpPageSnapshotsOutputSchema, CreditsInfoInputSchema, WorkflowIdSchema2, WorkflowListInputSchema, WorkflowSuggestInputSchema, WorkflowRunInputSchema, WorkflowStepInputSchema, WorkflowStatusInputSchema, WorkflowArtifactReadInputSchema, WorkflowRecipeOutput, WorkflowDefinitionOutput, WorkflowArtifactOutput, WorkflowListOutputSchema, WorkflowSuggestOutputSchema, WorkflowRunOutputSchema, WorkflowStepOutputSchema, WorkflowStatusOutputSchema, WorkflowArtifactReadOutputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema, ReportArtifactReadInputSchema, ReportArtifactReadOutputSchema, ListServiceConnectionsInputSchema, ListServiceConnectionsOutputSchema, ReadServiceConnectionInputSchema, ReadServiceConnectionOutputSchema, ImportServiceConnectionToMemoryInputSchema, ImportServiceConnectionToMemoryOutputSchema, DescribeServiceConnectionToolInputSchema, DescribeServiceConnectionToolOutputSchema, ConnectedDataContinuationSchema, ExportConnectedServiceDataInputSchema, ConnectedDataArtifactSchema, ExportConnectedServiceDataOutputSchema, RenewConnectedDataExportDownloadInputSchema, RenewConnectedDataExportDownloadOutputSchema, CallServiceConnectionActionInputSchema, CallServiceConnectionActionOutputSchema, SetScheduledActionConnectionsInputSchema, SetScheduledActionConnectionsOutputSchema, SlackSendMessageInputSchema, SlackSendMessageOutputSchema, GmailSendMessageInputSchema, GmailSendMessageOutputSchema, GoogleCalendarCreateEventInputSchema, GoogleCalendarCreateEventOutputSchema, ZoomCreateMeetingInputSchema, ZoomCreateMeetingOutputSchema;
|
|
28247
|
+
var import_zod33, HarvestPaaInputSchema, ExtractUrlInputSchema, DiffPageInputSchema, MapSiteUrlsInputSchema, ExtractSiteInputSchema, AuditSiteInputSchema, CheckSiteExportInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, RedditThreadInputSchema, VideoFrameAnalysisInputSchema, VideoFrameAnalysisStatusInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, GoogleAdsSearchInputSchema, GoogleAdsPageIntelInputSchema, GoogleAdsTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, TrustpilotReviewsInputSchema, G2ReviewsInputSchema, ReviewCardSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, ArtifactPointerOutputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryWorkflowOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, DiffPageOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, CheckSiteExportOutputSchema, MapsPlaceIntelOutputSchema, TrustpilotReviewsOutputSchema, G2ReviewsOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, VideoFrameAnalysisOutputSchema, VideoFrameAnalysisStatusOutputSchema, RedditThreadOutputSchema, FacebookPageIntelOutputSchema, GoogleAdsSearchOutputSchema, GoogleAdsPageIntelOutputSchema, TranscriptSignalOutput, FacebookVideoTranscribeOutputSchema, TranscriptChunkOutput, InstagramBrowserOutput, InstagramPaginationOutput, InstagramProfileContentOutputSchema, InstagramMediaTrackOutput, InstagramDownloadOutput, InstagramMediaDownloadOutputSchema, YoutubeTranscribeOutputSchema, FacebookAdTranscribeOutputSchema, GoogleAdsTranscribeOutputSchema, CaptureSerpSnapshotOutputSchema, CaptureSerpPageSnapshotsOutputSchema, CreditsInfoInputSchema, WorkflowIdSchema2, WorkflowListInputSchema, WorkflowSuggestInputSchema, WorkflowRunInputSchema, WorkflowStepInputSchema, WorkflowStatusInputSchema, WorkflowArtifactReadInputSchema, WorkflowRecipeOutput, WorkflowDefinitionOutput, WorkflowArtifactOutput, WorkflowListOutputSchema, WorkflowSuggestOutputSchema, WorkflowRunOutputSchema, WorkflowStepOutputSchema, WorkflowStatusOutputSchema, WorkflowArtifactReadOutputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema, ReportArtifactReadInputSchema, ReportArtifactReadOutputSchema, ListServiceConnectionsInputSchema, ListServiceConnectionsOutputSchema, ReadServiceConnectionInputSchema, ReadServiceConnectionOutputSchema, MetaAdCreativeMediaInputSchema, MetaAdCreativeMediaOutputSchema, ImportServiceConnectionToMemoryInputSchema, ImportServiceConnectionToMemoryOutputSchema, DescribeServiceConnectionToolInputSchema, DescribeServiceConnectionToolOutputSchema, ConnectedDataContinuationSchema, ExportConnectedServiceDataInputSchema, ConnectedDataArtifactSchema, ExportConnectedServiceDataOutputSchema, RenewConnectedDataExportDownloadInputSchema, RenewConnectedDataExportDownloadOutputSchema, CallServiceConnectionActionInputSchema, CallServiceConnectionActionOutputSchema, SetScheduledActionConnectionsInputSchema, SetScheduledActionConnectionsOutputSchema, SlackSendMessageInputSchema, SlackSendMessageOutputSchema, GmailSendMessageInputSchema, GmailSendMessageOutputSchema, GoogleCalendarCreateEventInputSchema, GoogleCalendarCreateEventOutputSchema, ZoomCreateMeetingInputSchema, ZoomCreateMeetingOutputSchema;
|
|
27819
28248
|
var init_mcp_tool_schemas = __esm({
|
|
27820
28249
|
"src/mcp/mcp-tool-schemas.ts"() {
|
|
27821
28250
|
"use strict";
|
|
@@ -27857,13 +28286,20 @@ var init_mcp_tool_schemas = __esm({
|
|
|
27857
28286
|
maxPages: import_zod33.z.number().int().min(1).max(1e4).optional().describe("Maximum pages to extract. Bulk crawls (over 25 pages) switch to folder mode: each page saved as its own Markdown file, with a summary plus folder path returned instead of inlining content."),
|
|
27858
28287
|
rotateProxies: import_zod33.z.boolean().optional().describe("Route page fetches through rotating residential proxies to defeat rate-limiting and bot blocks (403/429). Slower and pricier \u2014 use only when a site blocks normal crawling."),
|
|
27859
28288
|
rotateProxyEvery: import_zod33.z.number().int().min(1).max(100).optional().describe("When rotateProxies is on, pages fetched per proxy before rotating. Default 30."),
|
|
27860
|
-
formats: import_zod33.z.array(import_zod33.z.enum(["markdown", "links", "json", "images", "branding"])).optional().describe("Per-page output formats: markdown, links, json, images are captured cheaply from HTML; branding (site-level logo/colors/fonts) requires a browser and adds time. Defaults to markdown+links.")
|
|
28289
|
+
formats: import_zod33.z.array(import_zod33.z.enum(["markdown", "links", "json", "images", "branding"])).optional().describe("Per-page output formats: markdown, links, json, images are captured cheaply from HTML; branding (site-level logo/colors/fonts) requires a browser and adds time. Defaults to markdown+links."),
|
|
28290
|
+
background: import_zod33.z.boolean().default(false).describe("Run the crawl as a background job instead of blocking this call, returning a jobId immediately \u2014 poll it with check_site_export to get a downloadable zip (all page content, plus real image files if downloadImages is set) once ready. Use for large sites where a synchronous call would be slow."),
|
|
28291
|
+
downloadImages: import_zod33.z.boolean().default(false).describe("Download every discovered image as a real file into the export bundle (not just image URLs/stats). OFF by default \u2014 must be explicitly set true. Implies background regardless of the background flag, since downloading a whole site's images is too slow to run synchronously. Capped at 20 images/page and 500 images/site.")
|
|
27861
28292
|
};
|
|
27862
28293
|
AuditSiteInputSchema = {
|
|
27863
28294
|
url: import_zod33.z.string().url().describe("Public website URL or domain for a full technical SEO audit (issues, link graph, indexability, headings, images). For plain content use extract_site instead."),
|
|
27864
28295
|
maxPages: import_zod33.z.number().int().min(1).max(1e4).optional().describe("Maximum pages to crawl and audit. Always writes a folder of analysis files plus per-page content, returning a summary plus the folder path."),
|
|
27865
28296
|
rotateProxies: import_zod33.z.boolean().optional().describe("Route page fetches through rotating residential proxies to defeat rate-limiting and bot blocks. Slower/pricier \u2014 use only when a site blocks normal crawling."),
|
|
27866
|
-
rotateProxyEvery: import_zod33.z.number().int().min(1).max(100).optional().describe("When rotateProxies is on, pages fetched per proxy before rotating. Default 30.")
|
|
28297
|
+
rotateProxyEvery: import_zod33.z.number().int().min(1).max(100).optional().describe("When rotateProxies is on, pages fetched per proxy before rotating. Default 30."),
|
|
28298
|
+
background: import_zod33.z.boolean().default(false).describe("Run the audit as a background job instead of blocking this call, returning a jobId immediately \u2014 poll it with check_site_export to get a downloadable zip (full audit report, all page content, plus real image files if downloadImages is set) once ready. Use for large sites where a synchronous call would be slow."),
|
|
28299
|
+
downloadImages: import_zod33.z.boolean().default(false).describe("Download every discovered image as a real file into the export bundle (not just image URLs/stats). OFF by default \u2014 must be explicitly set true. Implies background regardless of the background flag, since downloading a whole site's images is too slow to run synchronously. Capped at 20 images/page and 500 images/site.")
|
|
28300
|
+
};
|
|
28301
|
+
CheckSiteExportInputSchema = {
|
|
28302
|
+
jobId: import_zod33.z.string().min(1).describe('The jobId returned by extract_site or audit_site when called with background (or downloadImages) set \u2014 poll this until status is "complete" (or "failed").')
|
|
27867
28303
|
};
|
|
27868
28304
|
YoutubeHarvestInputSchema = {
|
|
27869
28305
|
mode: import_zod33.z.enum(["search", "channel"]).describe("Use search for topic/keyword requests. Use channel when the user provides @handle, channel ID, or channel URL."),
|
|
@@ -27878,7 +28314,7 @@ var init_mcp_tool_schemas = __esm({
|
|
|
27878
28314
|
FacebookPageIntelInputSchema = {
|
|
27879
28315
|
pageId: import_zod33.z.string().optional().describe("Facebook advertiser/page ID. Use only a value returned by facebook_ad_search or copied from Ad Library."),
|
|
27880
28316
|
libraryId: import_zod33.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."),
|
|
27881
|
-
query: import_zod33.z.string().optional().describe("
|
|
28317
|
+
query: import_zod33.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."),
|
|
27882
28318
|
maxAds: import_zod33.z.number().int().min(1).max(200).default(50).describe("Maximum ads to inspect. Default 50, maximum 200."),
|
|
27883
28319
|
country: import_zod33.z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US.")
|
|
27884
28320
|
};
|
|
@@ -27902,10 +28338,10 @@ var init_mcp_tool_schemas = __esm({
|
|
|
27902
28338
|
runId: import_zod33.z.string().min(1).describe("The runId returned by video_frame_analysis.")
|
|
27903
28339
|
};
|
|
27904
28340
|
FacebookAdTranscribeInputSchema = {
|
|
27905
|
-
videoUrl: import_zod33.z.string().url().describe("Direct Facebook CDN video URL from facebook_page_intel. Do not pass a public post/reel/share URL
|
|
28341
|
+
videoUrl: import_zod33.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.")
|
|
27906
28342
|
};
|
|
27907
28343
|
FacebookVideoTranscribeInputSchema = {
|
|
27908
|
-
url: import_zod33.z.string().url().describe("
|
|
28344
|
+
url: import_zod33.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."),
|
|
27909
28345
|
quality: import_zod33.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.")
|
|
27910
28346
|
};
|
|
27911
28347
|
GoogleAdsSearchInputSchema = {
|
|
@@ -28266,36 +28702,52 @@ var init_mcp_tool_schemas = __esm({
|
|
|
28266
28702
|
};
|
|
28267
28703
|
ExtractSiteOutputSchema = {
|
|
28268
28704
|
url: import_zod33.z.string(),
|
|
28269
|
-
pageCount: import_zod33.z.number().int().min(0),
|
|
28705
|
+
pageCount: import_zod33.z.number().int().min(0).optional().describe("Absent when background is true \u2014 the crawl has not finished yet."),
|
|
28270
28706
|
pages: import_zod33.z.array(import_zod33.z.object({
|
|
28271
28707
|
url: import_zod33.z.string(),
|
|
28272
28708
|
title: NullableString,
|
|
28273
28709
|
schemaTypes: import_zod33.z.array(import_zod33.z.string())
|
|
28274
|
-
})),
|
|
28275
|
-
durationMs: import_zod33.z.number().min(0),
|
|
28710
|
+
})).optional().describe("Absent when background is true \u2014 the crawl has not finished yet."),
|
|
28711
|
+
durationMs: import_zod33.z.number().min(0).optional().describe("Absent when background is true \u2014 the crawl has not finished yet."),
|
|
28276
28712
|
truncatedCount: import_zod33.z.number().int().min(0).optional(),
|
|
28277
|
-
artifact: ArtifactPointerOutputSchema.optional()
|
|
28713
|
+
artifact: ArtifactPointerOutputSchema.optional(),
|
|
28714
|
+
jobId: import_zod33.z.string().optional().describe("Present when background (or downloadImages) was set \u2014 poll with check_site_export."),
|
|
28715
|
+
status: import_zod33.z.enum(["pending"]).optional().describe("Present when background (or downloadImages) was set."),
|
|
28716
|
+
statusUrl: import_zod33.z.string().optional().describe("Present when background (or downloadImages) was set \u2014 informational; use check_site_export with jobId, not this URL directly.")
|
|
28278
28717
|
};
|
|
28279
28718
|
AuditSiteOutputSchema = {
|
|
28280
28719
|
url: import_zod33.z.string(),
|
|
28281
|
-
pageCount: import_zod33.z.number().int().min(0),
|
|
28282
|
-
durationMs: import_zod33.z.number().min(0),
|
|
28283
|
-
bulkFolder: import_zod33.z.string().nullable(),
|
|
28284
|
-
issues: import_zod33.z.record(import_zod33.z.string(), import_zod33.z.number()),
|
|
28720
|
+
pageCount: import_zod33.z.number().int().min(0).optional().describe("Absent when background is true \u2014 the audit has not finished yet."),
|
|
28721
|
+
durationMs: import_zod33.z.number().min(0).optional().describe("Absent when background is true \u2014 the audit has not finished yet."),
|
|
28722
|
+
bulkFolder: import_zod33.z.string().nullable().optional().describe("Absent when background is true \u2014 the audit has not finished yet."),
|
|
28723
|
+
issues: import_zod33.z.record(import_zod33.z.string(), import_zod33.z.number()).optional().describe("Absent when background is true \u2014 the audit has not finished yet."),
|
|
28285
28724
|
images: import_zod33.z.object({
|
|
28286
28725
|
unique: import_zod33.z.number().int().min(0),
|
|
28287
28726
|
totalBytes: import_zod33.z.number().min(0),
|
|
28288
28727
|
over100kb: import_zod33.z.number().int().min(0),
|
|
28289
28728
|
legacyFormat: import_zod33.z.number().int().min(0)
|
|
28290
|
-
}),
|
|
28729
|
+
}).optional().describe("Absent when background is true \u2014 the audit has not finished yet."),
|
|
28291
28730
|
links: import_zod33.z.object({
|
|
28292
28731
|
internal: import_zod33.z.number().int().min(0),
|
|
28293
28732
|
external: import_zod33.z.number().int().min(0),
|
|
28294
28733
|
orphans: import_zod33.z.number().int().min(0),
|
|
28295
28734
|
brokenInternal: import_zod33.z.number().int().min(0),
|
|
28296
28735
|
externalDomains: import_zod33.z.number().int().min(0)
|
|
28297
|
-
}),
|
|
28298
|
-
artifact: ArtifactPointerOutputSchema.optional()
|
|
28736
|
+
}).optional().describe("Absent when background is true \u2014 the audit has not finished yet."),
|
|
28737
|
+
artifact: ArtifactPointerOutputSchema.optional(),
|
|
28738
|
+
jobId: import_zod33.z.string().optional().describe("Present when background (or downloadImages) was set \u2014 poll with check_site_export."),
|
|
28739
|
+
status: import_zod33.z.enum(["pending"]).optional().describe("Present when background (or downloadImages) was set."),
|
|
28740
|
+
statusUrl: import_zod33.z.string().optional().describe("Present when background (or downloadImages) was set \u2014 informational; use check_site_export with jobId, not this URL directly.")
|
|
28741
|
+
};
|
|
28742
|
+
CheckSiteExportOutputSchema = {
|
|
28743
|
+
jobId: import_zod33.z.string(),
|
|
28744
|
+
status: import_zod33.z.enum(["pending", "running", "complete", "failed"]),
|
|
28745
|
+
startUrl: import_zod33.z.string().optional(),
|
|
28746
|
+
totalUrls: import_zod33.z.number().int().min(0).optional(),
|
|
28747
|
+
doneUrls: import_zod33.z.number().int().min(0).optional(),
|
|
28748
|
+
bundleUrl: import_zod33.z.string().nullable().describe("Downloadable zip URL once status is complete; null otherwise."),
|
|
28749
|
+
bundleBytes: import_zod33.z.number().int().min(0).nullable().describe("Zip size in bytes once status is complete; null otherwise."),
|
|
28750
|
+
error: import_zod33.z.string().nullable().optional().describe("Present with a message when status is failed.")
|
|
28299
28751
|
};
|
|
28300
28752
|
MapsPlaceIntelOutputSchema = {
|
|
28301
28753
|
name: import_zod33.z.string(),
|
|
@@ -28460,6 +28912,11 @@ var init_mcp_tool_schemas = __esm({
|
|
|
28460
28912
|
};
|
|
28461
28913
|
FacebookPageIntelOutputSchema = {
|
|
28462
28914
|
advertiserName: NullableString,
|
|
28915
|
+
inputMode: import_zod33.z.enum(["pageId", "libraryId", "query"]),
|
|
28916
|
+
matchConfidence: import_zod33.z.enum(["high", "medium", "low"]),
|
|
28917
|
+
matchReason: import_zod33.z.string(),
|
|
28918
|
+
warnings: import_zod33.z.array(import_zod33.z.string()),
|
|
28919
|
+
matchedAdvertisers: import_zod33.z.array(import_zod33.z.object({ name: import_zod33.z.string(), adCount: import_zod33.z.number().int().min(0) })),
|
|
28463
28920
|
totalAds: import_zod33.z.number().int().min(0),
|
|
28464
28921
|
activeCount: import_zod33.z.number().int().min(0),
|
|
28465
28922
|
videoCount: import_zod33.z.number().int().min(0),
|
|
@@ -28514,6 +28971,16 @@ var init_mcp_tool_schemas = __esm({
|
|
|
28514
28971
|
variations: import_zod33.z.number().int().nullable()
|
|
28515
28972
|
}))
|
|
28516
28973
|
};
|
|
28974
|
+
TranscriptSignalOutput = import_zod33.z.object({
|
|
28975
|
+
status: import_zod33.z.enum(["speech_detected", "low_speech_signal", "empty"]),
|
|
28976
|
+
speechDetected: import_zod33.z.boolean(),
|
|
28977
|
+
confidence: import_zod33.z.enum(["medium", "low"]),
|
|
28978
|
+
basis: import_zod33.z.literal("transcript_word_count_and_timing"),
|
|
28979
|
+
mediaDurationSec: import_zod33.z.number().nullable(),
|
|
28980
|
+
wordsPerMinute: import_zod33.z.number().nullable(),
|
|
28981
|
+
retryRecommended: import_zod33.z.boolean(),
|
|
28982
|
+
warnings: import_zod33.z.array(import_zod33.z.string())
|
|
28983
|
+
});
|
|
28517
28984
|
FacebookVideoTranscribeOutputSchema = {
|
|
28518
28985
|
sourceUrl: import_zod33.z.string().url(),
|
|
28519
28986
|
pageUrl: import_zod33.z.string().url(),
|
|
@@ -28530,7 +28997,8 @@ var init_mcp_tool_schemas = __esm({
|
|
|
28530
28997
|
startSec: import_zod33.z.number(),
|
|
28531
28998
|
endSec: import_zod33.z.number(),
|
|
28532
28999
|
text: import_zod33.z.string()
|
|
28533
|
-
}))
|
|
29000
|
+
})),
|
|
29001
|
+
transcriptSignal: TranscriptSignalOutput
|
|
28534
29002
|
};
|
|
28535
29003
|
TranscriptChunkOutput = import_zod33.z.object({
|
|
28536
29004
|
startSec: import_zod33.z.number(),
|
|
@@ -28652,6 +29120,7 @@ var init_mcp_tool_schemas = __esm({
|
|
|
28652
29120
|
durationMs: import_zod33.z.number().nullable(),
|
|
28653
29121
|
transcriptText: import_zod33.z.string(),
|
|
28654
29122
|
chunks: import_zod33.z.array(TranscriptChunkOutput),
|
|
29123
|
+
transcriptSignal: TranscriptSignalOutput,
|
|
28655
29124
|
resolvedInputs: import_zod33.z.object({
|
|
28656
29125
|
videoUrl: import_zod33.z.string().url()
|
|
28657
29126
|
})
|
|
@@ -28884,6 +29353,43 @@ var init_mcp_tool_schemas = __esm({
|
|
|
28884
29353
|
result: import_zod33.z.unknown().optional(),
|
|
28885
29354
|
error: NullableString
|
|
28886
29355
|
};
|
|
29356
|
+
MetaAdCreativeMediaInputSchema = {
|
|
29357
|
+
connectionId: import_zod33.z.string().min(1).describe("Tenant-owned Meta Marketing connectionId from list_service_connections."),
|
|
29358
|
+
adId: import_zod33.z.string().regex(/^\d{5,30}$/).describe("Meta ad ID from the connected ad account. This is not an Ad Library archive ID."),
|
|
29359
|
+
imageMode: import_zod33.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."),
|
|
29360
|
+
maxInlineImages: import_zod33.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.")
|
|
29361
|
+
};
|
|
29362
|
+
MetaAdCreativeMediaOutputSchema = {
|
|
29363
|
+
ok: import_zod33.z.boolean(),
|
|
29364
|
+
adId: import_zod33.z.string(),
|
|
29365
|
+
adName: NullableString,
|
|
29366
|
+
adAccountId: NullableString,
|
|
29367
|
+
creativeId: import_zod33.z.string(),
|
|
29368
|
+
creativeName: NullableString,
|
|
29369
|
+
effectiveObjectStoryId: NullableString,
|
|
29370
|
+
sourceFacebookPostId: NullableString,
|
|
29371
|
+
objectStoryId: NullableString,
|
|
29372
|
+
pageId: NullableString,
|
|
29373
|
+
postId: NullableString,
|
|
29374
|
+
publicPostCandidateUrl: NullableString,
|
|
29375
|
+
publicPostStatus: import_zod33.z.literal("unverified"),
|
|
29376
|
+
mediaType: import_zod33.z.enum(["image", "video", "carousel", "mixed", "unknown"]),
|
|
29377
|
+
images: import_zod33.z.array(import_zod33.z.record(import_zod33.z.string(), import_zod33.z.unknown())),
|
|
29378
|
+
videos: import_zod33.z.array(import_zod33.z.record(import_zod33.z.string(), import_zod33.z.unknown())),
|
|
29379
|
+
inlineImages: import_zod33.z.array(import_zod33.z.object({
|
|
29380
|
+
sourceUrl: import_zod33.z.string().url(),
|
|
29381
|
+
mimeType: import_zod33.z.string(),
|
|
29382
|
+
bytes: import_zod33.z.number().int().min(0),
|
|
29383
|
+
contentIndex: import_zod33.z.number().int().min(1)
|
|
29384
|
+
})),
|
|
29385
|
+
nextActions: import_zod33.z.array(import_zod33.z.object({
|
|
29386
|
+
tool: import_zod33.z.enum(["facebook_ad_transcribe", "facebook_video_transcribe", "video_frame_analysis"]),
|
|
29387
|
+
args: import_zod33.z.record(import_zod33.z.string(), import_zod33.z.unknown()),
|
|
29388
|
+
reason: import_zod33.z.string()
|
|
29389
|
+
})),
|
|
29390
|
+
warnings: import_zod33.z.array(import_zod33.z.string()),
|
|
29391
|
+
limitations: import_zod33.z.array(import_zod33.z.string())
|
|
29392
|
+
};
|
|
28887
29393
|
ImportServiceConnectionToMemoryInputSchema = {
|
|
28888
29394
|
connectionId: import_zod33.z.string().min(1).max(200).describe("A tenant-owned connectionId from list_service_connections."),
|
|
28889
29395
|
providerConfigKey: import_zod33.z.string().min(1).max(200).describe("The exact providerConfigKey returned with that connection. It is matched together with connectionId against the authenticated caller."),
|
|
@@ -29553,6 +30059,13 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
29553
30059
|
outputSchema: recordOutputSchema("audit_site", AuditSiteOutputSchema),
|
|
29554
30060
|
annotations: liveWebToolAnnotations("Technical SEO Audit")
|
|
29555
30061
|
}, async (input) => formatAuditSite(await executor.auditSite(input), input, ctx));
|
|
30062
|
+
server.registerTool("check_site_export", {
|
|
30063
|
+
title: "Check Site Export",
|
|
30064
|
+
description: "Poll the status of a background extract_site or audit_site job (one started with background or downloadImages set). Returns a downloadable zip URL (all page content, plus real image files if downloadImages was set) once status is complete.",
|
|
30065
|
+
inputSchema: CheckSiteExportInputSchema,
|
|
30066
|
+
outputSchema: recordOutputSchema("check_site_export", CheckSiteExportOutputSchema),
|
|
30067
|
+
annotations: liveWebToolAnnotations("Check Site Export")
|
|
30068
|
+
}, async (input) => formatCheckSiteExport(await executor.checkSiteExport(input), input));
|
|
29556
30069
|
server.registerTool("youtube_harvest", {
|
|
29557
30070
|
title: "YouTube Video Harvest",
|
|
29558
30071
|
description: 'Harvest YouTube video metadata by topic search or channel library. Use mode "search" for keyword/topic requests, mode "channel" for @handles/channel IDs/URLs. Returns titles, views, durations, and videoIds.',
|
|
@@ -29569,7 +30082,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
29569
30082
|
}, async (input) => formatYoutubeTranscribe(await executor.youtubeTranscribe(input), input));
|
|
29570
30083
|
server.registerTool("facebook_page_intel", {
|
|
29571
30084
|
title: "Facebook Advertiser Ad Intel",
|
|
29572
|
-
description: "Harvest
|
|
30085
|
+
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.",
|
|
29573
30086
|
inputSchema: FacebookPageIntelInputSchema,
|
|
29574
30087
|
outputSchema: recordOutputSchema("facebook_page_intel", FacebookPageIntelOutputSchema),
|
|
29575
30088
|
annotations: liveWebToolAnnotations("Facebook Advertiser Ad Intel")
|
|
@@ -29604,7 +30117,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
29604
30117
|
}, async (input) => executor.videoFrameAnalysisStatus(input));
|
|
29605
30118
|
server.registerTool("facebook_ad_transcribe", {
|
|
29606
30119
|
title: "Facebook Ad Transcription",
|
|
29607
|
-
description: "Transcribe
|
|
30120
|
+
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.",
|
|
29608
30121
|
inputSchema: FacebookAdTranscribeInputSchema,
|
|
29609
30122
|
outputSchema: recordOutputSchema("facebook_ad_transcribe", FacebookAdTranscribeOutputSchema),
|
|
29610
30123
|
annotations: liveWebToolAnnotations("Facebook Ad Transcription")
|
|
@@ -29632,7 +30145,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
29632
30145
|
}, async (input) => formatGoogleAdsTranscribe(await executor.googleAdsTranscribe(input), input));
|
|
29633
30146
|
server.registerTool("facebook_video_transcribe", {
|
|
29634
30147
|
title: "Facebook Organic Video Transcription",
|
|
29635
|
-
description: "Transcribe audio from
|
|
30148
|
+
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.",
|
|
29636
30149
|
inputSchema: FacebookVideoTranscribeInputSchema,
|
|
29637
30150
|
outputSchema: recordOutputSchema("facebook_video_transcribe", FacebookVideoTranscribeOutputSchema),
|
|
29638
30151
|
annotations: liveWebToolAnnotations("Facebook Organic Video Transcription")
|
|
@@ -29810,6 +30323,13 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
29810
30323
|
outputSchema: recordOutputSchema("read_service_connection", ReadServiceConnectionOutputSchema),
|
|
29811
30324
|
annotations: { title: "Read Connected Service", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
29812
30325
|
}, async (input) => executor.readServiceConnection(input));
|
|
30326
|
+
server.registerTool("meta_ad_creative_media", {
|
|
30327
|
+
title: "View Meta Ad Creative Media",
|
|
30328
|
+
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.",
|
|
30329
|
+
inputSchema: MetaAdCreativeMediaInputSchema,
|
|
30330
|
+
outputSchema: recordOutputSchema("meta_ad_creative_media", MetaAdCreativeMediaOutputSchema),
|
|
30331
|
+
annotations: { title: "View Meta Ad Creative Media", readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
30332
|
+
}, async (input) => buildMetaAdCreativeMediaResult(executor, input));
|
|
29813
30333
|
server.registerTool("import_service_connection_to_memory", {
|
|
29814
30334
|
title: "Import Connected Service Snapshot to Memory",
|
|
29815
30335
|
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.",
|
|
@@ -29866,6 +30386,7 @@ var init_paa_mcp_server = __esm({
|
|
|
29866
30386
|
init_server_instructions();
|
|
29867
30387
|
init_output_schema_registry();
|
|
29868
30388
|
init_report_artifact_offload();
|
|
30389
|
+
init_meta_ad_creative_media();
|
|
29869
30390
|
init_mcp_tool_schemas();
|
|
29870
30391
|
init_rank_tracker_blueprint();
|
|
29871
30392
|
init_mcp_response_formatter();
|
|
@@ -30062,6 +30583,9 @@ var init_http_mcp_tool_executor = __esm({
|
|
|
30062
30583
|
auditSite(input) {
|
|
30063
30584
|
return this.call("/extract-site", input);
|
|
30064
30585
|
}
|
|
30586
|
+
checkSiteExport(input) {
|
|
30587
|
+
return this.getJson(`/extract-site/status/${encodeURIComponent(input.jobId)}`);
|
|
30588
|
+
}
|
|
30065
30589
|
youtubeHarvest(input) {
|
|
30066
30590
|
return this.call("/youtube/harvest", input);
|
|
30067
30591
|
}
|
|
@@ -40070,7 +40594,7 @@ function cleanString(value, max = 300) {
|
|
|
40070
40594
|
const trimmed = value.trim();
|
|
40071
40595
|
return trimmed ? trimmed.slice(0, max) : null;
|
|
40072
40596
|
}
|
|
40073
|
-
function
|
|
40597
|
+
function firstString2(record, keys, max = 300) {
|
|
40074
40598
|
for (const key of keys) {
|
|
40075
40599
|
const value = cleanString(record[key], max);
|
|
40076
40600
|
if (value) return value;
|
|
@@ -40134,10 +40658,10 @@ function safeControlErrorPayload(body, responseStatus) {
|
|
|
40134
40658
|
const status = controlErrorStatus(responseStatus);
|
|
40135
40659
|
const unwrapped = unwrapData(body);
|
|
40136
40660
|
const record = isRecord2(unwrapped) ? unwrapped : isRecord2(body) ? body : null;
|
|
40137
|
-
const candidateCode = record ?
|
|
40661
|
+
const candidateCode = record ? firstString2(record, ["code", "errorCode", "error_code"], 100) : null;
|
|
40138
40662
|
const normalizedCandidateCode = candidateCode ? CONTROL_ERROR_CODE_ALIASES.get(candidateCode) ?? candidateCode : null;
|
|
40139
40663
|
const code = normalizedCandidateCode && SAFE_CONTROL_ERROR_CODES.has(normalizedCandidateCode) ? normalizedCandidateCode : defaultControlErrorCode(status);
|
|
40140
|
-
const candidateMessage = record ?
|
|
40664
|
+
const candidateMessage = record ? firstString2(record, ["error", "message"], 500) : null;
|
|
40141
40665
|
const message = normalizedCandidateCode && SAFE_CONTROL_ERROR_CODES.has(normalizedCandidateCode) && candidateMessage ? candidateMessage.replace(/[\u0000-\u001f\u007f]/g, " ") : defaultControlErrorMessage(status);
|
|
40142
40666
|
const retryable = record && typeof record.retryable === "boolean" ? record.retryable : status === 429 || status >= 500;
|
|
40143
40667
|
return { status, code, message, retryable };
|
|
@@ -40182,8 +40706,8 @@ function sanitizeScheduleConnectionSelections(value) {
|
|
|
40182
40706
|
const seen = /* @__PURE__ */ new Set();
|
|
40183
40707
|
for (const item of value) {
|
|
40184
40708
|
if (!isRecord2(item)) throw new ScheduleConnectionValidationError("Each service connection must be an object.");
|
|
40185
|
-
const connectionId =
|
|
40186
|
-
const providerConfigKey =
|
|
40709
|
+
const connectionId = firstString2(item, ["connectionId", "connection_id"]);
|
|
40710
|
+
const providerConfigKey = firstString2(item, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id"]);
|
|
40187
40711
|
const allowedTools = cleanTools(item.allowedTools ?? item.allowed_tools ?? item.allowedCapabilities ?? item.allowed_capabilities);
|
|
40188
40712
|
if (!connectionId || !providerConfigKey) throw new ScheduleConnectionValidationError("Each service connection needs a connectionId and providerConfigKey.");
|
|
40189
40713
|
if (allowedTools.length === 0) throw new ScheduleConnectionValidationError("Each selected service needs at least one allowed tool.");
|
|
@@ -40200,14 +40724,14 @@ async function getNangoCatalog() {
|
|
|
40200
40724
|
const result = [];
|
|
40201
40725
|
for (const row of rows) {
|
|
40202
40726
|
if (!isRecord2(row)) continue;
|
|
40203
|
-
const providerConfigKey =
|
|
40727
|
+
const providerConfigKey = firstString2(row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "id"]);
|
|
40204
40728
|
if (!providerConfigKey) continue;
|
|
40205
|
-
const provider =
|
|
40206
|
-
const label =
|
|
40207
|
-
const description =
|
|
40729
|
+
const provider = firstString2(row, ["provider"]);
|
|
40730
|
+
const label = firstString2(row, ["label", "displayName", "display_name", "name"]) || providerConfigKey;
|
|
40731
|
+
const description = firstString2(row, ["description"], 500);
|
|
40208
40732
|
const logoUrl = cleanHttpsUrl(row.logoUrl ?? row.logo_url ?? row.logo);
|
|
40209
40733
|
const docsUrl = cleanHttpsUrl(row.docsUrl ?? row.docs_url ?? row.docs);
|
|
40210
|
-
const authMode =
|
|
40734
|
+
const authMode = firstString2(row, ["authMode", "auth_mode"], 100);
|
|
40211
40735
|
const categories = cleanStringArray(row.categories);
|
|
40212
40736
|
const disabledTools = DISABLED_NANGO_TOOLS[providerConfigKey];
|
|
40213
40737
|
const safeDefaultAllowedTools = cleanTools(
|
|
@@ -40234,14 +40758,14 @@ async function getNangoCatalog() {
|
|
|
40234
40758
|
requiredFeaturesByTool[cleanTool] = cleanStringArray(features, 32, 200);
|
|
40235
40759
|
}
|
|
40236
40760
|
}
|
|
40237
|
-
const platformSetupStatus =
|
|
40761
|
+
const platformSetupStatus = firstString2(row, [
|
|
40238
40762
|
"platformSetupStatus",
|
|
40239
40763
|
"platform_setup_status",
|
|
40240
40764
|
"setupStatus",
|
|
40241
40765
|
"setup_status"
|
|
40242
40766
|
], 100);
|
|
40243
|
-
const appReviewStatus = row.appReviewLimited === true || row.app_review_limited === true ? "limited" :
|
|
40244
|
-
const appReviewNote =
|
|
40767
|
+
const appReviewStatus = row.appReviewLimited === true || row.app_review_limited === true ? "limited" : firstString2(row, ["appReviewStatus", "app_review_status", "reviewStatus", "review_status"], 100);
|
|
40768
|
+
const appReviewNote = firstString2(row, ["appReviewNote", "app_review_note", "reviewNote", "review_note"], 500);
|
|
40245
40769
|
const connectionSyncRequiredTools = [...CONNECTION_SYNC_REQUIRED_TOOLS[providerConfigKey] ?? []];
|
|
40246
40770
|
const connectionSyncOptionalTools = [...CONNECTION_SYNC_OPTIONAL_TOOLS[providerConfigKey] ?? []];
|
|
40247
40771
|
result.push({
|
|
@@ -40275,12 +40799,12 @@ async function getNangoConnections(identity) {
|
|
|
40275
40799
|
const result = [];
|
|
40276
40800
|
for (const row of rows) {
|
|
40277
40801
|
if (!isRecord2(row)) continue;
|
|
40278
|
-
const connectionId =
|
|
40279
|
-
const providerConfigKey =
|
|
40802
|
+
const connectionId = firstString2(row, ["connectionId", "connection_id", "id"]);
|
|
40803
|
+
const providerConfigKey = firstString2(row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "provider"]);
|
|
40280
40804
|
if (!connectionId || !providerConfigKey) continue;
|
|
40281
|
-
const provider =
|
|
40282
|
-
const label =
|
|
40283
|
-
const rawStatus =
|
|
40805
|
+
const provider = firstString2(row, ["provider"]);
|
|
40806
|
+
const label = firstString2(row, ["label", "accountLabel", "account_label", "displayName", "display_name", "endUserEmail", "end_user_email"]) || providerConfigKey;
|
|
40807
|
+
const rawStatus = firstString2(row, ["status"], 64) || "connected";
|
|
40284
40808
|
const reconnectRequired = row.reconnectRequired === true || row.reconnect_required === true || rawStatus === "needs_reauth" || rawStatus === "reauth_required" || rawStatus === "invalid" || rawStatus === "error" || rawStatus === "revoked" || rawStatus === "disabled";
|
|
40285
40809
|
const toolCapabilities = [];
|
|
40286
40810
|
const rawToolCapabilities = row.toolCapabilities ?? row.tool_capabilities;
|
|
@@ -40322,11 +40846,11 @@ async function getNangoConnections(identity) {
|
|
|
40322
40846
|
permissionVerification,
|
|
40323
40847
|
mcpEndpoint: null,
|
|
40324
40848
|
schemaDiscovery: "compatibility_describe",
|
|
40325
|
-
toolRevision:
|
|
40326
|
-
vaultName:
|
|
40327
|
-
tableName:
|
|
40328
|
-
createdAt:
|
|
40329
|
-
updatedAt:
|
|
40849
|
+
toolRevision: firstString2(row, ["toolRevision", "tool_revision"], 200),
|
|
40850
|
+
vaultName: firstString2(row, ["vaultName", "vault_name"], 100),
|
|
40851
|
+
tableName: firstString2(row, ["tableName", "table_name"], 100),
|
|
40852
|
+
createdAt: firstString2(row, ["createdAt", "created_at"], 100),
|
|
40853
|
+
updatedAt: firstString2(row, ["updatedAt", "updated_at"], 100)
|
|
40330
40854
|
});
|
|
40331
40855
|
}
|
|
40332
40856
|
return result;
|
|
@@ -40334,12 +40858,12 @@ async function getNangoConnections(identity) {
|
|
|
40334
40858
|
function sanitizeConnectSession(body) {
|
|
40335
40859
|
const data = unwrapData(body);
|
|
40336
40860
|
if (!isRecord2(data)) throw new NangoControlError("The connection service returned an invalid connect session.");
|
|
40337
|
-
const connectLink =
|
|
40861
|
+
const connectLink = firstString2(data, ["connectLink", "connect_link"], 2e3);
|
|
40338
40862
|
if (!connectLink) throw new NangoControlError("The connection service did not return a connect link.");
|
|
40339
40863
|
return {
|
|
40340
40864
|
connectLink,
|
|
40341
|
-
sessionToken:
|
|
40342
|
-
expiresAt:
|
|
40865
|
+
sessionToken: firstString2(data, ["sessionToken", "session_token", "token"], 2e3),
|
|
40866
|
+
expiresAt: firstString2(data, ["expiresAt", "expires_at"], 100)
|
|
40343
40867
|
};
|
|
40344
40868
|
}
|
|
40345
40869
|
async function createNangoConnectSession(identity, providerConfigKey) {
|
|
@@ -40367,7 +40891,7 @@ function sanitizeBindingRows(body, defaultScheduleActionId) {
|
|
|
40367
40891
|
const rows = arrayFromPayload(data, ["bindings", "connections"]);
|
|
40368
40892
|
for (const row of rows) {
|
|
40369
40893
|
if (isRecord2(row) && Array.isArray(row.connections)) {
|
|
40370
|
-
const scheduleActionId =
|
|
40894
|
+
const scheduleActionId = firstString2(row, ["scheduleActionId", "schedule_action_id", "scheduleId", "schedule_id"]) || defaultScheduleActionId;
|
|
40371
40895
|
row.connections.forEach((connection) => flattened.push({ row: connection, scheduleActionId }));
|
|
40372
40896
|
} else {
|
|
40373
40897
|
flattened.push({ row, scheduleActionId: defaultScheduleActionId });
|
|
@@ -40377,15 +40901,15 @@ function sanitizeBindingRows(body, defaultScheduleActionId) {
|
|
|
40377
40901
|
const result = [];
|
|
40378
40902
|
for (const item of flattened) {
|
|
40379
40903
|
if (!isRecord2(item.row)) continue;
|
|
40380
|
-
const connectionId =
|
|
40381
|
-
const providerConfigKey =
|
|
40904
|
+
const connectionId = firstString2(item.row, ["connectionId", "connection_id"]);
|
|
40905
|
+
const providerConfigKey = firstString2(item.row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "provider"]);
|
|
40382
40906
|
if (!connectionId || !providerConfigKey) continue;
|
|
40383
40907
|
result.push({
|
|
40384
|
-
scheduleActionId:
|
|
40908
|
+
scheduleActionId: firstString2(item.row, ["scheduleActionId", "schedule_action_id", "scheduleId", "schedule_id"]) || item.scheduleActionId,
|
|
40385
40909
|
connectionId,
|
|
40386
40910
|
providerConfigKey,
|
|
40387
40911
|
allowedTools: cleanTools(item.row.allowedTools ?? item.row.allowed_tools ?? item.row.allowedCapabilities ?? item.row.allowed_capabilities),
|
|
40388
|
-
label:
|
|
40912
|
+
label: firstString2(item.row, ["label", "accountLabel", "account_label", "displayName", "display_name"])
|
|
40389
40913
|
});
|
|
40390
40914
|
}
|
|
40391
40915
|
return result;
|
|
@@ -40767,7 +41291,7 @@ function cleanString2(value, max = 300) {
|
|
|
40767
41291
|
const trimmed = value.trim();
|
|
40768
41292
|
return trimmed ? trimmed.slice(0, max) : null;
|
|
40769
41293
|
}
|
|
40770
|
-
function
|
|
41294
|
+
function firstString3(record, keys, max = 300) {
|
|
40771
41295
|
for (const key of keys) {
|
|
40772
41296
|
const value = cleanString2(record[key], max);
|
|
40773
41297
|
if (value) return value;
|
|
@@ -40839,7 +41363,7 @@ async function getResendCatalog() {
|
|
|
40839
41363
|
const result = [];
|
|
40840
41364
|
for (const row of rows) {
|
|
40841
41365
|
if (!isRecord3(row)) continue;
|
|
40842
|
-
const id =
|
|
41366
|
+
const id = firstString3(row, ["providerConfigKey", "provider_config_key", "id"]);
|
|
40843
41367
|
if (id !== RESEND_PROVIDER_CONFIG_KEY) continue;
|
|
40844
41368
|
const safeDefaultAllowedTools = cleanTools2(
|
|
40845
41369
|
row.safeDefaultAllowedTools ?? row.safe_default_allowed_tools ?? row.readTools ?? row.read_tools
|
|
@@ -40849,8 +41373,8 @@ async function getResendCatalog() {
|
|
|
40849
41373
|
result.push({
|
|
40850
41374
|
providerConfigKey: RESEND_PROVIDER_CONFIG_KEY,
|
|
40851
41375
|
provider: RESEND_PROVIDER_CONFIG_KEY,
|
|
40852
|
-
label:
|
|
40853
|
-
description:
|
|
41376
|
+
label: firstString3(row, ["label", "displayName", "display_name", "name"]) || "Resend",
|
|
41377
|
+
description: firstString3(row, ["description"], 500) || "Connect Resend through its official remote MCP to read email operations and run explicitly enabled delivery workflows.",
|
|
40854
41378
|
logoUrl: cleanHttpsUrl2(row.logoUrl ?? row.logo_url ?? row.logo) || RESEND_LOGO_URL,
|
|
40855
41379
|
docsUrl: cleanHttpsUrl2(row.docsUrl ?? row.docs_url ?? row.docs) || RESEND_DOCS_URL,
|
|
40856
41380
|
authMode: "REMOTE_MCP_OAUTH2",
|
|
@@ -40860,7 +41384,7 @@ async function getResendCatalog() {
|
|
|
40860
41384
|
adminBlockedTools: adminBlockedTools.length > 0 ? adminBlockedTools : [...RESEND_ADMIN_BLOCKED_TOOLS],
|
|
40861
41385
|
connectionSyncSupported: true,
|
|
40862
41386
|
connectionSyncRequiredTools: [...RESEND_CONNECTION_SYNC_REQUIRED_TOOLS],
|
|
40863
|
-
platformSetupStatus:
|
|
41387
|
+
platformSetupStatus: firstString3(row, ["platformSetupStatus", "platform_setup_status", "setupStatus", "setup_status"], 100) || "ready",
|
|
40864
41388
|
appReviewStatus: null,
|
|
40865
41389
|
appReviewNote: "Secret-returning webhook creation, API-key, OAuth-grant, and raw editor-session access is permanently blocked from agents and schedules.",
|
|
40866
41390
|
transport: "remote_mcp"
|
|
@@ -40875,10 +41399,10 @@ async function getResendConnections(identity) {
|
|
|
40875
41399
|
const result = [];
|
|
40876
41400
|
for (const row of rows) {
|
|
40877
41401
|
if (!isRecord3(row)) continue;
|
|
40878
|
-
const connectionId =
|
|
40879
|
-
const providerConfigKey =
|
|
41402
|
+
const connectionId = firstString3(row, ["connectionId", "connection_id", "id"]);
|
|
41403
|
+
const providerConfigKey = firstString3(row, ["providerConfigKey", "provider_config_key", "provider"]) || RESEND_PROVIDER_CONFIG_KEY;
|
|
40880
41404
|
if (!connectionId || providerConfigKey !== RESEND_PROVIDER_CONFIG_KEY) continue;
|
|
40881
|
-
const rawStatus = (
|
|
41405
|
+
const rawStatus = (firstString3(row, ["status"], 64) || "needs_reauth").toLowerCase();
|
|
40882
41406
|
const pending = ["pending", "pending_oauth", "authorizing", "authorization_pending"].includes(rawStatus);
|
|
40883
41407
|
const active = ["active", "connected", "ready"].includes(rawStatus);
|
|
40884
41408
|
const reconnectRequired = row.reconnectRequired === true || row.reconnect_required === true || !pending && !active;
|
|
@@ -40886,7 +41410,7 @@ async function getResendConnections(identity) {
|
|
|
40886
41410
|
connectionId,
|
|
40887
41411
|
providerConfigKey: RESEND_PROVIDER_CONFIG_KEY,
|
|
40888
41412
|
provider: RESEND_PROVIDER_CONFIG_KEY,
|
|
40889
|
-
label:
|
|
41413
|
+
label: firstString3(row, ["label", "accountLabel", "account_label", "displayName", "display_name"]) || "Resend account",
|
|
40890
41414
|
status: pending ? "pending_oauth" : reconnectRequired ? "needs_reauth" : "connected",
|
|
40891
41415
|
reconnectRequired,
|
|
40892
41416
|
actionsEnabled: row.actionsEnabled === true || row.actions_enabled === true,
|
|
@@ -40895,11 +41419,11 @@ async function getResendConnections(identity) {
|
|
|
40895
41419
|
adminBlockedTools: cleanTools2(row.adminBlockedTools ?? row.admin_blocked_tools).length > 0 ? cleanTools2(row.adminBlockedTools ?? row.admin_blocked_tools) : [...RESEND_ADMIN_BLOCKED_TOOLS],
|
|
40896
41420
|
mcpEndpoint: null,
|
|
40897
41421
|
schemaDiscovery: "compatibility_describe",
|
|
40898
|
-
toolRevision:
|
|
40899
|
-
vaultName:
|
|
40900
|
-
tableName:
|
|
40901
|
-
createdAt:
|
|
40902
|
-
updatedAt:
|
|
41422
|
+
toolRevision: firstString3(row, ["toolRevision", "tool_revision"], 200),
|
|
41423
|
+
vaultName: firstString3(row, ["vaultName", "vault_name"], 100),
|
|
41424
|
+
tableName: firstString3(row, ["tableName", "table_name"], 100),
|
|
41425
|
+
createdAt: firstString3(row, ["createdAt", "created_at"], 100),
|
|
41426
|
+
updatedAt: firstString3(row, ["updatedAt", "updated_at"], 100),
|
|
40903
41427
|
transport: "remote_mcp"
|
|
40904
41428
|
});
|
|
40905
41429
|
}
|
|
@@ -40913,7 +41437,7 @@ function sanitizeConnectSession2(body) {
|
|
|
40913
41437
|
return {
|
|
40914
41438
|
connectLink,
|
|
40915
41439
|
sessionToken: null,
|
|
40916
|
-
expiresAt:
|
|
41440
|
+
expiresAt: firstString3(data, ["expiresAt", "expires_at"], 100)
|
|
40917
41441
|
};
|
|
40918
41442
|
}
|
|
40919
41443
|
async function createResendConnectSession(identity, redirectUri) {
|
|
@@ -40975,16 +41499,16 @@ async function describeResendTool(identity, connectionId, tool) {
|
|
|
40975
41499
|
const data = unwrapData2(body);
|
|
40976
41500
|
const rawTool = isRecord3(data) && isRecord3(data.tool) ? data.tool : data;
|
|
40977
41501
|
if (!isRecord3(rawTool)) throw new ResendControlError("Resend returned an invalid tool description.");
|
|
40978
|
-
const name =
|
|
40979
|
-
const classification =
|
|
41502
|
+
const name = firstString3(rawTool, ["name"], 200);
|
|
41503
|
+
const classification = firstString3(rawTool, ["classification", "kind"], 20);
|
|
40980
41504
|
const inputSchema = rawTool.inputSchema ?? rawTool.input_schema;
|
|
40981
41505
|
if (!name || name !== tool || classification !== "read" && classification !== "action" || !isRecord3(inputSchema)) {
|
|
40982
41506
|
throw new ResendControlError("Resend returned an invalid tool description.");
|
|
40983
41507
|
}
|
|
40984
41508
|
return {
|
|
40985
41509
|
name,
|
|
40986
|
-
title:
|
|
40987
|
-
description:
|
|
41510
|
+
title: firstString3(rawTool, ["title"], 300),
|
|
41511
|
+
description: firstString3(rawTool, ["description"], 2e3),
|
|
40988
41512
|
inputSchema,
|
|
40989
41513
|
classification
|
|
40990
41514
|
};
|
|
@@ -41532,7 +42056,7 @@ var init_server = __esm({
|
|
|
41532
42056
|
const user = await getUserByApiKey(key);
|
|
41533
42057
|
if (!user) return c.json({ error: "Invalid or inactive API key" }, 401);
|
|
41534
42058
|
const refreshed = await applyMonthlyFreeRefresh(user);
|
|
41535
|
-
const balanceMc = await reconcileBalanceMc(refreshed.id);
|
|
42059
|
+
const balanceMc = await reconcileBalanceMc(refreshed.id, refreshed.balance_mc);
|
|
41536
42060
|
c.set("user", { ...refreshed, balance_mc: balanceMc });
|
|
41537
42061
|
return next();
|
|
41538
42062
|
});
|
|
@@ -41551,7 +42075,7 @@ var init_server = __esm({
|
|
|
41551
42075
|
const user = await getUserById(userId);
|
|
41552
42076
|
if (!user) return c.json({ error: "User not found" }, 401);
|
|
41553
42077
|
const refreshed = await applyMonthlyFreeRefresh(user);
|
|
41554
|
-
const balanceMc = await reconcileBalanceMc(refreshed.id);
|
|
42078
|
+
const balanceMc = await reconcileBalanceMc(refreshed.id, refreshed.balance_mc);
|
|
41555
42079
|
c.set("sessionUser", { ...refreshed, balance_mc: balanceMc });
|
|
41556
42080
|
return next();
|
|
41557
42081
|
});
|
|
@@ -41741,7 +42265,7 @@ var init_server = __esm({
|
|
|
41741
42265
|
}
|
|
41742
42266
|
const refreshed = await applyMonthlyFreeRefresh(foundUser);
|
|
41743
42267
|
const [balanceMc, stats] = await Promise.all([
|
|
41744
|
-
reconcileBalanceMc(refreshed.id),
|
|
42268
|
+
reconcileBalanceMc(refreshed.id, refreshed.balance_mc),
|
|
41745
42269
|
getUserStats(refreshed.id)
|
|
41746
42270
|
]);
|
|
41747
42271
|
const user = { ...refreshed, balance_mc: balanceMc };
|
|
@@ -43009,7 +43533,8 @@ var init_server = __esm({
|
|
|
43009
43533
|
if (checked.error || !checked.parsed) return c.json({ error: checked.error ?? "Invalid URL" }, 400);
|
|
43010
43534
|
const parsed = checked.parsed;
|
|
43011
43535
|
const user = c.get("user");
|
|
43012
|
-
|
|
43536
|
+
const downloadImages = body.downloadImages === true;
|
|
43537
|
+
if (body.background === true || downloadImages) {
|
|
43013
43538
|
const affordablePages = Math.floor(user.balance_mc / MC_COSTS.page_scrape);
|
|
43014
43539
|
if (affordablePages < 1) return c.json(insufficientBalanceResponse(user.balance_mc, MC_COSTS.page_scrape), 402);
|
|
43015
43540
|
const maxPages = Math.min(Math.min(1e4, Math.max(1, body.maxPages ?? 1e4)), affordablePages);
|
|
@@ -43024,6 +43549,7 @@ var init_server = __esm({
|
|
|
43024
43549
|
concurrency: concurrencyLimitForUser(user),
|
|
43025
43550
|
urlsPerBrowser: body.rotateProxyEvery ?? 10,
|
|
43026
43551
|
formats: body.formats,
|
|
43552
|
+
downloadImages,
|
|
43027
43553
|
heldMc
|
|
43028
43554
|
});
|
|
43029
43555
|
jobCreated = true;
|
|
@@ -43332,7 +43858,7 @@ var init_server = __esm({
|
|
|
43332
43858
|
});
|
|
43333
43859
|
app.get("/billing/balance", auth2, async (c) => {
|
|
43334
43860
|
const user = c.get("user");
|
|
43335
|
-
const balanceMc =
|
|
43861
|
+
const balanceMc = user.balance_mc;
|
|
43336
43862
|
const ledger = await getLedger(user.id, 20);
|
|
43337
43863
|
const freeCredits = await getFreeCreditBreakdown(user.id);
|
|
43338
43864
|
return c.json({
|
|
@@ -43344,7 +43870,7 @@ var init_server = __esm({
|
|
|
43344
43870
|
});
|
|
43345
43871
|
app.post("/billing/credits", auth2, async (c) => {
|
|
43346
43872
|
const user = c.get("user");
|
|
43347
|
-
const balanceMc =
|
|
43873
|
+
const balanceMc = user.balance_mc;
|
|
43348
43874
|
const body = await c.req.json().catch(() => ({}));
|
|
43349
43875
|
const query = body.item?.trim().toLowerCase();
|
|
43350
43876
|
const costs = CREDIT_COST_CATALOG.map(({ aliases, ...cost }) => cost);
|