google-ads-transparency-mcp
This server lets AI assistants search and analyze Google Ads Transparency Center data without an API key, including looking up advertisers, retrieving and decoding ad creatives, downloading assets, and comparing competitors.
Search advertisers by domain (
search_advertiser_by_domain) or by keyword/company name (search_advertisers)Get ad details with decoded text content (
get_ads,get_ad_detail) including headline, description, destination URL, format, and last-shown dateCompare advertisers across up to 50 domains (
compare_advertisers) with statuses (ok/absent/blocked/error) and parent-entity flagsSummarize an advertiser (
advertiser_summary) showing format mix, copy samples, CTA labels, destination hosts, and active ad countsDownload creatives individually (
fetch_ad_creative) or in bulk (download_ad_creatives) — images at original resolution, videos sampled to JPEG frames, with a manifestUse as a Python library for programmatic access, including offline aggregation helpers
Region-specific search via two-letter country codes, or global "anywhere" results
Proxy and rate-limit handling to route around blocked IPs automatically
Provides tools for searching and retrieving ad creatives from Google's Ads Transparency Center, including advertiser lookup by domain, keyword search, and decoding text ad content.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@google-ads-transparency-mcpWhat ads is nike.com running?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Google Ads Transparency MCP Server
An MCP server that lets AI assistants look up any advertiser's Google ads. Search by domain or company name, retrieve ad creatives, and decode text ad content — all from Google's Ads Transparency Center.
No API key. No paid service. No browser required.
Google has no official API for their Ads Transparency Center. The only open-source option was an abandoned Python package (last commit July 2023, broken domain search, crashed on image ads). Paid alternatives start at $75/mo. This project fixes the scraper and wraps it as an MCP server so any AI assistant can query Google's ad database directly.
Tools
Tool | Description |
| Find an advertiser by website domain (e.g. |
| Search advertisers by keyword or company name |
| Get ads for an advertiser with decoded content |
| Get full details for a specific ad creative |
| Active ad count per domain across a competitor set, with a status per row |
| Format mix, copy sample and destination hosts for one advertiser |
| Download one creative — the image file, or video sampled to frames |
| Bulk-download an advertiser's creatives into a folder with a manifest |
Related MCP server: google-ads-mcp
Install
# With uvx (recommended)
uvx google-ads-transparency-mcp
# With pip
pip install google-ads-transparency-mcpOptional: video creatives
Downloading video ads needs yt-dlp and
ffmpeg on your PATH:
brew install yt-dlp ffmpeg # macOSEverything else works without them. If they are missing, only the video download tools fail, and they say which binary is absent.
Configure
Claude Code
claude mcp add google-ads-transparency -- uvx google-ads-transparency-mcpClaude Desktop
Add to your claude_desktop_config.json:
{
"mcpServers": {
"google-ads-transparency": {
"command": "uvx",
"args": ["google-ads-transparency-mcp"]
}
}
}Cursor / VS Code
Add to your MCP settings:
{
"mcpServers": {
"google-ads-transparency": {
"command": "uvx",
"args": ["google-ads-transparency-mcp"]
}
}
}Example Usage
Once connected, ask your AI assistant things like:
"What ads is nike.com running?"
"Search for crypto exchange advertisers on Google"
"Get the last 5 ads from Coinbase"
"Look up ad details for advertiser AR05099026886533578753"
Example: Domain Search
> search_advertiser_by_domain("nike.com")
{
"advertiser_id": "AR14188379519798214657",
"name": "Nike, Inc.",
"ad_count": 1842
}Example: Get Ads with Decoded Text Content
> get_ads("Coinbase", count=1)
[
{
"advertiser_id": "AR09076382774528",
"creative_id": "CR10813648716908961793",
"format": "text",
"last_shown": "2025-01-15",
"advertiser_name": "Coinbase",
"content": {
"preview_url": "https://ads-rendering-prod.corp.google.com/search?...",
"headline": "Most Trusted Crypto Exchange",
"description": "Buy, Sell & Trade Bitcoin, Ethereum & More...",
"destination_url": "coinbase.com"
}
}
]How It Works
This server reverse-engineers Google's internal RPC endpoints:
Endpoint | Purpose |
| Keyword/domain search |
| Get ad creative IDs by advertiser |
| Get individual ad details |
Text ads carry their copy in the overlay parameter of the creative preview
URL: urlsafe base64 wrapping gzip wrapping protobuf, whose named fields give the
headline, description and destination URL exactly rather than by guesswork.
Text ads rendered through content.js instead carry the same triple in a
data-p attribute, which is decoded too.
Image and video ads hand back a JavaScript renderer instead of an asset. The
server fetches it and pulls out the real simgad image URL, or the YouTube
video id plus the on-creative headline and button label.
Rate limiting
Google throttles these endpoints per source IP and answers a bursty client with
an HTTP 429 and a CAPTCHA interstitial. Requests are spaced out and retried with
backoff. If your IP is already blocked the tools raise AdsTransparencyBlocked
rather than returning empty results, so it is obvious what happened.
An empty result never means "blocked" — that distinction is the whole point of the exception. If you see one, the advertiser really has no ads.
Blocks are per address, not per host, and IPv4 and IPv6 are blocked separately. A machine whose IPv6 is banned will still work over IPv4, which is worth knowing before concluding a host is burned.
Routing Around a Blocked IP
If your own IP is blocked, the scraper can borrow another machine's. Set a proxy for this package alone:
export GOOGLE_ADS_TRANSPARENCY_PROXY=socks5h://127.0.0.1:1080The h matters. socks5h resolves DNS at the proxy; socks5 resolves it
locally. If a local resolver blocks ad hosts — NextDNS nulls out
tpc.googlesyndication.com, where the image creatives live — only socks5h
can reach them.
This variable is read instead of the ambient HTTP_PROXY/ALL_PROXY, on
purpose: routing an entire machine through a proxy to fix one host is both a
privacy change and a good way to break unrelated tools.
Value | Effect |
unset | Walk the route chain below, advancing when a route is blocked or unreachable |
a proxy URL | Use it and nothing else |
| Force a direct connection, skip the checks |
With the variable unset the scraper tries, in order:
Residential — a rotating residential proxy, if
~/.config/google-ads-transparency/residential-proxyexists. One line, the fullsocks5h://user:pass@host:portURL. Every request exits from a different IP, so per-IP bans never stick, and the file lives outside the repo so it cannot be committed. Proxy credentials are scrubbed from error text before it is returned.Direct — your own connection.
Tunnel — a SOCKS relay at
127.0.0.1:1080, if something is listening there.
Because an unset variable auto-detects, no MCP client registration needs to know about any of this, and nothing breaks when none of it is set up.
Relaying through a remote host
scripts/socks_over_ssh.py is a local SOCKS5 proxy that relays each connection
through an SSH exec channel to a host you name. It exists in that shape for
hosts whose sshd sets AllowTcpForwarding no, which refuses ssh -D; exec
channels are not covered by that directive, so it needs no server-side change.
python scripts/socks_over_ssh.py --ssh-target my-box # any ~/.ssh/config alias
curl -s --socks5-hostname 127.0.0.1:1080 https://api.ipify.org # prints the remote IPKeep it alive with whatever your OS uses (a launchd agent on macOS, a systemd user unit on Linux) and the scraper picks it up automatically.
Security posture, since this is an unauthenticated proxy:
Loopback only. The script refuses to bind anything but
127.0.0.1, so it is reachable from this machine and nowhere else.No agent forwarding (
ssh -a), so the remote host is never handed this machine's keys. Also no X11 (-x) and no pty (-T).No server-side change. The remote sshd config is untouched.
Nothing else is routed through it. Only this package reads the variable that points at it.
If the tunnel is down, the tools say so rather than failing obscurely, and the scraper moves on to the next route.
Use as a Python Library
from google_ads_transparency_mcp import GoogleAdsTransparency
client = GoogleAdsTransparency()
# Search by domain
advertiser = client.search_advertiser_by_domain("nike.com")
print(advertiser) # {"advertiser_id": "...", "name": "Nike, Inc.", "ad_count": 1842}
# Get ads with decoded content
ads = client.get_ads("Coinbase", count=5)
for ad in ads:
print(ad["format"], ad["content"])
# Region-specific search
client_uk = GoogleAdsTransparency(region="GB")Getting the Creatives Themselves
The ad copy is only half the story. Both download tools write real files to disk and return their paths, so the creatives can go straight into a deck, a report, or a vision model.
Images are always saved at their original resolution — the file you get back is the creative as it ran, nothing downscaled.
Videos are sampled into JPEG frames, in one of two modes:
Mode | Frames | Use |
Default | 320px wide, ~12KB each | Vision analysis — cheap enough to send a batch of them |
| Native resolution (e.g. 1080×1080) | Slides, decks, anything a person will look at |
from google_ads_transparency_mcp.frames import download_image, extract_video_frames
# Full-resolution image creative
path = download_image(ad["content"]["image_url"], "out/", name=ad["creative_id"])
# Deck-quality stills from a video ad
result = extract_video_frames(
ad["content"]["video_url"], "out/", frames=4, full_quality=True
)
print(result["frames"]) # ['out/frame_01.jpg', ...]
print(result["video_path"]) # the downloaded video itselfCollecting a whole advertiser
download_ad_creatives pulls a batch in one call. Each creative gets its own
subfolder, and a manifest.json records every file alongside its decoded copy,
format and last-shown date — which is what you want when assembling examples.
> download_ad_creatives("Buildkite", out_dir="creatives/", count=10)
creatives/
├── manifest.json
├── CR04431036747610062849/
│ └── CR04431036747610062849.png # 400×400, as it ran
└── CR01542648288744833025/
├── frame_01.jpg # 1080×1080 stills
├── frame_02.jpg
└── video.mp4One dead creative never sinks the batch — it gets an error field in the
manifest and the rest continue.
Comparing and Summarising Advertisers
Two tools turn the raw lookups into the questions people actually ask.
Who in this market is running ads, and how many?
client.compare_advertisers(["nike.com", "adidas.com", "github.com"])[
{"domain": "nike.com", "status": "ok", "advertiser_name": "Nike Retail BV", "ad_count": 100000, "parent_entity": false},
{"domain": "adidas.com", "status": "ok", "advertiser_name": "Adidas America, Inc", "ad_count": 3000, "parent_entity": false},
{"domain": "github.com", "status": "ok", "advertiser_name": "TRENT SLADE", "ad_count": 1000, "parent_entity": true,
"note": "resolves to 'TRENT SLADE', which shares no name token with 'github.com': ... Do not cite ad_count as this domain's own."}
]Every row carries a status, and the distinction matters:
Status | Meaning |
| An advertiser was found; |
| No advertiser is registered for that domain. A genuine zero. |
| Google rate-limited the lookup. Not a zero. Never cite it as one |
| Anything else; the note says what |
Once every egress route is blocked, the remaining domains are marked blocked
without further requests rather than hammering a ban.
parent_entity is the guard against the mistake that produces confidently
wrong numbers. Domain search returns whoever's ads mention the domain, which
can be the parent company, a reseller, or a stranger. When the advertiser name
shares no word with the domain the row is flagged and carries a note. It is a
heuristic: it flags, it does not filter. Pass company_names={"gh.io": "GitHub"}
to sharpen the check for domains that do not contain the company name.
What does this advertiser's paid surface look like?
client.summarize_advertiser("nike.com", sample=50){
"status": "ok", "advertiser_name": "Nike Retail BV", "active_ads": 100000,
"sample": {
"sampled": 50,
"by_format": {"image": 41, "text": 6, "video": 3},
"format_share": {"image": 0.82, "text": 0.12, "video": 0.06},
"last_shown": {"earliest": "2026-08-30", "latest": "2026-09-09"},
"headlines": ["..."], "descriptions": ["..."], "cta_labels": [{"label": "Shop now", "count": 4}],
"destination_hosts": [{"host": "nike.com", "count": 6}],
"with_video": 3, "with_image": 41
}
}advertiser may be an id (AR...), a domain, or a name. The sample is the
creatives the Transparency Center lists first, and costs roughly one to two
requests per creative. The summary is pure aggregation: it tells you the mix,
and leaves the reading of it (brand display heavy, no search presence, and so
on) to you or the model calling the tool.
The aggregation itself is importable and offline, so it works on ads you have already fetched:
from google_ads_transparency_mcp import summarize_ads, is_parent_entity
summary = summarize_ads(ads) # ads = a list from get_ads / get_ad_detail
is_parent_entity("github.com", "Microsoft Corporation") # TrueDevelopment
uv sync --all-groups
uv run ruff check src tests
uv run pytest -q # offline; ADS_LIVE=1 adds one live round-tripSupported Regions
Pass a two-letter country code to filter by region. Use "anywhere" (default) for global results. All 240+ regions from Google's Ads Transparency Center are supported.
Credits
Based on GoogleAdsTransparencyScraper by Farhan Ahmed. Forked and fixed by Sam Town — domain search, image ad parsing, text ad decoding, error handling, and MCP server. Later: overlay protobuf decoding, creative asset resolution, video frame sampling, and rate limit handling.
License
MIT
Available Tools
8 toolsadvertiser_summaryA
Summarise one advertiser: format mix, copy sample, destinations.
Fetches a sample of the advertiser's creatives and aggregates them: counts and share by format (text/image/video), the last-shown date range, de-duplicated headlines, descriptions and CTA labels, and the hosts the ads send people to. Pure aggregation — read strategy into it yourself.
Args: advertiser: An advertiser id ("AR..."), a domain ("nike.com") or a name ("Nike Retail BV"). sample: Creatives to sample (default 50, max 100). Roughly one to two requests per creative.
Returns: {status, advertiser_id, advertiser_name, active_ads, region, sample: {sampled, by_format, format_share, last_shown, headlines, descriptions, cta_labels, destination_hosts, with_video, with_image}}
| Name | Required | Description | Default |
|---|---|---|---|
| sample | No | ||
| advertiser | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden and delivers: it discloses the sampling behavior ('Fetches a sample of the advertiser's creatives'), the cost model ('Roughly one to two requests per creative'), and the non-opinionated nature ('Pure aggregation — read strategy into it yourself'). It does not cover error handling for unknown advertisers, but the core behaviors are transparent.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded one-line summary, then a detailed aggregation list, then an Args/Returns block. Every sentence adds information, and the Args documentation is justified because the schema has no parameter descriptions. Slightly long, but well-organized with critical scope info first.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 2-parameter tool with no annotations and no output schema, the description compensates well by including an explicit Returns section listing the full result shape (status, advertiser_id, sampled, by_format, etc.). The only gap is failure behavior for unknown advertiser ids, which is minor for a read-only summary tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description fully compensates: it documents advertiser's three accepted formats (id 'AR...', domain, name) and sample's default (50), maximum (100), and per-creative request cost. This is exactly the semantic information the input schema omits.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Summarise') and resource ('one advertiser') and enumerates the three aggregation outputs (format mix, copy sample, destinations). The detailed list of what is aggregated (headlines, descriptions, CTA labels, destination hosts) distinguishes it from siblings like compare_advertisers (multiple advertisers), get_ads/get_ad_detail (single ad retrieval), and search_advertisers (finding advertisers).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The scope 'Summarise one advertiser' and the disclaimer 'Pure aggregation — read strategy into it yourself' imply the tool is for read-style analysis of a single advertiser rather than decision-making. However, it never explicitly names a sibling alternative or states when not to use it (e.g., versus compare_advertisers for comparing multiple advertisers); the usage context is implied, not stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
compare_advertisersA
Compare active Google ad counts across a set of domains, one row each.
The competitor paid-surface question in one call: who is running ads, and how many. Rows come back in input order; sort by ad_count yourself.
Every row has a status. "ok" means an advertiser was found; "absent" means no advertiser is registered for that domain and IS a genuine zero; "blocked" means Google rate-limited the lookup and is NOT a zero; "error" is anything else. Rows flagged parent_entity resolved to a differently named advertiser (a parent company, a reseller, or a stranger whose ads mention the domain) — never cite those counts as the domain's own.
Args: domains: Up to 50 domains or URLs (e.g. ["nike.com", "adidas.com"]).
Returns: [{domain, status, advertiser_id, advertiser_name, ad_count, parent_entity, note}, ...]
| Name | Required | Description | Default |
|---|---|---|---|
| domains | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations present, the description shoulders the full burden and does it thoroughly. It explains exact status semantics, especially the subtle trap that "blocked" is a Google rate-limit and is NOT a zero, while "absent" IS a genuine zero. It also discloses row ordering and parent_entity caveats, materially helping an agent interpret results correctly.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Every section earns its place: a one-line core purpose, a short positioning sentence, then a compact but vital status legend, and shorthand input/output contracts. The front-loaded purpose lets an agent quickly identify the tool, and deeper detail is deferred until after the first paragraph.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description fully covers the only input param, the non-obvious status codes, the specific meaning of parent_entity rows, and the ordering guarantees. Since an output schema exists, the Returns listing is a convenience, and the tool description still appears complete for an agent to invoke it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Since schema description coverage is 0%, the description must compensate for the single `domains` parameter, and it does: it imposes the "Up to 50" limit and states that either domains or URLs are accepted, with an example. It leaves minor edge cases unstated (behavior with more than 50 items or duplicates), but covers enough for normal use.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb-plus-resource statement: "Compare active Google ad counts across a set of domains, one row each." It separates this tool from the sibling search/get/creative tools by emphasizing the cross-domain, batch comparison shape and the single-call competitive question it answers.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description clearly frames when to use it: "The competitor paid-surface question in one call: who is running ads, and how many." It also gives a meaningful exclusion—"never cite those counts as the domain's own" for parent_entity rows—but it never explicitly names sibling tools or says when to prefer them (e.g., for a single domain or for ad-level detail).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
download_ad_creativesA
Download a whole advertiser's creatives into one folder, with a manifest.
This is the bulk path for collecting real ad examples — images at their original resolution, videos sampled to stills — so they can be dropped straight into a deck or a report. Each creative gets its own subfolder.
Args: advertiser_name: The advertiser's name (e.g. "Buildkite", "Vercel Inc.") out_dir: Directory to write into. Created if missing. count: How many creatives to pull (default 10, max 100). full_quality: Native-resolution assets, suitable for presentation. frames: Frames to sample per video creative (default 4).
Returns: {advertiser, out_dir, manifest_path, creatives: [...]} where each entry carries its format, local file paths, and any decoded ad copy.
| Name | Required | Description | Default |
|---|---|---|---|
| count | No | ||
| frames | No | ||
| out_dir | Yes | ||
| full_quality | No | ||
| advertiser_name | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden, and it discloses meaningful behavior: files are written to out_dir, the directory is created if missing, each creative gets its own subfolder, videos are sampled to stills, and a manifest is returned. It doesn't address overwrite behavior or authentication/permissions, but it is substantially transparent for a download operation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with an introductory purpose paragraph followed by Args and Returns sections. Every sentence adds value: the bulk-path context, the presentation use case, parameter details, and the return shape. No filler or redundant restatement of the tool name.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with no output schema and no annotations, the description is remarkably complete: it covers the operation's side effects, each input, and the return structure. The only notable gap is unspecified behavior when out_dir already contains files or the manifest already exists, which could matter for repeated calls.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, but the description compensates fully by explaining every parameter: advertiser_name with examples, out_dir behavior, count with default and max, full_quality meaning, and frames default with its purpose. This is exactly what an agent needs beyond the bare schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific action ('Download'), a precise resource ('a whole advertiser's creatives'), and an output shape ('into one folder, with a manifest'). It also positions itself as 'the bulk path', distinguishing it from the single-creative sibling fetch_ad_creative without needing to open that tool's schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives clear context for when this tool is appropriate: collecting real ad examples for decks or reports, in bulk rather than one at a time. It doesn't name alternative tools explicitly or state when not to use it, but the 'bulk path' framing makes the intended use unambiguous.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fetch_ad_creativeA
Download an ad's actual creative to local files.
Image ads are always saved at their original resolution, so the file you get back is the real creative — usable in a deck or a slide as-is. Video ads are downloaded and sampled into evenly spaced JPEG frames.
Args: advertiser_id: The advertiser's ID (e.g. "AR04139428671737823233") creative_id: The creative/ad ID (e.g. "CR01542648288744833025") out_dir: Directory to write into. Defaults to a fresh temp directory. frames: How many frames to sample from a video (default 8). width: Frame width in pixels, height follows aspect (default 320). Ignored when full_quality is set; pass 0 for native resolution. full_quality: Keep the best video stream and write frames at native resolution. Use for presentation; leave off for vision analysis.
Returns: {format, out_dir, files, ...}. Text ads carry no downloadable asset and return their decoded copy instead.
| Name | Required | Description | Default |
|---|---|---|---|
| width | No | ||
| frames | No | ||
| out_dir | No | ||
| creative_id | Yes | ||
| full_quality | No | ||
| advertiser_id | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure. It explains that image ads are saved at original resolution, video ads are sampled into JPEG frames, the frames/width/full_quality parameters affect output, and text ads return decoded copy instead of a file. This is thorough and lets an agent predict the tool's behavior accurately.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured: a concise opening, then an Args section listing each parameter with explanations, and a Returns section. It is front-loaded with the core purpose, and every sentence adds value—no filler. The length is justified given the parameter complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite no output schema, the description includes a Returns section summarizing the return format and noting the text-ad special case. All six parameters are fully explained, including defaults and interactions. The tool's complexity is high (multiple formats, sampling options), and the description covers what an agent needs to call it correctly without missing critical information.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has 0% description coverage, so the description must compensate. It does so thoroughly: the Args section explains each parameter (advertiser_id, creative_id, out_dir, frames, width, full_quality) with usage details, defaults, and edge cases (e.g., width ignored when full_quality is set). This adds substantial meaning beyond the raw schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a precise verb and resource: 'Download an ad's actual creative to local files.' It clearly states what the tool does and distinguishes image vs video handling, making its purpose unambiguous. It also implicitly differentiates from siblings like get_ads or get_ad_detail by focusing on downloading the creative asset itself.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear guidance on when to use specific options: 'Use for presentation; leave off for vision analysis' (referring to full_quality). It explains the difference between image and video handling and when to use full_quality vs not. However, it does not explicitly compare with sibling tools like download_ad_creatives, so it lacks an explicit 'when not to use' or alternative selection.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_ad_detailA
Get full details for a specific ad creative.
Args: advertiser_id: The advertiser's ID (e.g. "AR05099026886533578753") creative_id: The creative/ad ID (e.g. "CR10813648716908961793")
Returns: Ad detail with format (text/image/video), last_shown date, and content. For text ads, content includes headline, description, and destination_url. For image ads, content includes image_url (a directly fetchable asset). For video ads, content includes video_id, video_url (YouTube), and the on-creative headline and button_label.
| Name | Required | Description | Default |
|---|---|---|---|
| creative_id | Yes | ||
| advertiser_id | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, this description carries the full behavioral burden, but it only reveals the verb 'Get' and the return structure. It does not mention side effects, auth requirements, error behavior, rate limits, or any other operational traits, which is a notable gap for a no-annotation tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is cleanly structured with a one-line summary followed by Args and Returns sections. It efficiently uses bullet-like formatting and avoids filler, though the Args/Returns sections could be considered slightly more verbose than strictly needed.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 2-parameter tool with no output schema, the description provides a fairly complete picture: it lists all parameters, explains their purpose, and describes the varying return shapes by ad type. It misses only the operational edge cases (errors, permission requirements), which keeps it from full completeness.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, and the description effectively compensates by explaining both parameters with concrete examples. It identifies advertiser_id as the advertiser's ID and creative_id as the creative/ad ID, including readable example values, making it easy for an agent to fill them correctly.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description starts with 'Get full details for a specific ad creative,' which names a clear verb and resource. The return details (format, last_shown, content type) add specificity, but it does not explicitly differentiate from siblings like fetch_ad_creative or get_ads.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The implied use case is clear: you need this when you have both advertiser_id and creative_id and want detailed info on a single creative. However, there is no explicit distinction from siblings or any guidance on when not to use it, leaving the agent to infer without direct routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_adsA
Get ads for an advertiser with decoded content.
Searches for the advertiser by name, fetches their ad creatives, and returns full details including decoded text ad content (headline, description, destination URL).
Args: advertiser_name: The advertiser's name (e.g. "Nike, Inc.", "Coinbase") count: Number of ads to retrieve (default 10, max 100)
Returns: List of ad details with format, date, and content fields.
| Name | Required | Description | Default |
|---|---|---|---|
| count | No | ||
| advertiser_name | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses key behaviors: it searches by name, decodes text content, returns a list with specific fields (format, date, content). No annotations were provided, so the description carries the burden and does so adequately, though it lacks details on error handling or authentication.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise, front-loaded with purpose, and structured with Args and Returns sections. Every sentence adds value without redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given only two simple parameters and an output schema present, the description fully covers what the tool does, its inputs, and output structure. It is complete for its complexity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With schema description coverage at 0%, the description fully compensates by explaining advertiser_name (with examples) and count (default, max). This adds significant meaning beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states it gets ads for an advertiser by name, with decoded content. It distinguishes from sibling tools like get_ad_detail (single ad) and search_advertiser_by_domain (domain-based search).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explains how the tool works (searches by name, fetches creatives, decodes content) and when to use it. However, it does not explicitly state when not to use it or alternative tools, though the sibling context implies differentiation.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_advertiser_by_domainA
Find an advertiser by their website domain.
Args: domain: The domain to search for (e.g. "nike.com", "coinbase.com")
Returns: Advertiser info with advertiser_id, name, and ad_count — or null if not found.
| Name | Required | Description | Default |
|---|---|---|---|
| domain | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Despite no annotations, the description discloses return values (advertiser info with fields and null for not found) and implies a read-only operation through the verb 'search'. It could be more transparent about potential errors or edge cases, but it covers the main behavioral aspects.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a concise docstring with clearly separated Args and Returns sections. Every sentence adds value, and there is no redundancy or fluff.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple single-parameter search tool with an output schema, the description covers purpose, parameter format, and return value adequately. No additional context is necessary for an AI agent to invoke it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has 0% coverage for the 'domain' parameter, but the description adds significant meaning by specifying it is a website domain and providing examples ('nike.com', 'coinbase.com'). This fully compensates for the lack of schema description.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states 'Find an advertiser by their website domain,' which specifies the verb, resource, and search criterion. It distinguishes itself from siblings like 'search_advertisers' by focusing on domain-based lookup.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides a clear context for when to use the tool (when you have a domain) and includes an example. However, it does not explicitly mention alternatives or when not to use it, though the purpose is sufficiently self-explanatory.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_advertisersA
Search for advertisers by keyword or company name.
Args: query: Search query (e.g. "Nike", "Coinbase", "crypto exchange")
Returns: List of matching advertisers with advertiser_id, name, region, and ad_count.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, and the description only mentions the return fields (id, name, region, ad_count). It lacks details on behavioral traits such as pagination, result limits, ordering, or any side effects.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with Args/Returns sections and is clear, though the 'Args:' line is slightly redundant since there is only one parameter. It is efficient overall.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool has an output schema (context signal: true), the description provides the return fields sufficiently. For a simple one-parameter search tool, it is nearly complete, but lacks behavioral details like pagination.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0% for the only parameter 'query', but the description adds concrete examples ('Nike', 'Coinbase', 'crypto exchange'), giving clear semantic meaning beyond the raw schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states it searches for advertisers by keyword or company name, with a specific verb ('Search') and resource ('advertisers'). It distinguishes from siblings like 'search_advertiser_by_domain' which is a different search variant.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage by providing example queries (e.g., 'Nike'), but does not explicitly state when to use this vs alternatives, nor does it mention when not to use it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v0.3.0- Added
advertiser_summary - Added
compare_advertisers - Added
download_ad_creatives - Added
fetch_ad_creative
4 tool updates
v0.1.0- First observed
get_ad_detail - First observed
get_ads - First observed
search_advertiser_by_domain - First observed
search_advertisers
TDQS
Scored across 8 tools
Most tools target a distinct operation: searching advertisers, fetching ads, comparing domains, summarizing, and downloading creatives. The main overlap is get_ads vs get_ad_detail (both return decoded ad content) and search_advertiser_by_domain vs compare_advertisers (both do domain lookups), but the descriptions sufficiently distinguish single vs batch and list vs individual.
The set mostly follows verb_noun snake_case (get_ads, search_advertisers, compare_advertisers, fetch_ad_creative, download_ad_creatives). It is marred by 'advertiser_summary' (noun-style instead of summarize_advertiser) and inconsistent singular/plural in search_advertiser_by_domain vs search_advertisers, but these are minor.
8 tools is well-scoped for a Google Ads transparency server; there is no redundancy extreme and each tool earns its place. This falls comfortably in the ideal range for a domain-specific server.
The surface covers the main user journeys: find an advertiser (keyword/domain/batch compare), inspect their ads (list/detail/summary), and download creatives (single/bulk). Minor gaps like pagination beyond count or filtering ads by date/format exist, but agents can accomplish the core transparency-research tasks without dead ends.
Maintenance
Related MCP Connectors
Google Ads MCP server — manage campaigns, keywords, and metrics.
MCP-native ad server. Monetize AI chatbots and agents with conversational ads.
Google Ads, Meta Ads & GA4 MCP server - 250+ tools for campaigns, creatives, audiences & reports.
Search Meta, Google Ads, LinkedIn, and TikTok ad libraries plus creative analysis via MCP.
Related MCP Servers
- AlicenseAqualityFmaintenanceA Model Context Protocol server that enables AI assistants to research competitor ads, analyze advertiser profiles, and compare ad presence across 42 regions using publicly available Google Ads Transparency data.56 npm1MIT

google-ads-mcpofficial
AlicenseAqualityAmaintenanceMCP server that provides tools and resources for interacting with Google Ads API, enabling search, metadata retrieval, and account management through natural language.3929Apache 2.0- AlicenseAqualityDmaintenanceAn MCP server for the OpenAI Ads (ChatGPT Ads) Advertiser API, providing tools for agent-led performance marketing such as finding dead ads, auditing near-duplicate copy, and auditing ad-group context hints.18MIT
- AlicenseBqualityBmaintenanceOpen-source MCP server for Google Ads (Meta Ads coming soon) that lets AI assistants manage ad campaigns, reporting, keywords, and targeting in plain English from any MCP client, with safety-first creation of paused campaigns.5456 npm3MIT