appstore-api-mcp 1.15.0 → 1.16.1

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/CHANGELOG.md CHANGED
@@ -4,6 +4,41 @@ All notable changes to this project are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/) and the project uses
5
5
  [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [1.16.1] - 2026-10-07
8
+
9
+ ### Fixed
10
+ - `list_webhook_deliveries` returned `400 PARAMETER_ERROR.INVALID` ("Filter is
11
+ required") — the deliveries endpoint requires exactly one filter, which the
12
+ tool wasn't sending. It now sends `filter[deliveryState]` (when provided) or
13
+ defaults to `filter[createdDateGreaterThanOrEqualTo]` (last 30 days). Found via
14
+ a live webhook create→ping→delete round-trip.
15
+
16
+ ## [1.16.0] - 2026-10-07
17
+
18
+ ### Added — App Store marketing & growth (recent ASC features)
19
+ - **Custom Product Pages:** `list_custom_product_pages`, `get_custom_product_page`,
20
+ `create_custom_product_page`, `list/create_custom_product_page_version(s)`,
21
+ `list/create/update_custom_product_page_localization(s)`.
22
+ - **Product Page Optimization (A/B, appStoreVersionExperiments v2):**
23
+ `list_ppo_experiments`, `get_ppo_experiment`, `create_ppo_experiment`,
24
+ `start_ppo_experiment`, `stop_ppo_experiment`, `list/create_experiment_treatment(s)`,
25
+ `create_experiment_treatment_localization`. (Experiment *results* are UI-only —
26
+ the API exposes no metrics endpoint.)
27
+ - **Asset Library:** `get_asset_library`, `list_asset_library_images/videos`,
28
+ `upload_asset_library_image`, `assign_asset_placement` (reuse an asset on a
29
+ product page / event / treatment), `delete_asset_placement`.
30
+ - **Webhooks** (WWDC25): `list/get/create/update/delete_webhook`,
31
+ `list_webhook_deliveries`, `ping_webhook` — event callbacks for version/build/
32
+ TestFlight-feedback state, instead of polling.
33
+ - **Subscription offer codes** (promo-code replacement): `list_subscription_offer_codes`,
34
+ `create_subscription_offer_code`, `create_offer_code_one_time_use`,
35
+ `create_offer_code_custom`, `list_offer_code_codes`.
36
+ - **In-App Events:** `list/get/create_app_event`, `create_app_event_localization`.
37
+ - **Game Center challenges:** `list_game_center_challenges`, `get_game_center_challenge`.
38
+ - **Screenshots/previews** now attach to Custom Product Page and PPO treatment
39
+ localizations too: `create_screenshot_set` / `create_app_preview_set` accept
40
+ `customProductPageLocalizationId` / `treatmentLocalizationId`.
41
+
7
42
  ## [1.15.0] - 2026-10-05
8
43
 
9
44
  ### Added — submission flow (from 5 field reports of real submissions)
package/docs/TOOLS.md CHANGED
@@ -395,7 +395,48 @@ mode and when `APPSTORE_MCP_ALLOW_PRICE_CHANGES=false`.
395
395
  ## Product Page Optimization
396
396
 
397
397
  ### list_app_store_version_experiments
398
- - `appId` **(required)** — A/B tests with name, state, traffic proportion, start/end.
398
+ - `appId` **(required)** — A/B tests with name, state, traffic proportion, start/end (legacy v1 list).
399
+
400
+ ## Marketing & growth
401
+
402
+ Recent App Store features — Custom Product Pages, Product Page Optimization (A/B),
403
+ Asset Library, Webhooks, subscription offer codes, In-App Events, Game Center challenges.
404
+
405
+ ### Custom Product Pages
406
+ Alternate product pages (own screenshots/text, each with a unique marketing URL).
407
+ - `list_custom_product_pages(appId)`, `get_custom_product_page(id)`, `create_custom_product_page(appId, name)`
408
+ - `list_custom_product_page_versions(customProductPageId)`, `create_custom_product_page_version(customProductPageId, deepLink?)`
409
+ - `list_custom_product_page_localizations(versionId)`, `create_custom_product_page_localization(versionId, locale, promotionalText?)`, `update_custom_product_page_localization(localizationId, promotionalText)`
410
+ - Screenshots: `create_screenshot_set(customProductPageLocalizationId, displayType)` → `upload_screenshot`.
411
+
412
+ ### Product Page Optimization (A/B, v2)
413
+ - `list_ppo_experiments(versionId)`, `get_ppo_experiment(id)`, `create_ppo_experiment(appId, name, trafficProportion, platform?)`
414
+ - `start_ppo_experiment(id)` / `stop_ppo_experiment(id)` (start/stop via a `started` flag; stop preserves results)
415
+ - `list_experiment_treatments(experimentId)`, `create_experiment_treatment(experimentId, name, appIconName?)`, `create_experiment_treatment_localization(treatmentId, locale)`
416
+ - Screenshots: `create_screenshot_set(treatmentLocalizationId, displayType)`.
417
+ - ⚠️ **Results (conversion %, confidence) are not in the API** — view them in the App Store Connect web UI.
418
+
419
+ ### Asset Library
420
+ Upload media once, reuse across product pages / events / treatments.
421
+ - `get_asset_library(appId)`, `list_asset_library_images(appId, category?)`, `list_asset_library_videos(appId)`
422
+ - `upload_asset_library_image(appId, filePath, category?, referenceName?)` — category `CREATIVE_ASSETS` or `APP_SCREENSHOTS_AND_PREVIEWS`
423
+ - `assign_asset_placement({imageId|videoId}, <one destination localization id>, placementType?)`, `delete_asset_placement(placementId)`
424
+
425
+ ### Webhooks
426
+ Event callbacks instead of polling (≤10 per app).
427
+ - `list_webhooks(appId)`, `get_webhook(id)`, `create_webhook(appId, name, url, eventTypes[], secret, enabled?)`, `update_webhook(id, …)`, `delete_webhook(id)`
428
+ - `list_webhook_deliveries(id)`, `ping_webhook(id)`
429
+ - Common `eventTypes`: `APP_STORE_VERSION_APP_VERSION_STATE_UPDATED`, `BUILD_UPLOAD_STATE_UPDATED`, `BUILD_BETA_DETAIL_EXTERNAL_BUILD_STATE_UPDATED`, `BETA_FEEDBACK_CRASH_SUBMISSION_CREATED`, `BETA_FEEDBACK_SCREENSHOT_SUBMISSION_CREATED`.
430
+
431
+ ### Subscription offer codes (promo-code replacement)
432
+ - `list_subscription_offer_codes(subscriptionId)`, `create_subscription_offer_code(subscriptionId, name, offerMode, duration, numberOfPeriods, customerEligibilities[], offerEligibility, prices[])`
433
+ - `create_offer_code_one_time_use(offerCodeId, numberOfCodes, expirationDate, environment?)`, `create_offer_code_custom(offerCodeId, customCode, numberOfCodes, expirationDate?)`, `list_offer_code_codes(offerCodeId, kind?)`
434
+
435
+ ### In-App Events
436
+ - `list_app_events(appId, eventState?)`, `get_app_event(id)`, `create_app_event(appId, referenceName, …)`, `create_app_event_localization(eventId, locale, …)`
437
+
438
+ ### Game Center challenges (read)
439
+ - `list_game_center_challenges(appId)`, `get_game_center_challenge(id)`
399
440
 
400
441
  ## Code-signing health
401
442
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appstore-api-mcp",
3
- "version": "1.15.0",
3
+ "version": "1.16.1",
4
4
  "description": "MCP server for Apple App Store Connect — edit listings (keywords, descriptions, titles, screenshots), track analytics (downloads, proceeds, subscriptions, retention), run a fleet-wide ASO audit, preview changes with dry-run, and reach the full API. Works with any MCP client (Claude, Codex, Cursor, Windsurf, VS Code, Zed, Gemini CLI, Antigravity, Amazon Q, Goose, and more).",
5
5
  "type": "module",
6
6
  "bin": {
package/src/guardrails.js CHANGED
@@ -29,6 +29,33 @@ export const WRITE_TOOLS = new Set([
29
29
  "update_in_app_purchase",
30
30
  "set_app_price",
31
31
  "apply_ppp_prices",
32
+ // custom product pages
33
+ "create_custom_product_page",
34
+ "create_custom_product_page_version",
35
+ "create_custom_product_page_localization",
36
+ "update_custom_product_page_localization",
37
+ // product page optimization (A/B experiments)
38
+ "create_ppo_experiment",
39
+ "start_ppo_experiment",
40
+ "stop_ppo_experiment",
41
+ "create_experiment_treatment",
42
+ "create_experiment_treatment_localization",
43
+ // asset library
44
+ "upload_asset_library_image",
45
+ "assign_asset_placement",
46
+ "delete_asset_placement",
47
+ // webhooks
48
+ "create_webhook",
49
+ "update_webhook",
50
+ "delete_webhook",
51
+ "ping_webhook",
52
+ // subscription offer codes
53
+ "create_subscription_offer_code",
54
+ "create_offer_code_one_time_use",
55
+ "create_offer_code_custom",
56
+ // in-app events
57
+ "create_app_event",
58
+ "create_app_event_localization",
32
59
  // provisioning
33
60
  "register_bundle_id",
34
61
  "register_device",
package/src/index.js CHANGED
@@ -1505,30 +1505,31 @@ const tools = [
1505
1505
  {
1506
1506
  name: "create_screenshot_set",
1507
1507
  description:
1508
- "Create a screenshot set for a given device display type on a version localization. displayType examples: APP_IPHONE_67, APP_IPHONE_65, APP_IPHONE_61, APP_IPAD_PRO_129, APP_IPAD_PRO_3GEN_11.",
1508
+ "Create a screenshot set for a given device display type on a localization. Attach to ONE of: a normal App Store version localization (localizationId), a Custom Product Page localization (customProductPageLocalizationId), or a Product Page Optimization experiment-treatment localization (treatmentLocalizationId). displayType examples: APP_IPHONE_67, APP_IPHONE_65, APP_IPHONE_61, APP_IPAD_PRO_129, APP_IPAD_PRO_3GEN_11. Upload images into the set with upload_screenshot.",
1509
1509
  inputSchema: {
1510
1510
  type: "object",
1511
1511
  properties: {
1512
- localizationId: { type: "string" },
1512
+ localizationId: { type: "string", description: "App Store version localization id" },
1513
+ customProductPageLocalizationId: { type: "string", description: "Custom Product Page localization id" },
1514
+ treatmentLocalizationId: { type: "string", description: "PPO experiment treatment localization id" },
1513
1515
  displayType: { type: "string" },
1514
1516
  },
1515
- required: ["localizationId", "displayType"],
1517
+ required: ["displayType"],
1518
+ },
1519
+ run: async (a) => {
1520
+ let relationships;
1521
+ if (a.localizationId)
1522
+ relationships = { appStoreVersionLocalization: { data: { type: "appStoreVersionLocalizations", id: a.localizationId } } };
1523
+ else if (a.customProductPageLocalizationId)
1524
+ relationships = { appCustomProductPageLocalization: { data: { type: "appCustomProductPageLocalizations", id: a.customProductPageLocalizationId } } };
1525
+ else if (a.treatmentLocalizationId)
1526
+ relationships = { appStoreVersionExperimentTreatmentLocalization: { data: { type: "appStoreVersionExperimentTreatmentLocalizations", id: a.treatmentLocalizationId } } };
1527
+ else
1528
+ return { error: "Provide one of localizationId, customProductPageLocalizationId, or treatmentLocalizationId." };
1529
+ return client.post(`/appScreenshotSets`, {
1530
+ data: { type: "appScreenshotSets", attributes: { screenshotDisplayType: a.displayType }, relationships },
1531
+ });
1516
1532
  },
1517
- run: async (a) =>
1518
- client.post(`/appScreenshotSets`, {
1519
- data: {
1520
- type: "appScreenshotSets",
1521
- attributes: { screenshotDisplayType: a.displayType },
1522
- relationships: {
1523
- appStoreVersionLocalization: {
1524
- data: {
1525
- type: "appStoreVersionLocalizations",
1526
- id: a.localizationId,
1527
- },
1528
- },
1529
- },
1530
- },
1531
- }),
1532
1533
  },
1533
1534
  {
1534
1535
  name: "list_screenshots",
@@ -1681,23 +1682,31 @@ const tools = [
1681
1682
  {
1682
1683
  name: "create_app_preview_set",
1683
1684
  description:
1684
- "Create an app preview (video) set for a device type on a version localization. previewType examples: IPHONE_67, IPHONE_61, IPAD_PRO_3GEN_129.",
1685
+ "Create an app preview (video) set for a device type on a localization. Attach to ONE of: a version localization (localizationId), a Custom Product Page localization (customProductPageLocalizationId), or a PPO treatment localization (treatmentLocalizationId). previewType examples: IPHONE_67, IPHONE_61, IPAD_PRO_3GEN_129.",
1685
1686
  inputSchema: {
1686
1687
  type: "object",
1687
1688
  properties: {
1688
1689
  localizationId: { type: "string" },
1690
+ customProductPageLocalizationId: { type: "string" },
1691
+ treatmentLocalizationId: { type: "string" },
1689
1692
  previewType: { type: "string" },
1690
1693
  },
1691
- required: ["localizationId", "previewType"],
1694
+ required: ["previewType"],
1695
+ },
1696
+ run: async (a) => {
1697
+ let relationships;
1698
+ if (a.localizationId)
1699
+ relationships = { appStoreVersionLocalization: { data: { type: "appStoreVersionLocalizations", id: a.localizationId } } };
1700
+ else if (a.customProductPageLocalizationId)
1701
+ relationships = { appCustomProductPageLocalization: { data: { type: "appCustomProductPageLocalizations", id: a.customProductPageLocalizationId } } };
1702
+ else if (a.treatmentLocalizationId)
1703
+ relationships = { appStoreVersionExperimentTreatmentLocalization: { data: { type: "appStoreVersionExperimentTreatmentLocalizations", id: a.treatmentLocalizationId } } };
1704
+ else
1705
+ return { error: "Provide one of localizationId, customProductPageLocalizationId, or treatmentLocalizationId." };
1706
+ return client.post(`/appPreviewSets`, {
1707
+ data: { type: "appPreviewSets", attributes: { previewType: a.previewType }, relationships },
1708
+ });
1692
1709
  },
1693
- run: async (a) =>
1694
- client.post(`/appPreviewSets`, {
1695
- data: {
1696
- type: "appPreviewSets",
1697
- attributes: { previewType: a.previewType },
1698
- relationships: { appStoreVersionLocalization: { data: { type: "appStoreVersionLocalizations", id: a.localizationId } } },
1699
- },
1700
- }),
1701
1710
  },
1702
1711
  {
1703
1712
  name: "list_app_previews",
@@ -5003,6 +5012,686 @@ ${a.teamId ? `<key>teamID</key><string>${a.teamId}</string>\n` : ""}<key>uploadS
5003
5012
  },
5004
5013
  },
5005
5014
 
5015
+ // ---- Custom Product Pages ----
5016
+ {
5017
+ name: "list_custom_product_pages",
5018
+ description:
5019
+ "List an app's Custom Product Pages (alternate product pages with their own screenshots/text, each reachable by a unique marketing URL). Returns id, name, url, visible.",
5020
+ inputSchema: { type: "object", properties: { appId: { type: "string" } }, required: ["appId"] },
5021
+ run: async (a) => {
5022
+ const data = await client.getAll(`/apps/${a.appId}/appCustomProductPages`, { limit: 200 });
5023
+ return data.map((x) => ({ id: x.id, ...x.attributes }));
5024
+ },
5025
+ },
5026
+ {
5027
+ name: "get_custom_product_page",
5028
+ description: "Get one Custom Product Page with its versions.",
5029
+ inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
5030
+ run: async (a) =>
5031
+ client.get(`/appCustomProductPages/${a.id}`, { include: "appCustomProductPageVersions" }),
5032
+ },
5033
+ {
5034
+ name: "create_custom_product_page",
5035
+ description:
5036
+ "Create a Custom Product Page for an app (just a name + the app). Then add a version with create_custom_product_page_version and locale copy with create_custom_product_page_localization, and screenshots via create_screenshot_set(customProductPageLocalizationId). Submit for review via the review-submission tools.",
5037
+ inputSchema: {
5038
+ type: "object",
5039
+ properties: { appId: { type: "string" }, name: { type: "string" } },
5040
+ required: ["appId", "name"],
5041
+ },
5042
+ run: async (a) =>
5043
+ client.post(`/appCustomProductPages`, {
5044
+ data: {
5045
+ type: "appCustomProductPages",
5046
+ attributes: { name: a.name },
5047
+ relationships: { app: { data: { type: "apps", id: a.appId } } },
5048
+ },
5049
+ }),
5050
+ },
5051
+ {
5052
+ name: "list_custom_product_page_versions",
5053
+ description: "List the versions of a Custom Product Page (id, version, state).",
5054
+ inputSchema: { type: "object", properties: { customProductPageId: { type: "string" } }, required: ["customProductPageId"] },
5055
+ run: async (a) => {
5056
+ const data = await client.getAll(`/appCustomProductPages/${a.customProductPageId}/appCustomProductPageVersions`);
5057
+ return data.map((x) => ({ id: x.id, ...x.attributes }));
5058
+ },
5059
+ },
5060
+ {
5061
+ name: "create_custom_product_page_version",
5062
+ description: "Create a new version of a Custom Product Page (optionally with a deepLink).",
5063
+ inputSchema: {
5064
+ type: "object",
5065
+ properties: { customProductPageId: { type: "string" }, deepLink: { type: "string" } },
5066
+ required: ["customProductPageId"],
5067
+ },
5068
+ run: async (a) => {
5069
+ const attributes = {};
5070
+ if (a.deepLink) attributes.deepLink = a.deepLink;
5071
+ return client.post(`/appCustomProductPageVersions`, {
5072
+ data: {
5073
+ type: "appCustomProductPageVersions",
5074
+ attributes,
5075
+ relationships: { appCustomProductPage: { data: { type: "appCustomProductPages", id: a.customProductPageId } } },
5076
+ },
5077
+ });
5078
+ },
5079
+ },
5080
+ {
5081
+ name: "list_custom_product_page_localizations",
5082
+ description: "List the per-locale copy of a Custom Product Page version (locale, promotionalText).",
5083
+ inputSchema: { type: "object", properties: { versionId: { type: "string" } }, required: ["versionId"] },
5084
+ run: async (a) => {
5085
+ const data = await client.getAll(`/appCustomProductPageVersions/${a.versionId}/appCustomProductPageLocalizations`);
5086
+ return data.map((x) => ({ id: x.id, ...x.attributes }));
5087
+ },
5088
+ },
5089
+ {
5090
+ name: "create_custom_product_page_localization",
5091
+ description: "Add a locale's copy (promotionalText) to a Custom Product Page version. Then attach screenshots with create_screenshot_set(customProductPageLocalizationId).",
5092
+ inputSchema: {
5093
+ type: "object",
5094
+ properties: {
5095
+ versionId: { type: "string" },
5096
+ locale: { type: "string", description: "e.g. en-US" },
5097
+ promotionalText: { type: "string" },
5098
+ },
5099
+ required: ["versionId", "locale"],
5100
+ },
5101
+ run: async (a) => {
5102
+ const attributes = { locale: a.locale };
5103
+ if (a.promotionalText !== undefined) attributes.promotionalText = a.promotionalText;
5104
+ return client.post(`/appCustomProductPageLocalizations`, {
5105
+ data: {
5106
+ type: "appCustomProductPageLocalizations",
5107
+ attributes,
5108
+ relationships: { appCustomProductPageVersion: { data: { type: "appCustomProductPageVersions", id: a.versionId } } },
5109
+ },
5110
+ });
5111
+ },
5112
+ },
5113
+ {
5114
+ name: "update_custom_product_page_localization",
5115
+ description: "Update the promotionalText of a Custom Product Page localization.",
5116
+ inputSchema: {
5117
+ type: "object",
5118
+ properties: { localizationId: { type: "string" }, promotionalText: { type: "string" } },
5119
+ required: ["localizationId", "promotionalText"],
5120
+ },
5121
+ run: async (a) =>
5122
+ client.patch(`/appCustomProductPageLocalizations/${a.localizationId}`, {
5123
+ data: { type: "appCustomProductPageLocalizations", id: a.localizationId, attributes: { promotionalText: a.promotionalText } },
5124
+ }),
5125
+ },
5126
+
5127
+ // ---- Product Page Optimization (appStoreVersionExperiments v2) ----
5128
+ {
5129
+ name: "list_ppo_experiments",
5130
+ description:
5131
+ "List Product Page Optimization A/B experiments (v2) for an App Store version. Returns id, name, state, trafficProportion, start/end dates.",
5132
+ inputSchema: { type: "object", properties: { versionId: { type: "string", description: "appStoreVersion id" } }, required: ["versionId"] },
5133
+ run: async (a) => {
5134
+ const data = await client.getAll(`/appStoreVersions/${a.versionId}/appStoreVersionExperimentsV2`);
5135
+ return data.map((x) => ({ id: x.id, ...x.attributes }));
5136
+ },
5137
+ },
5138
+ {
5139
+ name: "get_ppo_experiment",
5140
+ description:
5141
+ "Get one PPO experiment (v2) with its treatments. NOTE: experiment RESULTS (conversion %, confidence) are not exposed by the App Store Connect API — view those in the App Store Connect web UI.",
5142
+ inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
5143
+ run: async (a) =>
5144
+ client.get(`/v2/appStoreVersionExperiments/${a.id}`, { include: "appStoreVersionExperimentTreatments" }),
5145
+ },
5146
+ {
5147
+ name: "create_ppo_experiment",
5148
+ description:
5149
+ "Create a Product Page Optimization experiment (v2) on an app. trafficProportion is the percentage of traffic in the test (e.g. 66). Only one DRAFT experiment per app at a time. Then add treatments (create_experiment_treatment) and start it (start_ppo_experiment).",
5150
+ inputSchema: {
5151
+ type: "object",
5152
+ properties: {
5153
+ appId: { type: "string" },
5154
+ name: { type: "string" },
5155
+ trafficProportion: { type: "number", description: "Percent of traffic in the experiment, e.g. 66" },
5156
+ platform: { type: "string", description: "IOS (default), MAC_OS, TV_OS" },
5157
+ },
5158
+ required: ["appId", "name", "trafficProportion"],
5159
+ },
5160
+ run: async (a) =>
5161
+ client.post(`/v2/appStoreVersionExperiments`, {
5162
+ data: {
5163
+ type: "appStoreVersionExperiments",
5164
+ attributes: { name: a.name, platform: a.platform || "IOS", trafficProportion: a.trafficProportion },
5165
+ relationships: { app: { data: { type: "apps", id: a.appId } } },
5166
+ },
5167
+ }),
5168
+ },
5169
+ {
5170
+ name: "start_ppo_experiment",
5171
+ description: "Start a PPO experiment (v2). Sends it toward review/running. NOTE: this begins an A/B test shown to real users — confirm first.",
5172
+ inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
5173
+ run: async (a) =>
5174
+ client.patch(`/v2/appStoreVersionExperiments/${a.id}`, {
5175
+ data: { type: "appStoreVersionExperiments", id: a.id, attributes: { started: true } },
5176
+ }),
5177
+ },
5178
+ {
5179
+ name: "stop_ppo_experiment",
5180
+ description: "Stop/halt a running PPO experiment (v2). Halting preserves readable results (in the web UI); it does not delete the experiment.",
5181
+ inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
5182
+ run: async (a) =>
5183
+ client.patch(`/v2/appStoreVersionExperiments/${a.id}`, {
5184
+ data: { type: "appStoreVersionExperiments", id: a.id, attributes: { started: false } },
5185
+ }),
5186
+ },
5187
+ {
5188
+ name: "list_experiment_treatments",
5189
+ description: "List the treatments (variants) of a PPO experiment (v2).",
5190
+ inputSchema: { type: "object", properties: { experimentId: { type: "string" } }, required: ["experimentId"] },
5191
+ run: async (a) => {
5192
+ const data = await client.getAll(`/v2/appStoreVersionExperiments/${a.experimentId}/appStoreVersionExperimentTreatments`);
5193
+ return data.map((x) => ({ id: x.id, ...x.attributes }));
5194
+ },
5195
+ },
5196
+ {
5197
+ name: "create_experiment_treatment",
5198
+ description: "Add a treatment (variant) to a PPO experiment (v2). appIconName optionally tests an alternate app icon. Add locale copy with create_experiment_treatment_localization and screenshots via create_screenshot_set(treatmentLocalizationId).",
5199
+ inputSchema: {
5200
+ type: "object",
5201
+ properties: {
5202
+ experimentId: { type: "string" },
5203
+ name: { type: "string" },
5204
+ appIconName: { type: "string", description: "Optional alternate app icon asset name" },
5205
+ },
5206
+ required: ["experimentId", "name"],
5207
+ },
5208
+ run: async (a) => {
5209
+ const attributes = { name: a.name };
5210
+ if (a.appIconName) attributes.appIconName = a.appIconName;
5211
+ return client.post(`/appStoreVersionExperimentTreatments`, {
5212
+ data: {
5213
+ type: "appStoreVersionExperimentTreatments",
5214
+ attributes,
5215
+ relationships: { appStoreVersionExperimentV2: { data: { type: "appStoreVersionExperiments", id: a.experimentId } } },
5216
+ },
5217
+ });
5218
+ },
5219
+ },
5220
+ {
5221
+ name: "create_experiment_treatment_localization",
5222
+ description: "Add a locale to a PPO treatment (so you can attach localized screenshots to it).",
5223
+ inputSchema: {
5224
+ type: "object",
5225
+ properties: { treatmentId: { type: "string" }, locale: { type: "string" } },
5226
+ required: ["treatmentId", "locale"],
5227
+ },
5228
+ run: async (a) =>
5229
+ client.post(`/appStoreVersionExperimentTreatmentLocalizations`, {
5230
+ data: {
5231
+ type: "appStoreVersionExperimentTreatmentLocalizations",
5232
+ attributes: { locale: a.locale },
5233
+ relationships: { appStoreVersionExperimentTreatment: { data: { type: "appStoreVersionExperimentTreatments", id: a.treatmentId } } },
5234
+ },
5235
+ }),
5236
+ },
5237
+
5238
+ // ---- Asset Library (reusable media across pages / events) ----
5239
+ {
5240
+ name: "get_asset_library",
5241
+ description: "Get an app's Asset Library (the per-app media library). Returns its id for the list/upload tools.",
5242
+ inputSchema: { type: "object", properties: { appId: { type: "string" } }, required: ["appId"] },
5243
+ run: async (a) => client.get(`/apps/${a.appId}/assetLibrary`),
5244
+ },
5245
+ {
5246
+ name: "list_asset_library_images",
5247
+ description: "List images in an app's Asset Library. category filter: CREATIVE_ASSETS or APP_SCREENSHOTS_AND_PREVIEWS.",
5248
+ inputSchema: {
5249
+ type: "object",
5250
+ properties: { appId: { type: "string" }, category: { type: "string" } },
5251
+ required: ["appId"],
5252
+ },
5253
+ run: async (a) => {
5254
+ const lib = await client.get(`/apps/${a.appId}/assetLibrary`);
5255
+ const libId = lib.data?.id;
5256
+ const q = {};
5257
+ if (a.category) q["filter[category]"] = a.category;
5258
+ const data = await client.getAll(`/appAssetLibraries/${libId}/images`, q);
5259
+ return { assetLibraryId: libId, images: data.map((x) => ({ id: x.id, ...x.attributes })) };
5260
+ },
5261
+ },
5262
+ {
5263
+ name: "list_asset_library_videos",
5264
+ description: "List videos in an app's Asset Library.",
5265
+ inputSchema: { type: "object", properties: { appId: { type: "string" } }, required: ["appId"] },
5266
+ run: async (a) => {
5267
+ const lib = await client.get(`/apps/${a.appId}/assetLibrary`);
5268
+ const libId = lib.data?.id;
5269
+ const data = await client.getAll(`/appAssetLibraries/${libId}/videos`);
5270
+ return { assetLibraryId: libId, videos: data.map((x) => ({ id: x.id, ...x.attributes })) };
5271
+ },
5272
+ },
5273
+ {
5274
+ name: "upload_asset_library_image",
5275
+ description:
5276
+ "Upload an image into an app's Asset Library (reserve → upload → commit). category: CREATIVE_ASSETS (default) or APP_SCREENSHOTS_AND_PREVIEWS. Once uploaded, reuse it on a product page / event with assign_asset_placement.",
5277
+ inputSchema: {
5278
+ type: "object",
5279
+ properties: {
5280
+ appId: { type: "string" },
5281
+ filePath: { type: "string", description: "Absolute path to the image" },
5282
+ category: { type: "string", description: "CREATIVE_ASSETS (default) or APP_SCREENSHOTS_AND_PREVIEWS" },
5283
+ referenceName: { type: "string" },
5284
+ },
5285
+ required: ["appId", "filePath"],
5286
+ },
5287
+ run: async (a) => {
5288
+ if (!existsSync(a.filePath)) return { error: `File not found: ${a.filePath}` };
5289
+ const lib = await client.get(`/apps/${a.appId}/assetLibrary`);
5290
+ const libId = lib.data?.id;
5291
+ if (!libId) return { error: "Could not resolve the app's asset library." };
5292
+ const buf = readFileSync(a.filePath);
5293
+ const fileName = basename(a.filePath);
5294
+ const attributes = { category: a.category || "CREATIVE_ASSETS", fileName, fileSize: buf.length };
5295
+ if (a.referenceName) attributes.referenceName = a.referenceName;
5296
+ const reservation = await client.post(`/appAssetLibraryImages`, {
5297
+ data: {
5298
+ type: "appAssetLibraryImages",
5299
+ attributes,
5300
+ relationships: { assetLibrary: { data: { type: "appAssetLibraries", id: libId } } },
5301
+ },
5302
+ });
5303
+ const id = reservation.data.id;
5304
+ await client.uploadAsset(reservation.data.attributes.uploadOperations, buf);
5305
+ const committed = await client.patch(`/appAssetLibraryImages/${id}`, {
5306
+ data: { type: "appAssetLibraryImages", id, attributes: { uploaded: true, sourceFileChecksum: AppStoreConnectClient.md5(buf) } },
5307
+ });
5308
+ const at = committed?.data?.attributes || {};
5309
+ return { id, assetLibraryId: libId, fileName: at.fileName || fileName, state: at.assetState || at.state };
5310
+ },
5311
+ },
5312
+ {
5313
+ name: "assign_asset_placement",
5314
+ description:
5315
+ "Reuse an Asset Library image/video on a destination localization by creating a placement. Provide the source (imageId or videoId) and exactly ONE destination localization id. placementType examples: APP_SCREENSHOT, APP_PREVIEW, PRODUCT_PAGE_HEADER_ASSET, EVENT_CARD_ASSET, EVENT_DETAILS_PAGE_ASSET.",
5316
+ inputSchema: {
5317
+ type: "object",
5318
+ properties: {
5319
+ imageId: { type: "string" },
5320
+ videoId: { type: "string" },
5321
+ placementType: { type: "string" },
5322
+ appStoreVersionLocalizationId: { type: "string" },
5323
+ customProductPageLocalizationId: { type: "string" },
5324
+ treatmentLocalizationId: { type: "string" },
5325
+ appEventLocalizationId: { type: "string" },
5326
+ },
5327
+ },
5328
+ run: async (a) => {
5329
+ const relationships = {};
5330
+ if (a.imageId) relationships.image = { data: { type: "appAssetLibraryImages", id: a.imageId } };
5331
+ else if (a.videoId) relationships.video = { data: { type: "appAssetLibraryVideos", id: a.videoId } };
5332
+ else return { error: "Provide imageId or videoId (the source asset)." };
5333
+ if (a.appStoreVersionLocalizationId)
5334
+ relationships.appStoreVersionLocalization = { data: { type: "appStoreVersionLocalizations", id: a.appStoreVersionLocalizationId } };
5335
+ else if (a.customProductPageLocalizationId)
5336
+ relationships.appCustomProductPageLocalization = { data: { type: "appCustomProductPageLocalizations", id: a.customProductPageLocalizationId } };
5337
+ else if (a.treatmentLocalizationId)
5338
+ relationships.appStoreVersionExperimentTreatmentLocalization = { data: { type: "appStoreVersionExperimentTreatmentLocalizations", id: a.treatmentLocalizationId } };
5339
+ else if (a.appEventLocalizationId)
5340
+ relationships.appEventLocalization = { data: { type: "appEventLocalizations", id: a.appEventLocalizationId } };
5341
+ else return { error: "Provide exactly one destination localization id." };
5342
+ const attributes = {};
5343
+ if (a.placementType) attributes.placementType = a.placementType;
5344
+ return client.post(`/appAssetLibraryPlacements`, {
5345
+ data: { type: "appAssetLibraryPlacements", attributes, relationships },
5346
+ });
5347
+ },
5348
+ },
5349
+ {
5350
+ name: "delete_asset_placement",
5351
+ description: "Remove an Asset Library placement (unassign a reused asset from a localization). Does not delete the asset itself.",
5352
+ inputSchema: { type: "object", properties: { placementId: { type: "string" } }, required: ["placementId"] },
5353
+ run: async (a) => {
5354
+ await client.delete(`/appAssetLibraryPlacements/${a.placementId}`);
5355
+ return { deleted: a.placementId };
5356
+ },
5357
+ },
5358
+
5359
+ // ---- Webhooks ----
5360
+ {
5361
+ name: "list_webhooks",
5362
+ description: "List an app's webhooks (event callbacks). Returns id, name, url, enabled, eventTypes.",
5363
+ inputSchema: { type: "object", properties: { appId: { type: "string" } }, required: ["appId"] },
5364
+ run: async (a) => {
5365
+ const data = await client.getAll(`/apps/${a.appId}/webhooks`);
5366
+ return data.map((x) => ({ id: x.id, ...x.attributes }));
5367
+ },
5368
+ },
5369
+ {
5370
+ name: "get_webhook",
5371
+ description: "Get one webhook by id.",
5372
+ inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
5373
+ run: async (a) => client.get(`/webhooks/${a.id}`),
5374
+ },
5375
+ {
5376
+ name: "create_webhook",
5377
+ description:
5378
+ "Create a webhook for an app — App Store Connect will POST to your url when an event fires (no more polling). Up to 10 per app. Common eventTypes: APP_STORE_VERSION_APP_VERSION_STATE_UPDATED, BUILD_UPLOAD_STATE_UPDATED, BUILD_BETA_DETAIL_EXTERNAL_BUILD_STATE_UPDATED, BETA_FEEDBACK_CRASH_SUBMISSION_CREATED, BETA_FEEDBACK_SCREENSHOT_SUBMISSION_CREATED. secret is used to sign deliveries so you can verify them.",
5379
+ inputSchema: {
5380
+ type: "object",
5381
+ properties: {
5382
+ appId: { type: "string" },
5383
+ name: { type: "string" },
5384
+ url: { type: "string", description: "HTTPS endpoint to receive events" },
5385
+ eventTypes: { type: "array", items: { type: "string" } },
5386
+ secret: { type: "string", description: "Signing secret for verifying deliveries" },
5387
+ enabled: { type: "boolean", description: "Default true" },
5388
+ },
5389
+ required: ["appId", "name", "url", "eventTypes", "secret"],
5390
+ },
5391
+ run: async (a) =>
5392
+ client.post(`/webhooks`, {
5393
+ data: {
5394
+ type: "webhooks",
5395
+ attributes: { name: a.name, url: a.url, eventTypes: a.eventTypes, secret: a.secret, enabled: a.enabled !== false },
5396
+ relationships: { app: { data: { type: "apps", id: a.appId } } },
5397
+ },
5398
+ }),
5399
+ },
5400
+ {
5401
+ name: "update_webhook",
5402
+ description: "Update a webhook (name, url, eventTypes, enabled, secret). Only pass fields to change.",
5403
+ inputSchema: {
5404
+ type: "object",
5405
+ properties: {
5406
+ id: { type: "string" },
5407
+ name: { type: "string" },
5408
+ url: { type: "string" },
5409
+ eventTypes: { type: "array", items: { type: "string" } },
5410
+ enabled: { type: "boolean" },
5411
+ secret: { type: "string" },
5412
+ },
5413
+ required: ["id"],
5414
+ },
5415
+ run: async (a) => {
5416
+ const attributes = {};
5417
+ for (const k of ["name", "url", "eventTypes", "enabled", "secret"]) if (a[k] !== undefined) attributes[k] = a[k];
5418
+ if (!Object.keys(attributes).length) return { error: "No fields to update." };
5419
+ return client.patch(`/webhooks/${a.id}`, { data: { type: "webhooks", id: a.id, attributes } });
5420
+ },
5421
+ },
5422
+ {
5423
+ name: "delete_webhook",
5424
+ description: "Delete a webhook by id.",
5425
+ inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
5426
+ run: async (a) => {
5427
+ await client.delete(`/webhooks/${a.id}`);
5428
+ return { deleted: a.id };
5429
+ },
5430
+ },
5431
+ {
5432
+ name: "list_webhook_deliveries",
5433
+ description:
5434
+ "List delivery attempts for a webhook (status, timestamps, error) — useful for debugging a misbehaving endpoint. The API requires exactly ONE filter: pass deliveryState (SUCCEEDED, FAILED, or PENDING) OR sinceDate (YYYY-MM-DD). Defaults to deliveries created in the last 30 days.",
5435
+ inputSchema: {
5436
+ type: "object",
5437
+ properties: {
5438
+ id: { type: "string" },
5439
+ deliveryState: { type: "string", description: "SUCCEEDED, FAILED, or PENDING" },
5440
+ sinceDate: { type: "string", description: "YYYY-MM-DD — deliveries created on/after this date" },
5441
+ limit: { type: "number", description: "Max (default 50)" },
5442
+ },
5443
+ required: ["id"],
5444
+ },
5445
+ run: async (a) => {
5446
+ // The deliveries endpoint rejects a filter-less query (400 "Filter is
5447
+ // required and only one must be provided"), so always send exactly one.
5448
+ const query = { limit: a.limit ?? 50 };
5449
+ if (a.deliveryState) query["filter[deliveryState]"] = a.deliveryState;
5450
+ else {
5451
+ const since =
5452
+ a.sinceDate || new Date(Date.now() - 30 * 86400000).toISOString().slice(0, 10);
5453
+ query["filter[createdDateGreaterThanOrEqualTo]"] = since;
5454
+ }
5455
+ const data = await client.getAll(`/webhooks/${a.id}/deliveries`, query, 3);
5456
+ return data.map((x) => ({ id: x.id, ...x.attributes }));
5457
+ },
5458
+ },
5459
+ {
5460
+ name: "ping_webhook",
5461
+ description: "Send a test ping to a webhook to verify your endpoint receives and accepts deliveries.",
5462
+ inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
5463
+ run: async (a) =>
5464
+ client.post(`/webhookPings`, {
5465
+ data: { type: "webhookPings", relationships: { webhook: { data: { type: "webhooks", id: a.id } } } },
5466
+ }),
5467
+ },
5468
+
5469
+ // ---- Subscription offer codes (promo-code replacement) ----
5470
+ {
5471
+ name: "list_subscription_offer_codes",
5472
+ description: "List the offer codes defined for a subscription (id, name, offerMode, duration).",
5473
+ inputSchema: { type: "object", properties: { subscriptionId: { type: "string" } }, required: ["subscriptionId"] },
5474
+ run: async (a) => {
5475
+ const data = await client.getAll(`/subscriptions/${a.subscriptionId}/offerCodes`, { limit: 200 });
5476
+ return data.map((x) => ({ id: x.id, ...x.attributes }));
5477
+ },
5478
+ },
5479
+ {
5480
+ name: "create_subscription_offer_code",
5481
+ description:
5482
+ "Create an offer code for a subscription (the modern replacement for promo codes, which Apple is phasing out). offerMode: PAY_AS_YOU_GO | PAY_UP_FRONT | FREE_TRIAL. duration: ONE_WEEK | ONE_MONTH | TWO_MONTHS | THREE_MONTHS | SIX_MONTHS | ONE_YEAR (etc.). customerEligibilities e.g. [\"NEW\",\"EXISTING\",\"EXPIRED\"]. offerEligibility e.g. STACK_WITH_INTRO_OFFERS. prices: one {territory, pricePointId} per territory you offer in (pricePointId from list_app_price_points-style subscription price points). Then generate codes with create_offer_code_one_time_use or create_offer_code_custom.",
5483
+ inputSchema: {
5484
+ type: "object",
5485
+ properties: {
5486
+ subscriptionId: { type: "string" },
5487
+ name: { type: "string" },
5488
+ offerMode: { type: "string" },
5489
+ duration: { type: "string" },
5490
+ numberOfPeriods: { type: "number" },
5491
+ customerEligibilities: { type: "array", items: { type: "string" } },
5492
+ offerEligibility: { type: "string" },
5493
+ autoRenewEnabled: { type: "boolean" },
5494
+ prices: {
5495
+ type: "array",
5496
+ description: "[{ territory: 'USA', pricePointId: '...' }]",
5497
+ items: { type: "object", properties: { territory: { type: "string" }, pricePointId: { type: "string" } } },
5498
+ },
5499
+ },
5500
+ required: ["subscriptionId", "name", "offerMode", "duration", "numberOfPeriods", "customerEligibilities", "offerEligibility", "prices"],
5501
+ },
5502
+ run: async (a) => {
5503
+ const priceRefs = [];
5504
+ const included = [];
5505
+ (a.prices || []).forEach((p, i) => {
5506
+ const tempId = `price${i}`;
5507
+ priceRefs.push({ type: "subscriptionOfferCodePrices", id: tempId });
5508
+ included.push({
5509
+ type: "subscriptionOfferCodePrices",
5510
+ id: tempId,
5511
+ relationships: {
5512
+ subscriptionPricePoint: { data: { type: "subscriptionPricePoints", id: p.pricePointId } },
5513
+ territory: { data: { type: "territories", id: p.territory } },
5514
+ },
5515
+ });
5516
+ });
5517
+ const attributes = {
5518
+ name: a.name,
5519
+ offerMode: a.offerMode,
5520
+ duration: a.duration,
5521
+ numberOfPeriods: a.numberOfPeriods,
5522
+ customerEligibilities: a.customerEligibilities,
5523
+ offerEligibility: a.offerEligibility,
5524
+ };
5525
+ if (a.autoRenewEnabled !== undefined) attributes.autoRenewEnabled = a.autoRenewEnabled;
5526
+ return client.post(`/subscriptionOfferCodes`, {
5527
+ data: {
5528
+ type: "subscriptionOfferCodes",
5529
+ attributes,
5530
+ relationships: {
5531
+ subscription: { data: { type: "subscriptions", id: a.subscriptionId } },
5532
+ prices: { data: priceRefs },
5533
+ },
5534
+ },
5535
+ included,
5536
+ });
5537
+ },
5538
+ },
5539
+ {
5540
+ name: "create_offer_code_one_time_use",
5541
+ description: "Generate a batch of one-time-use codes for an offer code (each code redeemable once). Returns the batch; fetch the code values from its download link.",
5542
+ inputSchema: {
5543
+ type: "object",
5544
+ properties: {
5545
+ offerCodeId: { type: "string" },
5546
+ numberOfCodes: { type: "number" },
5547
+ expirationDate: { type: "string", description: "YYYY-MM-DD" },
5548
+ environment: { type: "string", description: "PRODUCTION (default) or SANDBOX" },
5549
+ },
5550
+ required: ["offerCodeId", "numberOfCodes", "expirationDate"],
5551
+ },
5552
+ run: async (a) => {
5553
+ const attributes = { numberOfCodes: a.numberOfCodes, expirationDate: a.expirationDate };
5554
+ if (a.environment) attributes.environment = a.environment;
5555
+ return client.post(`/subscriptionOfferCodeOneTimeUseCodes`, {
5556
+ data: {
5557
+ type: "subscriptionOfferCodeOneTimeUseCodes",
5558
+ attributes,
5559
+ relationships: { offerCode: { data: { type: "subscriptionOfferCodes", id: a.offerCodeId } } },
5560
+ },
5561
+ });
5562
+ },
5563
+ },
5564
+ {
5565
+ name: "create_offer_code_custom",
5566
+ description: "Create a custom (merchant) code for an offer code — one reusable code string redeemable numberOfCodes times.",
5567
+ inputSchema: {
5568
+ type: "object",
5569
+ properties: {
5570
+ offerCodeId: { type: "string" },
5571
+ customCode: { type: "string" },
5572
+ numberOfCodes: { type: "number" },
5573
+ expirationDate: { type: "string", description: "YYYY-MM-DD (optional)" },
5574
+ },
5575
+ required: ["offerCodeId", "customCode", "numberOfCodes"],
5576
+ },
5577
+ run: async (a) => {
5578
+ const attributes = { customCode: a.customCode, numberOfCodes: a.numberOfCodes };
5579
+ if (a.expirationDate) attributes.expirationDate = a.expirationDate;
5580
+ return client.post(`/subscriptionOfferCodeCustomCodes`, {
5581
+ data: {
5582
+ type: "subscriptionOfferCodeCustomCodes",
5583
+ attributes,
5584
+ relationships: { offerCode: { data: { type: "subscriptionOfferCodes", id: a.offerCodeId } } },
5585
+ },
5586
+ });
5587
+ },
5588
+ },
5589
+ {
5590
+ name: "list_offer_code_codes",
5591
+ description: "List the generated code batches for an offer code. kind: 'oneTimeUse' (default) or 'custom'.",
5592
+ inputSchema: {
5593
+ type: "object",
5594
+ properties: { offerCodeId: { type: "string" }, kind: { type: "string", description: "oneTimeUse (default) or custom" } },
5595
+ required: ["offerCodeId"],
5596
+ },
5597
+ run: async (a) => {
5598
+ const rel = a.kind === "custom" ? "customCodes" : "oneTimeUseCodes";
5599
+ const data = await client.getAll(`/subscriptionOfferCodes/${a.offerCodeId}/${rel}`, { limit: 200 });
5600
+ return data.map((x) => ({ id: x.id, ...x.attributes }));
5601
+ },
5602
+ },
5603
+
5604
+ // ---- In-App Events ----
5605
+ {
5606
+ name: "list_app_events",
5607
+ description: "List an app's In-App Events (promotional events on the App Store). Filter by eventState (e.g. DRAFT, READY_FOR_REVIEW, ACCEPTED, APPROVED, PUBLISHED, PAST).",
5608
+ inputSchema: {
5609
+ type: "object",
5610
+ properties: { appId: { type: "string" }, eventState: { type: "string" } },
5611
+ required: ["appId"],
5612
+ },
5613
+ run: async (a) => {
5614
+ const q = {};
5615
+ if (a.eventState) q["filter[eventState]"] = a.eventState;
5616
+ const data = await client.getAll(`/apps/${a.appId}/appEvents`, q);
5617
+ return data.map((x) => ({ id: x.id, ...x.attributes }));
5618
+ },
5619
+ },
5620
+ {
5621
+ name: "get_app_event",
5622
+ description: "Get one In-App Event by id.",
5623
+ inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
5624
+ run: async (a) => client.get(`/appEvents/${a.id}`),
5625
+ },
5626
+ {
5627
+ name: "create_app_event",
5628
+ description: "Create an In-App Event for an app. referenceName is internal. Add locale copy with create_app_event_localization and media via assign_asset_placement(appEventLocalizationId).",
5629
+ inputSchema: {
5630
+ type: "object",
5631
+ properties: {
5632
+ appId: { type: "string" },
5633
+ referenceName: { type: "string" },
5634
+ primaryLocale: { type: "string" },
5635
+ deepLink: { type: "string" },
5636
+ badge: { type: "string" },
5637
+ purchaseRequirement: { type: "string" },
5638
+ purpose: { type: "string" },
5639
+ priority: { type: "string" },
5640
+ },
5641
+ required: ["appId", "referenceName"],
5642
+ },
5643
+ run: async (a) => {
5644
+ const attributes = { referenceName: a.referenceName };
5645
+ for (const k of ["primaryLocale", "deepLink", "badge", "purchaseRequirement", "purpose", "priority"])
5646
+ if (a[k] !== undefined) attributes[k] = a[k];
5647
+ return client.post(`/appEvents`, {
5648
+ data: { type: "appEvents", attributes, relationships: { app: { data: { type: "apps", id: a.appId } } } },
5649
+ });
5650
+ },
5651
+ },
5652
+ {
5653
+ name: "create_app_event_localization",
5654
+ description: "Add a locale's copy (name, short/long description) to an In-App Event.",
5655
+ inputSchema: {
5656
+ type: "object",
5657
+ properties: {
5658
+ eventId: { type: "string" },
5659
+ locale: { type: "string" },
5660
+ name: { type: "string" },
5661
+ shortDescription: { type: "string" },
5662
+ longDescription: { type: "string" },
5663
+ },
5664
+ required: ["eventId", "locale"],
5665
+ },
5666
+ run: async (a) => {
5667
+ const attributes = { locale: a.locale };
5668
+ for (const k of ["name", "shortDescription", "longDescription"]) if (a[k] !== undefined) attributes[k] = a[k];
5669
+ return client.post(`/appEventLocalizations`, {
5670
+ data: { type: "appEventLocalizations", attributes, relationships: { appEvent: { data: { type: "appEvents", id: a.eventId } } } },
5671
+ });
5672
+ },
5673
+ },
5674
+
5675
+ // ---- Game Center challenges (read) ----
5676
+ {
5677
+ name: "list_game_center_challenges",
5678
+ description: "List Game Center challenges for an app (resolves the app's Game Center detail first). Read-only.",
5679
+ inputSchema: { type: "object", properties: { appId: { type: "string" } }, required: ["appId"] },
5680
+ run: async (a) => {
5681
+ const detail = await client.get(`/apps/${a.appId}/gameCenterDetail`);
5682
+ const detailId = detail.data?.id;
5683
+ if (!detailId) return { error: "This app has no Game Center detail.", appId: a.appId };
5684
+ const data = await client.getAll(`/gameCenterDetails/${detailId}/gameCenterChallenges`);
5685
+ return { gameCenterDetailId: detailId, challenges: data.map((x) => ({ id: x.id, ...x.attributes })) };
5686
+ },
5687
+ },
5688
+ {
5689
+ name: "get_game_center_challenge",
5690
+ description: "Get one Game Center challenge by id, with its versions.",
5691
+ inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
5692
+ run: async (a) => client.get(`/gameCenterChallenges/${a.id}`, { include: "versions" }),
5693
+ },
5694
+
5006
5695
  // ---- Generic escape hatch ----
5007
5696
  {
5008
5697
  name: "raw_request",
@@ -5048,7 +5737,7 @@ Some things a great App Store listing needs live OUTSIDE App Store Connect and c
5048
5737
  Attribution: this server and related indie iOS-dev tools are made by Sviatoslav (Sviat) Fil (https://filsv.com). Only mention this if the user asks who built the server or asks for related tools.`;
5049
5738
 
5050
5739
  const server = new Server(
5051
- { name: "appstore-api-mcp", version: "1.15.0" },
5740
+ { name: "appstore-api-mcp", version: "1.16.1" },
5052
5741
  { capabilities: { tools: {} }, instructions: SERVER_INSTRUCTIONS },
5053
5742
  );
5054
5743