mcp-scraper 0.3.28 → 0.3.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/bin/api-server.cjs +2459 -1137
  2. package/dist/bin/api-server.cjs.map +1 -1
  3. package/dist/bin/api-server.js +3 -3
  4. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  5. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  6. package/dist/bin/mcp-scraper-cli.js +1 -1
  7. package/dist/bin/mcp-scraper-install.cjs +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  9. package/dist/bin/mcp-scraper-install.js +1 -1
  10. package/dist/bin/mcp-stdio-server.cjs +237 -6
  11. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  12. package/dist/bin/mcp-stdio-server.js +2 -2
  13. package/dist/bin/paa-harvest.cjs +109 -5
  14. package/dist/bin/paa-harvest.cjs.map +1 -1
  15. package/dist/bin/paa-harvest.js +2 -2
  16. package/dist/{chunk-TRNSXR46.js → chunk-DVRPXPYH.js} +22 -3
  17. package/dist/chunk-DVRPXPYH.js.map +1 -0
  18. package/dist/{chunk-QSSH4RCX.js → chunk-GMTS35L6.js} +111 -7
  19. package/dist/chunk-GMTS35L6.js.map +1 -0
  20. package/dist/chunk-I5EFBTYE.js +7 -0
  21. package/dist/chunk-I5EFBTYE.js.map +1 -0
  22. package/dist/{chunk-H74L743B.js → chunk-KJWNVQFL.js} +39 -2
  23. package/dist/chunk-KJWNVQFL.js.map +1 -0
  24. package/dist/{chunk-NGD4ITAO.js → chunk-QLQYNE7E.js} +238 -7
  25. package/dist/chunk-QLQYNE7E.js.map +1 -0
  26. package/dist/{db-FP2ABUU4.js → db-LYQENFPW.js} +2 -2
  27. package/dist/index.cjs +109 -5
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +8 -0
  30. package/dist/index.d.ts +8 -0
  31. package/dist/index.js +2 -2
  32. package/dist/{server-IDDX7KGT.js → server-HB7WJO3N.js} +1154 -251
  33. package/dist/server-HB7WJO3N.js.map +1 -0
  34. package/dist/{worker-NV4GTIRG.js → worker-TL4J65DE.js} +4 -4
  35. package/package.json +1 -1
  36. package/dist/chunk-7MY2NSQX.js +0 -7
  37. package/dist/chunk-7MY2NSQX.js.map +0 -1
  38. package/dist/chunk-H74L743B.js.map +0 -1
  39. package/dist/chunk-NGD4ITAO.js.map +0 -1
  40. package/dist/chunk-QSSH4RCX.js.map +0 -1
  41. package/dist/chunk-TRNSXR46.js.map +0 -1
  42. package/dist/server-IDDX7KGT.js.map +0 -1
  43. /package/dist/{db-FP2ABUU4.js.map → db-LYQENFPW.js.map} +0 -0
  44. /package/dist/{worker-NV4GTIRG.js.map → worker-TL4J65DE.js.map} +0 -0
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  finishHarvestAttempt,
8
8
  startHarvestAttempt
9
- } from "./chunk-TRNSXR46.js";
9
+ } from "./chunk-DVRPXPYH.js";
10
10
 
11
11
  // src/api/rates.ts
