ads-mcp tool reference

Shared reference for every skill

ads-mcp tool reference

Canonical mapping for MGID MCP skills. Source of truth: the tool list the server advertises over MCP (tools/list). Skills must use ads-mcp tool names only — never a tool name borrowed from another MGID interface.

Skills are served by the server itself as MCP prompts (prompts/list), so a connected client always gets the version matching the running tool surface.


Auth ID vs client ID (do not confuse)

MGID exposes two account identifiers:

ID Example Where
auth_id 200316 get_account → authId; v1 API (get_campaign, list_teasers, get_portfolio_statistics)
client_id 330838 get_account → clientId; v2 API path /ads/account/{clientId}/…; optional clientId tool arg

Rules for skills:

  1. v2 tools (list_campaigns, get_campaign_statistics, run_custom_report, get_selective_bidding_statistics, list_conversion_targets): omit clientId when operating on the logged-in account (ads-mcp uses the token's stored client_id). Pass clientId only for sub-account access.
  2. Never pass authId as clientId — v2 answers 401. get_account returns both, correctly labelled.
  3. Plan files: store both auth_id and client_id in scope when known.
"scope": {
  "auth_id": 200316,
  "client_id": 330838,
  "account_label": "Internovus / Plexop"
}

Read tools (available on the read-only OAuth scope)

Tool Purpose
get_account v1 client profile plus both ids: authId (v1) and clientId (v2), timezone, wallet
get_portfolio_statistics One row per campaign for the whole account in one call — imps, clicks, spent, avcpc, conversions per funnel stage, revenue, EPC, profit
list_campaigns Campaign config only: name, status, type, bidding, limits, conversionTracking, sourcesOptimization, targeting fields
get_campaign Single campaign config (same fields as list)
get_campaign_statistics Daily rows + totals for one campaign (spend, clicks, conversionsBuy, conversionsCostBuy, roas, ctr, …). Max 90-day range
get_selective_bidding_statistics Per-entity stats. entity: sources | widgets | providers
get_quality_factors Current QF multipliers. entity: sources | widgets | providers
get_source_filters Block/allow filters for a campaign
list_teasers Teaser list (config + status + rejection reason; no performance metrics)
get_teaser Single teaser config
get_teaser_statistics Teaser performance (requires teaserId; date range or dateInterval)
list_conversion_targets Account conversion targets (postback/event/url)
lookup_dictionary Geo, OS, browser, language, search-feed-provider pickers
run_custom_report Flexible report via v2 GET …/statistics/reports — requires customReports grant; params flattened to PHP-style query (see ads-mcp objectToQuery)
raw_api_request Escape hatch — avoid in skills unless a tool is missing

Critical API facts

  1. Portfolio reads go through get_portfolio_statistics. list_campaigns has no spend or conversion metrics. Do not loop get_campaign_statistics over every campaign to find spend — that is one call per campaign. Use get_campaign_statistics only once a campaign is selected and you need its day-by-day rows (cap detection, period-over-period).

  2. Selective bidding entity choice (from campaign config):

    • sourcesOptimization: true → entity: sources
    • sourcesOptimization: false → entity: widgets
    • Push / provider bidding → entity: providers
  3. Target CPA / bid comes from get_campaign → bidding.strategy + bidding.price, or from the user. There is no get_account_goals.

  4. Date ranges on v2 statistics tools: max 90 days; end date = yesterday for complete days. get_portfolio_statistics uses v1 named intervals (lastSeven, last30Days, thisMonth, …) or startDate+endDate.


Upstream contract traps (verified against the api2 source)

Upstream refuses a wrong value with "not a valid value" and never lists the alternatives, so these are the shapes that cost round-trips. Each one is now also stated in the tool description; this is the same information in one place.

limits is a discriminated object

limitType has exactly three values and decides which cap fields exist:

limitType Cap fields Units
unlimited none (any caps sent are ignored) —
budget dailyLimit, overallLimit account currency, float
clicks dailyLimit, overallLimit whole clicks, int
"limits": {"limitType": "budget", "dailyLimit": 100, "overallLimit": 1000}

day, daily, perDay, total, overall and limited are not values — a budget cap is budget. Optional splitDailyLimitEvenly spreads the daily cap across the hours of the day and is unavailable on push campaigns. The read side reports unlimited when no cap is set, which is the upstream default, not an error.

run_custom_report takes closed enumerations

metrics and dimensions are both required. dimensions accepts at most 3 values, and the range at most 90 days.

  • Dimensions: month, week, day, hour, campaignId, campaignName, campaignType, teaserId, country, region, os, browser, deviceType, widgetId, source. There is no campaign — that is campaignId or campaignName.
  • Metrics: adRequests, impressions, viewability, clicks, ctr, spent, cpm, cpc, cpcWithoutDataFee, conversions, conversionsInterest, conversionsBuy, conversionsDecision, conversionsRateInterest, conversionsRateBuy, conversionsRateDecision, conversionsCostInterest, conversionsCostBuy, conversionsCostDecision, revenue, profit, roas, epc, vCpm, vCtr, etr, actions, engagement, engagementTimeOnSite, engagementVisits, engagementCostPerVisit, engagementLandingRate.
  • orders may only sort by a field that is already in metrics or dimensions.
  • Dates: pass YYYY-MM-DD; the tool expands them to the RFC3339 datetimes the endpoint demands. A bare date sent straight through answers 422.

Conversion targets do not round-trip

target.type is a discriminator — url, event or postback. Every type needs name and categoryId; description is optional.

A url target takes conditions: an array of exactly one {type, value}, where type is starts, contain or ends. list_conversion_targets returns that same condition as a single urlCondition object. The write shape and the read shape differ upstream, so a target that was read cannot be resent as a write — rebuild it. There is no delete tool: creation is one-way.

Units differ between adjacent tools

Field Tool Unit
bidding.price create_campaign, update_campaign dollars (0.06)
priceOfClick create_teaser, set_teaser_status US cents (6)

Response shapes to defend against

  • list_teasers returns [] when empty but an object keyed by teaser id when populated — never assume an array.
  • v1 can answer HTTP 200 with an errors[] body; the tool surfaces that as an error result reading "MGID API error (HTTP 200)", which is a real failure despite the status.
  • get_campaign (v1) and get_source_filters (v2) can disagree about a campaign's filters — they read different sides. Trust whichever backs the tool you are about to call, and re-read after a write rather than assuming.

Campaign types — check before applying any heuristic

get_campaign / list_campaigns → type is one of:

type Serving model Skill applicability
content Native content teasers All skills apply
product Product/e-commerce teasers All skills apply
push Push notifications via providers Use entity: providers for selective bidding; teaser fatigue math is weaker (no widget-level placement)
search_feed Search feed via a searchFeedProviderId Not supported. No teasers, no source/widget entities — CTR, fatigue and source-audit rules are meaningless. Skip with an explicit note
newsletter Newsletter placements Not supported. Treat like search_feed until rules exist

Every skill that computes CTR, CPA-per-source, or creative rules must branch on type first and state plainly which campaigns it skipped and why. Silently applying content math to a search_feed campaign produces confident nonsense.


Write tools (require the full OAuth scope)

Not enabled in all deployments — a read-only connection hides them, and a server with no audit-log storage refuses them. Skills run simulation without them; the apply phase marks blocked_by: ["mcp_tool_unavailable"] when missing.

Tool Purpose
create_campaign Create a campaign, including targeting, schedule, limits, conversion tracking
update_campaign Partial update — name, bidding, limits, schedule, targeting, conversion tracking. type, showName and sourcesOptimization are not editable
set_campaign_status active / paused
create_teaser Create a teaser under a campaign (v1, own account, hosted imageLink only)
update_teaser Full replace (v1 PUT) — fetch the teaser first and resend every field you want to keep
set_teaser_status Block/unblock a teaser, optionally adjust priceOfClick
create_conversion_target Add a url/event/postback target
update_source_filters Block/unblock sources, widgets or providers
delete_source_filters Remove a campaign's filters for one entity
update_quality_factors QF changes (entity: sources | widgets | providers)
delete_campaign Two-phase: preview → confirmationToken → execute
delete_teaser Two-phase, same shape

Upstream rejects unknown fields on create_campaign / update_campaign — do not invent parameter names; the call fails rather than silently ignoring them.

There is no send_to_moderation, batch-update-status, or batch-update-quality-factor tool. Moderation is submitted by creating or editing the teaser; its verdict is read back from the teaser payload.

Creative generation is not the agent's job

MGID has its own teaser and image generation with tuned prompts (creative auto-recommendation strategies, text-to-image and image-to-image in the advertiser panel). Skills must not write ad copy or invent creative concepts. Until those generators are exposed on the advertiser API and wired into ads-mcp, a creative step ends with "produce variants in MGID Campaign Studio / Media Gallery" — not with model-written headlines.


Not provided by skills

Skills must not assume the agent or the skill text provides:

  • Plan persistence / plan storage
  • Idempotent apply (plan_id:recommendation_id)
  • Scheduled digests

A skill produces a plan file and reads it back at apply time. Calls are recorded server-side, but no tool reads that record back.


Typical call patterns

Portfolio triage (start here for anything account-wide)

get_portfolio_statistics(dateInterval: lastSeven)   // 1 call, all campaigns
  → rank by spend / CPA / profit, pick the campaigns worth a closer look
list_campaigns → config for the picked ids (type, bidding, limits, sourcesOptimization)

Tracking gate (per campaign)

get_portfolio_statistics → campaigns with spend
list_campaigns → status=active, conversionTracking, bidding
list_conversion_targets → account has targets configured
get_campaign_statistics(campaignId, dateFrom, dateTo) → daily rows when a
  campaign needs the when-did-it-stop answer

Source audit (one campaign)

get_campaign(campaignId) → type, sourcesOptimization, bidding
get_selective_bidding_statistics(campaignId, entity, dateFrom, dateTo)
get_quality_factors(campaignId, entity)  // optional, for proposed QF from/to
get_source_filters(campaignId, entity)   // avoid duplicate recommendations

Budget reallocation (portfolio)

get_portfolio_statistics → spend + conversions per campaign
list_campaigns → limits.dailyLimit
get_campaign_statistics(candidate ids) → daily rows for cap-binding detection

Creative fatigue (one campaign)

Primary (customReports grant):

run_custom_report(current window) + run_custom_report(baseline window)
  dimensions: [teaserId]
  metrics: [clicks, spent, ctr, conversionsBuy, conversionsCostBuy]
  filters: { campaigns: [campaignId], dateRange }
  → join on teaserId for period-over-period rules (2 calls/campaign)
list_teasers(campaignId) → active count (never pause last active teaser)
get_campaign_statistics(campaignId) → campaign avg CPA/CTR for relative rules

v2 custom report has no comparison mode — do not pass dateRangePrevious (it is ignored). Run two reports and join them instead.

Fallback (no customReports grant):

list_teasers(campaignId) → active teaser ids (cap 20)
get_teaser_statistics(teaserId, startDate, endDate) → current vs baseline
get_campaign_statistics(campaignId) → campaign avg CPA/CTR