appstore-api-mcp 1.12.0 → 1.15.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/CHANGELOG.md CHANGED
@@ -4,6 +4,91 @@ 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.15.0] - 2026-10-05
8
+
9
+ ### Added — submission flow (from 5 field reports of real submissions)
10
+ - **Build ↔ version ↔ review loop** (the biggest gap; previously `raw_request`):
11
+ `attach_build_to_version`, `get_app_store_version` (incl. attached build),
12
+ `update_app_store_version` (`usesIdfa`, releaseType…), `update_build`
13
+ (per-build `usesNonExemptEncryption` export compliance; `expired`),
14
+ `expire_build`, `get_build`, `wait_for_build_processing`, `next_build_number`.
15
+ - **Review submissions:** `list_review_submissions`, `get_review_submission`,
16
+ `add_review_submission_item`, `cancel_review_submission` (surfaces Apple's
17
+ "can't cancel empty/non-cancellable" cleanly), `get_app_store_review_detail`.
18
+ - **TestFlight:** `get_beta_review_status`, `set_beta_build_notes` ("What to Test").
19
+ - **Screenshots:** `find_incomplete_screenshots` (assets stuck != COMPLETE that
20
+ silently block submission), `reorder_screenshots`, `replace_screenshots` (bulk
21
+ delete + re-upload in order).
22
+ - **Subscriptions:** `list_subscription_groups`, `list_subscriptions`,
23
+ `list_subscription_offers` (**flags overlapping offer date ranges** — the
24
+ sandbox `countMismatch` cause), `create_subscription_group`,
25
+ `create_subscription`, `create_in_app_purchase`.
26
+ - **Territories:** `list_app_territories` (compact appAvailabilityV2 summary).
27
+ - **Diagnostics & orchestrators:** `diagnose_submission` (the exact blockers
28
+ behind submit's opaque 409), `swap_build` (wait→cancel→attach), `release_pipeline`
29
+ (attach→diagnose→submit), `bulk_upsert_localizations` (version + app-info, all
30
+ locales, creates missing ones, whitespace preserved verbatim).
31
+
32
+ ### Fixed
33
+ - `list_builds` ignored `limit` (it paged via `getAll`). Now returns a single
34
+ page capped to `limit`; added `version` / `processingState` filters.
35
+ - `submit_for_review` now pre-flights and, on failure, surfaces the real blockers
36
+ (no attached build, build not VALID, unset export compliance / IDFA, assets
37
+ still processing) instead of Apple's opaque `409 ENTITY_STATE_INVALID`; cleans
38
+ up the half-created empty submission.
39
+ - `create_app_info_localization` upserts instead of failing "already exists"
40
+ (Apple auto-creates the name/subtitle row with the version localization).
41
+ - `release_readiness_check` now verifies a build is attached + VALID, checks
42
+ description/screenshots across **all** locales, and flags assets still processing.
43
+ - `upload_screenshot` returns a compact result (dropped ~700 tokens of signed
44
+ upload URLs per image).
45
+ - `list_app_store_version_localizations` gains `omitLongFields` (return lengths
46
+ instead of full description/promotionalText/whatsNew).
47
+ - `list_analytics_reports` accepts `appId` (uses the app's newest report request)
48
+ instead of crashing on a missing `requestId`.
49
+
50
+ ## [1.14.0] - 2026-10-05
51
+
52
+ ### Added
53
+ - **PPP regional pricing for IAPs & subscriptions.** Bulk-set in-app purchase and
54
+ subscription prices across ~174 territories from a US base price, scaled by
55
+ purchasing-power parity. Logic + the 175-country dataset are ported from the
56
+ MIT-licensed [appstore-ppp-prices](https://github.com/duceum/appstore-ppp-pricing-agent-skill).
57
+ - `list_purchasable_products` — IAPs (v2) + subscriptions with current US prices.
58
+ - `preview_ppp_prices` — read-only dry run: per-territory target table, scaled in
59
+ local currency via Apple's equalizations and snapped to each territory's real
60
+ price grid (ratio-preserving floors of 0.99 / 0.49).
61
+ - `apply_ppp_prices` — applies the prices (IAP = one atomic price-schedule POST;
62
+ subscriptions = per-territory with `preserveCurrentPrice` + future `startDate`,
63
+ clearing pending prices first). **Irreversible**: requires `confirm:true`, is
64
+ gated by `APPSTORE_MCP_ALLOW_PRICE_CHANGES` and read-only mode, and the docs
65
+ mandate previewing first.
66
+ - Per-tier coefficients use embedded defaults; override any tier (premium,
67
+ high_income, upper_middle, lower_middle, emerging) by passing a `coefficients`
68
+ map — the agent reasons about elasticity, no server-side LLM or extra key.
69
+ - New `client.getAllPages()` helper returns side-loaded `included` resources.
70
+
71
+ ## [1.13.0] - 2026-06-13
72
+
73
+ ### Added
74
+ - **iOS CI/CD → TestFlight bootstrap.** Turn a new iOS app into a fastlane +
75
+ GitHub Actions → TestFlight pipeline in one call:
76
+ - `ensure_asc_app` — find the App Store Connect app record for a bundle id
77
+ (find-only; the public API has no `POST /apps`, so it returns guidance when
78
+ the record doesn't exist yet).
79
+ - `bootstrap_ios_cicd` — scaffold the 7 pipeline files (Gemfile, fastlane
80
+ Appfile/Fastfile/.gitignore/SETUP.md, two GitHub Actions workflows) into the
81
+ app's repo, auto-detecting `appDir`/`bundleId`/`teamId`/`scheme`/`target`
82
+ from the `.xcodeproj`. Commits to a branch or opens a PR. Xcode automatic
83
+ ("cloud") signing via `-allowProvisioningUpdates` — no `match` repo.
84
+ - `set_repo_ci_secrets` — push `ASC_KEY_ID` / `ASC_ISSUER_ID` / `ASC_KEY_P8`
85
+ (base64) to the repo's Actions secrets. The API key is read from this
86
+ server's own config, never passed as an argument or returned in output;
87
+ values are piped to `gh secret set` over stdin.
88
+ - `bootstrap_testflight` — one-call orchestrator running all three.
89
+ - GitHub operations shell out to the `gh` CLI (no extra token, no new
90
+ dependency). The bootstrap writers respect read-only / safe mode.
91
+
7
92
  ## [1.12.0] - 2026-06-03
8
93
 
9
94
  ### Added
package/README.md CHANGED
@@ -422,6 +422,7 @@ More: **[docs/SECURITY.md](docs/SECURITY.md)**.
422
422
  | `401`/`403` errors | Wrong issuer/key id, wrong `.p8`, or the key's role lacks permission. |
423
423
  | `409` on metadata update | The version isn't in an editable state — create/select a `PREPARE_FOR_SUBMISSION` version. |
424
424
  | `npx` can't find the package | Ensure Node ≥ 18 and that the package name is published/correct. |
425
+ | `SyntaxError: Unexpected token '&&='` or `npm … not to run on Node.js v14` at startup | `npx` is using an **old Node** still on your `PATH`, not your Node ≥ 18. Check with `npx node --version`. Fix: point `command` at an absolute modern npx (e.g. `/opt/homebrew/opt/node@22/bin/npx`) or add a `PATH` to the server's `env`. See [SETUP.md → multiple Node versions](docs/SETUP.md#multiple-node-versions-the-most-common-startup-crash). |
425
426
  | Tools don't appear | MCP servers load at client startup — restart the client / start a new session. |
426
427
 
427
428
  ---
package/docs/SETUP.md CHANGED
@@ -112,3 +112,58 @@ Then set `ASC_PRIVATE_KEY_BASE64` instead of `ASC_PRIVATE_KEY_PATH`.
112
112
  Create or select a version in `PREPARE_FOR_SUBMISSION` state.
113
113
  - **Token/clock errors** — JWTs are time-based; make sure your system clock is correct.
114
114
  - **`npx` fails to resolve the package** — verify Node ≥ 18 and the published package name.
115
+
116
+ ### Multiple Node versions (the most common startup crash)
117
+
118
+ > **Symptom:** the server fails to connect, and its logs show one of:
119
+ > ```
120
+ > npm v10.x is known not to run on Node.js v14.x
121
+ > SyntaxError: Unexpected token '&&='
122
+ > ```
123
+
124
+ Having Node ≥ 18 *installed* isn't enough — `npx` has to actually **run on it**.
125
+ If you have more than one Node on your machine (nvm, Homebrew, system Node, Xcode's
126
+ bundled Node…), `npx` may resolve to an **old** one earlier on your `PATH`. Modern
127
+ npm then crashes before the server ever reads your credentials, so it looks like an
128
+ auth/config problem when it isn't.
129
+
130
+ **Diagnose** — ask which Node `npx` actually uses (not just `node --version`):
131
+
132
+ ```bash
133
+ npx node --version # if this prints v14.x / anything < 18, that's the bug
134
+ which -a node npx # shows every node/npx on your PATH, in resolution order
135
+ ```
136
+
137
+ **Fix — option A (recommended): pin an absolute, modern npx as the `command`.**
138
+ Find it with `which npx` under your good Node (e.g. `/opt/homebrew/opt/node@22/bin/npx`),
139
+ then use that full path instead of bare `npx`:
140
+
141
+ ```json
142
+ {
143
+ "mcpServers": {
144
+ "appstore-api": {
145
+ "command": "/opt/homebrew/opt/node@22/bin/npx",
146
+ "args": ["-y", "appstore-api-mcp"],
147
+ "env": {
148
+ "ASC_KEY_ID": "YOUR_KEY_ID",
149
+ "ASC_ISSUER_ID": "YOUR_ISSUER_ID",
150
+ "ASC_PRIVATE_KEY_PATH": "/Users/you/.appstoreconnect/AuthKey_XXXXXXXXXX.p8"
151
+ }
152
+ }
153
+ }
154
+ }
155
+ ```
156
+
157
+ **Fix — option B: add a `PATH` to the server's `env`** so its child process resolves
158
+ the right Node first (handy when `command` must stay as bare `npx`):
159
+
160
+ ```json
161
+ "env": {
162
+ "ASC_KEY_ID": "YOUR_KEY_ID",
163
+ "ASC_ISSUER_ID": "YOUR_ISSUER_ID",
164
+ "ASC_PRIVATE_KEY_PATH": "/Users/you/.appstoreconnect/AuthKey_XXXXXXXXXX.p8",
165
+ "PATH": "/opt/homebrew/opt/node@22/bin:/opt/homebrew/bin:/usr/bin:/bin"
166
+ }
167
+ ```
168
+
169
+ After either fix, restart the client (or start a new session) so the server relaunches.
package/docs/TOOLS.md CHANGED
@@ -352,6 +352,46 @@ The agent does the translating; this writes them.
352
352
  Creates a new price schedule from a price point. **Changes live pricing — confirm first.**
353
353
  - `appId` **(required)**, `baseTerritory` **(required)**, `pricePointId` **(required)**, `startDate` (YYYY-MM-DD, optional)
354
354
 
355
+ ## PPP regional pricing (IAP & subscriptions)
356
+
357
+ Bulk-set **in-app purchase / subscription** prices across ~174 territories from a
358
+ US base price, scaled by purchasing-power-parity (PPP). Distinct from
359
+ `set_app_price` (which sets the paid-*app* price). The target price is scaled in
360
+ each territory's **local currency** (via Apple's equalizations) and snapped to a
361
+ real price-point on that territory's grid — not a dollar figure equalized to a
362
+ coarse subset. Ported from the MIT-licensed
363
+ [appstore-ppp-prices](https://github.com/duceum/appstore-ppp-pricing-agent-skill).
364
+
365
+ > **Always `preview_ppp_prices` → show the user the table → `apply_ppp_prices`.**
366
+ > Prices are customer-facing across ~174 territories and **cannot be undone.**
367
+
368
+ Coefficients come from an embedded 175-country table (tiers: `premium`,
369
+ `high_income`, `upper_middle`, `lower_middle`, `emerging`; USA is always the 1.00
370
+ base). There is **no server-side LLM** — reason about the app's elasticity (games =
371
+ high → discount poorer markets more; AI/productivity = low → discount less) and
372
+ pass per-tier overrides. Floors: 0.99 (premium/high_income), 0.49 (others), with
373
+ ratio preservation across multiple products.
374
+
375
+ ### list_purchasable_products
376
+ Every IAP + subscription with its current US price, tagged `IAP`/`SUB`.
377
+ - `appId` **(required)** — use the returned `productId` with the tools below.
378
+
379
+ ### preview_ppp_prices
380
+ Dry run (read-only): the full per-territory price table without writing.
381
+ - `appId`, `productId` **(required)**
382
+ - `usPrice` (override the US base), `coefficients` (per-tier overrides, each 0.1–2.0),
383
+ `exclude` (territory codes, e.g. `["RUS","BLR"]`)
384
+
385
+ ### apply_ppp_prices
386
+ Applies the prices. **Irreversible.** Requires `confirm:true`; blocked in read-only
387
+ mode and when `APPSTORE_MCP_ALLOW_PRICE_CHANGES=false`.
388
+ - `appId`, `productId`, `confirm:true` **(required)**
389
+ - `usPrice`, `coefficients`, `exclude` (same as preview)
390
+ - `preserveCurrentPrice` (subscriptions; default **true** — existing subscribers keep
391
+ their price), `startDate` (subscriptions; `YYYY-MM-DD`, default 2 days out)
392
+ - IAPs are set in one atomic request; subscriptions per-territory (pending price
393
+ changes are cleared first).
394
+
355
395
  ## Product Page Optimization
356
396
 
357
397
  ### list_app_store_version_experiments
@@ -451,7 +491,7 @@ clear error):
451
491
  | --- | --- |
452
492
  | `APPSTORE_MCP_READ_ONLY=true` | block all writes |
453
493
  | `APPSTORE_MCP_ALLOW_RELEASE=false` | block `release_version` / `set_phased_release` |
454
- | `APPSTORE_MCP_ALLOW_PRICE_CHANGES=false` | block `set_app_price` |
494
+ | `APPSTORE_MCP_ALLOW_PRICE_CHANGES=false` | block `set_app_price` / `apply_ppp_prices` |
455
495
  | `APPSTORE_MCP_ALLOW_REVIEW_REPLIES=false` | block public review replies |
456
496
  | `APPSTORE_MCP_ALLOW_EXTERNAL_TESTFLIGHT=false` | block `submit_beta_review` |
457
497
 
@@ -477,6 +517,46 @@ clear error):
477
517
  > See **[RECIPES.md](RECIPES.md)** for copy-paste prompts that chain these into
478
518
  > workflows (prepare-version, release-train-with-gates, review→notes, portfolio audit).
479
519
 
520
+ ## Submission flow (build ↔ version ↔ review)
521
+
522
+ The steps a real submission needs beyond `submit_for_review` — attaching a build,
523
+ fixing the silent blockers behind Apple's opaque `409`, and swapping a build that's
524
+ already in review.
525
+
526
+ ### Build & version
527
+ - `attach_build_to_version` — `versionId` + (`buildId` or `buildNumber`+`appId`). The mandatory pre-submit link.
528
+ - `get_app_store_version(versionId)` — state, releaseType, `usesIdfa`, and the **attached build** (or null).
529
+ - `update_app_store_version(versionId, …)` — `usesIdfa`, `releaseType`, `earliestReleaseDate`, `versionString`, `downloadable`.
530
+ - `update_build` — `usesNonExemptEncryption` (export compliance, **per-build, doesn't carry over**), `expired`. By `buildId` or `buildNumber`+`appId`.
531
+ - `expire_build`, `get_build`, `next_build_number(appId)`.
532
+ - `wait_for_build_processing(appId, buildNumber, timeoutSeconds?)` — polls to VALID/INVALID/FAILED.
533
+
534
+ ### Review submissions
535
+ - `list_review_submissions(appId, state?)`, `get_review_submission(id)`.
536
+ - `add_review_submission_item(reviewSubmissionId, versionId)`.
537
+ - `cancel_review_submission` — by `submissionId` or `appId` (current in-flight); optional `waitSeconds`. Surfaces Apple's refusal on empty/non-cancellable submissions.
538
+ - `get_app_store_review_detail(versionId)` — contact info, demo account, notes.
539
+
540
+ ### TestFlight
541
+ - `get_beta_review_status` (build's `betaReviewState`), `set_beta_build_notes(whatsNew, locale?)` ("What to Test", upsert).
542
+
543
+ ### Screenshots
544
+ - `find_incomplete_screenshots(versionId)` — assets whose `assetDeliveryState != COMPLETE` (silent submit blockers).
545
+ - `reorder_screenshots(screenshotSetId, orderedIds)` — new uploads append last, so re-order after re-upload.
546
+ - `replace_screenshots(screenshotSetId, filePaths[])` — delete all + upload in order, one call.
547
+
548
+ ### Subscriptions
549
+ - `list_subscription_groups(appId)`, `list_subscriptions(groupId)`.
550
+ - `list_subscription_offers(subscriptionId)` — **flags overlapping offer date ranges** (the sandbox `countMismatch` cause).
551
+ - `create_subscription_group`, `create_subscription`, `create_in_app_purchase`. (First-time products still need ticking for review on the version page — the API can't submit them.)
552
+
553
+ ### Diagnostics & orchestrators
554
+ - `diagnose_submission(appId, versionId)` — read-only: the exact blockers behind submit's 409.
555
+ - `swap_build(appId, versionId, buildNumber|buildId)` — wait for processing → cancel in-flight review → attach.
556
+ - `release_pipeline(appId, versionId, buildNumber?, submit?)` — attach → diagnose → (if clean and `submit:true`) submit.
557
+ - `bulk_upsert_localizations(versionId, { locale: {…} })` — version + app-info fields across all locales, creates missing ones; `dryRun` supported.
558
+ - `list_app_territories(appId)` — compact availability summary.
559
+
480
560
  ## Build & ship (macOS + Xcode)
481
561
 
482
562
  These run local Xcode tooling, so they only work on a Mac with Xcode installed.
@@ -503,6 +583,49 @@ in `list_builds` and can be submitted with `submit_for_review`.
503
583
 
504
584
  ---
505
585
 
586
+ ## iOS CI/CD → TestFlight bootstrap
587
+
588
+ Turn a new iOS app into a fastlane + GitHub Actions → TestFlight pipeline in one
589
+ call. Signing is Xcode automatic ("cloud") signing via `-allowProvisioningUpdates`
590
+ — no `match` repo. The GitHub side shells out to the `gh` CLI (install + `gh auth
591
+ login` required; reuses your existing auth, no extra token). The App Store Connect
592
+ API key lives only in this server's environment and is exposed only through these
593
+ high-level actions — never as an argument, never in output.
594
+
595
+ > Create the App Store Connect API key **once at the TEAM level** (App Store
596
+ > Connect → Users and Access → Integrations) so the same three secret values work
597
+ > for every app/repo.
598
+
599
+ ### ensure_asc_app
600
+ Find the App Store Connect app record for a bundle id. **Find-only:** the public
601
+ API cannot create app records (there is no `POST /apps`), so `created` is always
602
+ `false`. If missing, returns `found:false` plus guidance (register the bundle id,
603
+ create the record once in the web UI).
604
+ - `bundleId` **(required)**, `name`, `sku`, `platform`, `primaryLocale`
605
+ - **Returns:** `{ app_id, created, found, bundleId, name }`
606
+
607
+ ### bootstrap_ios_cicd
608
+ Scaffold the 7 pipeline files (`Gemfile`, `fastlane/{Appfile,Fastfile,.gitignore,
609
+ SETUP.md}`, `.github/workflows/{ios-ci.yml,ios-testflight.yml}`). Auto-detects
610
+ `appDir`/`bundleId`/`teamId`/`scheme`/`target` from the repo's `.xcodeproj`.
611
+ - `repoDir` (local clone, default `.`), `repo` (`owner/name` for the PR), `owner`
612
+ - overrides: `appDir`, `bundleId`, `teamId`, `scheme`, `target`
613
+ - `mode`: `pr` (default — branch + push + open PR), `branch`, `commit`, `files`
614
+ - `branch`, `baseBranch`, `dryRun` (preview detected values + rendered files)
615
+
616
+ ### set_repo_ci_secrets
617
+ Push `ASC_KEY_ID`, `ASC_ISSUER_ID`, `ASC_KEY_P8` (base64) to the repo's Actions
618
+ secrets via `gh secret set` (values piped over stdin; `gh` does the libsodium
619
+ sealed-box encryption). Key material is read from this server's env, never echoed.
620
+ - `repo` (`owner/name`), `owner`, `repoDir` (derive `owner/name` from `origin`)
621
+
622
+ ### bootstrap_testflight
623
+ One-call orchestrator: `ensure_asc_app` → `bootstrap_ios_cicd` → `set_repo_ci_secrets`.
624
+ If the app record doesn't exist yet, it still scaffolds + sets secrets and tells
625
+ you to create the record in the web UI. Same options as `bootstrap_ios_cicd`.
626
+
627
+ ---
628
+
506
629
  ## Rate limits
507
630
 
508
631
  App Store Connect allows ~3,500 requests/hour and returns `429` when exceeded.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appstore-api-mcp",
3
- "version": "1.12.0",
3
+ "version": "1.15.0",
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": {