@revclamp/google-ads-transparency-mcp-actor 1.0.0 → 1.0.2

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/actor_config.json CHANGED
@@ -21,7 +21,7 @@
21
21
  ],
22
22
  "manifest": {
23
23
  "name": "google-ads-transparency-mcp-actor",
24
- "version": "1.0.0",
24
+ "version": "1.0.2",
25
25
  "title": "RevClamp AdPulse - For Google Ads Transparency Center (MCP & Apify Actor)",
26
26
  "websiteUrl": "https://revclamp.com",
27
27
  "description": "Find verified Google Ads advertiser entities and extract active or historical ad creatives (Text, Image, Video), preview links, and run dates without browser or proxy overhead. Independent intelligence tool by RevClamp; not affiliated with or endorsed by Google LLC."
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@revclamp/google-ads-transparency-mcp-actor",
3
3
  "mcpName": "com.revclamp/google-ads-transparency-mcp-actor",
4
- "version": "1.0.0",
4
+ "version": "1.0.2",
5
5
  "type": "module",
6
6
  "description": "RevClamp AdPulse - For Google Ads Transparency Center (MCP & Apify Actor). Independent intelligence tool by RevClamp; not affiliated with or endorsed by Google LLC.",
7
7
  "homepage": "https://revclamp.com",
package/server.json CHANGED
@@ -2,8 +2,8 @@
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
3
3
  "name": "com.revclamp/google-ads-transparency-mcp-actor",
4
4
  "title": "RevClamp AdPulse (Google Ads Transparency MCP)",
5
- "description": "Find verified Google Ads advertiser entities and extract active or historical ad creatives (Text, Image, Video), preview links, and run dates. Independent intelligence tool by RevClamp; not affiliated with or endorsed by Google LLC.",
6
- "version": "1.0.0",
5
+ "description": "Google Ads Transparency Center advertiser & creative intelligence MCP server by RevClamp.",
6
+ "version": "1.0.2",
7
7
  "websiteUrl": "https://revclamp.com",
8
8
  "repository": {
9
9
  "url": "https://github.com/lhphong09t2/revclamp-mcp",
@@ -27,7 +27,7 @@
27
27
  "registryType": "npm",
28
28
  "registryBaseUrl": "https://registry.npmjs.org",
29
29
  "identifier": "@revclamp/google-ads-transparency-mcp-actor",
30
- "version": "1.0.0",
30
+ "version": "1.0.2",
31
31
  "transport": {
32
32
  "type": "stdio"
33
33
  }
@@ -1255,6 +1255,7 @@ export async function runGoogleAdsTransparencyActor(input = {}, hooks = {}) {
1255
1255
  advertiserId: targetItem.advertiserId,
1256
1256
  creativeId: targetItem.creativeId,
1257
1257
  includeAdCopy: Boolean(input?.includeAdCopy),
1258
+ fallbackPreviewUrl: targetItem.previewUrl,
1258
1259
  fetchImpl: hooks.fetchImpl,
1259
1260
  nowFn: nowMs,
1260
1261
  sleepFn,
@@ -1282,6 +1283,14 @@ export async function runGoogleAdsTransparencyActor(input = {}, hooks = {}) {
1282
1283
  rateLimitedInEnrich = true;
1283
1284
  }
1284
1285
  }
1286
+ } else if (detailRes.ok && detailRes.untrustedAdContent) {
1287
+ targetItem.untrustedAdContent = detailRes.untrustedAdContent;
1288
+ if (
1289
+ detailRes.untrustedAdContent.extractionStatus === 'UPSTREAM_RATE_LIMITED' ||
1290
+ detailRes.untrustedAdContent.contentJsFetchStatus === 'UPSTREAM_RATE_LIMITED'
1291
+ ) {
1292
+ rateLimitedInEnrich = true;
1293
+ }
1285
1294
  }
1286
1295
  }
1287
1296
  }
