Skip to content

Tool Reference

The Adthena MCP server exposes 24 tools organized by domain: the 23 data tools below, plus list_accessible_accounts, which is only registered in the gateway (OAuth/HTTP) deployment - stdio and standalone HTTP expose the 23 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_local_service_ads Local Service Ads data - how often they show, which businesses take the spots and the positions they hold. Returns Local Service Ads only, as volume-weighted sums, not rates -
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_intelligence reports the aggregate rates and share; get_llm_ad_library returns the individual creatives behind them. Both describe only the prompts the account already tracks; get_ai_ads_prompt_expansion is 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_local_service_ads responses

Local Service Ads are the "Google Guaranteed" / "Google Screened" listings above the text ads, used by local trades and professional services. The response carries no badge field - only the business name - so it cannot distinguish Guaranteed from Screened. An advertiser is a business name, not a domain, so competitor and competitor-group filters do not apply and the tool does not accept them.

segment_by chooses what each row covers: date, device, search_term, search_term_group, location, business_name, in any combination. Every row carries ad_type lsa: this tool serves one result type, so it takes no ad_type parameter and ad_type is not a grain. Segmenting by business_name reads the per-advertiser data, which leaves weighted_total_appearances and weighted_scrape_count null (that data counts a business once per search, and measures only the searches each business appeared on, which arrive as weighted_advertiser_scrape_count) and is the only way to get weighted_rank_sum, weighted_ranked_appearances and best_rank. A dimension you did not segment by reports the comma-separated set of values the row covers, "desktop, mobile", the same convention as the other tools; one the data does not carry at that level is null.

The response carries sums, not rates. Compute what you need:

Figure Formula
Frequency - share of tracked searches where the result appeared summed weighted_appearances / summed weighted_scrape_count - only without business_name, where that denominator is null. Substituting weighted_advertiser_scrape_count answers a different question and always reads higher, from 8x to several thousand depending on how rare Local Service Ads are
Local Service Ads per search - counting every appearance, not just the first summed weighted_total_appearances / summed weighted_scrape_count
A business's market share of all Local Service Ads that business's weighted_appearances / the total across every business - use page_size=0, or the denominator is one page rather than the market
A business's average position summed weighted_rank_sum / summed weighted_ranked_appearances - not over weighted_scrape_count, which counts searches the business was absent from, nor weighted_appearances, which counts periods from before position was recorded; either returns a wrong position, often below 1. Position starts 2026-10-08 and was not backfilled, so an earlier window has none and a spanning one covers only the part from that date. A position here is a slot, not a rank in a field of competitors: "position 2" quoted bare invites "second out of many". Correct it from the rows fetched - what the positions actually run from and to for those terms and that period - never from a belief about the format. Do not state how many businesses a unit holds or how many positions exist; Google owns that format and changes it without telling us. Never report it as "X of N": the rows are summed over every scrape, so no N exists here, and the distinct-advertiser count is everyone who rotated through the slots over the window, which on a heavily scraped term runs into the hundreds
Whether an advertiser is a constant or an occasional presence periods_seen and last_seen, which report presence directly - a returned row does not imply one