12
12
  var MC_COSTS = {
@@ -23,6 +23,9 @@ var MC_COSTS = {
23
23
  maps_review: 100,
24
24
  fb_search: 600,
25
25
  fb_transcribe: 200,
26
+ google_ads_search: 600,
27
+ google_ads_intel: 200,
28
+ google_ads_transcribe: 200,
26
29
  instagram_profile: 400,
27
30
  instagram_media: 400,
28
31
  instagram_transcribe: 50,
@@ -122,6 +125,30 @@ var CREDIT_COST_CATALOG = [
122
125
  unit: "per search",
123
126
  notes: "Browser automation to search Facebook Ads Library by keyword."
124
127
  },
128
+ {
129
+ key: "google_ads_search",
130
+ label: "Google Ads Transparency search",
131
+ aliases: ["google_ads_search", "google ads search", "ads transparency search"],
132
+ credits: mcToCredits(MC_COSTS.google_ads_search),
133
+ unit: "per search",
134
+ notes: "Browser automation to find advertisers in Google Ads Transparency Center by domain or name."
135
+ },
136
+ {
137
+ key: "google_ads_intel",
138
+ label: "Google Ads Transparency advertiser intel",
139
+ aliases: ["google_ads_page_intel", "google ads intel", "ads transparency intel"],
140
+ credits: mcToCredits(MC_COSTS.google_ads_intel),
141
+ unit: "per call",
142
+ notes: "Lists and hydrates an advertiser's creatives with image URLs and video references."
143
+ },
144
+ {
145
+ key: "google_ads_transcribe",
146
+ label: "Google ad video transcription",
147
+ aliases: ["google_ads_transcribe", "google ad transcript"],
148
+ credits: mcToCredits(MC_COSTS.google_ads_transcribe),
149
+ unit: "per minute",
150
+ notes: "A hold is taken, then reconciled to actual video duration."
151
+ },
125
152
  {
126
153
  key: "fb_transcribe",
127
154
  label: "Facebook video / ad transcription",
@@ -223,6 +250,9 @@ var MEMORY_PLANS = {
223
250
  var MEMORY_PLAN_QUOTA = { free: MEMORY_FREE_QUOTA_BYTES, pro: 5e9, team: 5e10 };
224
251
  var MEMORY_MARGIN_MULTIPLE = 3;
225
252
  var MEMORY_PRO_COST_BUDGET_USD = 19 / MEMORY_MARGIN_MULTIPLE;
253
+ var SCHEDULING_PLANS = {
254
+ "price_1ToXcHS8aAcsk3TGf3cW7zHx": { price_id: "price_1ToXcHS8aAcsk3TGf3cW7zHx", label: "Scheduled Actions", interval: "month", monthly_usd: 10, quota_per_period: 1e3 }
255
+ };
226
256
  var LedgerOperation = {
227
257
  TOPUP: "topup",
228
258
  SUBSCRIPTION: "subscription",
@@ -253,6 +283,12 @@ var LedgerOperation = {
253
283
  FB_TRANSCRIBE: "fb_transcribe",
254
284
  FB_SEARCH_REFUND: "fb_search_refund",
255
285
  FB_TRANSCRIBE_REFUND: "fb_transcribe_refund",
286
+ GOOGLE_ADS_SEARCH: "google_ads_search",
287
+ GOOGLE_ADS_SEARCH_REFUND: "google_ads_search_refund",
288
+ GOOGLE_ADS_INTEL: "google_ads_intel",
289
+ GOOGLE_ADS_INTEL_REFUND: "google_ads_intel_refund",
290
+ GOOGLE_ADS_TRANSCRIBE: "google_ads_transcribe",
291
+ GOOGLE_ADS_TRANSCRIBE_REFUND: "google_ads_transcribe_refund",
256
292
  INSTAGRAM_PROFILE: "instagram_profile",
257
293
  INSTAGRAM_PROFILE_REFUND: "instagram_profile_refund",
258
294
  INSTAGRAM_MEDIA: "instagram_media",
@@ -419,10 +455,11 @@ export {
419
455
  insufficientBalanceResponse,
420
456
  MEMORY_PLANS,
421
457
  MEMORY_PLAN_QUOTA,
458
+ SCHEDULING_PLANS,
422
459
  LedgerOperation,
423
460
  classifyHarvestProblem,
424
461
  serializeHarvestProblem,
425
462
  harvestProblemResponse,
426
463
  createHarvestAttemptRecorder
427
464
  };
428
- //# sourceMappingURL=chunk-H74L743B.js.map
465
+ //# sourceMappingURL=chunk-KJWNVQFL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/api/rates.ts","../src/api/harvest-problems.ts","../src/api/harvest-attempt-events.ts"],"sourcesContent":["export const MC_COSTS = {\n serp: 100,\n paa: 200,\n paa_base: 1_000,\n page_scrape: 100,\n url_map: 500,\n yt_channel: 200,\n yt_transcription: 200,\n fb_ad: 200,\n maps_search: 500,\n maps_place: 3_000,\n maps_review: 100,\n fb_search: 600,\n fb_transcribe: 200,\n google_ads_search: 600,\n google_ads_intel: 200,\n google_ads_transcribe: 200,\n instagram_profile: 400,\n instagram_media: 400,\n instagram_transcribe: 50,\n browser_minute: 12_000,\n reddit_thread: 2_000,\n} as const\n\nexport type McCostKey = keyof typeof MC_COSTS\n\nexport const MC_PER_BROWSER_MS = MC_COSTS.browser_minute / 60_000\n\nexport function browserActiveCostMc(activeMs: number): number {\n return Math.round(activeMs * MC_PER_BROWSER_MS)\n}\n\nexport const BROWSER_OPEN_MIN_BALANCE_MC = 1_000\n\nexport const MC_PER_CREDIT = 1_000\n\nexport const CREDIT_COST_CATALOG: Array<{\n key: McCostKey\n label: string\n aliases: string[]\n credits: number\n unit: string\n notes?: string\n}> = [\n {\n key: 'serp',\n label: 'SERP search',\n aliases: ['search_serp', 'serp', 'google search', 'organic results'],\n credits: mcToCredits(MC_COSTS.serp),\n unit: 'per search',\n notes: 'Returns AI Overview, PAA snippet, videos, forums, and local pack.',\n },\n {\n key: 'paa',\n label: 'PAA harvest',\n aliases: ['harvest_paa', 'paa', 'people also ask', 'questions'],\n credits: mcToCredits(MC_COSTS.paa),\n unit: `per question (+${mcToCredits(MC_COSTS.paa_base)} credit base)`,\n notes: `Full SERP feature extraction. Billed ${mcToCredits(MC_COSTS.paa_base)} credit base + ${mcToCredits(MC_COSTS.paa)} per question actually returned (unused estimate refunded).`,\n },\n {\n key: 'page_scrape',\n label: 'Page crawl / extract',\n aliases: ['extract_url', 'extract_site', 'page scrape', 'url scrape', 'single page', 'site crawl'],\n credits: mcToCredits(MC_COSTS.page_scrape),\n unit: 'per page',\n notes: 'Applies to both single-URL extraction and per-page site crawls.',\n },\n {\n key: 'url_map',\n label: 'Site URL mapping',\n aliases: ['map_site_urls', 'url map', 'site map', 'crawl urls'],\n credits: mcToCredits(MC_COSTS.url_map),\n unit: 'per mapping operation',\n notes: 'Flat rate for the full /map-urls call regardless of URL count discovered.',\n },\n {\n key: 'yt_channel',\n label: 'YouTube search / channel harvest',\n aliases: ['youtube_harvest', 'youtube search', 'youtube channel', 'yt_channel'],\n credits: mcToCredits(MC_COSTS.yt_channel),\n unit: 'per call',\n },\n {\n key: 'yt_transcription',\n label: 'YouTube transcription',\n aliases: ['youtube_transcribe', 'youtube transcript', 'transcription', 'yt_transcription'],\n credits: mcToCredits(MC_COSTS.yt_transcription),\n unit: 'per minute',\n notes: 'A 5-minute hold is taken, then reconciled to actual video duration.',\n },\n {\n key: 'fb_ad',\n label: 'Facebook search / ad lookup',\n aliases: ['facebook_page_intel', 'facebook_ad_search', 'facebook_ad', 'facebook ads', 'fb ads'],\n credits: mcToCredits(MC_COSTS.fb_ad),\n unit: 'per call',\n },\n {\n key: 'maps_search',\n label: 'Maps business search',\n aliases: ['maps_search', 'google maps search', 'gmb search', 'gbp search', 'business profiles'],\n credits: mcToCredits(MC_COSTS.maps_search),\n unit: 'per search',\n notes: 'Returns up to 50 Google Maps business/profile candidates. Use maps_place_intel to hydrate selected businesses.',\n },\n {\n key: 'maps_place',\n label: 'Maps business lookup',\n aliases: ['maps_place_intel', 'google maps', 'maps place', 'place intel'],\n credits: mcToCredits(MC_COSTS.maps_place),\n unit: 'per business',\n notes: 'Base lookup. Reviews billed separately per card at maps_review rate.',\n },\n {\n key: 'maps_review',\n label: 'Maps review',\n aliases: ['maps_reviews', 'google reviews', 'review cards', 'reviews'],\n credits: mcToCredits(MC_COSTS.maps_review),\n unit: 'per review card',\n notes: 'Charged after extraction when includeReviews is true.',\n },\n {\n key: 'fb_search',\n label: 'Facebook ad library search',\n aliases: ['facebook_search', 'fb_search', 'fb ad search'],\n credits: mcToCredits(MC_COSTS.fb_search),\n unit: 'per search',\n notes: 'Browser automation to search Facebook Ads Library by keyword.',\n },\n {\n key: 'google_ads_search',\n label: 'Google Ads Transparency search',\n aliases: ['google_ads_search', 'google ads search', 'ads transparency search'],\n credits: mcToCredits(MC_COSTS.google_ads_search),\n unit: 'per search',\n notes: 'Browser automation to find advertisers in Google Ads Transparency Center by domain or name.',\n },\n {\n key: 'google_ads_intel',\n label: 'Google Ads Transparency advertiser intel',\n aliases: ['google_ads_page_intel', 'google ads intel', 'ads transparency intel'],\n credits: mcToCredits(MC_COSTS.google_ads_intel),\n unit: 'per call',\n notes: 'Lists and hydrates an advertiser\\'s creatives with image URLs and video references.',\n },\n {\n key: 'google_ads_transcribe',\n label: 'Google ad video transcription',\n aliases: ['google_ads_transcribe', 'google ad transcript'],\n credits: mcToCredits(MC_COSTS.google_ads_transcribe),\n unit: 'per minute',\n notes: 'A hold is taken, then reconciled to actual video duration.',\n },\n {\n key: 'fb_transcribe',\n label: 'Facebook video / ad transcription',\n aliases: ['facebook_transcribe', 'facebook_video_transcribe', 'fb_transcribe', 'fb ad transcript'],\n credits: mcToCredits(MC_COSTS.fb_transcribe),\n unit: 'per minute',\n notes: 'A hold is taken, then reconciled to actual video duration.',\n },\n {\n key: 'instagram_profile',\n label: 'Instagram profile content discovery',\n aliases: ['instagram_profile_content', 'instagram profile', 'instagram content list', 'ig profile'],\n credits: mcToCredits(MC_COSTS.instagram_profile),\n unit: 'per profile scan',\n notes: 'Browser extraction of public Instagram profile grid links. Complete history may require a logged-in profile.',\n },\n {\n key: 'instagram_media',\n label: 'Instagram media download',\n aliases: ['instagram_media_download', 'instagram reel download', 'instagram post download', 'ig media'],\n credits: mcToCredits(MC_COSTS.instagram_media),\n unit: 'per post or reel',\n notes: 'Extracts post text, image URLs, and reel audio/video tracks, with local downloads when the server can write files.',\n },\n {\n key: 'instagram_transcribe',\n label: 'Instagram media transcription',\n aliases: ['instagram transcript', 'instagram reel transcript', 'ig transcribe'],\n credits: mcToCredits(MC_COSTS.instagram_transcribe),\n unit: 'per call',\n notes: 'Whisper transcription of selected Instagram audio/video media via fal.ai.',\n },\n {\n key: 'browser_minute',\n label: 'Interactive browser session',\n aliases: ['browser_open', 'browser agent', 'browser_agent', 'live browser', 'browse', 'browser control', 'interactive browser'],\n credits: mcToCredits(MC_COSTS.browser_minute),\n unit: 'per minute of use',\n notes: 'Metered per minute of use for the whole time the browser session is open. Close the session to stop the meter; abandoned sessions are auto-closed after a short idle window.',\n },\n {\n key: 'reddit_thread',\n label: 'Reddit thread + comments',\n aliases: ['reddit', 'reddit_thread', 'reddit comments', 'subreddit', 'reddit post'],\n credits: mcToCredits(MC_COSTS.reddit_thread),\n unit: 'per thread',\n notes: 'Captures a Reddit post and its comment tree. Charged on success; refunded if the thread cannot be retrieved.',\n },\n]\n\nexport const CONCURRENCY_PRICE_ID = 'price_1Ta1NRS8aAcsk3TGwsRnYbix'\n\nexport interface SubscriptionTier { tier: string; label: string; price_id: string; monthly_usd: number; credits_mc: number; concurrency: number; intro_coupon: string | null }\n\nexport const SUBSCRIPTION_TIERS: Record<string, SubscriptionTier> = {\n 'price_1TmiHRS8aAcsk3TGwmSNfNIa': { tier: 'starter', label: 'Starter', price_id: 'price_1TmiHRS8aAcsk3TGwmSNfNIa', monthly_usd: 12, credits_mc: 8_000_000, concurrency: 3, intro_coupon: 'mcp-starter-1dollar-intro-12' },\n 'price_1Tmg1nS8aAcsk3TGe8zcnGTM': { tier: 'growth', label: 'Growth', price_id: 'price_1Tmg1nS8aAcsk3TGe8zcnGTM', monthly_usd: 100, credits_mc: 80_000_000, concurrency: 10, intro_coupon: null },\n 'price_1Tmg1nS8aAcsk3TGsZw34iXS': { tier: 'scale', label: 'Scale', price_id: 'price_1Tmg1nS8aAcsk3TGsZw34iXS', monthly_usd: 250, credits_mc: 240_000_000, concurrency: 20, intro_coupon: null },\n}\n\nexport const SUBSCRIPTION_TIER_BY_KEY: Record<string, SubscriptionTier> = Object.fromEntries(\n Object.values(SUBSCRIPTION_TIERS).map(t => [t.tier, t]),\n)\nexport const CONCURRENCY_SLOT_PRICE_USD = 5\nexport const CONCURRENCY_SLOT_PRICE_CURRENCY = 'usd'\nexport const CONCURRENCY_SLOT_PRICE_INTERVAL = 'month'\nexport const CONCURRENCY_SLOT_PRICE_LABEL = '$5/month'\nexport const CONCURRENCY_SLOT_TERMINAL_COMMAND = 'npx -y -p mcp-scraper@latest mcp-scraper-cli billing concurrency checkout'\n\nexport function concurrencySlotBillingInfo() {\n const billingUrl = process.env.CONCURRENCY_BILLING_URL ?? 'https://mcpscraper.dev/billing'\n return {\n product: 'Extra concurrency slot',\n price_label: CONCURRENCY_SLOT_PRICE_LABEL,\n unit_amount_usd: CONCURRENCY_SLOT_PRICE_USD,\n currency: CONCURRENCY_SLOT_PRICE_CURRENCY,\n interval: CONCURRENCY_SLOT_PRICE_INTERVAL,\n billing_url: billingUrl,\n terminal_command: CONCURRENCY_SLOT_TERMINAL_COMMAND,\n terminal_command_with_api_key_env: `MCP_SCRAPER_API_KEY=sk_live_your_key ${CONCURRENCY_SLOT_TERMINAL_COMMAND}`,\n }\n}\n\nexport const FREE_SIGNUP_MC = 100_000\nexport const FREE_MONTHLY_REFRESH_MC = 250_000\n\nexport const BALANCE_PRICE_IDS: Record<string, number> = {\n 'price_1TZx6rS8aAcsk3TGNMc1Vgpo': 11_000_000,\n 'price_1TZx6sS8aAcsk3TGxgqB7khO': 27_500_000,\n 'price_1TZx6tS8aAcsk3TG8PnJqHlG': 60_500_000,\n 'price_1TZx6tS8aAcsk3TGNgRMpy0e': 121_000_000,\n}\n\nexport const BALANCE_PACK_LABELS: Record<string, string> = {\n 'price_1TZx6rS8aAcsk3TGNMc1Vgpo': '$10',\n 'price_1TZx6sS8aAcsk3TGxgqB7khO': '$25',\n 'price_1TZx6tS8aAcsk3TG8PnJqHlG': '$50',\n 'price_1TZx6tS8aAcsk3TGNgRMpy0e': '$100',\n}\n\nexport function mcToCredits(mc: number): number {\n return mc / MC_PER_CREDIT\n}\n\nexport function insufficientBalanceResponse(balanceMc: number, requiredMc: number) {\n const topupUrl = process.env.TOPUP_URL ?? 'https://mcpscraper.dev/billing'\n const balanceCredits = mcToCredits(balanceMc)\n const requiredCredits = mcToCredits(requiredMc)\n return {\n error: 'insufficient_balance',\n error_code: 'insufficient_balance' as const,\n message: `Insufficient credits. Balance: ${balanceCredits} credits. This call requires ${requiredCredits} credits. Top up at ${topupUrl}`,\n balance_credits: balanceCredits,\n required_credits: requiredCredits,\n topup_url: topupUrl,\n }\n}\n\nexport interface MemoryPlan {\n plan: 'pro' | 'team'\n label: string\n price_id: string\n interval: 'month' | 'year'\n monthly_usd: number\n quota_bytes: number\n}\n\nexport const MEMORY_FREE_QUOTA_BYTES = 0.1e9 * 1\n\nexport const MEMORY_PLANS: Record<string, MemoryPlan> = {\n 'price_1TnMSTS8aAcsk3TGBgiwuvqL': { plan: 'pro', label: 'Pro', price_id: 'price_1TnMSTS8aAcsk3TGBgiwuvqL', interval: 'month', monthly_usd: 19, quota_bytes: 5e9 },\n 'price_1TnMSTS8aAcsk3TGWBVU2agY': { plan: 'pro', label: 'Pro', price_id: 'price_1TnMSTS8aAcsk3TGWBVU2agY', interval: 'year', monthly_usd: 19, quota_bytes: 5e9 },\n}\n\nexport const MEMORY_PLAN_QUOTA: Record<string, number> = { free: MEMORY_FREE_QUOTA_BYTES, pro: 5e9, team: 50e9 }\n\nexport const MEMORY_MARGIN_MULTIPLE = 3\nexport const MEMORY_FREE_COST_CAP_USD = 1.0\nexport const MEMORY_PRO_COST_BUDGET_USD = 19 / MEMORY_MARGIN_MULTIPLE\n\nexport interface SchedulingPlan {\n price_id: string\n label: string\n interval: 'month'\n monthly_usd: number\n quota_per_period: number\n}\n\nexport const SCHEDULING_PLANS: Record<string, SchedulingPlan> = {\n 'price_1ToXcHS8aAcsk3TGf3cW7zHx': { price_id: 'price_1ToXcHS8aAcsk3TGf3cW7zHx', label: 'Scheduled Actions', interval: 'month', monthly_usd: 10, quota_per_period: 1000 },\n}\n\nexport const LedgerOperation = {\n TOPUP: 'topup',\n SUBSCRIPTION: 'subscription',\n SIGNUP_GRANT: 'signup_grant',\n MONTHLY_REFRESH: 'monthly_free_refresh',\n PAA: 'paa',\n PAA_REFUND: 'paa_refund',\n SERP: 'serp',\n REFUND: 'refund',\n TRANSCRIPTION: 'transcription',\n TRANSCRIPTION_HOLD: 'transcription_hold',\n TRANSCRIPTION_REFUND: 'transcription_refund',\n YT_CHANNEL: 'yt_channel',\n FB_AD: 'fb_ad',\n MAPS_SEARCH: 'maps_search',\n MAPS_PLACE: 'maps_place',\n MAPS_REVIEW: 'maps_review',\n MAPS_REVIEW_REFUND: 'maps_review_refund',\n EXTRACT_SITE: 'extract_site',\n EXTRACT_SITE_REFUND: 'extract_site_refund',\n EXTRACT_URL: 'page_scrape',\n URL_MAP: 'url_map',\n EXTRACT_SITE_HOLD: 'extract_site_hold',\n YT_CHANNEL_REFUND: 'yt_channel_refund',\n FB_AD_REFUND: 'fb_ad_refund',\n URL_MAP_REFUND: 'url_map_refund',\n FB_SEARCH: 'fb_search',\n FB_TRANSCRIBE: 'fb_transcribe',\n FB_SEARCH_REFUND: 'fb_search_refund',\n FB_TRANSCRIBE_REFUND: 'fb_transcribe_refund',\n GOOGLE_ADS_SEARCH: 'google_ads_search',\n GOOGLE_ADS_SEARCH_REFUND: 'google_ads_search_refund',\n GOOGLE_ADS_INTEL: 'google_ads_intel',\n GOOGLE_ADS_INTEL_REFUND: 'google_ads_intel_refund',\n GOOGLE_ADS_TRANSCRIBE: 'google_ads_transcribe',\n GOOGLE_ADS_TRANSCRIBE_REFUND: 'google_ads_transcribe_refund',\n INSTAGRAM_PROFILE: 'instagram_profile',\n INSTAGRAM_PROFILE_REFUND: 'instagram_profile_refund',\n INSTAGRAM_MEDIA: 'instagram_media',\n INSTAGRAM_MEDIA_REFUND:'instagram_media_refund',\n INSTAGRAM_TRANSCRIBE: 'instagram_transcribe',\n INSTAGRAM_TRANSCRIBE_REFUND: 'instagram_transcribe_refund',\n BROWSER_SESSION: 'browser_session',\n REDDIT_THREAD: 'reddit_thread',\n REDDIT_THREAD_REFUND: 'reddit_thread_refund',\n} as const\n\nexport type LedgerOperation = typeof LedgerOperation[keyof typeof LedgerOperation]\n","import { CaptchaError, LocationMismatchError, RequestAbortedError } from '../errors.js'\n\nexport type HarvestProblemCode =\n | 'request_aborted'\n | 'captcha_exhausted'\n | 'location_mismatch'\n | 'proxy_tunnel_failed'\n | 'proxy_unavailable'\n | 'harvest_timeout'\n | 'extraction_failed'\n\nexport interface HarvestProblem {\n error_code: HarvestProblemCode\n error_type: string\n message: string\n retryable: boolean\n httpStatus: number\n terminalStatus: 'cancelled' | 'failed'\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\nfunction looksLikeTimeout(err: unknown, message: string): boolean {\n if (err instanceof DOMException && (err.name === 'TimeoutError' || err.name === 'AbortError')) return true\n return /timeout|timed out|Timeout \\d+ms exceeded|deadline/i.test(message)\n}\n\nfunction looksLikeCaptcha(message: string): boolean {\n return /captcha|recaptcha|unusual traffic|google\\.com\\/sorry|blocked/i.test(message)\n}\n\nfunction looksLikeProxyTunnelFailure(message: string): boolean {\n return /ERR_TUNNEL_CONNECTION_FAILED|ERR_PROXY_CONNECTION_FAILED|ERR_SOCKS_CONNECTION_FAILED|tunnel connection failed|proxy connection failed|transport error: proxy/i.test(message)\n}\n\nfunction looksLikeProxyUnavailable(message: string): boolean {\n return /proxy unavailable|proxy_unavailable|connection_test_failed|did not return a proxy id|configured fallback/i.test(message)\n}\n\nexport function classifyHarvestProblem(err: unknown): HarvestProblem {\n const message = errorMessage(err)\n\n if (err instanceof RequestAbortedError) {\n return {\n error_code: 'request_aborted',\n error_type: 'request_aborted',\n message,\n retryable: true,\n httpStatus: 408,\n terminalStatus: 'cancelled',\n }\n }\n\n if (err instanceof CaptchaError || looksLikeCaptcha(message)) {\n return {\n error_code: 'captcha_exhausted',\n error_type: 'captcha',\n message,\n retryable: true,\n httpStatus: 503,\n terminalStatus: 'failed',\n }\n }\n\n if (err instanceof LocationMismatchError) {\n return {\n error_code: 'location_mismatch',\n error_type: 'location_mismatch',\n message,\n retryable: true,\n httpStatus: 503,\n terminalStatus: 'failed',\n }\n }\n\n if (looksLikeProxyTunnelFailure(message)) {\n return {\n error_code: 'proxy_tunnel_failed',\n error_type: 'proxy_tunnel_failed',\n message,\n retryable: true,\n httpStatus: 503,\n terminalStatus: 'failed',\n }\n }\n\n if (looksLikeProxyUnavailable(message)) {\n return {\n error_code: 'proxy_unavailable',\n error_type: 'proxy_unavailable',\n message,\n retryable: true,\n httpStatus: 503,\n terminalStatus: 'failed',\n }\n }\n\n if (looksLikeTimeout(err, message)) {\n return {\n error_code: 'harvest_timeout',\n error_type: 'timeout',\n message,\n retryable: true,\n httpStatus: 504,\n terminalStatus: 'failed',\n }\n }\n\n return {\n error_code: 'extraction_failed',\n error_type: 'extraction',\n message,\n retryable: false,\n httpStatus: 500,\n terminalStatus: 'failed',\n }\n}\n\nexport function serializeHarvestProblem(problem: HarvestProblem): string {\n return JSON.stringify({\n error_code: problem.error_code,\n error_type: problem.error_type,\n message: problem.message,\n retryable: problem.retryable,\n })\n}\n\nexport function harvestProblemResponse(problem: HarvestProblem): {\n error: string\n error_code: HarvestProblemCode\n error_type: string\n retryable: boolean\n} {\n return {\n error: problem.message,\n error_code: problem.error_code,\n error_type: problem.error_type,\n retryable: problem.retryable,\n }\n}\n","import type { HarvestAttemptLogEvent } from '../harvest.js'\nimport { finishHarvestAttempt, startHarvestAttempt } from './db.js'\n\nexport function createHarvestAttemptRecorder(jobId: string, userId: number | bigint) {\n return async (event: HarvestAttemptLogEvent): Promise<void> => {\n if (event.type === 'started') {\n await startHarvestAttempt({\n jobId,\n userId,\n attemptNumber: event.attemptNumber,\n maxAttempts: event.maxAttempts,\n query: event.query,\n location: event.location,\n maxQuestions: event.maxQuestions,\n startedAt: event.startedAt,\n })\n return\n }\n\n await finishHarvestAttempt({\n jobId,\n attemptNumber: event.attemptNumber,\n outcome: event.outcome,\n kernelSessionId: event.kernelSessionId,\n questionCount: event.questionCount,\n durationMs: event.durationMs,\n error: event.error,\n willRetry: event.willRetry,\n kernelDeleteStarted: event.cleanup.kernelDeleteStarted,\n kernelDeleteSucceeded: event.cleanup.kernelDeleteSucceeded,\n kernelDeleteError: event.cleanup.kernelDeleteError,\n browserCloseSucceeded: event.cleanup.browserCloseSucceeded,\n browserCloseError: event.cleanup.browserCloseError,\n debug: event.debug,\n completedAt: event.completedAt,\n })\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  PACKAGE_VERSION
3
- } from "./chunk-7MY2NSQX.js";
3
+ } from "./chunk-I5EFBTYE.js";
4
4
  import {
5
5
  browserServiceProfileName,
6
6
  browserServiceProfileSaveChanges,
@@ -127,6 +127,13 @@ seam is noted so you can chain them.
127
127
  - Transcribe an ad video -> **facebook_ad_transcribe** (takes a videoUrl from \`facebook_page_intel\`).
128
128
  - Transcribe an organic reel/video/post -> **facebook_video_transcribe** (takes the Facebook url directly).
129
129
 
130
+ ## Google Ads (Ads Transparency Center)
131
+ - Find advertisers by domain or brand -> **google_ads_search** (returns \`advertisers[]\` with advertiserId).
132
+ - One advertiser's ads -> **google_ads_page_intel** (takes advertiserId or domain; returns \`ads[]\` with
133
+ format, imageUrls, youtubeVideoId, and videoUrl).
134
+ - Transcribe a video ad -> **google_ads_transcribe** (takes a googlevideo \`videoUrl\` from
135
+ \`google_ads_page_intel\`), or **youtube_transcribe** for a returned \`youtubeVideoId\`.
136
+
130
137
  ## Instagram
131
138
  - Profile inventory -> **instagram_profile_content** (returns post/reel/tv urls).
132
139
  - One post or reel -> **instagram_media_download** (takes a url from \`instagram_profile_content\`, or a
@@ -1012,7 +1019,9 @@ ${paaRows}` : "## People Also Ask\n*Google did not return a People Also Ask bloc
1012
1019
  ${serpRows}` : "";
1013
1020
  const aiSection = aiOvw?.detected && aiOvw.text ? `
1014
1021
  ## AI Overview
1015
- > ${truncate(aiOvw.text, 600)}` : "";
1022
+ > ${truncate(aiOvw.text, 600)}` + (aiOvw.shareUrl ? `
1023
+
1024
+ **Shareable link:** ${aiOvw.shareUrl}` : "") : "";
1016
1025
  const statsLine = durationMs ? `
1017
1026
  ## Stats
1018
1027
  - Status: ${diagnostics?.completionStatus ?? (flat.length ? "paa_found" : "no_paa")} \xB7 Questions: ${flat.length} \xB7 Duration: ${(durationMs / 1e3).toFixed(1)}s` : "";
@@ -1045,7 +1054,7 @@ ${paaTable}${serpTable}${entityIdsSection(entityIds)}${aiSection}${statsLine}${d
1045
1054
  domain: String(r.domain ?? ""),
1046
1055
  snippet: r.snippet ?? null
1047
1056
  })),
1048
- aiOverview: aiOvw ? { detected: aiOvw.detected === true, text: aiOvw.text ?? null } : null,
1057
+ aiOverview: aiOvw ? { detected: aiOvw.detected === true, text: aiOvw.text ?? null, shareUrl: aiOvw.shareUrl ?? null } : null,
1049
1058
  entityIds: entityIds ? { entities: entityIds.entities ?? [], kgIds: entityIds.kgIds ?? [], cids: entityIds.cids ?? [], gcids: entityIds.gcids ?? [] } : null,
1050
1059
  durationMs: durationMs ?? null
1051
1060
  }
@@ -1077,7 +1086,9 @@ ${serpRows}` : "## Organic Results\n*None found*";
1077
1086
  ${localRows}` : "";
1078
1087
  const aiSection = aiOvw?.detected && aiOvw.text ? `
1079
1088
  ## AI Overview
1080
- > ${truncate(aiOvw.text, 600)}` : "";
1089
+ > ${truncate(aiOvw.text, 600)}` + (aiOvw.shareUrl ? `
1090
+
1091
+ **Shareable link:** ${aiOvw.shareUrl}` : "") : "";
1081
1092
  const tips = `
1082
1093
  ---
1083
1094
  \u{1F4A1} **Tips**
@@ -1106,7 +1117,7 @@ ${serpTable}${localSection}${entityIdsSection(entityIds)}${aiSection}${debugSect
1106
1117
  reviewCount: b.reviewCount ?? null,
1107
1118
  websiteUrl: b.websiteUrl ?? null
1108
1119
  })),
1109
- aiOverview: aiOvw ? { detected: aiOvw.detected === true, text: aiOvw.text ?? null } : null,
1120
+ aiOverview: aiOvw ? { detected: aiOvw.detected === true, text: aiOvw.text ?? null, shareUrl: aiOvw.shareUrl ?? null } : null,
1110
1121
  entityIds: entityIds ? { entities: entityIds.entities ?? [], kgIds: entityIds.kgIds ?? [], cids: entityIds.cids ?? [], gcids: entityIds.gcids ?? [] } : null
1111
1122
  }