@@ -43,7 +43,7 @@ export const ENCODED_BRAND_ICON_DATA_URI = `data:image/svg+xml;utf8,${encodeURIC
43
43
  export const MCP_SERVER_INFO = Object.freeze({
44
44
  name: 'google-ads-transparency-mcp-actor',
45
45
  title: 'RevClamp AdPulse - For Google Ads Transparency Center (MCP & Apify Actor)',
46
- version: '1.0.0',
46
+ version: '1.0.2',
47
47
  description:
48
48
  'Find verified Google Ads advertiser entities and extract active or historical ad creatives (Text, Image, Video), preview links, and run dates. Independent intelligence tool by RevClamp; not affiliated with or endorsed by Google LLC.',
49
49
  // Forward-compatible MCP Registry & Glama metadata extensions (ignored by older stdio clients)
@@ -1928,7 +1928,7 @@ export async function resolveAdvertiserEntities(options = {}) {
1928
1928
  const endpointUrl = options.endpointUrl || GOOGLE_ADS_RPC_ENDPOINTS.SearchSuggestions;
1929
1929
  const rpcRes = await executeGoogleAdsRpcPost(
1930
1930
  endpointUrl,
1931
- { '1': searchQueryStr, '2': 10, '3': 10 },
1931
+ { '1': searchQueryStr, '2': 50, '3': 10 },
1932
1932
  options
1933
1933
  );
1934
1934
 
@@ -2291,8 +2291,20 @@ function isAllowedLandingHostname(hostname) {
2291
2291
  return true;
2292
2292
  }
2293
2293
 
2294
+ const VAST_CDATA_WRAPPER_SUFFIX_RE =
2295
+ /(?:\]\]>|\]\]%3E|%5D%5D%3E|%5D%5D%253E|<\/[A-Za-z0-9_:-]+>|%3C\/[A-Za-z0-9_:-]+%3E|%3C%2F[A-Za-z0-9_:-]+%3E|%253C%2F[A-Za-z0-9_:-]+%253E).*$/i;
2296
+
2297
+ function stripVastXmlCdataArtifacts(rawStr) {
2298
+ if (typeof rawStr !== 'string' || !rawStr) return '';
2299
+ return rawStr
2300
+ .replace(/^(?:<!\[CDATA\[|%3C!%5BCDATA%5B)+/i, '')
2301
+ .replace(VAST_CDATA_WRAPPER_SUFFIX_RE, '')
2302
+ .replace(/[\]>]+$/, '')
2303
+ .trim();
2304
+ }
2305
+
2294
2306
  function normalizeCandidateLandingUrl(rawUrl) {
2295
- const cleaned = sanitizeUntrustedAdText(rawUrl, 2048);
2307
+ const cleaned = stripVastXmlCdataArtifacts(sanitizeUntrustedAdText(rawUrl, 2048));
2296
2308
  if (!cleaned) return null;
2297
2309
  try {
2298
2310
  const parsed = new URL(cleaned);
@@ -2363,7 +2375,7 @@ function unwrapCandidateUrlOrAdUrlParam(rawUrl) {
2363
2375
  const adUrlMatch = decodedRaw.match(/[?&]adurl=([^"'\s&\\]+)/i);
2364
2376
  if (adUrlMatch && adUrlMatch[1]) {
2365
2377
  try {
2366
- const target = decodeURIComponent(adUrlMatch[1]);
2378
+ const target = decodeURIComponent(stripVastXmlCdataArtifacts(adUrlMatch[1]));
2367
2379
  const normalizedTarget = normalizeCandidateLandingUrl(target);
2368
2380
  if (normalizedTarget) return normalizedTarget;
2369
2381
  } catch {
@@ -2583,7 +2595,9 @@ export function extractStaticAdCopyFromContentJs(rawJsText) {
2583
2595
  const adUrlParamMatch = surface.match(/[?&]adurl=(https?%3A%2F%2F[^"'\s&\\]+)/i);
2584
2596
  if (adUrlParamMatch && adUrlParamMatch[1]) {
2585
2597
  try {
2586
- landingPageUrl = normalizeCandidateLandingUrl(decodeURIComponent(adUrlParamMatch[1]));
2598
+ landingPageUrl = normalizeCandidateLandingUrl(
2599
+ decodeURIComponent(stripVastXmlCdataArtifacts(adUrlParamMatch[1]))
2600
+ );
2587
2601
  if (landingPageUrl) break;
2588
2602
  } catch {
2589
2603
  landingPageUrl = null;
@@ -3558,17 +3572,33 @@ export async function getCreativeDetailsById(options = {}) {
3558
3572
  };
3559
3573
  }
3560
3574
 
3575
+ const nowFn = typeof options.nowFn === 'function' ? options.nowFn : Date.now;
3576
+ const fallbackPreviewUrl =
3577
+ typeof options.fallbackPreviewUrl === 'string' && options.fallbackPreviewUrl.trim()
3578
+ ? options.fallbackPreviewUrl.trim()
3579
+ : null;
3580
+
3561
3581
  if (canary.status === 'EMPTY_RESULT') {
3582
+ let fallbackUntrustedAdContent = null;
3583
+ if (
3584
+ options.includeAdCopy === true &&
3585
+ fallbackPreviewUrl &&
3586
+ (!options.deadlineAtMs || nowFn() + 200 < options.deadlineAtMs)
3587
+ ) {
3588
+ fallbackUntrustedAdContent = await fetchAndExtractStaticAdCopy(fallbackPreviewUrl, options);
3589
+ }
3562
3590
  return {
3563
3591
  ok: true,
3564
3592
  status: 'EMPTY_RESULT',
3565
3593
  detail: null,
3594
+ ...(fallbackUntrustedAdContent ? { untrustedAdContent: fallbackUntrustedAdContent } : {}),
3566
3595
  rpcAttempts: rpcRes.attempts,
3567
3596
  };
3568
3597
  }
3569
3598
 
3570
3599
  const detailObj = rpcRes.data['1'];
3571
3600
  const { previewUrl, archiveImageUrls } = extractCreativeAssetUrls(detailObj?.['5'] ?? detailObj);
3601
+ const effectivePreviewUrl = previewUrl || fallbackPreviewUrl;
3572
3602
  const rawRegions = Array.isArray(detailObj?.['17']) ? detailObj['17'] : [];
3573
3603
  const regionalPresence = rawRegions
3574
3604
  .filter((r) => r && typeof r === 'object')
@@ -3588,15 +3618,14 @@ export async function getCreativeDetailsById(options = {}) {
3588
3618
  let untrustedAdContent = extractStaticAdCopyFromContentJs(
3589
3619
  JSON.stringify(detailObj?.['5'] ?? detailObj)
3590
3620
  );
3591
- const nowFn = typeof options.nowFn === 'function' ? options.nowFn : Date.now;
3592
3621
  if (
3593
3622
  !untrustedAdContent.headline &&
3594
3623
  !untrustedAdContent.bodyText &&
3595
3624
  options.includeAdCopy === true &&
3596
- previewUrl &&
3625
+ effectivePreviewUrl &&
3597
3626
  (!options.deadlineAtMs || nowFn() + 200 < options.deadlineAtMs)
3598
3627
  ) {
3599
- const fetchedCopy = await fetchAndExtractStaticAdCopy(previewUrl, options);
3628
+ const fetchedCopy = await fetchAndExtractStaticAdCopy(effectivePreviewUrl, options);
3600
3629
  if (fetchedCopy.extractionStatus === 'EXTRACTED') {
3601
3630
  untrustedAdContent = {
3602
3631
  ...fetchedCopy,