Summing a result segmented by date, device or search_term reconstructs the unsegmented weighted measures exactly. The others each need their own rule: best_rank takes MIN, last_seen MAX, and periods_seen cannot be recombined at all, because a period seen on both devices is counted in the row for each. Three dimensions do not reconstruct even the weighted measures: business_name reconstructs weighted_total_appearances rather than weighted_appearances (counting per business counts every ad, not every search carrying one - an advertiser's share is still exactly right); location widens to every account in the location family, whose rows are those accounts' own data rather than sub-totals of the one you asked for, so it totals more; and search_term_group double-counts terms in several groups while omitting terms in none. Rates are never additive: averaging a frequency across rows gives a wrong answer because the denominators differ. Always recompute from the sums.

Measurement change, 8 Oct 2026. Before that date a search was counted only where a result appeared; from it, every tracked search is counted. The denominator grew and rates fell, with no change in the market. filters.tracking.state is none, all_earlier or mixed, and when it is not none, filters.tracking.caveat carries a sentence explaining it - report that caveat alongside any figure from such a window, and do not compute a change or trend across first_clean_period.

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_by comes 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 and last_seen - so the row count is not a prompt count. De-duplicate on prompt when 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_share is 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_size defaults to 0 (all results), unlike the 50 default elsewhere. Prompt-expansion volumes are small; pass a specific page_size only if you want smaller pages.
  • signal picks 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 from filters rather 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_rate is always present and numeric (period-level and per-day in time_series). The DAO zero-fills buckets with no data and substitutes 0 for any undefined value, so a 0 is ambiguous - it can mean "no scrapes occurred" OR "scrapes occurred but the brand was not detected". Use the total_prompts field 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 = 0 is a real "brand absent" signal. total_prompts = 0 ⇒ no scrapes ⇒ no signal.
    • Per-day level: each time_series point carries its own total_prompts (distinct prompts scraped that day). Same rule: total_prompts > 0 that day ⇒ rate of 0 means the brand was absent that day; total_prompts = 0 that day ⇒ no scrapes that day, no signal.
    • Never recommend action ("the brand is invisible here, push a campaign") based on ad_presence_rate = 0 alone - always check the matching total_prompts first.
    • prompts_analysis has no such ambiguity - the SQL HAVING clause guarantees every returned row has scrapes. Absent your_ads_detection_rate is a definitive "brand absent" signal there.
  • 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_ads is 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 from advertiser_share_trends, the brand had no detections in that bucket.
  • top_competitor key omitted → no competitors were detected in that bucket / on that prompt. Interpret as "uncontested", not "missing data".
  • Gap prompt → in prompts_analysis, a row with your_ads_detection_rate absent AND competitors_count > 0. Brand had zero detections; competitors did.
  • Greenfield prompt → is_greenfield: true is set explicitly, and top_competitor is absent. Brand has detections AND no competitors were detected. Highest-priority targets.
  • is_greenfield is always present (boolean).
  • The advertiser domain is the identity → advertisers are grouped and counted by domain, and placements with no domain are unattributable and dropped before aggregation, so every competitors entry, top_competitor and advertiser_share_trends row carries a non-empty domain. competitors_count / competitors_detected count distinct domains. The name is 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 on domain, never on name.
  • Zero-fill is by bucket, not by day → each time_series carries one point per bucket of the resolved time_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 the date sequence rather than assuming consecutive days, and check filters.time_period for the granularity you actually got.
  • Empty segments → in performance_trends every requested (engine, location) bucket is always emitted, zero-filled when it has no data. advertiser_share_trends is built only from advertisers that had detections, so a bucket with no detections yields no segment at all.
  • engine field shape → with segment_by=[] the engine field is a sorted comma-joined label like "chatgpt, googleai" (the aggregated bucket). With segment_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 and advertiser is a display name. The same creative appears once per engine it ran on, so summing detections across rows is not a unique-creative count, and rollups should group on advertiser_domain.
  • days_seen is a span, not a tally → inclusive days between the first and last observation. A creative seen once in January and once in June has a days_seen of about 150, not 2. On list rows the span covers the creative's whole observed lifetime; on prompts rows it is measured inside the requested window.
  • first_seen / last_seen / state on list rows describe the creative's lifetime, not your date range → a creative can report a last_seen before your start_date and still return detections inside the range.
  • state is measured against end_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.
  • trend is not zero-filled → buckets with no detections are absent from the response. This is the opposite of get_ai_ads_intelligence's zero-filled time_series. Check the date sequence before reading a gap as a zero.
  • destinations counts 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 and prompts / trend count 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_url is a sample, not an identifier → it is the most-detected landing URL over the range, tracking tokens and all. Group on landing_path (scheme, host, query and fragment removed) and use report_type="destinations" for the full set.
  • search narrows → the parameter is repeatable and terms are ANDed, so search=["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 24 tools (the 23 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
Local Service Ads A measurement change on 2026-10-08 makes figures either side of it not directly comparable; filters.tracking reports where the window sits. Daily data covers roughly the last month, so from about 8 Nov 2026 the daily grain only sees the current measurement.
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).