Skip to main content
Glama
MCP Apple Ads

MCP Apple Ads

by mgcrea

@mgcrea/mcp-apple-ads

Model Context Protocol server for the Apple Ads Platform API — ad accounts, campaigns, ad groups, keywords, ads and reporting.

Writes are off by default, everything is created paused, and optional ceilings cap what any tool may spend.

Features

  • 34 tools across accounts, apps, campaigns, ad groups, keywords, ads, shared budgets and reporting — plus a raw-request escape hatch for the rest of the API.

  • Reporting that fits in a context window. Totals by default, zero-valued metrics dropped, metadata trimmed to the fields that identify a row, and per-period rows opt-in and capped. A 90-day × 200-keyword report shapes down by more than 10×.

  • Money safety in four layers — see Spending safety.

  • No SDK dependency. Two runtime dependencies (@modelcontextprotocol/server, zod); the ES256 client secret is signed with node:crypto.

Related MCP server: apple-ads-mcp

API version and the 2027 sunset

This server targets the Apple Ads Platform API 1.0 (https://api.ads.apple.com/v1/), which Apple shipped in August 2026.

It does not target the older Campaign Management API v5 (api.searchads.apple.com/api/v5), which Apple sunsets on 26 January 2027. There is no version switch: a second shaping path for an API that will be gone is not worth the code, and anyone still on v5 has to migrate regardless.

Security

Your credentials. The private key is read once at startup and never leaves the process. It is never logged, never returned by a tool, and apple_ads_auth_status reports only whether it loaded. Prefer APPLE_ADS_PRIVATE_KEY (inline PEM, from a secret store) over a path on disk. If you do use a file, chmod 600 it — the server warns when it is group-readable.

Unlike App Store Connect, nothing here is downloadable-once: you generate the key pair and register only the public half, so a lost key is regenerable.

Blast radius. This server can spend money. That is why:

  • writes are unregistered, not merely refused, unless APPLE_ADS_ALLOW_WRITES=1;

  • every write — including creates — requires confirm: true;

  • everything is created PAUSED unless activate: true;

  • APPLE_ADS_MAX_DAILY_BUDGET and APPLE_ADS_MAX_BID are checked locally before any request goes out.

Configure

Getting the key

Apple Ads credentials are not App Store Connect credentials, and not Apple Business Manager credentials. All three consoles issue similar-looking EC keys and none of them interoperate.

mkdir -p ~/.config/apple-ads && chmod 700 ~/.config/apple-ads
cd ~/.config/apple-ads
openssl ecparam -genkey -name prime256v1 -noout -out apple-ads-private.pem
openssl ec -in apple-ads-private.pem -pubout -out apple-ads-public.pem
chmod 600 apple-ads-private.pem

Then, at ads.apple.com:

  1. Account Settings → User Management — invite a user with an API role (API Account Manager for writes, API Account Read Only for reads). Inviting yourself is fine.

  2. Sign in as that API user.

  3. Account Settings → API — paste the contents of apple-ads-public.pem. Apple then prints clientId, teamId and keyId above the field.

The trap that costs everyone an afternoon: an Account Admin has no Public Key field. That role sees a "Manage API Access" table instead, and the API page looks like it has nothing to create. You must invite an API-role user and sign in as that user before the field appears. Apple's own help page describes step 1 and step 3 as though they happen to the same person, which is why this is easy to miss.

A related wrong turn: Apple Business Manager (business.apple.com) has an "Add API Account" dialog that looks almost identical and hands you a downloadable-once private key. It is a different service. Its clientId starts BUSINESSAPI.; an Apple Ads one starts SEARCHADS..

The API user's access level is the real permission boundary — APPLE_ADS_ALLOW_WRITES is a second gate on top, not a substitute for picking the right role.

Environment

Variable

Required

Notes

APPLE_ADS_CLIENT_ID

yes

SEARCHADS.<uuid>

APPLE_ADS_TEAM_ID

yes

SEARCHADS.<uuid>, usually identical to the client id — expected, not a paste error

APPLE_ADS_KEY_ID

yes

UUID of the registered public key

APPLE_ADS_PRIVATE_KEY

one of

EC P-256 private key, inline PEM

APPLE_ADS_PRIVATE_KEY_PATH

one of

path to the .pem

APPLE_ADS_AD_ACCOUNT_ID

no

default ad account; required when the login reaches several

APPLE_ADS_ALLOW_WRITES

no

registers the mutating tools

APPLE_ADS_MAX_DAILY_BUDGET

no

ceiling in major units on any daily budget a tool may set

APPLE_ADS_MAX_BID

no

ceiling in major units on any bid

APPLE_ADS_CONFIG

no

config file path (default ~/.config/apple-ads/config.json)

APPLE_ADS_MAX_RETRIES

no

default 3

APPLE_ADS_DEBUG

no

stderr debug logging

Environment wins over the config file per field, so a one-off APPLE_ADS_ALLOW_WRITES=0 overrides a file that says true.

Quick start

// .mcp.json
{
  "mcpServers": {
    "apple-ads": {
      "command": "npx",
      "args": ["-y", "@mgcrea/mcp-apple-ads"],
      "env": {
        "APPLE_ADS_CLIENT_ID": "SEARCHADS.…",
        "APPLE_ADS_TEAM_ID": "SEARCHADS.…",
        "APPLE_ADS_KEY_ID": "…",
        "APPLE_ADS_PRIVATE_KEY_PATH": "/Users/you/.config/apple-ads/apple-ads-private.pem",
      },
    },
  },
}

Inspect the tools

npx @modelcontextprotocol/inspector npx -y @mgcrea/mcp-apple-ads

Tools

Tool

What it does

Writes?

apple_ads_auth_status

Credentials, reachable ad accounts, ceilings, setup steps

apple_ads_list_accounts

Ad accounts this key can reach — the bootstrap call

apple_ads_search_apps

Find an app's adamId

apple_ads_get_app

One app's advertising details

apple_ads_list_campaigns / apple_ads_get_campaign

Campaign configuration

apple_ads_list_ad_groups / apple_ads_get_ad_group

Ad group configuration

apple_ads_list_keywords

Targeting keywords with bids

apple_ads_list_negative_keywords

Excluded terms

apple_ads_suggest_keywords

Apple's keyword suggestions

apple_ads_list_ads / apple_ads_list_creatives / apple_ads_list_product_pages

Creative surface

apple_ads_list_budgets

Shared budgets

apple_ads_get_report

The performance numbers, at five levels

apple_ads_get_impression_share

Headroom per search term

apple_ads_get_search_term_popularity

Search demand per term

apple_ads_request

Any endpoint no tool wraps

gated

apple_ads_create_campaign / update / delete

Campaign lifecycle

✓

apple_ads_create_ad_group / update / delete

Ad group lifecycle

✓

apple_ads_create_keywords / update / delete

Keyword management, bulk

✓

apple_ads_create_negative_keywords / delete

Exclusions

✓

apple_ads_create_ad / delete

Ads

✓

apple_ads_set_entity_status

Pause or resume any of the four

✓

apple_ads_update_budget

Change a shared budget

✓

Listing cost, measured: 26 KB / ~6.5k tokens with writes off, 49 KB / ~12k tokens with writes on.

Deliberately not wrapped

Reachable through apple_ads_request: Maps advertising (/business-brands/* and its five report variants), asset upload and creative authoring, recommendations apply/dismiss, ad-account creation, location targeting, change history.

Worked example

Which keywords lost me money last month, and pause them.

  1. apple_ads_list_accounts → the ad account id

  2. apple_ads_get_report at level: "keyword", last 30 days, no granularity → totals per keyword

  3. read localSpend against totalInstalls

  4. apple_ads_set_entity_status with entity: "keyword", status: "PAUSED" on each loser

Pausing, not deleting: a paused keyword stops spending and keeps its history.

Spending safety

Layer

What it does

Write gate

Without APPLE_ADS_ALLOW_WRITES=1 the 15 write tools are not in tools/list at all

confirm: true

Required on every write, creates included — a campaign created ENABLED spends immediately

Created PAUSED

activate: false is the default on every create

Ceilings

APPLE_ADS_MAX_DAILY_BUDGET / APPLE_ADS_MAX_BID refuse locally, before any request is sent

The ceilings do not apply to apple_ads_request, which bypasses them by construction. Its description says so.

Apple Ads vs App Store Connect

Two APIs, two consoles, two keys — which is why they are two servers rather than one. They share exactly one thing: adamId is the same integer as an app's id in App Store Connect, and as the digits in its App Store URL.

If you run @mgcrea/mcp-appstore-connect too, app_store_connect_list_apps is the cheaper way to get an adamId than apple_ads_search_apps.

Traps worth knowing

  • A 403 is usually the ad-account context, not your credentials. If the token minted but calls fail, the X-Ap-Context header is missing or names an account this login has no role on. apple_ads_auth_status lists what is reachable.

  • /acls and /me are the only calls that take no ad-account context — necessarily, since they are how you discover which accounts exist.

  • Money amounts are strings upstream. Money.amount is "50.00", not 50. This server converts both ways; if you use apple_ads_request you are on your own.

  • Keyword deletion is permanent and takes the reporting history with it. Pausing is nearly always what was meant.

  • A multi-account login must name an account. Tools refuse rather than guess, because guessing spends money in someone else's account.

  • Report granularity has windows. HOURLY needs a start within 7 days (and is unavailable for search-term and ad reports), DAILY within 90, WEEKLY within 365 with an end 14+ days old, MONTHLY with an end 90+ days old. These are checked locally, so you get the rule rather than INVALID_PARAMETER.

  • Search-term reports reject timeZone: "UTC". Use ORTZ, which is the default here.

Troubleshooting

Symptom

Cause

Tool missing from the list

Writes disabled, or no credentials — run apple_ads_auth_status

HTTP 401 on every call

Client/team/key id mismatch, or the private key is not the pair of the registered public key

HTTP 403 with good credentials

Ad-account context — see Traps

"This login can reach N ad accounts"

Pass adAccountId, or set APPLE_ADS_AD_ACCOUNT_ID

Budget refused with no request sent

A ceiling is set — this is working as intended

Develop

pnpm install
pnpm build
pnpm test
pnpm typecheck && pnpm lint

Tests are fully offline: fetch is mocked and the MCP client is in-memory. No credentials are needed or read.

License

MIT

Available Tools

19 tools
apple_ads_auth_statusApple Ads: Auth StatusA
Read-only

Report whether the server has working credentials, which ad accounts they reach, whether writes and spending ceilings are set, and — when it is not configured — exactly what to set to fix that. Call this first when a tool you expected is missing, or when a call fails with 403.

Inputs: no named parameters.

apple_ads_get_ad_groupApple Ads: Get Ad GroupA
Read-only

Get one ad group in full, including its bid, pricing model and targeting.

Inputs: adGroupId (required), savePath, adAccountId.

apple_ads_get_appApple Ads: Get AppA
Read-only

Get an app's advertising details by adamId — what Apple knows about it as an ad destination, including the locales its product page supports.

Inputs: adamId (required), savePath, adAccountId.

apple_ads_get_campaignApple Ads: Get CampaignA
Read-only

Get one campaign in full, including its targeting, budget and bid strategy. Use this before updating one, so the change is made against what is actually configured.

Inputs: campaignId (required), savePath, adAccountId.

apple_ads_get_impression_shareApple Ads: Get Impression ShareA
Read-only

How often your app appeared for a search term relative to how often it could have — the measure of headroom. Use it to tell 'this keyword is exhausted' from 'this keyword is underfunded'. Separate from apple_ads_get_report because it needs an app, not a campaign.

Inputs: adamIds (required), endDate (required), startDate (required), limit, +4 more.

apple_ads_get_reportApple Ads: Get ReportA
Read-only

The performance numbers: impressions, taps, installs, spend, and the rates between them, for any level of the account. This is where spend and conversion live — the list tools return configuration only. Returns TOTALS per row by default, which answers most questions; per-period rows are opt-in and much larger. For a wide report, pass savePath and read the file rather than raising maxRows.

Inputs: level (required), endDate (required), startDate (required), limit, +10 more.

apple_ads_get_search_term_popularityApple Ads: Get Search Term PopularityA
Read-only

The most-searched terms on the App Store, ranked, per country and genre — Apple's own demand data (terms with 500+ searches). The discovery side of keyword research: run it for your app's genre and market to see what people actually type, then pair with apple_ads_suggest_keywords. MONTHLY refreshes on the 5th; WEEKLY_SUN_SAT on Mondays.

Inputs: endDate (required), startDate (required), genre, limit, +6 more.

apple_ads_list_accountsApple Ads: List Ad AccountsA
Read-only

List the ad accounts this API key can reach, with the roles it holds on each. This is the bootstrap call: every other tool needs an ad account, and the id here is what to pass as adAccountId or set as APPLE_ADS_AD_ACCOUNT_ID. Also returns the API identity itself. Costs two requests.

Inputs: savePath.

apple_ads_list_ad_groupsApple Ads: List Ad GroupsA
Read-only

List ad groups, optionally filtered to one campaign. An ad group holds the keywords, the default bid and the audience refinement; the budget lives on its campaign. Spend and performance come from apple_ads_get_report, not from here.

Inputs: limit, offset, savePath, campaignId, +1 more.

apple_ads_list_adsApple Ads: List AdsA
Read-only

List the ads in an ad account, optionally filtered to one ad group. An ad pairs a creative with an ad group; what it cost and returned comes from apple_ads_get_report at level "ad".

Inputs: limit, offset, savePath, adGroupId, +1 more.

apple_ads_list_budgetsApple Ads: List Shared BudgetsA
Read-only

List shared budgets — spending pools several campaigns draw from, as opposed to the per-campaign daily budget set by apple_ads_create_campaign. Note Apple deprecated lifetime budgets in June 2026; shared budgets are what replaced them.

Inputs: limit, offset, savePath, adAccountId.

apple_ads_list_campaignsApple Ads: List CampaignsA
Read-only

List campaigns in an ad account, with their status, daily budget and the app each promotes. Returns the identifying fields, not the full targeting projection — use apple_ads_get_campaign for one campaign in full. Spend and performance are NOT here: those come from apple_ads_get_report.

Inputs: limit, offset, savePath, adAccountId.

apple_ads_list_creativesApple Ads: List CreativesA
Read-only

List the creatives available to use in ads. A creative is what the ad actually shows — usually a custom product page or the default App Store listing. Authoring creatives and uploading assets is not wrapped here; reach those through apple_ads_request.

Inputs: limit, offset, savePath, adAccountId.

apple_ads_list_keywordsApple Ads: List KeywordsA
Read-only

List targeting keywords, optionally filtered to one ad group, with their match type, bid and status. What each keyword actually cost and returned comes from apple_ads_get_report at level "keyword", not from here.

Inputs: limit, offset, savePath, adGroupId, +1 more.

apple_ads_list_negative_keywordsApple Ads: List Negative KeywordsA
Read-only

List negative keywords — terms that must NOT trigger the ad. These are how wasted spend on irrelevant searches is stopped without narrowing the positive targeting.

Inputs: limit, offset, savePath, adGroupId, +1 more.

apple_ads_list_product_pagesApple Ads: List Product PagesA
Read-only

List the custom product pages available as ad destinations. These are the SAME pages configured in App Store Connect — app_store_connect_list_versions and the screenshot tools manage their content; this only lists them for targeting.

Inputs: limit, adamId, offset, savePath, +1 more.

apple_ads_requestApple Ads: Raw API RequestA
Read-only

Call any Apple Ads Platform API endpoint that no dedicated tool wraps — Maps advertising (/business-brands/*), asset upload, recommendations, location targeting, change history. The ad-account context header is injected for you. You get no argument validation, no response shaping and no safety checks: prefer a dedicated tool where one exists. Writes are DISABLED: only GET is permitted. Set APPLE_ADS_ALLOW_WRITES=1 to allow mutations.

Inputs: path (required), body, method, savePath, +1 more.

apple_ads_search_appsApple Ads: Search AppsA
Read-only

Search the App Store for an app and get its adamId, which every campaign needs. The adamId is the SAME number as the app's id in App Store Connect — if the app_store_connect MCP server is loaded, app_store_connect_list_apps gives it directly and is both cheaper and unambiguous. Use this when it is not, or to find a competitor's app.

Inputs: query (required), limit, savePath, adAccountId.

apple_ads_suggest_keywordsApple Ads: Suggest KeywordsA
Read-only

Ask Apple for keyword suggestions for an app or a seed term. The research tool: use it before creating keywords, and pair it with apple_ads_get_report at level "search_term" to see which real searches already reach you.

Inputs: adamId (required), limit, savePath, adAccountId.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.9/5.0

    Scored across 19 tools

    Disambiguation4/5

    The list_ vs get_ pairs for campaigns and ad groups are cleanly separated, and the descriptions explicitly fence off overlapping concepts (configuration in list tools vs performance in get_report, suggest_keywords vs get_search_term_popularity, list_creatives vs list_product_pages vs list_ads). The only residual friction is between apple_ads_get_app and apple_ads_search_apps, and between the many 'list' lookups layered under the same account, but each description preempts the confusion with explicit 'use X instead' guidance.

    Naming Consistency4/5

    A consistent apple_ads_ namespace with a predictable verb_noun pattern (list_campaigns, get_campaign, get_report, suggest_keywords) holds across nearly the whole set. Minor deviations are apple_ads_auth_status (noun phrase, no verb) and apple_ads_request (bare verb escape hatch), plus get_search_term_popularity's longer compound noun.

    Tool Count4/5

    Nineteen tools is on the heavier side but each maps to a distinct Apple Ads resource (accounts, campaigns, ad groups, keywords, negatives, ads, creatives, product pages, budgets, reports, impression share, research, auth). Nothing looks like filler, though the count sits just inside the 'feels heavy' band.

    Completeness3/5

    The read surface is genuinely thorough end-to-end, but the set is essentially read-only: there are no create/update/delete operations for campaigns, ad groups, keywords, or negatives, and mutations are only reachable through the unvalidated apple_ads_request with writes disabled by default. Worse, apple_ads_list_budgets references 'apple_ads_create_campaign', a tool that is not in this set, which is a dangling lifecycle gap an agent will hit immediately.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      C
      maintenance
      An MCP server for the Apple Ads Platform API that exposes 99 operations across all resources through four token-efficient tools. It enables AI agents to read and write campaigns, ad groups, keywords, creatives, budgets, reports, and more.
      78 npm
      2
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables searching an offline catalog of Apple Ads Platform API documentation and issuing authenticated read and write calls to manage App Store and Apple Maps campaigns, reports, keywords, budgets, and recommendations. Read and write are split into separate tools, with read-only mode, pagination merging, and large-report file output.
      78 npm
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables autonomous AI agents to safely inspect, optimize, and manage live search campaigns through 38 purpose-built tools covering bids, budgets, keywords, RSAs, assets, and negative keyword harvesting. Bundles two-layer Pydantic execution shields with bounded bid ceilings and fixed stepping, a preflight gatekeeper, and W3C distributed tracing that separates upstream Google Ads API latency from local execution overhead.
      MIT
    • A
      license
      B
      quality
      B
      maintenance
      It provides 31 read tools and 22 opt-in, preview-first write tools for inspecting campaigns, reporting, and targeting, and for preparing confirmed changes within explicitly allowlisted ad accounts.
      31
      316 npm
      Apache 2.0