appstore-api-mcp 1.16.4 → 1.18.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 CHANGED
@@ -35,3 +35,13 @@ ASC_PRIVATE_KEY_PATH=/absolute/path/to/AuthKey_XXXXXXXXXX.p8
35
35
  # APPSTORE_MCP_SNAPSHOT_DIR=
36
36
  # Optional: auto-snapshot an app's text metadata before the first listing edit (a safety net for "revert")
37
37
  # APPSTORE_MCP_AUTO_SNAPSHOT=true
38
+
39
+ # --- Retention Messaging API (optional; separate, access-gated StoreKit API) ---
40
+ # Runs on api.storekit.apple.com, NOT the App Store Connect host. Needs the
41
+ # Retention Messaging API entitlement (apply: developer.apple.com/contact/request/retention-messaging-api)
42
+ # and typically an In-App Purchase key (Users and Access → Integrations → In-App Purchase).
43
+ # If these are unset, the retention_* tools reuse the ASC_* key above.
44
+ # RETENTION_KEY_ID=
45
+ # RETENTION_ISSUER_ID=
46
+ # RETENTION_PRIVATE_KEY_PATH=./secrets/RetentionKey_XXXXXXXXXX.p8
47
+ # RETENTION_BUNDLE_ID=technology.fil.SpeakerMate # default app; tools also take a per-call bundleId
package/CHANGELOG.md CHANGED
@@ -4,6 +4,58 @@ 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.18.0] - 2026-11-05
8
+
9
+ ### Added — Retention Messaging (subscription cancellation flow)
10
+ - Tools for Apple's new **Retention Messaging API** (announced 2026-11-04):
11
+ `retention_doctor`, `retention_upload_message`, `retention_list_messages`,
12
+ `retention_delete_message`, `retention_set_default_message`,
13
+ `retention_get_default_message`, `retention_delete_default_message`,
14
+ `retention_set_realtime_url`, `retention_get_realtime_url`,
15
+ `retention_delete_realtime_url`, and a `retention_request` escape hatch.
16
+ - This is a **separate, access-gated API** on `api.storekit.apple.com` (not the
17
+ App Store Connect host), so it ships as a dedicated `StoreKitClient` (ES256
18
+ JWT, App Store Server API style, per-app `bid`). Configure with `RETENTION_*`
19
+ env (an In-App Purchase key) or it reuses the `ASC_*` key; every call takes a
20
+ `bundleId`, and `sandbox:true` targets the sandbox host. Writes respect
21
+ read-only mode. Message text is validated against Apple's limits (header ≤66,
22
+ body ≤144, ≤5 bullets ≤66) before upload.
23
+ - **Access required:** the Retention Messaging API is entitlement-gated (apply at
24
+ developer.apple.com/contact/request/retention-messaging-api); without it Apple
25
+ returns `404`. The real-time per-subscriber callback is the developer's own
26
+ server, not a tool here. A couple of request-body field names and the exact
27
+ key type are pending live verification once access is granted; the hosts and
28
+ endpoint paths were confirmed to respond (401 without auth).
29
+
30
+ ## [1.17.0] - 2026-10-31
31
+
32
+ ### Added
33
+ - **Preflight guideline review before every submission.** A new read-only
34
+ `preflight_review` tool checks a version against common App Store Review
35
+ Guidelines and App Store Connect requirements, citing the Apple rule for each
36
+ finding and returning a `PASS / FAIL / WARN / MANUAL / ASC / NA` verdict with
37
+ evidence and a fix.
38
+ - *App Store Connect checks* (need `appId` + `versionId`): privacy-policy URL,
39
+ description and support URL per locale, screenshots per locale, media still
40
+ processing, age rating, build attached & VALID, per-build export compliance,
41
+ IDFA declaration, and a review demo account / notes when the app has a login.
42
+ - *Local source checks* (pass `projectRoot`, the app's source tree on this
43
+ machine): purpose strings for permission APIs, required-reason APIs declared
44
+ in a privacy manifest, SDKs that need a manifest, App Tracking Transparency,
45
+ account creation without in-app account deletion, Sign in with Apple,
46
+ deprecated `UIWebView`, private-API hints, placeholder content, insecure
47
+ HTTP, external payment SDKs, Restore Purchases, and the export-compliance key.
48
+ - `submit_for_review` and `release_pipeline` now run preflight automatically and
49
+ **refuse to submit when any finding is FAIL**. New `skipPreflight` (bypass the
50
+ guideline review; the opaque-409 diagnosis still runs) and `projectRoot`
51
+ (also scan local source) options on both.
52
+
53
+ The local scanner is a zero-dependency JavaScript port of the scanner from the
54
+ MIT-licensed [app-store-preflight](https://github.com/ilan-uzan/app-store-preflight)
55
+ project by Ilan Uzan; the bundled required-reason / purpose-string / SDK data
56
+ lists come from the same project (see `src/preflight-data/NOTICE.md`). The rules
57
+ that turn facts into guideline-cited verdicts are original to this project.
58
+
7
59
  ## [1.16.4] - 2026-10-08
8
60
 
9
61
  ### Fixed
package/README.md CHANGED
@@ -264,7 +264,8 @@ Full parameter reference: **[docs/TOOLS.md](docs/TOOLS.md)**.
264
264
  | `list_app_preview_sets` / `list_app_previews` / `get_app_preview` / `create_app_preview_set` / `upload_app_preview` / `delete_app_preview` | 🎬 **App preview videos** — list, inspect (incl. `videoUrl`), upload, delete |
265
265
  | `audit_apps` | 🩺 **Fleet ASO audit** — scan all apps for missing subtitle/keywords/description, under-used keyword field, single-locale listings, missing screenshots. Read-only |
266
266
  | `apps_review_status` | 🗂️ **Fleet review board** — every app's current version + state (waiting / in-review / rejected / ready) in one call |
267
- | `submit_for_review` / `release_version` / `set_phased_release` | 🚀 Submit a version to Apple review (full flow), release an approved build, and control phased rollout |
267
+ | `submit_for_review` / `release_version` / `set_phased_release` | 🚀 Submit a version to Apple review (full flow, **gated by preflight**), release an approved build, and control phased rollout |
268
+ | `preflight_review` | 🛫 **Pre-submission guideline review** — checks the version (and, with `projectRoot`, the local source) against App Store Review Guidelines + App Store Connect requirements, citing the Apple rule for each finding. `submit_for_review` blocks on any `FAIL`. Read-only |
268
269
  | `doctor` | 🩺 Diagnose setup: Node, creds, key works, role capabilities, vendor number, Mac build tools, write mode |
269
270
  | `snapshot_app_metadata` / `diff_app_metadata_snapshot` / `restore_app_metadata` / `restore_screenshots` / `restore_app_previews` | 💾 Back up / compare / restore an app's metadata. Text always saved; `includeScreenshots:true` and `includePreviews:true` also download the images/videos so **deleted screenshots and previews can be re-uploaded**. Set `APPSTORE_MCP_AUTO_SNAPSHOT=true` to auto-snapshot text before the first edit |
270
271
  | `release_readiness_check` | ✅ One-call **go/no-go report** — build, metadata, ASO, screenshots, compliance, TestFlight, reviews |
@@ -453,6 +454,11 @@ filling out a listing.
453
454
 
454
455
  Built by **Sviatoslav (Sviat) Fil** — [filsv.com](https://filsv.com) — indie iOS apps & developer tools.
455
456
 
457
+ The `preflight_review` tool's local-project scanner and its required-reason /
458
+ purpose-string / SDK data lists are derived from the MIT-licensed
459
+ **[app-store-preflight](https://github.com/ilan-uzan/app-store-preflight)**
460
+ project by Ilan Uzan (see [`src/preflight-data/NOTICE.md`](src/preflight-data/NOTICE.md)).
461
+
456
462
  ## License
457
463
 
458
464
  MIT © Sviatoslav Fil — see [LICENSE](LICENSE).
package/docs/RECIPES.md CHANGED
@@ -35,6 +35,20 @@ blockers first.
35
35
 
36
36
  Uses: `release_readiness_check` (single call), or the individual tools.
37
37
 
38
+ For a **guideline/metadata** review (not just completeness), add `preflight_review`
39
+ — it cites the App Store Review Guideline for each finding and marks blocking
40
+ `FAIL`s. Pass `projectRoot` to also scan the local source (purpose strings,
41
+ privacy manifests, account deletion, ATT, UIWebView, …):
42
+
43
+ ```text
44
+ Run a preflight review for AppName v1.4.0 against the App Store guidelines. My
45
+ Xcode project is at /path/to/AppName. Show the status, every FAIL with its
46
+ Apple rule and fix, then the warnings.
47
+ ```
48
+
49
+ Uses: `preflight_review` (read-only). `submit_for_review` runs this automatically
50
+ and refuses to submit on any `FAIL`.
51
+
38
52
  <details><summary>Example output (fake data)</summary>
39
53
 
40
54
  | Area | Status | Detail |
@@ -65,6 +79,11 @@ Uses: `list_builds`, `list_beta_groups`, `list_beta_testers`,
65
79
  `add_build_to_beta_group`, `submit_beta_review`, `submit_for_review`,
66
80
  `release_version`, `set_phased_release`. **Stops at each human gate.**
67
81
 
82
+ > At gate (4), `submit_for_review` runs `diagnose_submission` **and**
83
+ > `preflight_review` first and refuses to submit on any `FAIL`. Pass
84
+ > `projectRoot` to also scan the local source, or `skipPreflight:true` to bypass
85
+ > the guideline review (the opaque-409 diagnosis still runs).
86
+
68
87
  ## 4. Customer reviews → release notes loop
69
88
 
70
89
  ```text
package/docs/TOOLS.md CHANGED
@@ -319,8 +319,11 @@ Fleet review-status board across all (or selected) apps. Read-only.
319
319
 
320
320
  ### submit_for_review
321
321
  Submits a version to Apple review (create submission → add version → submit).
322
- **Sends the app to review — confirm first.**
322
+ Runs `diagnose_submission` **and** `preflight_review` first and refuses to submit
323
+ if either finds a blocker. **Sends the app to review — confirm first.**
323
324
  - `appId` **(required)**, `versionId` **(required)**, `platform` (default IOS)
325
+ - `skipPreflight` — bypass the guideline review (the 409 diagnosis still runs)
326
+ - `projectRoot` — also scan the local source during preflight
324
327
 
325
328
  ### release_version
326
329
  Release an approved version waiting for manual release (PENDING_DEVELOPER_RELEASE).
@@ -593,11 +596,50 @@ already in review.
593
596
 
594
597
  ### Diagnostics & orchestrators
595
598
  - `diagnose_submission(appId, versionId)` — read-only: the exact blockers behind submit's 409.
599
+ - `preflight_review(appId?, versionId?, projectRoot?, only?)` — read-only guideline/metadata review (see below).
596
600
  - `swap_build(appId, versionId, buildNumber|buildId)` — wait for processing → cancel in-flight review → attach.
597
- - `release_pipeline(appId, versionId, buildNumber?, submit?)` — attach → diagnose → (if clean and `submit:true`) submit.
601
+ - `release_pipeline(appId, versionId, buildNumber?, submit?, skipPreflight?, projectRoot?)` — attach → diagnose → **preflight** → (if clean and `submit:true`) submit.
598
602
  - `bulk_upsert_localizations(versionId, { locale: {…} })` — version + app-info fields across all locales, creates missing ones; `dryRun` supported.
599
603
  - `list_app_territories(appId)` — compact availability summary.
600
604
 
605
+ ## Preflight — App Store guideline review
606
+
607
+ A pre-submission review that checks an app against common **App Store Review
608
+ Guidelines** and **App Store Connect** requirements, citing the Apple rule for
609
+ each finding. Inspired by (and partly ported from) the MIT-licensed
610
+ [app-store-preflight](https://github.com/ilan-uzan/app-store-preflight) project.
611
+
612
+ ### `preflight_review`
613
+ Read-only; writes nothing.
614
+ - `appId`, `versionId` — the version to review (runs the App Store Connect checks).
615
+ - `projectRoot` — absolute path to the app's local source tree (Xcode / React
616
+ Native / Flutter). When given, the code is scanned too; when omitted, only the
617
+ App Store Connect checks run. The path must be on the machine the server runs on.
618
+ - `only` — array of substrings; keep only findings whose id or guideline ref matches
619
+ (e.g. `["privacy", "5.1.1", "purpose-string"]`).
620
+ - **Returns:** `{ status, ready, summary, blocking, findings, meta }`. Each finding has
621
+ `{ id, verdict, ref, url, title, detail, evidence?, fix? }`.
622
+
623
+ **Verdicts:** `PASS` · `FAIL` (blocks `submit_for_review`) · `WARN` · `MANUAL`
624
+ (only the developer can confirm) · `ASC` · `NA`.
625
+
626
+ **What it checks**
627
+
628
+ - *Local source (needs `projectRoot`):* purpose strings for permission APIs
629
+ (camera, mic, location, contacts, calendar, HealthKit, …); required-reason APIs
630
+ declared in a privacy manifest; SDKs that need a manifest; App Tracking
631
+ Transparency; account creation without in-app account deletion; Sign in with
632
+ Apple; deprecated `UIWebView`; private-API hints; placeholder content; insecure
633
+ HTTP; external payment SDKs; Restore Purchases; export-compliance key.
634
+ - *App Store Connect (needs `appId` + `versionId`):* privacy-policy URL,
635
+ description and support URL per locale, screenshots per locale, media still
636
+ processing, age rating, build attached & VALID, per-build export compliance,
637
+ IDFA declaration, and a review demo account / notes when the app has a login.
638
+
639
+ `submit_for_review` and `release_pipeline` run this automatically and **refuse to
640
+ submit when any finding is FAIL** — pass `skipPreflight:true` to bypass the
641
+ guideline review (the opaque-409 diagnosis still runs).
642
+
601
643
  ## Build & ship (macOS + Xcode)
602
644
 
603
645
  These run local Xcode tooling, so they only work on a Mac with Xcode installed.
@@ -667,6 +709,27 @@ you to create the record in the web UI. Same options as `bootstrap_ios_cicd`.
667
709
 
668
710
  ---
669
711
 
712
+ ## Retention Messaging (subscription cancellation flow)
713
+
714
+ Messages/offers shown when a subscriber moves to cancel. ⚠️ **This is a separate,
715
+ access-gated API** on `api.storekit.apple.com` (not the App Store Connect host).
716
+ It needs the **Retention Messaging API entitlement** (apply at
717
+ [developer.apple.com/contact/request/retention-messaging-api](https://developer.apple.com/contact/request/retention-messaging-api))
718
+ and typically an **In-App Purchase key** (`RETENTION_*` env; otherwise the `ASC_*`
719
+ key is reused). Every call takes the app's **`bundleId`**. Without access, Apple
720
+ returns `404`. The real-time per-subscriber callback is **your own server**, not a tool here.
721
+
722
+ - `retention_doctor(bundleId?)` — **start here**: checks key config and probes whether your account has access.
723
+ - `retention_upload_message(bundleId, header, body, bulletPoints?, image?, …)` — create a message (header ≤66, body ≤144, ≤5 bullets ≤66; PENDING until App Review).
724
+ - `retention_list_messages(bundleId)`, `retention_delete_message(bundleId, messageId)`
725
+ - `retention_set_default_message(bundleId, productId, locale, messageId)` — "assign" a default per product+locale; `retention_get_default_message`, `retention_delete_default_message`
726
+ - `retention_set_realtime_url(bundleId, url)` — register your real-time endpoint (prod needs Apple's performance test first); `retention_get_realtime_url`, `retention_delete_realtime_url`
727
+ - `retention_request(bundleId, method, path, body?)` — escape hatch for any `/inApps/v1/messaging/*` endpoint (image upload, performance test, etc.)
728
+
729
+ All retention tools take an optional `sandbox: true` to use `api.storekit-sandbox.apple.com`.
730
+
731
+ ---
732
+
670
733
  ## Rate limits
671
734
 
672
735
  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.16.4",
3
+ "version": "1.18.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": {
package/src/guardrails.js CHANGED
@@ -93,6 +93,13 @@ export const WRITE_TOOLS = new Set([
93
93
  // snapshots
94
94
  "restore_app_metadata",
95
95
  "restore_screenshots",
96
+ // retention messaging (StoreKit host)
97
+ "retention_upload_message",
98
+ "retention_delete_message",
99
+ "retention_set_default_message",
100
+ "retention_delete_default_message",
101
+ "retention_set_realtime_url",
102
+ "retention_delete_realtime_url",
96
103
  ]);
97
104
 
98
105
  // High-impact categories with their own opt-out env flags.
@@ -109,9 +116,9 @@ export function envBool(value, dflt = false) {
109
116
  return /^(1|true|yes|on)$/i.test(String(value).trim());
110
117
  }
111
118
 
112
- /** Is this tool call a write? raw_request is a write unless the method is GET. */
119
+ /** Is this tool call a write? raw_request / retention_request are writes unless GET. */
113
120
  export function isWriteTool(name, args = {}) {
114
- if (name === "raw_request") {
121
+ if (name === "raw_request" || name === "retention_request") {
115
122
  const m = (args.method || "GET").toUpperCase();
116
123
  return m !== "GET";
117
124
  }