@revclamp/google-ads-transparency-mcp 1.0.11 → 1.0.12

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.
@@ -42,13 +42,15 @@
42
42
  "title": "Include Creative Region & Legal Details",
43
43
  "type": "boolean",
44
44
  "description": "When enabled, fetches detailed regional presence and legal entity records for up to 10 returned creatives (default 5).",
45
- "default": false
45
+ "default": true,
46
+ "prefill": true
46
47
  },
47
48
  "includeAdCopy": {
48
49
  "title": "Include Static Ad Copy (Optional)",
49
50
  "type": "boolean",
50
51
  "description": "When enabled alongside Include Creative Region & Legal Details, extracts static headline, body text, CTA, and destination domain from allowlisted preview scripts without eval.",
51
- "default": false
52
+ "default": true,
53
+ "prefill": true
52
54
  },
53
55
  "maxEntities": {
54
56
  "title": "Max Advertiser Entities",
@@ -65,7 +67,7 @@
65
67
  "minimum": 1,
66
68
  "maximum": 200,
67
69
  "default": 40,
68
- "prefill": 10
70
+ "prefill": 5
69
71
  },
70
72
  "pageToken": {
71
73
  "title": "Next-Page Cursor Token (Optional)",
package/README.md CHANGED
@@ -23,12 +23,38 @@ Find verified Google Ads advertiser entities and extract active or historical ad
23
23
 
24
24
  ---
25
25
 
26
- ## Quick Start (MCP Server for Claude Desktop, Cursor, Windsurf)
26
+ ## 1. Apify Store Cloud, Clay / n8n & REST API Quick Start
27
27
 
28
- Run directly via `npx`:
28
+ Run **RevClamp AdPulse** in the cloud on [Apify Store (`revclamp/google-ads-transparency-mcp`)](https://apify.com/revclamp/google-ads-transparency-mcp) with zero local installation, or trigger it directly from **Clay**, **n8n**, **Make**, or any HTTP pipeline via the Apify REST API:
29
29
 
30
30
  ```bash
31
- npx @revclamp/google-ads-transparency-mcp --mcp-stdio
31
+ curl -s -X POST \
32
+ "https://api.apify.com/v2/acts/revclamp~google-ads-transparency-mcp/run-sync-get-dataset-items" \
33
+ -H "Authorization: Bearer $APIFY_TOKEN" \
34
+ -H "Content-Type: application/json" \
35
+ -d '{
36
+ "query": "Stripe",
37
+ "formatFilter": "ALL",
38
+ "includeCreativeDetails": true,
39
+ "includeAdCopy": true,
40
+ "maxResults": 5
41
+ }'
42
+ ```
43
+
44
+ ### Transparent Pay-per-Event Pricing & Free-Tier Credit Math
45
+
46
+ - **Event Price:** `$0.0012` per extracted ad creative (`ad-creative-extracted`).
47
+ - **What `$1.00` in Free Apify Credits Gets You:** Approximately **`~830` verified Google Ads creatives** (including advertiser entity disambiguation, `TEXT`/`IMAGE`/`VIDEO` format classification, regional presence, and static ad copy extraction).
48
+ - **Zero-Waste Billing Guarantee:** You pay **`$0.00`** on `EMPTY_RESULT` (`0` matching creatives), `INVALID_INPUT`, or upstream rate limits (`UPSTREAM_RATE_LIMITED`). Charges apply strictly to verified, deduplicated ad creatives delivered to your Dataset.
49
+
50
+ ---
51
+
52
+ ## 2. Local AI Agent & MCP Server Quick Start (Claude Desktop, Cursor, Windsurf)
53
+
54
+ Run directly via `npx` (supports natural parameter aliases `query`, `domain`, `brand`, `company`, or `url`):
55
+
56
+ ```bash
57
+ npx -y @revclamp/google-ads-transparency-mcp --mcp-stdio
32
58
  ```
33
59
 
34
60
  ### Claude Desktop / Cursor Configuration (`claude_desktop_config.json`)
package/actor_config.json CHANGED
@@ -21,7 +21,7 @@
21
21
  ],
22
22
  "manifest": {
23
23
  "name": "google-ads-transparency-mcp",
24
- "version": "1.0.11",
24
+ "version": "1.0.12",
25
25
  "title": "RevClamp AdPulse - Google Ads Transparency Scraper & MCP",
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",
3
3
  "mcpName": "com.revclamp/google-ads-transparency-mcp",
4
- "version": "1.0.11",
4
+ "version": "1.0.12",
5
5
  "type": "module",
6
6
  "description": "RevClamp AdPulse - Google Ads Transparency Scraper & MCP. Find verified advertiser entities and extract active or historical ad creatives (Text, Image, Video), preview links, and run dates. Independent B2B tool by RevClamp.",
7
7
  "homepage": "https://revclamp.com",
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "com.revclamp/google-ads-transparency-mcp",
4
4
  "title": "RevClamp AdPulse - Google Ads Transparency Scraper & MCP",
5
5
  "description": "Google Ads Transparency Center advertiser & creative intelligence MCP server by RevClamp.",
6
- "version": "1.0.11",
6
+ "version": "1.0.12",
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",
30
- "version": "1.0.11",
30
+ "version": "1.0.12",
31
31
  "runtimeHint": "npx",
32
32
  "transport": {
33
33
  "type": "stdio"
@@ -12,6 +12,7 @@
12
12
  * for a specific `(advertiserId, creativeId)` pair, including regional presence and legal name.
13
13
  */
14
14
 
15
+ import fs from 'node:fs';
15
16
  import {
16
17
  resolveAdvertiserEntities,
17
18
  getCreativeDetailsById,
@@ -26,6 +27,10 @@ import {
26
27
  } from './rpc_client.mjs';
27
28
  import { runGoogleAdsTransparencyActor } from './apify_actor.mjs';
28
29
 
30
+ const PKG_VERSION = JSON.parse(
31
+ fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')
32
+ ).version;
33
+
29
34
  export const MCP_MAX_ENVELOPE_BYTES = 30000;
30
35
  export const MCP_MAX_ITEMS_LIMIT = 25;
31
36
  export const MCP_MAX_REQ_ID_BYTES = 256;
@@ -43,7 +48,7 @@ export const ENCODED_BRAND_ICON_DATA_URI = `data:image/svg+xml;utf8,${encodeURIC
43
48
  export const MCP_SERVER_INFO = Object.freeze({
44
49
  name: 'google-ads-transparency-mcp',
45
50
  title: 'RevClamp AdPulse - Google Ads Transparency Scraper & MCP',
46
- version: '1.0.9',
51
+ version: PKG_VERSION,
47
52
  description:
48
53
  '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
54
  // Forward-compatible MCP Registry & Glama metadata extensions (ignored by older stdio clients)
@@ -90,6 +95,30 @@ export const MCP_TOOLS_MANIFEST = Object.freeze([
90
95
  maxLength: 512,
91
96
  description: 'Brand name or domain to search (e.g. "nike.com", "Stripe", "Booking.com").',
92
97
  },
98
+ domain: {
99
+ title: 'Brand Domain (Alias)',
100
+ type: 'string',
101
+ maxLength: 512,
102
+ description: 'Alias for query (advertiser domain, e.g. "nike.com", "stripe.com").',
103
+ },
104
+ brand: {
105
+ title: 'Brand Name (Alias)',
106
+ type: 'string',
107
+ maxLength: 512,
108
+ description: 'Alias for query (brand name, e.g. "Nike", "Stripe").',
109
+ },
110
+ company: {
111
+ title: 'Company Name (Alias)',
112
+ type: 'string',
113
+ maxLength: 512,
114
+ description: 'Alias for query (company name, e.g. "Stripe", "Shopify").',
115
+ },
116
+ url: {
117
+ title: 'Advertiser or Transparency URL (Alias)',
118
+ type: 'string',
119
+ maxLength: 512,
120
+ description: 'Alias for query (website URL or adstransparency.google.com URL).',
121
+ },
93
122
  advertiserId: {
94
123
  title: 'Advertiser ID (Optional)',
95
124
  type: 'string',
@@ -171,6 +200,30 @@ export const MCP_TOOLS_MANIFEST = Object.freeze([
171
200
  maxLength: 512,
172
201
  description: 'Brand name or domain to resolve (e.g. "nike.com", "Stripe", "Apple").',
173
202
  },
203
+ domain: {
204
+ title: 'Brand Domain (Alias)',
205
+ type: 'string',
206
+ maxLength: 512,
207
+ description: 'Alias for query (advertiser domain, e.g. "nike.com", "stripe.com").',
208
+ },
209
+ brand: {
210
+ title: 'Brand Name (Alias)',
211
+ type: 'string',
212
+ maxLength: 512,
213
+ description: 'Alias for query (brand name, e.g. "Nike", "Stripe").',
214
+ },
215
+ company: {
216
+ title: 'Company Name (Alias)',
217
+ type: 'string',
218
+ maxLength: 512,
219
+ description: 'Alias for query (company name, e.g. "Stripe", "Shopify").',
220
+ },
221
+ url: {
222
+ title: 'Advertiser or Transparency URL (Alias)',
223
+ type: 'string',
224
+ maxLength: 512,
225
+ description: 'Alias for query (website URL or adstransparency.google.com URL).',
226
+ },
174
227
  countryCode: {
175
228
  title: 'Preferred Country (Optional)',
176
229
  type: 'string',
@@ -191,7 +244,6 @@ export const MCP_TOOLS_MANIFEST = Object.freeze([
191
244
  description: 'Maximum number of verified regional legal entities to select (1 to 3).',
192
245
  },
193
246
  },
194
- required: ['query'],
195
247
  additionalProperties: false,
196
248
  },
197
249
  },
@@ -650,6 +702,10 @@ function validateJsonRpcRequestId(rawId) {
650
702
 
651
703
  const SEARCH_ALLOWED_ARG_KEYS = new Set([
652
704
  'query',
705
+ 'domain',
706
+ 'brand',
707
+ 'company',
708
+ 'url',
653
709
  'advertiserId',
654
710
  'countryCode',
655
711
  'formatFilter',
@@ -665,6 +721,10 @@ const SEARCH_ALLOWED_ARG_KEYS = new Set([
665
721
 
666
722
  const RESOLVE_ALLOWED_ARG_KEYS = new Set([
667
723
  'query',
724
+ 'domain',
725
+ 'brand',
726
+ 'company',
727
+ 'url',
668
728
  'countryCode',
669
729
  'includeSubsidiaries',
670
730
  'maxEntities',
@@ -683,11 +743,13 @@ export function validateMcpToolArguments(toolName, rawArgs) {
683
743
  if (rawArgs !== undefined && (!rawArgs || typeof rawArgs !== 'object' || Array.isArray(rawArgs))) {
684
744
  return { ok: false, message: 'Tool arguments must be a JSON object' };
685
745
  }
686
- const args = rawArgs || {};
746
+ const canonicalToolName =
747
+ toolName === 'search_google_ads_creatives' ? 'search_google_ads_transparency' : toolName;
748
+ const args = { ...(rawArgs || {}) };
687
749
  const allowedSet =
688
- toolName === 'search_google_ads_transparency'
750
+ canonicalToolName === 'search_google_ads_transparency'
689
751
  ? SEARCH_ALLOWED_ARG_KEYS
690
- : toolName === 'resolve_google_ads_advertiser'
752
+ : canonicalToolName === 'resolve_google_ads_advertiser'
691
753
  ? RESOLVE_ALLOWED_ARG_KEYS
692
754
  : DETAIL_ALLOWED_ARG_KEYS;
693
755
 
@@ -700,6 +762,20 @@ export function validateMcpToolArguments(toolName, rawArgs) {
700
762
  }
701
763
  }
702
764
 
765
+ for (const aliasKey of ['domain', 'brand', 'company', 'url']) {
766
+ if (args[aliasKey] !== undefined && typeof args[aliasKey] !== 'string') {
767
+ return { ok: false, message: `Invalid ${aliasKey}: expected a string` };
768
+ }
769
+ }
770
+ if (args.query === undefined || args.query === null || String(args.query).trim() === '') {
771
+ const aliasValue = [args.domain, args.brand, args.company, args.url].find(
772
+ (v) => typeof v === 'string' && v.trim().length > 0
773
+ );
774
+ if (aliasValue) {
775
+ args.query = aliasValue.trim();
776
+ }
777
+ }
778
+
703
779
  if (toolName === 'get_google_ads_creative_detail') {
704
780
  if (
705
781
  typeof args.advertiserId !== 'string' ||
@@ -977,11 +1053,15 @@ export async function handleMcpJsonRpcRequest(request, options = {}) {
977
1053
 
978
1054
  const rawToolName = String(request.params.name ?? '');
979
1055
  const safeReflectedToolName = sliceUtf8Safe(rawToolName, 64);
1056
+ const canonicalToolName =
1057
+ rawToolName === 'search_google_ads_creatives'
1058
+ ? 'search_google_ads_transparency'
1059
+ : rawToolName;
980
1060
 
981
1061
  if (
982
- rawToolName !== 'search_google_ads_transparency' &&
983
- rawToolName !== 'resolve_google_ads_advertiser' &&
984
- rawToolName !== 'get_google_ads_creative_detail'
1062
+ canonicalToolName !== 'search_google_ads_transparency' &&
1063
+ canonicalToolName !== 'resolve_google_ads_advertiser' &&
1064
+ canonicalToolName !== 'get_google_ads_creative_detail'
985
1065
  ) {
986
1066
  return {
987
1067
  jsonrpc: '2.0',
@@ -993,7 +1073,7 @@ export async function handleMcpJsonRpcRequest(request, options = {}) {
993
1073
  };
994
1074
  }
995
1075
 
996
- const argValidation = validateMcpToolArguments(rawToolName, request.params.arguments);
1076
+ const argValidation = validateMcpToolArguments(canonicalToolName, request.params.arguments);
997
1077
  if (!argValidation.ok) {
998
1078
  return {
999
1079
  jsonrpc: '2.0',
@@ -838,6 +838,42 @@ export function parseBrandQueryTokens(rawQuery) {
838
838
  let candidateHost = trimmed;
839
839
  let isDomainQuery = false;
840
840
 
841
+ if (/^(?:https?:\/\/)?(?:www\.)?adstransparency\.google\.com(?:\/|\?|$)/i.test(candidateHost)) {
842
+ try {
843
+ const normalizedUrl = /^https?:\/\//i.test(candidateHost)
844
+ ? candidateHost
845
+ : `https://${candidateHost}`;
846
+ const parsed = new URL(normalizedUrl);
847
+ const arPathMatch = /\/advertiser\/(AR\d{10,25})\b/i.exec(parsed.pathname);
848
+ if (arPathMatch) {
849
+ const extractedArId = arPathMatch[1].toUpperCase();
850
+ return {
851
+ rawQuery: trimmed,
852
+ cleanedQuery: extractedArId,
853
+ brandStem: extractedArId.toLowerCase(),
854
+ brandStemWithoutHq: extractedArId.toLowerCase(),
855
+ brandTokens: [extractedArId.toLowerCase()],
856
+ isDomainQuery: false,
857
+ extractedAdvertiserId: extractedArId,
858
+ };
859
+ }
860
+ const paramQuery = (
861
+ parsed.searchParams.get('domain') ||
862
+ parsed.searchParams.get('q') ||
863
+ parsed.searchParams.get('query') ||
864
+ ''
865
+ ).trim();
866
+ if (paramQuery) {
867
+ return {
868
+ ...parseBrandQueryTokens(paramQuery),
869
+ rawQuery: trimmed,
870
+ };
871
+ }
872
+ } catch {
873
+ // Fall back to standard URL/text tokenization
874
+ }
875
+ }
876
+
841
877
  if (/^https?:\/\//i.test(candidateHost)) {
842
878
  try {
843
879
  const parsed = new URL(candidateHost);
@@ -2054,7 +2090,18 @@ export async function executeGoogleAdsRpcPost(endpointUrl, fReqPayload, options
2054
2090
  * @returns {Promise<{ok: boolean, status: string, queryMeta: object, primaryEntity: object|null, selectedEntities: object[], allCandidates: object[], rpcAttempts: number, billableCount: number, reason?: string}>}
2055
2091
  */
2056
2092
  export async function resolveAdvertiserEntities(options = {}) {
2057
- const explicitAdvertiserId = String(options.advertiserId ?? '').trim();
2093
+ let explicitAdvertiserId = String(options.advertiserId ?? '').trim();
2094
+ if (!explicitAdvertiserId && typeof options.query === 'string') {
2095
+ const qTrim = options.query.trim();
2096
+ if (ADVERTISER_ID_RE.test(qTrim)) {
2097
+ explicitAdvertiserId = qTrim.toUpperCase();
2098
+ } else if (/adstransparency\.google\.com/i.test(qTrim)) {
2099
+ const arUrlMatch = /\/advertiser\/(AR\d{10,25})\b/i.exec(qTrim);
2100
+ if (arUrlMatch) {
2101
+ explicitAdvertiserId = arUrlMatch[1].toUpperCase();
2102
+ }
2103
+ }
2104
+ }
2058
2105
  const rawCountryNorm = String(options.countryCode ?? '').trim().toUpperCase();
2059
2106
  const rawCountry = rawCountryNorm === 'ALL' ? '' : rawCountryNorm;
2060
2107