1112
1123
  };
@@ -1664,6 +1675,136 @@ ${rows}`,
1664
1675
  }
1665
1676
  };
1666
1677
  }
1678
+ function formatGoogleAdsSearch(raw, input) {
1679
+ const parsed = parseData(raw);
1680
+ if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
1681
+ const d = parsed.data;
1682
+ const advertisers = d.advertisers ?? [];
1683
+ const rows = advertisers.map(
1684
+ (a, i) => `| ${i + 1} | ${cell(a.name)} | ${cell(a.domain)} | ${a.approxAdCount ?? "\u2014"} | \`${a.advertiserId ?? "\u2014"}\` |`
1685
+ ).join("\n");
1686
+ const full = [
1687
+ `# Google Ads Transparency Search: "${input.query}"`,
1688
+ `**${advertisers.length} advertisers found**${d.region ? ` \xB7 ${d.region}` : ""}`,
1689
+ `
1690
+ ## Advertisers
1691
+ | # | Name | Domain | ~Ads | Advertiser ID |
1692
+ |---|------|--------|------|---------------|
1693
+ ${rows}`,
1694
+ `
1695
+ ---
1696
+ \u{1F4A1} **Tips**
1697
+ - Pull an advertiser's ads: use \`google_ads_page_intel\` with \`advertiserId\`
1698
+ - Or pass a \`domain\` directly to \`google_ads_page_intel\``
1699
+ ].join("\n");
1700
+ return {
1701
+ ...oneBlock(full),
1702
+ structuredContent: {
1703
+ query: input.query,
1704
+ region: d.region ?? "US",
1705
+ advertiserCount: advertisers.length,
1706
+ advertisers: advertisers.map((a) => ({
1707
+ advertiserId: a.advertiserId ?? null,
1708
+ name: a.name ?? null,
1709
+ domain: a.domain ?? null,
1710
+ approxAdCount: typeof a.approxAdCount === "number" ? a.approxAdCount : null,
1711
+ detailUrl: a.detailUrl ?? null
1712
+ }))
1713
+ }
1714
+ };
1715
+ }
1716
+ function formatGoogleAdsPageIntel(raw, input) {
1717
+ const parsed = parseData(raw);
1718
+ if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
1719
+ const d = parsed.data;
1720
+ const ads = d.ads ?? [];
1721
+ const rows = ads.map((a, i) => {
1722
+ const media = a.youtubeVideoId ? `yt:${a.youtubeVideoId}` : a.videoUrl ? "video" : a.imageUrls.length ? `${a.imageUrls.length} img` : "\u2014";
1723
+ return `| ${i + 1} | ${cell(a.format)} | ${media} | \`${a.creativeId ?? "\u2014"}\` |`;
1724
+ }).join("\n");
1725
+ const label = d.advertiserName ?? input.advertiserId ?? input.domain ?? "";
1726
+ const full = [
1727
+ `# Google Ads Transparency: ${label}`,
1728
+ `**${ads.length} creatives** \xB7 ${d.videoCount ?? 0} video \xB7 ${d.imageCount ?? 0} image \xB7 ${d.textCount ?? 0} text${d.region ? ` \xB7 ${d.region}` : ""}`,
1729
+ `
1730
+ ## Creatives
1731
+ | # | Format | Media | Creative ID |
1732
+ |---|--------|-------|-------------|
1733
+ ${rows}`,
1734
+ `
1735
+ ---
1736
+ \u{1F4A1} **Tips**
1737
+ - Transcribe a video ad: pass its \`videoUrl\` to \`google_ads_transcribe\`, or its \`youtubeVideoId\` to \`youtube_transcribe\`
1738
+ - Full ad detail is at each creative's \`detailUrl\``
1739
+ ].join("\n");
1740
+ return {
1741
+ ...oneBlock(full),
1742
+ structuredContent: {
1743
+ advertiserId: d.advertiserId ?? null,
1744
+ advertiserName: d.advertiserName ?? null,
1745
+ domain: d.domain ?? null,
1746
+ region: d.region ?? "US",
1747
+ totalCreatives: typeof d.totalCreatives === "number" ? d.totalCreatives : ads.length,
1748
+ videoCount: d.videoCount ?? 0,
1749
+ imageCount: d.imageCount ?? 0,
1750
+ textCount: d.textCount ?? 0,
1751
+ ads: ads.map((a) => ({
1752
+ creativeId: a.creativeId ?? null,
1753
+ advertiserId: a.advertiserId ?? null,
1754
+ format: a.format ?? null,
1755
+ lastShown: a.lastShown ?? null,
1756
+ detailUrl: a.detailUrl ?? null,
1757
+ landingDomain: a.landingDomain ?? null,
1758
+ imageUrls: Array.isArray(a.imageUrls) ? a.imageUrls : [],
1759
+ youtubeVideoId: a.youtubeVideoId ?? null,
1760
+ videoUrl: a.videoUrl ?? null,
1761
+ variations: typeof a.variations === "number" ? a.variations : null
1762
+ }))
1763
+ }
1764
+ };
1765
+ }
1766
+ function formatGoogleAdsTranscribe(raw, input) {
1767
+ const parsed = parseData(raw);
1768
+ if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
1769
+ const d = parsed.data;
1770
+ const text = d.text ?? "";
1771
+ const chunks = d.chunks ?? [];
1772
+ const durSec = d.durationMs ? (d.durationMs / 1e3).toFixed(0) : "\u2014";
1773
+ const words = wordCount(text);
1774
+ const chunkRows = chunks.slice(0, 50).map((c) => {
1775
+ const sec = Number.isFinite(c.timestamp[0]) ? Math.floor(c.timestamp[0]) : 0;
1776
+ const mm = String(Math.floor(sec / 60)).padStart(2, "0");
1777
+ const ss = String(sec % 60).padStart(2, "0");
1778
+ return `| ${mm}:${ss} | ${cell(truncate(c.text, 120))} |`;
1779
+ }).join("\n");
1780
+ const full = [
1781
+ `# Google Ad Transcript`,
1782
+ `**Duration:** ${durSec}s \xB7 **${words} words**`,
1783
+ `
1784
+ ## Full Transcript
1785
+ ${text}`,
1786
+ chunks.length ? `
1787
+ ## Timestamped Chunks
1788
+ | Time | Text |
1789
+ |------|------|
1790
+ ${chunkRows}` : "",
1791
+ `
1792
+ ---
1793
+ \u{1F4A1} Get more ads from this advertiser: use \`google_ads_page_intel\`.`
1794
+ ].filter(Boolean).join("\n");
1795
+ return {
1796
+ ...oneBlock(full),
1797
+ structuredContent: {
1798
+ videoUrl: input.videoUrl,
1799
+ wordCount: words,
1800
+ chunkCount: chunks.length,
1801
+ durationMs: typeof d.durationMs === "number" ? d.durationMs : null,
1802
+ transcriptText: text,
1803
+ chunks: structuredTranscriptChunks(chunks),
1804
+ resolvedInputs: { videoUrl: input.videoUrl }
1805
+ }
1806
+ };
1807
+ }
1667
1808
  function normalizeMapsAttempts(value) {
1668
1809
  const attempts = Array.isArray(value) ? value : [];
1669
1810
  return attempts.map((attempt, index) => ({
@@ -2719,6 +2860,20 @@ var FacebookVideoTranscribeInputSchema = {
2719
2860
  url: z.string().url().describe("Organic Facebook reel, video, watch, post, or share URL from facebook.com, m.facebook.com, or fb.watch. The tool renders the page, extracts the best matching public Facebook CDN MP4 URL, then transcribes it. Use this when the user pastes a normal Facebook video page URL and asks for the transcript or downloadable MP4."),
2720
2861
  quality: z.enum(["best", "hd", "sd"]).default("best").describe("Preferred progressive MP4 quality. Use best by default; hd prefers the highest HD progressive URL; sd forces the SD URL.")
2721
2862
  };
2863
+ var GoogleAdsSearchInputSchema = {
2864
+ query: z.string().min(1).describe("A domain (e.g. getviktor.com) or advertiser/brand name to look up in the Google Ads Transparency Center. Domains resolve to the advertisers running ads that point to that site."),
2865
+ region: z.string().length(2).default("US").describe("Two-letter region code for where the ads are shown. Default US. Examples: US, CA, GB, AU."),
2866
+ maxResults: z.number().int().min(1).max(20).default(10).describe("Maximum advertisers to return. Default 10, maximum 20.")
2867
+ };
2868
+ var GoogleAdsPageIntelInputSchema = {
2869
+ advertiserId: z.string().optional().describe("Google Ads Transparency advertiser ID (starts with AR...). Use one returned by google_ads_search; do not construct one yourself."),
2870
+ domain: z.string().optional().describe("A domain (e.g. getviktor.com) whose primary advertiser to inspect when advertiserId is unknown. One of advertiserId or domain is required."),
2871
+ region: z.string().length(2).default("US").describe("Two-letter region code for where the ads are shown. Default US."),
2872
+ maxAds: z.number().int().min(1).max(200).default(50).describe("Maximum creatives to inspect and hydrate. Default 50, maximum 200. Prefer 25-50 for focused scans.")
2873
+ };
2874
+ var GoogleAdsTranscribeInputSchema = {
2875
+ videoUrl: z.string().url().describe("Direct googlevideo.com playback URL from a google_ads_page_intel video ad result (the videoUrl field). For YouTube-hosted ads use youtube_transcribe with the returned youtubeVideoId instead.")
2876
+ };
2722
2877
  var InstagramProfileContentInputSchema = {
2723
2878
  handle: z.string().min(1).optional().describe("Instagram handle, with or without @. Provide handle or url."),
2724
2879
  url: z.string().url().optional().describe("Instagram profile URL, e.g. https://www.instagram.com/nasaartemis/. Provide handle or url."),
@@ -2929,7 +3084,8 @@ var OrganicResultOutput = z.object({
2929
3084
  });
2930
3085
  var AiOverviewOutput = z.object({
2931
3086
  detected: z.boolean(),
2932
- text: NullableString
3087
+ text: NullableString,
3088
+ shareUrl: NullableString.optional()
2933
3089
  }).nullable();
2934
3090
  var EntityIdsOutput = z.object({
2935
3091
  entities: z.array(z.object({
@@ -3163,6 +3319,40 @@ var FacebookPageIntelOutputSchema = {
3163
3319
  variations: z.number().int().nullable()
3164
3320
  }))
3165
3321
  };
3322
+ var GoogleAdsSearchOutputSchema = {
3323
+ query: z.string(),
3324
+ region: z.string(),
3325
+ advertiserCount: z.number().int().min(0),
3326
+ advertisers: z.array(z.object({
3327
+ advertiserId: NullableString,
3328
+ name: NullableString,
3329
+ domain: NullableString,
3330
+ approxAdCount: z.number().int().nullable(),
3331
+ detailUrl: NullableString
3332
+ }))
3333
+ };
3334
+ var GoogleAdsPageIntelOutputSchema = {
3335
+ advertiserId: NullableString,
3336
+ advertiserName: NullableString,
3337
+ domain: NullableString,
3338
+ region: z.string(),
3339
+ totalCreatives: z.number().int().min(0),
3340
+ videoCount: z.number().int().min(0),
3341
+ imageCount: z.number().int().min(0),
3342
+ textCount: z.number().int().min(0),
3343
+ ads: z.array(z.object({
3344
+ creativeId: NullableString,
3345
+ advertiserId: NullableString,
3346
+ format: NullableString,
3347
+ lastShown: NullableString,
3348
+ detailUrl: NullableString,
3349
+ landingDomain: NullableString,
3350
+ imageUrls: z.array(z.string()),
3351
+ youtubeVideoId: NullableString,
3352
+ videoUrl: NullableString,
3353
+ variations: z.number().int().nullable()
3354
+ }))
3355
+ };
3166
3356
  var FacebookVideoTranscribeOutputSchema = {
3167
3357
  sourceUrl: z.string().url(),
3168
3358
  pageUrl: z.string().url(),
@@ -3305,6 +3495,17 @@ var FacebookAdTranscribeOutputSchema = {
3305
3495
  videoUrl: z.string().url()
3306
3496
  })
3307
3497
  };
3498
+ var GoogleAdsTranscribeOutputSchema = {
3499
+ videoUrl: z.string().url(),
3500
+ wordCount: z.number().int().min(0),
3501
+ chunkCount: z.number().int().min(0),
3502
+ durationMs: z.number().nullable(),
3503
+ transcriptText: z.string(),
3504
+ chunks: z.array(TranscriptChunkOutput),
3505
+ resolvedInputs: z.object({
3506
+ videoUrl: z.string().url()
3507
+ })
3508
+ };
3308
3509
  var CaptureSerpSnapshotOutputSchema = {
3309
3510
  schemaVersion: z.literal("serp-intelligence.capture.v1"),
3310
3511
  status: z.string(),
@@ -3954,6 +4155,27 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
3954
4155
  outputSchema: FacebookAdTranscribeOutputSchema,
3955
4156
  annotations: liveWebToolAnnotations("Facebook Ad Transcription")
3956
4157
  }, async (input) => formatFacebookAdTranscribe(await executor.facebookAdTranscribe(input), input));
4158
+ server.registerTool("google_ads_search", {
4159
+ title: "Google Ads Transparency Search",
4160
+ description: withReportNote("Search the Google Ads Transparency Center to find advertisers running Google ads (Search, Display, YouTube) by domain or brand/advertiser name. Pass a domain like getviktor.com to see every advertiser account pointing ads at that site, or an advertiser name. Returns advertisers with their advertiser ID and an approximate ad count. Use this to discover an advertiserId, then pass it (or the domain) to google_ads_page_intel for the actual ad creatives."),
4161
+ inputSchema: GoogleAdsSearchInputSchema,
4162
+ outputSchema: GoogleAdsSearchOutputSchema,
4163
+ annotations: liveWebToolAnnotations("Google Ads Transparency Search")
4164
+ }, async (input) => formatGoogleAdsSearch(await executor.googleAdsSearch(input), input));
4165
+ server.registerTool("google_ads_page_intel", {
4166
+ title: "Google Ads Advertiser Intel",
4167
+ description: withReportNote("Harvest an advertiser's ads from the Google Ads Transparency Center when the user wants a competitor's Google/YouTube ad creatives, copy angles, image URLs, or video ads. Accepts an advertiserId (from google_ads_search) or a domain directly. Returns each creative's format (text/image/video), image URLs, landing info, a detail URL, and \u2014 for video ads \u2014 the YouTube video ID and/or a direct video URL ready for transcription. Use google_ads_search first when you only have a brand name. Transcribe video ads with google_ads_transcribe (video URL) or youtube_transcribe (YouTube ID)."),
4168
+ inputSchema: GoogleAdsPageIntelInputSchema,
4169
+ outputSchema: GoogleAdsPageIntelOutputSchema,
4170
+ annotations: liveWebToolAnnotations("Google Ads Advertiser Intel")
4171
+ }, async (input) => formatGoogleAdsPageIntel(await executor.googleAdsPageIntel(input), input));
4172
+ server.registerTool("google_ads_transcribe", {
4173
+ title: "Google Ad Video Transcription",
4174
+ description: "Transcribe audio from a Google video ad. Use this when google_ads_page_intel returned a direct videoUrl (a googlevideo.com playback URL) for a video creative and the user asks what the ad says or wants its claims/messaging. Returns full transcript, timestamped chunks, and word count. For YouTube-hosted ads, pass the returned youtubeVideoId to youtube_transcribe instead; run google_ads_page_intel first when you only have an advertiser/domain.",
4175
+ inputSchema: GoogleAdsTranscribeInputSchema,
4176
+ outputSchema: GoogleAdsTranscribeOutputSchema,
4177
+ annotations: liveWebToolAnnotations("Google Ad Video Transcription")
4178
+ }, async (input) => formatGoogleAdsTranscribe(await executor.googleAdsTranscribe(input), input));
3957
4179
  server.registerTool("facebook_video_transcribe", {
3958
4180
  title: "Facebook Organic Video Transcription",
3959
4181
  description: withReportNote("Transcribe audio from an organic Facebook reel, video, watch, post, or share URL, including fb.watch links. Use this when the user pastes a normal Facebook video page URL and wants the transcript or downloadable MP4. Renders the Facebook page in a browser, selects the best matching public Facebook CDN MP4 URL from page state, then returns sourceUrl, resolved pageUrl, videoId, ownerName, selectedQuality, bitrate, videoDurationSec, extracted MP4 URL, full transcript, and timestamped chunks."),
@@ -4273,6 +4495,15 @@ var HttpMcpToolExecutor = class {
4273
4495
  facebookVideoTranscribe(input) {
4274
4496
  return this.call("/facebook/video-transcribe", input, this.httpTimeoutOverrideMs ?? 24e4);
4275
4497
  }
4498
+ googleAdsSearch(input) {
4499
+ return this.call("/google-ads/search", input, this.httpTimeoutOverrideMs ?? 24e4);
4500
+ }
4501
+ googleAdsPageIntel(input) {
4502
+ return this.call("/google-ads/page-intel", input, this.httpTimeoutOverrideMs ?? 3e5);
4503
+ }
4504
+ googleAdsTranscribe(input) {
4505
+ return this.call("/google-ads/transcribe", input, this.httpTimeoutOverrideMs ?? 24e4);
4506
+ }
4276
4507
  instagramProfileContent(input) {
4277
4508
  return this.call("/instagram/profile-content", withDefaultHostedProfile(input), this.httpTimeoutOverrideMs ?? 24e4);
4278
4509
  }
@@ -5817,4 +6048,4 @@ export {
5817
6048
  exportFanout,
5818
6049
  registerBrowserAgentMcpTools
5819
6050
  };
5820
- //# sourceMappingURL=chunk-NGD4ITAO.js.map
6051
+ //# sourceMappingURL=chunk-QLQYNE7E.js.map