Tool Reference
The Adthena MCP server exposes 23 tools organized by domain: the 22 data tools below, plus list_accessible_accounts, which is only registered in the gateway (OAuth/HTTP) deployment - stdio and standalone HTTP expose the 22 data tools alone. All tools are read-only - they query data but never modify anything in your Adthena account, Google Ads campaigns, or any other system.
Tools
| Tool | Description | Report Types |
|---|---|---|
list_accessible_accounts |
List all Adthena accounts you have access to | - |
get_market_share |
Competitor performance: clicks, spend, impressions, position | detail, groups_and_locations, summary |
get_market_trends |
Competitor performance trends over time | - |
get_competitor_trends |
Unique competitor count trends over time | - |
get_competitor_overlap |
Search term overlap between you and competitors | - |
get_search_terms |
Search term analysis and opportunity discovery | detail, detail_summary, opportunities, opportunities_summary |
get_ads |
Most frequent ad copies and product listing ads | Text (ad_format="text") or PLA (ad_format="pla") |
get_ai_overview |
AI Overview appearance metrics and affected search terms | overview, search_terms |
get_ai_overview_content |
AI Overview citation analysis: sentiment, themes, competitor citations (premium) | content, competitors |
get_ai_overview_impact |
AI Overview CTR/CPC impact with frequency breakdown (premium) | impact, search_terms |
get_llm_performance |
LLM domain citation-share trends and per-domain performance scores | domain_trends, domain_performance |
get_llm_prompts |
List LLM prompts and prompt groups configured for an account | prompt_groups, prompts |
get_llm_prompt_metrics |
LLM prompt-level metrics: citation counts, brand share of citations, brand mention shares, and health status | prompt_groups, prompts_v2, prompts |
get_llm_ad_prompts |
List AI Ads prompts and prompt groups configured for an account (AI Ads Intelligence surface) | prompt_groups, prompts |
get_ai_ads_intelligence |
AI Ads Intelligence - brand and competitor presence in LLM paid ad placements (ChatGPT, Google AI) | performance_trends, advertiser_share_trends, prompts_analysis |
get_llm_ad_library |
The creatives behind AI Ads Intelligence - ad copy, shopping units, and per-creative prompts, trend and landing URLs | ads, plas, prompts, trend, destinations |
get_ai_ads_prompt_expansion |
Untracked prompts the brand or its named competitors advertise on - the AI Ads expansion candidates, outside the tracked prompt set | - |
get_serp_tracker |
SERP Tracker rule matches (formerly Infringement Tracker) - ads on the SERP that triggered a configured rule. Rule matches, not legal infringements | matches, summary, rules |
get_auto_takedown |
Adthena-detected trademark cases - the inputs to Google's Auto-Takedown system. For enforcement outcomes use get_trademark_takedowns |
matches, summary, rules |
get_ad_hijacking |
Ad hijacking and affiliate fraud detection | configurations, incidents, summary |
get_brand_activator |
Automated bid management activity and cost savings (savings data lags 4 days) | activity_log, daily_savings, daily_savings_summary |
get_trademark_takedowns |
Google trademark auto-takedown enforcement outcomes | detail, summary |
get_account_settings |
Account configuration: locations, groups, metrics | locations, search_term_groups, search_term_groups_metrics |
Common Parameters
Most tools accept these parameters:
| Parameter | Description |
|---|---|
account_id |
Required. Your Adthena account ID (from list_accessible_accounts). |
start_date / end_date |
Date range in YYYY-MM-DD format. Data is available up to yesterday. |
device |
List of "desktop" and/or "mobile". |
report_type |
Selects between detail, summary, and other sub-views. |
page / page_size |
Pagination. Default page size is 50, max 1000. Use page_size=0 to return all results. |
Common Filters
| Filter | Description |
|---|---|
search_term / excluded_search_term |
Filter by search term text (substring match). |
search_term_group / excluded_search_term_group |
Filter by user-defined search term group. |
competitor / excluded_competitor |
Filter by competitor domain. |
competitor_group / excluded_competitor_group |
Filter by user-defined competitor group. |
location |
Filter by geographic location. |
adwords_campaign / excluded_adwords_campaign |
Filter by Google Ads campaign name. |
ad_type |
"textad", "pla", or "organic". |
segment_by |
Controls data grouping: ["device"], ["ad_type"], or both. |
LLM Parameters
The LLM tools fall into two surfaces with different engine sets:
- Organic citations (
get_llm_performance,get_llm_prompts,get_llm_prompt_metrics) - tracks brand citations in LLM responses on ChatGPT, Perplexity and Google AI Mode. AI Mode (googleai) is not in the default engine list, so it has to be asked for. - Paid ad placements (
get_ai_ads_intelligence,get_llm_ad_library) - tracks paid ad detections in LLM responses on ChatGPT and Google AI.get_ai_ads_intelligencereports the aggregate rates and share;get_llm_ad_libraryreturns the individual creatives behind them. Both describe only the prompts the account already tracks;get_ai_ads_prompt_expansionis the complement, returning the untracked prompts that carried ads.
Both surfaces filter on prompts (prompt / prompt_group and their excluded_ twins) and, on get_llm_performance, get_ai_ads_intelligence and get_llm_ad_library, on domains via competitor / excluded_competitor / competitor_group / excluded_competitor_group. Competitor filtering is literal: the account's own domain is filterable like any other, so a selection that omits it hides the brand row/series. The matchable domains are the LLM-relevant competitors (every domain ever seen cited or advertising on the account's tracked prompts), not the Google relevant competitors. There are no search-term or device filters. The one exception is get_ai_ads_prompt_expansion, which is scoped by the account's own country and takes neither prompt nor prompt_group (its whole result set is prompts the account does not track); it narrows with search (free text over the prompt) and advertiser (a domain that must be among the row's relevant advertisers) instead.
| Parameter | Description |
|---|---|
engine |
Organic tools: "chatgpt", "perplexity" and/or "googleai" (default is chatgpt + perplexity, so AI Mode must be requested). Paid-ad tools: "chatgpt" and/or "googleai" (default both), except get_llm_ad_library(report_type="plas") where only "googleai" returns anything. An engine the account is not entitled to, or that is switched off for it, is dropped from the request; filters.engine in the response reports which ones answered. |
segment_by |
["engine"] to split results per LLM engine. Default aggregates engines into a comma-joined label. get_llm_prompt_metrics(report_type="prompts") always segments by engine; get_ai_ads_intelligence(report_type="prompts_analysis") ignores segment_by. get_ai_ads_prompt_expansion does not accept it at all - it is always engine-segmented (see below). |
prompt / excluded_prompt |
Filter by prompt text. |
prompt_group / excluded_prompt_group |
Filter by prompt group name. |
location |
Filter by geographic location. |
max_number_of_domains |
Top domains/competitors per bucket. get_llm_performance(domain_trends) default 10; get_ai_ads_intelligence(advertiser_share_trends) default 5 - brand row always included in addition. |
primary_dimension |
get_llm_performance(domain_trends) only: "frequency" or "average_position". |
filtering_options |
get_llm_performance (both report types) and get_ai_ads_intelligence(advertiser_share_trends): how a competitor / competitor_group selection meets the metrics. "relative" (default) filters before aggregation, so citations_frequency / share_of_ads and performance_score are recomputed over the selected domains and the shares sum to 1. "absolute" computes over the whole market and only filters the returned rows, so shares keep whole-market values. Exclusions apply in both modes. Same vocabulary as get_market_share / get_market_trends. |
prompt_status |
get_ai_ads_intelligence(prompts_analysis) only: "with_brand_ads", "gap", or "all" (default). |
LLM tools do NOT support search-term/device filters
These endpoints filter on prompts and (for get_llm_performance, get_ai_ads_intelligence, get_llm_ad_library) competitor domains / groups. Parameters like device, ad_type, search_term, adwords_campaign, and is_whole_market are not available. (get_ai_ads_prompt_expansion is the exception noted above: it takes search and advertiser, and none of the parameters in this table.)
How to read get_ai_ads_prompt_expansion responses
This tool is shaped differently from the rest of the LLM family, so the generic rules above do not all carry over:
- Always engine-segmented. One row per (prompt, engine);
filters.segment_bycomes back as["engine"]and there is no engine-aggregated shape to request. A prompt with ads on both engines returns two rows, each with its own advertisers, share andlast_seen- so the row count is not a prompt count. De-duplicate onpromptwhen you need one. - Relevance is per engine. A named competitor advertising on ChatGPT does not pull the prompt into the Google AI rows.
top_advertiser_shareis a fraction in[0, 1], published to 4 decimal places - not a percentage. Multiply by 100 to display it. It is a share of that row's ad placements, so it never exceeds 1.page_sizedefaults to 0 (all results), unlike the 50 default elsewhere. Prompt-expansion volumes are small; pass a specificpage_sizeonly if you want smaller pages.signalpicks the set:"you"(default) for prompts the brand's own ads ran on,"competitors"for prompts where only named competitors ran ads,"all"for both.- No
nulls. A filter the caller did not set is omitted fromfiltersrather than echoed as null - check key presence.
How to read AI Ads Intelligence responses
get_ai_ads_intelligence distinguishes between several "absence" states that look similar but mean different things. Reading them correctly is essential - getting it wrong leads to opposite conclusions.
The API never returns JSON null. Undefined values are omitted from the response (the key is absent from the object), never serialized as null. To detect "undefined", check whether the key is present in the JSON, not whether its value is null.
Not every rate is capped at 1. The brand-side rates (ad_presence_rate, your_ads_detection_rate) and share_of_ads are in [0, 1]. The competitor rates - top_competitor_detection_rate, top_competitor_rate, avg_competitor_detection_rate - are ad placements divided by scrapes, and every ad counts: a competitor showing several ads in one answer counts more than once, so these can exceed 1. Do not clamp them to 100%. you_vs_top_competitor_rate is the brand rate minus the top competitor rate, so it can go below -1.
ad_presence_rateis always present and numeric (period-level and per-day in time_series). The DAO zero-fills buckets with no data and substitutes0for any undefined value, so a0is ambiguous - it can mean "no scrapes occurred" OR "scrapes occurred but the brand was not detected". Use thetotal_promptsfield at the same level to disambiguate (never cross levels):- Period level: each segment carries a period-wide
total_prompts.total_prompts > 0⇒ scrapes occurred during the period ⇒ad_presence_rate = 0is a real "brand absent" signal.total_prompts = 0⇒ no scrapes ⇒ no signal. - Per-day level: each
time_seriespoint carries its owntotal_prompts(distinct prompts scraped that day). Same rule:total_prompts > 0that day ⇒ rate of0means the brand was absent that day;total_prompts = 0that day ⇒ no scrapes that day, no signal. - Never recommend action ("the brand is invisible here, push a campaign") based on
ad_presence_rate = 0alone - always check the matchingtotal_promptsfirst. prompts_analysishas no such ambiguity - the SQL HAVING clause guarantees every returned row has scrapes. Absentyour_ads_detection_rateis a definitive "brand absent" signal there.
- Period level: each segment carries a period-wide
- Period-level rate fields that may be omitted. Absence is a meaningful signal - it tells you which entity is missing from the data, not "data unavailable". The rate would have been 0 had it been emitted.
your_ads_detection_rate(prompts_analysis): absent ↔ the brand had zero detections on that prompt.top_competitor_rate,avg_competitor_detection_rate(prompts_analysis),top_competitor_detection_rate(performance_trends): absent ↔ no competitors were detected.you_vs_top_competitor_rate: absent ↔ either the brand or the competitors are absent (or both).
share_of_adsis always present (period and daily). Advertisers with zero detections in the period don't appear in the response at all - including the brand. If a brand row is missing fromadvertiser_share_trends, the brand had no detections in that bucket.top_competitorkey omitted → no competitors were detected in that bucket / on that prompt. Interpret as "uncontested", not "missing data".- Gap prompt → in
prompts_analysis, a row withyour_ads_detection_rateabsent ANDcompetitors_count > 0. Brand had zero detections; competitors did. - Greenfield prompt →
is_greenfield: trueis set explicitly, andtop_competitoris absent. Brand has detections AND no competitors were detected. Highest-priority targets. is_greenfieldis always present (boolean).- The advertiser
domainis the identity → advertisers are grouped and counted by domain, and placements with no domain are unattributable and dropped before aggregation, so everycompetitorsentry,top_competitorandadvertiser_share_trendsrow carries a non-emptydomain.competitors_count/competitors_detectedcount distinct domains. Thenameis a display label only - the bucket's most-detected advertiser name, falling back to the domain when no named placement exists. Match, dedupe and join ondomain, never onname. - Zero-fill is by bucket, not by day → each
time_seriescarries one point per bucket of the resolvedtime_period: every day when daily, only the Sunday week-starts when weekly, only the month-firsts when monthly. Bucket dates are clamped inward to[start_date, end_date], so a partial bucket at either end is not emitted. Read thedatesequence rather than assuming consecutive days, and checkfilters.time_periodfor the granularity you actually got. - Empty segments → in
performance_trendsevery requested (engine, location) bucket is always emitted, zero-filled when it has no data.advertiser_share_trendsis built only from advertisers that had detections, so a bucket with no detections yields no segment at all. enginefield shape → withsegment_by=[]the engine field is a sorted comma-joined label like"chatgpt, googleai"(the aggregated bucket). Withsegment_by=["engine"]it is a single engine name.- No prior-period deltas are computed server-side - call the tool twice with adjacent date ranges and diff client-side.
How to read LLM Ad Library responses
get_llm_ad_library shares the "never returns null, omits the key instead" rule above, but its own absence and time semantics differ from get_ai_ads_intelligence in ways that invert conclusions if missed.
- Grain → list rows (
ads,plas) are one per (creative, engine, advertiser domain) - the domain is the advertiser identity andadvertiseris a display name. The same creative appears once per engine it ran on, so summingdetectionsacross rows is not a unique-creative count, and rollups should group onadvertiser_domain. days_seenis a span, not a tally → inclusive days between the first and last observation. A creative seen once in January and once in June has adays_seenof about 150, not 2. On list rows the span covers the creative's whole observed lifetime; onpromptsrows it is measured inside the requested window.first_seen/last_seen/stateon list rows describe the creative's lifetime, not your date range → a creative can report alast_seenbefore yourstart_dateand still return detections inside the range.stateis measured againstend_date, not today →new= first observed within 7 days of the range end,quiet= last observed more than 7 days before it,live= neither. Query a historical range and you get the states as of that range.trendis not zero-filled → buckets with no detections are absent from the response. This is the opposite ofget_ai_ads_intelligence's zero-filledtime_series. Check the date sequence before reading a gap as a zero.destinationscounts on a wider basis → a landing URL is recorded against the creative alone with no record of the prompt that produced it, so its detections cover every prompt the creative was answered on, while list rows andprompts/trendcount only your tracked prompts. Expect the destinations total to be larger, and never present it as a share of the creative's detections. The prompt filters do not apply there for the same reason.sample_urlis a sample, not an identifier → it is the most-detected landing URL over the range, tracking tokens and all. Group onlanding_path(scheme, host, query and fragment removed) and usereport_type="destinations"for the full set.searchnarrows → the parameter is repeatable and terms are ANDed, sosearch=["laptop", "monitor"]returns only creatives matching both. Each single term still matches if any searched field contains it.- Empty
advertiser/advertiser_domain→ the source data carries no advertiser for that row. Treat as "unattributed", not a lookup failure. - Data depth varies by
time_period→ daily holds roughly the last month, weekly the last 13 months, monthly the full history. Weekly and monthly rows are stored on their bucket-start date, so a range containing no bucket start returns no rows at all.
Safety Annotations
Every tool includes MCP safety annotations. All tools use identical annotations because the entire server is read-only:
| Annotation | Value | Meaning |
|---|---|---|
readOnlyHint |
true |
The tool only reads data - it does not modify anything. |
destructiveHint |
false |
The tool cannot delete or destructively modify data. |
openWorldHint |
false |
The tool operates against a closed, fixed set of Adthena endpoints. |
This applies to all 23 tools (the 22 data tools plus the gateway-only list_accessible_accounts). The annotations are enforced in the source code and verified by automated tests.
Resources
The server also provides 5 read-only MCP resources that give the client context about your account and the data model:
| Resource | URI | Description |
|---|---|---|
| Quick Reference | docs://quick-reference |
Question-to-tool mapping table - read first to find the right tool. |
| Concepts | docs://concepts |
Core Adthena concepts and metric definitions (share, position, count metrics). |
| Workflows | docs://workflows |
Step-by-step workflows for 10 common analysis patterns. |
| Date Rules | docs://date-rules |
Date handling rules, time period auto-resolution, and data alignment. |
| Data Access | docs://data-access |
Pagination modes, segmentation (segment_by), and filtering options. |
Data Limits
| Constraint | Details |
|---|---|
| AI Overview | start_date must be on or after 2025-07-31 |
| AI Overview Content / Impact | start_date must be within the last 30 days |
| Brand Activator activity_log | Limited to the last 60 days (savings endpoints have no date restriction) |
| Search Term Opportunities | Limited to the last 30 days |
| LLM Performance / Prompt Metrics | Any range, with time_period selecting the grain. Exception: get_llm_prompt_metrics(report_type="prompts") is still capped at the last 30 days |
| LLM Ad Library history depth | Daily data covers roughly the last month, weekly the last 13 months, monthly the full history |
| LLM Prompts (listing) | No date range required - lists account configuration |
| Pagination | Max page_size is 1000. Use page_size=0 for all results (streamed). |
| Time periods | Auto-calculated: daily (last 30 days), weekly (last 13 months), monthly (older). |