Skip to main content
Glama
mgcrea

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 Server

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

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server for Apple Search Ads API v5 that enables natural language management of campaigns, keywords, budgets, creatives, and performance reporting. Supports 54 tools for campaign management, keyword optimization, search term analysis, and more.
    82 npm
    7
    MIT
  • 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.
    82 npm
    2
    MIT
  • A
    license
    C
    quality
    A
    maintenance
    Provides MCP clients typed tools for Apple Ads Platform research, reporting, auditing, and receipt-gated campaign management with a local-first, read-only-by-default safety model.
    110
    7
    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.
    82 npm
    MIT