@nerdsnipe-inc/apple-ads-mcp-server 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +20 -0
- package/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +534 -2
- package/bin/apple-ads-mcp-server.js +2 -0
- package/dist/client.d.ts +100 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +352 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +134 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +24 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +96 -0
- package/dist/errors.js.map +1 -0
- package/dist/generated/schemas.d.ts +8434 -0
- package/dist/generated/schemas.d.ts.map +1 -0
- package/dist/generated/schemas.js +477 -0
- package/dist/generated/schemas.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/redact.d.ts +6 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +32 -0
- package/dist/redact.js.map +1 -0
- package/dist/registry.d.ts +12 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +59 -0
- package/dist/registry.js.map +1 -0
- package/dist/server.d.ts +23 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +54 -0
- package/dist/server.js.map +1 -0
- package/dist/tooling.d.ts +55 -0
- package/dist/tooling.d.ts.map +1 -0
- package/dist/tooling.js +146 -0
- package/dist/tooling.js.map +1 -0
- package/dist/tools/accounts.d.ts +2 -0
- package/dist/tools/accounts.d.ts.map +1 -0
- package/dist/tools/accounts.js +107 -0
- package/dist/tools/accounts.js.map +1 -0
- package/dist/tools/ad_groups.d.ts +2 -0
- package/dist/tools/ad_groups.d.ts.map +1 -0
- package/dist/tools/ad_groups.js +87 -0
- package/dist/tools/ad_groups.js.map +1 -0
- package/dist/tools/ads.d.ts +2 -0
- package/dist/tools/ads.d.ts.map +1 -0
- package/dist/tools/ads.js +87 -0
- package/dist/tools/ads.js.map +1 -0
- package/dist/tools/apps.d.ts +2 -0
- package/dist/tools/apps.d.ts.map +1 -0
- package/dist/tools/apps.js +61 -0
- package/dist/tools/apps.js.map +1 -0
- package/dist/tools/assets.d.ts +2 -0
- package/dist/tools/assets.d.ts.map +1 -0
- package/dist/tools/assets.js +76 -0
- package/dist/tools/assets.js.map +1 -0
- package/dist/tools/brands.d.ts +2 -0
- package/dist/tools/brands.d.ts.map +1 -0
- package/dist/tools/brands.js +122 -0
- package/dist/tools/brands.js.map +1 -0
- package/dist/tools/budget_orders.d.ts +2 -0
- package/dist/tools/budget_orders.d.ts.map +1 -0
- package/dist/tools/budget_orders.js +87 -0
- package/dist/tools/budget_orders.js.map +1 -0
- package/dist/tools/bulk.d.ts +2 -0
- package/dist/tools/bulk.d.ts.map +1 -0
- package/dist/tools/bulk.js +65 -0
- package/dist/tools/bulk.js.map +1 -0
- package/dist/tools/campaigns.d.ts +2 -0
- package/dist/tools/campaigns.d.ts.map +1 -0
- package/dist/tools/campaigns.js +102 -0
- package/dist/tools/campaigns.js.map +1 -0
- package/dist/tools/change_history.d.ts +2 -0
- package/dist/tools/change_history.d.ts.map +1 -0
- package/dist/tools/change_history.js +44 -0
- package/dist/tools/change_history.js.map +1 -0
- package/dist/tools/creatives.d.ts +2 -0
- package/dist/tools/creatives.d.ts.map +1 -0
- package/dist/tools/creatives.js +87 -0
- package/dist/tools/creatives.js.map +1 -0
- package/dist/tools/eligibility.d.ts +2 -0
- package/dist/tools/eligibility.d.ts.map +1 -0
- package/dist/tools/eligibility.js +58 -0
- package/dist/tools/eligibility.js.map +1 -0
- package/dist/tools/geo.d.ts +2 -0
- package/dist/tools/geo.d.ts.map +1 -0
- package/dist/tools/geo.js +47 -0
- package/dist/tools/geo.js.map +1 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +48 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/insights.d.ts +2 -0
- package/dist/tools/insights.d.ts.map +1 -0
- package/dist/tools/insights.js +46 -0
- package/dist/tools/insights.js.map +1 -0
- package/dist/tools/keywords.d.ts +2 -0
- package/dist/tools/keywords.d.ts.map +1 -0
- package/dist/tools/keywords.js +87 -0
- package/dist/tools/keywords.js.map +1 -0
- package/dist/tools/location_groups.d.ts +2 -0
- package/dist/tools/location_groups.d.ts.map +1 -0
- package/dist/tools/location_groups.js +87 -0
- package/dist/tools/location_groups.js.map +1 -0
- package/dist/tools/meta.d.ts +7 -0
- package/dist/tools/meta.d.ts.map +1 -0
- package/dist/tools/meta.js +158 -0
- package/dist/tools/meta.js.map +1 -0
- package/dist/tools/negative_keywords.d.ts +2 -0
- package/dist/tools/negative_keywords.d.ts.map +1 -0
- package/dist/tools/negative_keywords.js +87 -0
- package/dist/tools/negative_keywords.js.map +1 -0
- package/dist/tools/product_pages.d.ts +2 -0
- package/dist/tools/product_pages.d.ts.map +1 -0
- package/dist/tools/product_pages.js +76 -0
- package/dist/tools/product_pages.js.map +1 -0
- package/dist/tools/recommendations.d.ts +2 -0
- package/dist/tools/recommendations.d.ts.map +1 -0
- package/dist/tools/recommendations.js +103 -0
- package/dist/tools/recommendations.js.map +1 -0
- package/dist/tools/reports_apps.d.ts +2 -0
- package/dist/tools/reports_apps.d.ts.map +1 -0
- package/dist/tools/reports_apps.js +114 -0
- package/dist/tools/reports_apps.js.map +1 -0
- package/dist/tools/reports_brands.d.ts +2 -0
- package/dist/tools/reports_brands.d.ts.map +1 -0
- package/dist/tools/reports_brands.js +114 -0
- package/dist/tools/reports_brands.js.map +1 -0
- package/dist/tools/suggestions.d.ts +2 -0
- package/dist/tools/suggestions.d.ts.map +1 -0
- package/dist/tools/suggestions.js +77 -0
- package/dist/tools/suggestions.js.map +1 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +26 -0
- package/dist/version.js.map +1 -0
- package/docs/COVERAGE.md +144 -0
- package/docs/DECISIONS.md +16 -0
- package/docs/coverage.json +1028 -0
- package/package.json +76 -4
package/.env.example
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Apple Ads Platform API credentials (placeholders only - never commit real values)
|
|
2
|
+
# Create them in Apple Ads > Account Settings > API (see README).
|
|
3
|
+
APPLE_ADS_CLIENT_ID=SEARCHADS.00000000-0000-0000-0000-000000000000
|
|
4
|
+
APPLE_ADS_TEAM_ID=SEARCHADS.00000000-0000-0000-0000-000000000000
|
|
5
|
+
APPLE_ADS_KEY_ID=00000000-0000-0000-0000-000000000000
|
|
6
|
+
# Path to the EC (prime256v1) private key; "~" is expanded.
|
|
7
|
+
APPLE_ADS_PRIVATE_KEY_PATH=~/.appleads/private_keys/private-key.pem
|
|
8
|
+
# Alternative to the path: the PEM contents inline (use \n for newlines).
|
|
9
|
+
# APPLE_ADS_PRIVATE_KEY=
|
|
10
|
+
|
|
11
|
+
# Optional defaults
|
|
12
|
+
# Default ad account ID sent as X-AP-Context: adAccountId=<id>
|
|
13
|
+
# APPLE_ADS_AD_ACCOUNT_ID=
|
|
14
|
+
# Default org ID for org-level calls (also used to resolve the ad account)
|
|
15
|
+
# APPLE_ADS_ORG_ID=
|
|
16
|
+
|
|
17
|
+
# Safety switches
|
|
18
|
+
# APPLE_ADS_READ_ONLY=true
|
|
19
|
+
# APPLE_ADS_ENABLED_GROUPS=accounts,campaigns,reports_apps
|
|
20
|
+
# APPLE_ADS_DISABLED_GROUPS=budget_orders
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/) and the project uses [SemVer](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [1.0.0] - 2026-10-10
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- Initial release: 99 tools, one per operation of `@apple/apple-ads-platform` 1.109.0 (Apple Ads Platform API v1), covering 22 groups.
|
|
10
|
+
- Meta tools: `apple_ads_request` (escape hatch), `apple_ads_list_operations`, `apple_ads_server_status`.
|
|
11
|
+
- Key-based OAuth (ES256 client secret generated by the SDK), token caching, refresh-and-retry on 401, Retry-After aware backoff on 429/5xx.
|
|
12
|
+
- Per-call `adAccountId` context override plus `APPLE_ADS_AD_ACCOUNT_ID` / `APPLE_ADS_ORG_ID` defaults.
|
|
13
|
+
- Safety switches: `APPLE_ADS_READ_ONLY`, `APPLE_ADS_ENABLED_GROUPS`, `APPLE_ADS_DISABLED_GROUPS`.
|
|
14
|
+
- Generator (`scripts/generate.mjs`) with a drift gate, `docs/COVERAGE.md` manifest and generated coverage test.
|
|
15
|
+
- `scripts/install-global.sh` for Claude Code and Claude Desktop.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nerdsnipe Inc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,535 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Apple Ads MCP Server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@nerdsnipe-inc/apple-ads-mcp-server)
|
|
4
|
+
[](https://www.npmjs.com/package/@nerdsnipe-inc/apple-ads-mcp-server)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
[](https://modelcontextprotocol.io)
|
|
8
|
+
|
|
9
|
+
A Model Context Protocol (MCP) server that gives AI assistants (Claude Code, Claude Desktop, Cursor, Windsurf, ...) full access to the **Apple Ads Platform API (v1)**, built on the official [`@apple/apple-ads-platform`](https://www.npmjs.com/package/@apple/apple-ads-platform) SDK.
|
|
10
|
+
|
|
11
|
+
> This targets the **Apple Ads Platform API** (`https://api.ads.apple.com/v1`), not the older Campaign Management API v5.
|
|
12
|
+
|
|
13
|
+
- One tool per SDK operation, 100% coverage (see [docs/COVERAGE.md](docs/COVERAGE.md))
|
|
14
|
+
- Escape hatch tool (`apple_ads_request`) for any endpoint Apple adds before the SDK does
|
|
15
|
+
- Automatic OAuth (ES256 client secret, token caching, refresh on 401), Retry-After aware retries on 429/5xx
|
|
16
|
+
- Safety switches: read-only mode and tool-group allow/deny lists
|
|
17
|
+
- Starts and lists tools even without credentials (calls then return an actionable error)
|
|
18
|
+
|
|
19
|
+
## Quick start (no local install)
|
|
20
|
+
|
|
21
|
+
There is nothing to clone or build. Your AI client launches the server on demand with `npx`, which downloads the published package the first time and caches it. You only need [Node.js 20+](https://nodejs.org) and your Apple Ads API credentials (see [Prerequisites](#prerequisites)).
|
|
22
|
+
|
|
23
|
+
**Claude Code** (all projects):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
claude mcp add apple-ads --scope user \
|
|
27
|
+
-e APPLE_ADS_CLIENT_ID=SEARCHADS.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
|
|
28
|
+
-e APPLE_ADS_TEAM_ID=SEARCHADS.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
|
|
29
|
+
-e APPLE_ADS_KEY_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
|
|
30
|
+
-e APPLE_ADS_PRIVATE_KEY_PATH=~/.appleads/private_keys/private-key.pem \
|
|
31
|
+
-- npx -y @nerdsnipe-inc/apple-ads-mcp-server
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Any other MCP client** uses the same block in its config file:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"mcpServers": {
|
|
39
|
+
"apple-ads": {
|
|
40
|
+
"command": "npx",
|
|
41
|
+
"args": ["-y", "@nerdsnipe-inc/apple-ads-mcp-server"],
|
|
42
|
+
"env": {
|
|
43
|
+
"APPLE_ADS_CLIENT_ID": "SEARCHADS.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
44
|
+
"APPLE_ADS_TEAM_ID": "SEARCHADS.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
45
|
+
"APPLE_ADS_KEY_ID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
46
|
+
"APPLE_ADS_PRIVATE_KEY_PATH": "/Users/you/.appleads/private_keys/private-key.pem"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Per-client details are in [Installation](#installation). To verify the package without credentials, run `npx -y @nerdsnipe-inc/apple-ads-mcp-server --list-tools`.
|
|
54
|
+
|
|
55
|
+
## Coverage
|
|
56
|
+
|
|
57
|
+
<!-- GENERATED:TOOLS:BEGIN -->
|
|
58
|
+
**102 tools** = 99 Apple Ads Platform API operations (100% of `@apple/apple-ads-platform`) + 3 meta tools.
|
|
59
|
+
|
|
60
|
+
| Group | Tools | Covers |
|
|
61
|
+
|---|---:|---|
|
|
62
|
+
| `accounts` | 7 | Org, user identity (me/ACLs), ad accounts and advertiser resources |
|
|
63
|
+
| `apps` | 3 | App Store app search, app details and supported ad languages |
|
|
64
|
+
| `eligibility` | 3 | App eligibility checks and App Store creative rejection reasons |
|
|
65
|
+
| `brands` | 7 | Apple Maps brands, business categories, brand rejection reasons and locations |
|
|
66
|
+
| `location_groups` | 5 | Apple Maps location groups (create, query, get, update, delete) |
|
|
67
|
+
| `campaigns` | 6 | Campaigns (App Store and Apple Maps) |
|
|
68
|
+
| `ad_groups` | 5 | Ad groups (targeting, bid strategy, schedule) |
|
|
69
|
+
| `ads` | 5 | Ads (link a creative to an ad group) |
|
|
70
|
+
| `creatives` | 5 | Ad creatives (product page and Apple Maps creatives) |
|
|
71
|
+
| `assets` | 4 | Apple Maps creative assets (upload, query, get, delete) |
|
|
72
|
+
| `keywords` | 5 | Keywords |
|
|
73
|
+
| `negative_keywords` | 5 | Negative keywords |
|
|
74
|
+
| `bulk` | 4 | Bulk create/update of keywords and negative keywords |
|
|
75
|
+
| `geo` | 2 | Geo targeting lookups |
|
|
76
|
+
| `product_pages` | 4 | App Store product pages and locale details |
|
|
77
|
+
| `budget_orders` | 5 | Budget orders (shared budgets) for monthly-invoiced accounts |
|
|
78
|
+
| `reports_apps` | 5 | App Store performance reports |
|
|
79
|
+
| `reports_brands` | 5 | Apple Maps (business brand) performance reports |
|
|
80
|
+
| `insights` | 2 | Impression share and search term popularity |
|
|
81
|
+
| `recommendations` | 6 | Daily budget and target CPA recommendations (query, apply, dismiss) |
|
|
82
|
+
| `suggestions` | 4 | Keyword, phrase, category and target CPA suggestions |
|
|
83
|
+
| `change_history` | 2 | Audit log of changes (summaries and field-level details) |
|
|
84
|
+
| `meta` | 3 | `apple_ads_request` (raw escape hatch), `apple_ads_list_operations`, `apple_ads_server_status` |
|
|
85
|
+
| **Total** | **102** | |
|
|
86
|
+
|
|
87
|
+
### All tools
|
|
88
|
+
|
|
89
|
+
#### accounts
|
|
90
|
+
|
|
91
|
+
| Tool | Method | Path | Description |
|
|
92
|
+
|---|---|---|---|
|
|
93
|
+
| `apple_ads_get_ad_account` | GET | `/v1/ad-accounts/{id}` | Get full details of an ad account (currency, timezone, payment model, status, product features, delegations). Defaults to the adAccountId in context. |
|
|
94
|
+
| `apple_ads_update_ad_account` | PUT | `/v1/ad-accounts/{id}` | Update an ad account's name or delegations. Array fields use full-replacement semantics, so send the complete desired array. |
|
|
95
|
+
| `apple_ads_create_ad_account` | POST | `/v1/ad-accounts` | Create a new ad account under the caller's org, optionally delegating an App Store content provider or Apple Maps brand to it. Needs no ad account context. |
|
|
96
|
+
| `apple_ads_get_advertiser_resources` | GET | `/v1/advertiser-resources` | List advertiser resources (App Store content providers or Apple Maps business brands) available in the org that can be delegated to an ad account. Needs no ad account context. |
|
|
97
|
+
| `apple_ads_get_me` | GET | `/v1/me` | Get the authenticated API user's userId and orgId. Needs no ad account context, so it is the best first call to verify credentials. |
|
|
98
|
+
| `apple_ads_get_user_acls` | GET | `/v1/acls` | List every ad account this API user can access, with the user's roles on each. Use it to discover adAccountId values (needs no ad account context). |
|
|
99
|
+
| `apple_ads_get_org` | GET | `/v1/orgs/{id}` | Get an organization's name, currency, timezone, payment model and system status. Defaults to the configured/authenticated org when orgId is omitted. |
|
|
100
|
+
|
|
101
|
+
#### apps
|
|
102
|
+
|
|
103
|
+
| Tool | Method | Path | Description |
|
|
104
|
+
|---|---|---|---|
|
|
105
|
+
| `apple_ads_get_app_details` | GET | `/v1/apps/{adamId}` | Get details for one app by its Adam ID (name, genres, device classes, available storefronts). |
|
|
106
|
+
| `apple_ads_query_supported_app_languages` | POST | `/v1/metadata/apps/supported-languages/query` | Query the languages supported for App Store advertising in each country or region. |
|
|
107
|
+
| `apple_ads_search_apps` | GET | `/v1/search/apps` | Search the App Store for apps by name/developer, by content provider IDs, or list the apps owned by your org. Use it to find the adamId used as a campaign's promotedObjectId. |
|
|
108
|
+
|
|
109
|
+
#### eligibility
|
|
110
|
+
|
|
111
|
+
| Tool | Method | Path | Description |
|
|
112
|
+
|---|---|---|---|
|
|
113
|
+
| `apple_ads_check_app_eligibility` | POST | `/v1/eligibilities/apps/query` | Check whether an app is eligible to advertise, per placement and country or region. |
|
|
114
|
+
| `apple_ads_query_app_rejection_reasons` | POST | `/v1/rejection-reasons/apps/query` | Query human-readable rejection reasons for App Store ad creatives of an app. |
|
|
115
|
+
| `apple_ads_get_app_rejection_reason` | GET | `/v1/rejection-reasons/apps/{rejectionReasonId}` | Get one App Store creative rejection reason by its ID. |
|
|
116
|
+
|
|
117
|
+
#### brands
|
|
118
|
+
|
|
119
|
+
| Tool | Method | Path | Description |
|
|
120
|
+
|---|---|---|---|
|
|
121
|
+
| `apple_ads_get_brand` | GET | `/v1/business-brands/{id}` | Get one Apple Maps business brand by ID. |
|
|
122
|
+
| `apple_ads_get_business_category` | GET | `/v1/business-categories/{id}` | Get one Apple Maps business category by ID. |
|
|
123
|
+
| `apple_ads_get_location` | GET | `/v1/locations/{id}` | Get one Apple Maps business location by ID. |
|
|
124
|
+
| `apple_ads_query_brands` | POST | `/v1/business-brands/query` | Query Apple Maps business brands. Filter by eligibility.status ELIGIBLE to find brands usable in campaigns. |
|
|
125
|
+
| `apple_ads_query_business_categories` | POST | `/v1/business-categories/query` | Query Apple Maps business categories. |
|
|
126
|
+
| `apple_ads_query_locations` | POST | `/v1/locations/query` | Query the business locations of your Apple Maps brands (read-only; filter by brandId and status OPEN to build location groups). |
|
|
127
|
+
| `apple_ads_query_brand_rejection_reasons` | POST | `/v1/rejection-reasons/business-brands/query` | Query policy assignments (rejection reasons) for Apple Maps brands and creatives, filtered by the brand's promotedObjectId. |
|
|
128
|
+
|
|
129
|
+
#### location_groups
|
|
130
|
+
|
|
131
|
+
| Tool | Method | Path | Description |
|
|
132
|
+
|---|---|---|---|
|
|
133
|
+
| `apple_ads_create_location_group` | POST | `/v1/location-groups` | Create an Apple Maps location group (STATIC list of location IDs or DYNAMIC rule-based) for ad group targeting. |
|
|
134
|
+
| `apple_ads_delete_location_group` | DELETE | `/v1/location-groups/{id}` | Permanently delete a location group. Ad groups targeting it lose that targeting constraint immediately. |
|
|
135
|
+
| `apple_ads_get_location_group` | GET | `/v1/location-groups/{id}` | Get one location group by ID. |
|
|
136
|
+
| `apple_ads_query_location_groups` | POST | `/v1/location-groups/query` | Query location groups with filters, sorting and pagination. |
|
|
137
|
+
| `apple_ads_update_location_group` | PUT | `/v1/location-groups/{id}` | Update a location group (name, description, location IDs or rules). INVALID/PENDING groups cannot be updated. |
|
|
138
|
+
|
|
139
|
+
#### campaigns
|
|
140
|
+
|
|
141
|
+
| Tool | Method | Path | Description |
|
|
142
|
+
|---|---|---|---|
|
|
143
|
+
| `apple_ads_delete_campaign` | DELETE | `/v1/campaigns/{id}` | Soft-delete a campaign by ID. |
|
|
144
|
+
| `apple_ads_get_campaign` | GET | `/v1/campaigns/{id}` | Get one campaign by ID, including status, systemStatus, bid strategy and budget. |
|
|
145
|
+
| `apple_ads_get_campaign_legacy_limited_status_details` | GET | `/v1/campaigns/{id}/legacy-app-limited-status-reason-details` | Get per-country limited-status reason details for a legacy app campaign. |
|
|
146
|
+
| `apple_ads_update_campaign` | PUT | `/v1/campaigns/{id}` | Partially update a campaign (name, status ENABLED/PAUSED, budget, bid strategy, targeting). Only send fields to change; arrays are fully replaced. |
|
|
147
|
+
| `apple_ads_create_campaign` | POST | `/v1/campaigns` | Create a campaign for an App Store app (APPSTORE_APP) or Apple Maps brand (BUSINESS_BRAND). promotedObjectType and promotedObjectId are immutable after creation. |
|
|
148
|
+
| `apple_ads_query_campaigns` | POST | `/v1/campaigns/query` | Query campaigns with filters, sorting and pagination. With no filters, returns all non-deleted campaigns in the ad account. |
|
|
149
|
+
|
|
150
|
+
#### ad_groups
|
|
151
|
+
|
|
152
|
+
| Tool | Method | Path | Description |
|
|
153
|
+
|---|---|---|---|
|
|
154
|
+
| `apple_ads_delete_ad_group` | DELETE | `/v1/adgroups/{id}` | Soft-delete an ad group by ID. |
|
|
155
|
+
| `apple_ads_get_ad_group` | GET | `/v1/adgroups/{id}` | Get one ad group by ID. |
|
|
156
|
+
| `apple_ads_update_ad_group` | PUT | `/v1/adgroups/{id}` | Partially update an ad group (status, bids, targeting, schedule). Arrays are fully replaced. |
|
|
157
|
+
| `apple_ads_create_ad_group` | POST | `/v1/adgroups` | Create an ad group inside a campaign (targeting, pricing model, bid strategy, schedule, Search Match). |
|
|
158
|
+
| `apple_ads_query_ad_groups` | POST | `/v1/adgroups/query` | Query ad groups with filters (e.g. campaignId), sorting and pagination. |
|
|
159
|
+
|
|
160
|
+
#### ads
|
|
161
|
+
|
|
162
|
+
| Tool | Method | Path | Description |
|
|
163
|
+
|---|---|---|---|
|
|
164
|
+
| `apple_ads_delete_ad` | DELETE | `/v1/ads/{id}` | Soft-delete an ad by ID. |
|
|
165
|
+
| `apple_ads_get_ad` | GET | `/v1/ads/{id}` | Get one ad by ID. |
|
|
166
|
+
| `apple_ads_update_ad` | PUT | `/v1/ads/{id}` | Update an ad. Only name and status (ENABLED/PAUSED) can change after creation. |
|
|
167
|
+
| `apple_ads_create_ad` | POST | `/v1/ads` | Create an ad linking an existing creative to an ad group. Create the creative first. |
|
|
168
|
+
| `apple_ads_query_ads` | POST | `/v1/ads/query` | Query ads with filters, sorting and pagination. |
|
|
169
|
+
|
|
170
|
+
#### creatives
|
|
171
|
+
|
|
172
|
+
| Tool | Method | Path | Description |
|
|
173
|
+
|---|---|---|---|
|
|
174
|
+
| `apple_ads_delete_creative` | DELETE | `/v1/creatives/{id}` | Soft-delete an ad creative by ID. |
|
|
175
|
+
| `apple_ads_get_creative` | GET | `/v1/creatives/{id}` | Get one ad creative by ID. |
|
|
176
|
+
| `apple_ads_update_creative` | PUT | `/v1/creatives/{id}` | Update an ad creative. |
|
|
177
|
+
| `apple_ads_create_creative` | POST | `/v1/creatives` | Create an ad creative (DEFAULT_PRODUCT_PAGE, CUSTOM_PRODUCT_PAGE or LOCAL_ADS_SEARCH_CREATIVE). creativeType cannot change later. |
|
|
178
|
+
| `apple_ads_query_creatives` | POST | `/v1/creatives/query` | Query ad creatives with filters (creativeType, systemStatus, name, eligibility.status), sorting and pagination. |
|
|
179
|
+
|
|
180
|
+
#### assets
|
|
181
|
+
|
|
182
|
+
| Tool | Method | Path | Description |
|
|
183
|
+
|---|---|---|---|
|
|
184
|
+
| `apple_ads_delete_asset` | DELETE | `/v1/assets/{id}` | Soft-delete an asset you uploaded. |
|
|
185
|
+
| `apple_ads_get_asset` | GET | `/v1/assets/{id}` | Get one asset by ID, including eligibility.status. Poll it after upload until ELIGIBLE. |
|
|
186
|
+
| `apple_ads_query_assets` | POST | `/v1/assets/query` | Query creative assets (App Store apps and Apple Maps brands) with filters, sorting and pagination. |
|
|
187
|
+
| `apple_ads_upload_asset` | POST | `/v1/assets/upload` | Upload an image asset (PNG, JPG or HEIC) for an Apple Maps brand. Provide a local filePath or base64 content. |
|
|
188
|
+
|
|
189
|
+
#### keywords
|
|
190
|
+
|
|
191
|
+
| Tool | Method | Path | Description |
|
|
192
|
+
|---|---|---|---|
|
|
193
|
+
| `apple_ads_delete_keyword` | DELETE | `/v1/keywords/{id}` | Delete a keyword by ID. |
|
|
194
|
+
| `apple_ads_get_keyword` | GET | `/v1/keywords/{id}` | Get one keyword by ID. |
|
|
195
|
+
| `apple_ads_update_keyword` | PUT | `/v1/keywords/{id}` | Update a keyword (bid, status). |
|
|
196
|
+
| `apple_ads_create_keyword` | POST | `/v1/keywords` | Create a keyword in an ad group. |
|
|
197
|
+
| `apple_ads_query_keywords` | POST | `/v1/keywords/query` | Query keywords with filters (e.g. adGroupId), sorting and pagination. |
|
|
198
|
+
|
|
199
|
+
#### negative_keywords
|
|
200
|
+
|
|
201
|
+
| Tool | Method | Path | Description |
|
|
202
|
+
|---|---|---|---|
|
|
203
|
+
| `apple_ads_delete_negative_keyword` | DELETE | `/v1/negative-keywords/{id}` | Delete a negative keyword by ID. |
|
|
204
|
+
| `apple_ads_get_negative_keyword` | GET | `/v1/negative-keywords/{id}` | Get one negative keyword by ID. |
|
|
205
|
+
| `apple_ads_update_negative_keyword` | PUT | `/v1/negative-keywords/{id}` | Update a negative keyword. |
|
|
206
|
+
| `apple_ads_create_negative_keyword` | POST | `/v1/negative-keywords` | Create a negative keyword at campaign or ad group level. |
|
|
207
|
+
| `apple_ads_query_negative_keywords` | POST | `/v1/negative-keywords/query` | Query negative keywords with filters, sorting and pagination. |
|
|
208
|
+
|
|
209
|
+
#### bulk
|
|
210
|
+
|
|
211
|
+
| Tool | Method | Path | Description |
|
|
212
|
+
|---|---|---|---|
|
|
213
|
+
| `apple_ads_bulk_create_keywords` | POST | `/v1/keywords/bulk-create` | Create many keywords in one request (counts as one call against the rate limit). Set allowPartialSuccess to keep valid items when others fail. |
|
|
214
|
+
| `apple_ads_bulk_update_keywords` | POST | `/v1/keywords/bulk-update` | Update many keywords in one request. Each item's data must include the keyword id plus only the fields to change. |
|
|
215
|
+
| `apple_ads_bulk_create_negative_keywords` | POST | `/v1/negative-keywords/bulk-create` | Create many negative keywords in one request. |
|
|
216
|
+
| `apple_ads_bulk_update_negative_keywords` | POST | `/v1/negative-keywords/bulk-update` | Update many negative keywords in one request. Each item's data must include the id plus only the fields to change. |
|
|
217
|
+
|
|
218
|
+
#### geo
|
|
219
|
+
|
|
220
|
+
| Tool | Method | Path | Description |
|
|
221
|
+
|---|---|---|---|
|
|
222
|
+
| `apple_ads_get_geos_by_ids` | POST | `/v1/search/geo` | Look up geo location metadata by entity ID or legacy ID (POST /search/geo). |
|
|
223
|
+
| `apple_ads_search_geos` | GET | `/v1/search/geo` | Search geo locations (country, admin area, locality, postal code) by name or wildcard to find IDs for ad group geo targeting. |
|
|
224
|
+
|
|
225
|
+
#### product_pages
|
|
226
|
+
|
|
227
|
+
| Tool | Method | Path | Description |
|
|
228
|
+
|---|---|---|---|
|
|
229
|
+
| `apple_ads_get_product_page` | GET | `/v1/product-pages/{productPageId}` | Get one product page by its productPageId. |
|
|
230
|
+
| `apple_ads_query_app_locale_details` | POST | `/v1/apps/{adamId}/locale-details/query` | Query the default product page's localized metadata for an app by Adam ID. |
|
|
231
|
+
| `apple_ads_query_product_page_locale_details` | POST | `/v1/product-pages/locale-details/query` | Query localized metadata (name, subtitle, promo text, screenshots) of product pages. |
|
|
232
|
+
| `apple_ads_query_product_pages` | POST | `/v1/product-pages/query` | Query App Store product pages (default, custom and optimized) available to the ad account. |
|
|
233
|
+
|
|
234
|
+
#### budget_orders
|
|
235
|
+
|
|
236
|
+
| Tool | Method | Path | Description |
|
|
237
|
+
|---|---|---|---|
|
|
238
|
+
| `apple_ads_delete_budget_order` | DELETE | `/v1/shared-budgets/{id}` | Delete a budget order by ID. |
|
|
239
|
+
| `apple_ads_get_budget_order` | GET | `/v1/shared-budgets/{id}` | Get one budget order by ID. |
|
|
240
|
+
| `apple_ads_update_budget_order` | PUT | `/v1/shared-budgets/{id}` | Update a budget order. |
|
|
241
|
+
| `apple_ads_create_budget_order` | POST | `/v1/shared-budgets` | Create a budget order (shared budget) capping total spend across campaigns. Requires a monthly-invoiced (LOC) ad account. |
|
|
242
|
+
| `apple_ads_query_budget_orders` | POST | `/v1/shared-budgets/query` | Query budget orders with filters, sorting and pagination. |
|
|
243
|
+
|
|
244
|
+
#### reports_apps
|
|
245
|
+
|
|
246
|
+
| Tool | Method | Path | Description |
|
|
247
|
+
|---|---|---|---|
|
|
248
|
+
| `apple_ads_report_apps_ad_groups` | POST | `/v1/reports/apps/adgroups/query` | App Store ad group performance report for a time range. |
|
|
249
|
+
| `apple_ads_report_apps_ads` | POST | `/v1/reports/apps/ads/query` | App Store ad performance report for a time range. |
|
|
250
|
+
| `apple_ads_report_apps_campaigns` | POST | `/v1/reports/apps/campaigns/query` | App Store campaign performance report (impressions, taps, installs, spend, ...) for a time range, with optional groupBy. |
|
|
251
|
+
| `apple_ads_report_apps_keywords` | POST | `/v1/reports/apps/keywords/query` | App Store keyword performance report for a time range. |
|
|
252
|
+
| `apple_ads_report_apps_search_terms` | POST | `/v1/reports/apps/searchterms/query` | App Store search term performance report for a time range. |
|
|
253
|
+
|
|
254
|
+
#### reports_brands
|
|
255
|
+
|
|
256
|
+
| Tool | Method | Path | Description |
|
|
257
|
+
|---|---|---|---|
|
|
258
|
+
| `apple_ads_report_brands_ad_groups` | POST | `/v1/reports/business-brands/adgroups/query` | Apple Maps ad group performance report for a time range. |
|
|
259
|
+
| `apple_ads_report_brands_ads` | POST | `/v1/reports/business-brands/ads/query` | Apple Maps ad performance report for a time range. |
|
|
260
|
+
| `apple_ads_report_brands_campaigns` | POST | `/v1/reports/business-brands/campaigns/query` | Apple Maps campaign performance report for a time range (group by locationId for per-location breakdowns). |
|
|
261
|
+
| `apple_ads_report_brands_keywords` | POST | `/v1/reports/business-brands/keywords/query` | Apple Maps keyword performance report for a time range. |
|
|
262
|
+
| `apple_ads_report_brands_search_terms` | POST | `/v1/reports/business-brands/searchterms/query` | Apple Maps search term performance report for a time range. |
|
|
263
|
+
|
|
264
|
+
#### insights
|
|
265
|
+
|
|
266
|
+
| Tool | Method | Path | Description |
|
|
267
|
+
|---|---|---|---|
|
|
268
|
+
| `apple_ads_query_impression_share` | POST | `/v1/insights/apps/impression-share/query` | Query App Store impression share for keywords over a time range. A promotedObjectId filter is required. |
|
|
269
|
+
| `apple_ads_query_search_term_popularity` | POST | `/v1/insights/apps/search-term-popularity/query` | Query App Store search term popularity rankings by country or region and genre. |
|
|
270
|
+
|
|
271
|
+
#### recommendations
|
|
272
|
+
|
|
273
|
+
| Tool | Method | Path | Description |
|
|
274
|
+
|---|---|---|---|
|
|
275
|
+
| `apple_ads_apply_daily_budget_recommendations` | POST | `/v1/recommendations/daily-budgets/apply` | Apply daily budget recommendations, which updates the campaigns' daily budgets. All items must share one promotedObjectId. |
|
|
276
|
+
| `apple_ads_apply_target_cpa_recommendations` | POST | `/v1/recommendations/target-cpas/apply` | Apply target CPA recommendations, updating the target CPA used by Maximize Conversions bidding. |
|
|
277
|
+
| `apple_ads_dismiss_daily_budget_recommendations` | POST | `/v1/recommendations/daily-budgets/dismiss` | Dismiss daily budget recommendations without changing any campaign. |
|
|
278
|
+
| `apple_ads_dismiss_target_cpa_recommendations` | POST | `/v1/recommendations/target-cpas/dismiss` | Dismiss target CPA recommendations without changing any campaign. |
|
|
279
|
+
| `apple_ads_query_daily_budget_recommendations` | POST | `/v1/recommendations/daily-budgets/query` | Query daily budget recommendations. promotedObjectId and promotedObjectType filters are mandatory. |
|
|
280
|
+
| `apple_ads_query_target_cpa_recommendations` | POST | `/v1/recommendations/target-cpas/query` | Query target CPA recommendations for App Store campaigns using Maximize Conversions. |
|
|
281
|
+
|
|
282
|
+
#### suggestions
|
|
283
|
+
|
|
284
|
+
| Tool | Method | Path | Description |
|
|
285
|
+
|---|---|---|---|
|
|
286
|
+
| `apple_ads_query_category_suggestions` | POST | `/v1/suggestions/categories/query` | Get category suggestions for an app or brand, or search categories by name. |
|
|
287
|
+
| `apple_ads_query_keyword_suggestions` | POST | `/v1/suggestions/keywords/query` | Get ranked keyword suggestions for an app (popularity 0-100). Feed good ones into apple_ads_create_keyword. |
|
|
288
|
+
| `apple_ads_query_phrase_suggestions` | POST | `/v1/suggestions/phrases/query` | Get natural-language search phrase suggestions for an app or brand, or look up phrase popularity. |
|
|
289
|
+
| `apple_ads_query_target_cpa_suggestion` | POST | `/v1/suggestions/target-cpas/query` | Get a suggested target CPA for an App Store app based on recent tap-install CPI data. |
|
|
290
|
+
|
|
291
|
+
#### change_history
|
|
292
|
+
|
|
293
|
+
| Tool | Method | Path | Description |
|
|
294
|
+
|---|---|---|---|
|
|
295
|
+
| `apple_ads_get_change_details` | GET | `/v1/change-history/{detailId}` | Get field-level before/after values for one change, by detailId (format EntityType.entityId.txnId). |
|
|
296
|
+
| `apple_ads_query_change_history` | POST | `/v1/change-history/query` | Query the change history (audit) summary grouped by transaction. An eventTime filter is required; set options.metadata to latest or snapshot to get detailIds. |
|
|
297
|
+
|
|
298
|
+
#### meta
|
|
299
|
+
|
|
300
|
+
| Tool | Description |
|
|
301
|
+
|---|---|
|
|
302
|
+
| `apple_ads_request` | Raw authenticated request to any Platform API path (forward-compat escape hatch) |
|
|
303
|
+
| `apple_ads_list_operations` | Lists every SDK operation, its HTTP route and the MCP tool that covers it |
|
|
304
|
+
| `apple_ads_server_status` | Redacted configuration/credential diagnostics and rate-limit state |
|
|
305
|
+
<!-- GENERATED:TOOLS:END -->
|
|
306
|
+
|
|
307
|
+
## Prerequisites
|
|
308
|
+
|
|
309
|
+
1. An Apple Ads account with API access (Account Settings > API) and Node.js 20+.
|
|
310
|
+
2. Generate an EC key pair:
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
openssl ecparam -genkey -name prime256v1 -noout -out private-key.pem
|
|
314
|
+
openssl ec -in private-key.pem -pubout -out public-key.pem
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
3. In Apple Ads go to **Account Settings > API**, paste the contents of `public-key.pem`, and save.
|
|
318
|
+
4. Copy the three values shown there: **clientId**, **teamId** and **keyId**.
|
|
319
|
+
5. Keep `private-key.pem` secret (for example in `~/.appleads/private_keys/`, `chmod 600`).
|
|
320
|
+
|
|
321
|
+
## Installation
|
|
322
|
+
|
|
323
|
+
All clients launch the same command, `npx -y @nerdsnipe-inc/apple-ads-mcp-server`, with the same environment variables (see [Environment variables](#environment-variables)). Only the config file differs. After editing a config, restart the client.
|
|
324
|
+
|
|
325
|
+
> **GUI apps and PATH.** Desktop apps (Claude Desktop, Cursor, Windsurf, VS Code) often do not inherit your shell `PATH`. If you see "spawn npx ENOENT", replace `"npx"` with the absolute path from `which npx` (for example `/opt/homebrew/bin/npx`).
|
|
326
|
+
|
|
327
|
+
> **No key file?** Instead of `APPLE_ADS_PRIVATE_KEY_PATH` you can set `APPLE_ADS_PRIVATE_KEY` to the PEM text itself (newlines written as `\n`). The key then lives only in the client's config. Treat that config file as a secret.
|
|
328
|
+
|
|
329
|
+
### Claude Code
|
|
330
|
+
|
|
331
|
+
User scope (every project), as shown in [Quick start](#quick-start-no-local-install). To share the server with a team through a project's `.mcp.json`, use `--scope project` and keep the secrets out of git by referencing environment variables:
|
|
332
|
+
|
|
333
|
+
```json
|
|
334
|
+
{
|
|
335
|
+
"mcpServers": {
|
|
336
|
+
"apple-ads": {
|
|
337
|
+
"command": "npx",
|
|
338
|
+
"args": ["-y", "@nerdsnipe-inc/apple-ads-mcp-server"],
|
|
339
|
+
"env": {
|
|
340
|
+
"APPLE_ADS_CLIENT_ID": "${APPLE_ADS_CLIENT_ID}",
|
|
341
|
+
"APPLE_ADS_TEAM_ID": "${APPLE_ADS_TEAM_ID}",
|
|
342
|
+
"APPLE_ADS_KEY_ID": "${APPLE_ADS_KEY_ID}",
|
|
343
|
+
"APPLE_ADS_PRIVATE_KEY_PATH": "${APPLE_ADS_PRIVATE_KEY_PATH}"
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Check it with `claude mcp list`.
|
|
351
|
+
|
|
352
|
+
### Claude Desktop
|
|
353
|
+
|
|
354
|
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (Windows: `%APPDATA%\Claude\claude_desktop_config.json`), then fully quit and reopen the app:
|
|
355
|
+
|
|
356
|
+
```json
|
|
357
|
+
{
|
|
358
|
+
"mcpServers": {
|
|
359
|
+
"apple-ads": {
|
|
360
|
+
"command": "/absolute/path/to/npx",
|
|
361
|
+
"args": ["-y", "@nerdsnipe-inc/apple-ads-mcp-server"],
|
|
362
|
+
"env": {
|
|
363
|
+
"APPLE_ADS_CLIENT_ID": "SEARCHADS.xxxxxxxx-...",
|
|
364
|
+
"APPLE_ADS_TEAM_ID": "SEARCHADS.xxxxxxxx-...",
|
|
365
|
+
"APPLE_ADS_KEY_ID": "xxxxxxxx-...",
|
|
366
|
+
"APPLE_ADS_PRIVATE_KEY_PATH": "/Users/you/.appleads/private_keys/private-key.pem"
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Cursor / Windsurf
|
|
374
|
+
|
|
375
|
+
Add the same `mcpServers` block as in Claude Desktop to `~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project) or `~/.codeium/windsurf/mcp_config.json`.
|
|
376
|
+
|
|
377
|
+
### VS Code (GitHub Copilot)
|
|
378
|
+
|
|
379
|
+
Create `.vscode/mcp.json` in your workspace. VS Code uses a `servers` key and can prompt for secrets instead of storing them:
|
|
380
|
+
|
|
381
|
+
```json
|
|
382
|
+
{
|
|
383
|
+
"inputs": [
|
|
384
|
+
{ "type": "promptString", "id": "ads-client-id", "description": "Apple Ads clientId" },
|
|
385
|
+
{ "type": "promptString", "id": "ads-team-id", "description": "Apple Ads teamId" },
|
|
386
|
+
{ "type": "promptString", "id": "ads-key-id", "description": "Apple Ads keyId" },
|
|
387
|
+
{ "type": "promptString", "id": "ads-key-path", "description": "Path to private-key.pem" }
|
|
388
|
+
],
|
|
389
|
+
"servers": {
|
|
390
|
+
"apple-ads": {
|
|
391
|
+
"type": "stdio",
|
|
392
|
+
"command": "npx",
|
|
393
|
+
"args": ["-y", "@nerdsnipe-inc/apple-ads-mcp-server"],
|
|
394
|
+
"env": {
|
|
395
|
+
"APPLE_ADS_CLIENT_ID": "${input:ads-client-id}",
|
|
396
|
+
"APPLE_ADS_TEAM_ID": "${input:ads-team-id}",
|
|
397
|
+
"APPLE_ADS_KEY_ID": "${input:ads-key-id}",
|
|
398
|
+
"APPLE_ADS_PRIVATE_KEY_PATH": "${input:ads-key-path}"
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Codex CLI
|
|
406
|
+
|
|
407
|
+
Add to `~/.codex/config.toml`:
|
|
408
|
+
|
|
409
|
+
```toml
|
|
410
|
+
[mcp_servers.apple-ads]
|
|
411
|
+
command = "npx"
|
|
412
|
+
args = ["-y", "@nerdsnipe-inc/apple-ads-mcp-server"]
|
|
413
|
+
|
|
414
|
+
[mcp_servers.apple-ads.env]
|
|
415
|
+
APPLE_ADS_CLIENT_ID = "SEARCHADS.xxxxxxxx-..."
|
|
416
|
+
APPLE_ADS_TEAM_ID = "SEARCHADS.xxxxxxxx-..."
|
|
417
|
+
APPLE_ADS_KEY_ID = "xxxxxxxx-..."
|
|
418
|
+
APPLE_ADS_PRIVATE_KEY_PATH = "/Users/you/.appleads/private_keys/private-key.pem"
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Gemini CLI
|
|
422
|
+
|
|
423
|
+
Add the standard `mcpServers` block from [Quick start](#quick-start-no-local-install) to `~/.gemini/settings.json`.
|
|
424
|
+
|
|
425
|
+
### Interactive installer (macOS)
|
|
426
|
+
|
|
427
|
+
If you cloned this repo, `scripts/install-global.sh` prompts for your credentials, copies the key to `~/.appleads/private_keys` (mode 600), and registers the server in Claude Code (user scope) and Claude Desktop.
|
|
428
|
+
|
|
429
|
+
### From source
|
|
430
|
+
|
|
431
|
+
```bash
|
|
432
|
+
git clone https://github.com/NerdSnipe-Inc/apple-ads-mcp-server.git
|
|
433
|
+
cd apple-ads-mcp-server && npm install && npm run build
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Then use `"command": "node"` with `"args": ["/absolute/path/to/apple-ads-mcp-server/dist/index.js"]` in any config above, or:
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
claude mcp add apple-ads --scope user -e APPLE_ADS_CLIENT_ID=... -e APPLE_ADS_TEAM_ID=... \
|
|
440
|
+
-e APPLE_ADS_KEY_ID=... -e APPLE_ADS_PRIVATE_KEY_PATH=... \
|
|
441
|
+
-- node /absolute/path/to/apple-ads-mcp-server/dist/index.js
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
## Environment variables
|
|
445
|
+
|
|
446
|
+
| Variable | Required | Description |
|
|
447
|
+
| --- | --- | --- |
|
|
448
|
+
| `APPLE_ADS_CLIENT_ID` (`ASA_CLIENT_ID`) | yes | Client ID from Account Settings > API |
|
|
449
|
+
| `APPLE_ADS_TEAM_ID` (`ASA_TEAM_ID`) | yes | Team ID |
|
|
450
|
+
| `APPLE_ADS_KEY_ID` (`ASA_KEY_ID`) | yes | Key ID |
|
|
451
|
+
| `APPLE_ADS_PRIVATE_KEY_PATH` (`ASA_PRIVATE_KEY_PATH`) | yes* | Path to the PEM private key (`~` supported) |
|
|
452
|
+
| `APPLE_ADS_PRIVATE_KEY` (`ASA_PRIVATE_KEY`) | yes* | Inline PEM instead of a path (`\n` escapes allowed) |
|
|
453
|
+
| `APPLE_ADS_AD_ACCOUNT_ID` (`ASA_AD_ACCOUNT_ID`) | no | Default ad account sent as `X-AP-Context: adAccountId=<id>` |
|
|
454
|
+
| `APPLE_ADS_ORG_ID` (`ASA_ORG_ID`) | no | Default organization ID for org-level calls |
|
|
455
|
+
| `APPLE_ADS_READ_ONLY` | no | `true` hides every tool that can modify data |
|
|
456
|
+
| `APPLE_ADS_ENABLED_GROUPS` | no | Comma list; only these groups are exposed |
|
|
457
|
+
| `APPLE_ADS_DISABLED_GROUPS` | no | Comma list of groups to hide |
|
|
458
|
+
| `APPLE_ADS_TIMEOUT_MS` | no | Request timeout (default 30000) |
|
|
459
|
+
| `APPLE_ADS_MAX_RETRIES` | no | Retries for 429/5xx (default 4) |
|
|
460
|
+
| `APPLE_ADS_RETRY_BASE_MS` / `APPLE_ADS_RETRY_MAX_MS` | no | Backoff base/cap (default 2000/16000) |
|
|
461
|
+
| `APPLE_ADS_LOG_LEVEL` | no | `error`, `warn` (default), `info`, `debug` (stderr only) |
|
|
462
|
+
| `APPLE_ADS_BASE_URL`, `APPLE_ADS_TOKEN_URL` | no | Test-only overrides (local mock servers) |
|
|
463
|
+
|
|
464
|
+
*One of the two key variables is required. `ASA_*` names are accepted as aliases; `APPLE_ADS_*` wins.
|
|
465
|
+
|
|
466
|
+
## Usage notes
|
|
467
|
+
|
|
468
|
+
- **Context = ad account.** Most calls need an ad account. Pass `adAccountId` on any tool, or set `APPLE_ADS_AD_ACCOUNT_ID`. If neither is set and your credentials reach exactly one ad account, it is discovered automatically. Run `apple_ads_get_user_acls` (or `apple_ads_server_status`) to list accounts.
|
|
469
|
+
- **Pagination.** Query tools take `pagination: {offset, pageSize, fetchTotalCount}` (page size max 1000); fetch the next page by adding `pageSize` to `offset`. List tools take `limit` and `offset`. Report tools take a date range and a `selector`.
|
|
470
|
+
- **Rate limits.** Responses carry `RateLimit-*` headers; on 429 the server waits for `Retry-After` and retries with capped backoff.
|
|
471
|
+
- **Token budget.** Listing all tools costs roughly 55k tokens. Use `APPLE_ADS_ENABLED_GROUPS` (for example `accounts,campaigns,ad_groups,keywords,reports_apps`) to trim.
|
|
472
|
+
- **Escape hatch.** `apple_ads_request` calls any `/v1` path with a method, query and body.
|
|
473
|
+
|
|
474
|
+
## Safety switches
|
|
475
|
+
|
|
476
|
+
```bash
|
|
477
|
+
APPLE_ADS_READ_ONLY=true # reporting only
|
|
478
|
+
APPLE_ADS_ENABLED_GROUPS=campaigns,reports_apps
|
|
479
|
+
APPLE_ADS_DISABLED_GROUPS=budget_orders
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
Group names: `accounts, apps, eligibility, brands, location_groups, campaigns, ad_groups, ads, creatives, assets, keywords, negative_keywords, bulk, geo, product_pages, budget_orders, reports_apps, reports_brands, insights, recommendations, suggestions, change_history` (the three meta tools live in `meta`, which is always available unless disabled).
|
|
483
|
+
|
|
484
|
+
## Troubleshooting
|
|
485
|
+
|
|
486
|
+
| Symptom | Fix |
|
|
487
|
+
| --- | --- |
|
|
488
|
+
| "Missing credentials" error naming variables | Set all of client ID, team ID, key ID and a key path/inline key |
|
|
489
|
+
| 401 after refresh | Wrong clientId/teamId/keyId, or the public key was not uploaded for that key |
|
|
490
|
+
| 403 | The API user lacks a role on that ad account or org |
|
|
491
|
+
| "ad account required" | Pass `adAccountId` or set `APPLE_ADS_AD_ACCOUNT_ID` |
|
|
492
|
+
| `spawn npx ENOENT` or server not found in a desktop app | Use the absolute path from `which npx` (or `which node`) as `command` |
|
|
493
|
+
| Old version keeps running | `npx` caches packages; pin a version (`@nerdsnipe-inc/apple-ads-mcp-server@1.0.1`) or run `npx clear-npx-cache` |
|
|
494
|
+
| Key errors | Must be an EC `prime256v1` private key in PEM format |
|
|
495
|
+
|
|
496
|
+
Run `npx -y @nerdsnipe-inc/apple-ads-mcp-server --list-tools` to verify installation without credentials.
|
|
497
|
+
|
|
498
|
+
## Development
|
|
499
|
+
|
|
500
|
+
```bash
|
|
501
|
+
npm install
|
|
502
|
+
npm run generate # regenerate tools, docs and README tables from the SDK
|
|
503
|
+
npm run typecheck && npm run build && npm test
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
`npm run generate:check` fails when generated files are stale (used in CI).
|
|
507
|
+
|
|
508
|
+
## Releasing
|
|
509
|
+
|
|
510
|
+
Publishing is automated by [.github/workflows/publish.yml](.github/workflows/publish.yml). Pushing a version tag builds, tests and publishes to npmjs.com and to GitHub Packages. The npm publish uses **trusted publishing (OIDC)**: there is no `NPM_TOKEN` secret, and provenance is attached automatically. This matters because npm is [phasing out granular access tokens that bypass 2FA](https://github.blog/changelog/2026-07-08-npm-install-time-security-and-gat-bypass2fa-deprecation/) (they lose direct publishing around January 2027).
|
|
511
|
+
|
|
512
|
+
**One-time setup (first release):**
|
|
513
|
+
|
|
514
|
+
1. Publish `1.0.0` once by hand so the package exists on npm. You are prompted for 2FA, and `prepublishOnly` rebuilds and tests first:
|
|
515
|
+
|
|
516
|
+
```bash
|
|
517
|
+
npm login
|
|
518
|
+
npm publish --access public
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
2. On npmjs.com open the package > **Settings** > **Trusted publisher** > GitHub Actions, and enter: organization `NerdSnipe-Inc`, repository `apple-ads-mcp-server`, workflow filename `publish.yml`.
|
|
522
|
+
3. Optional hardening: on the same settings page set publishing access to "Require two-factor authentication and disallow tokens".
|
|
523
|
+
|
|
524
|
+
**Every release after that:**
|
|
525
|
+
|
|
526
|
+
```bash
|
|
527
|
+
npm version patch # or minor / major: bumps package.json and creates the vX.Y.Z tag
|
|
528
|
+
git push --follow-tags
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
`GITHUB_TOKEN` is provided automatically for the GitHub Packages step. The workflow refuses to publish if the tag and `package.json` version differ.
|
|
532
|
+
|
|
533
|
+
## License
|
|
534
|
+
|
|
535
|
+
MIT, see [LICENSE](LICENSE).
|