@revclamp/google-ads-transparency-mcp 1.0.10 → 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.
- package/.actor/input_schema.json +5 -3
- package/README.md +29 -3
- package/actor_config.json +1 -1
- package/package.json +3 -3
- package/server.json +2 -2
- package/src/mcp_server.mjs +89 -9
- package/src/rpc_client.mjs +48 -1
package/.actor/input_schema.json
CHANGED
|
@@ -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":
|
|
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":
|
|
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":
|
|
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
|
-
##
|
|
26
|
+
## 1. Apify Store Cloud, Clay / n8n & REST API Quick Start
|
|
27
27
|
|
|
28
|
-
Run directly via
|
|
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
|
-
|
|
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.
|
|
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,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revclamp/google-ads-transparency-mcp",
|
|
3
3
|
"mcpName": "com.revclamp/google-ads-transparency-mcp",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.12",
|
|
5
5
|
"type": "module",
|
|
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
|
|
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",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
],
|
|
30
30
|
"keywords": [
|
|
31
31
|
"mcp",
|
|
32
|
+
"mcp-server",
|
|
32
33
|
"model-context-protocol",
|
|
33
34
|
"google-ads",
|
|
34
35
|
"ads-transparency",
|
|
@@ -38,7 +39,6 @@
|
|
|
38
39
|
"brand-safety",
|
|
39
40
|
"ppc",
|
|
40
41
|
"b2b-marketing",
|
|
41
|
-
"apify",
|
|
42
42
|
"revclamp"
|
|
43
43
|
],
|
|
44
44
|
"scripts": {
|
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.
|
|
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.
|
|
30
|
+
"version": "1.0.12",
|
|
31
31
|
"runtimeHint": "npx",
|
|
32
32
|
"transport": {
|
|
33
33
|
"type": "stdio"
|
package/src/mcp_server.mjs
CHANGED
|
@@ -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:
|
|
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
|
|
746
|
+
const canonicalToolName =
|
|
747
|
+
toolName === 'search_google_ads_creatives' ? 'search_google_ads_transparency' : toolName;
|
|
748
|
+
const args = { ...(rawArgs || {}) };
|
|
687
749
|
const allowedSet =
|
|
688
|
-
|
|
750
|
+
canonicalToolName === 'search_google_ads_transparency'
|
|
689
751
|
? SEARCH_ALLOWED_ARG_KEYS
|
|
690
|
-
:
|
|
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
|
-
|
|
983
|
-
|
|
984
|
-
|
|
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(
|
|
1076
|
+
const argValidation = validateMcpToolArguments(canonicalToolName, request.params.arguments);
|
|
997
1077
|
if (!argValidation.ok) {
|
|
998
1078
|
return {
|
|
999
1079
|
jsonrpc: '2.0',
|
package/src/rpc_client.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|