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 +10 -0
- package/CHANGELOG.md +52 -0
- package/README.md +7 -1
- package/docs/RECIPES.md +19 -0
- package/docs/TOOLS.md +65 -2
- package/package.json +1 -1
- package/src/guardrails.js +9 -2
- package/src/index.js +389 -4
- package/src/preflight-data/NOTICE.md +25 -0
- package/src/preflight-data/purpose_string_keys.json +703 -0
- package/src/preflight-data/required_reason_apis.json +343 -0
- package/src/preflight-data/sdks_requiring_manifest.json +591 -0
- package/src/preflight-rules.js +463 -0
- package/src/preflight-scan.js +615 -0
- package/src/storekit.js +136 -0
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
|
-
**
|
|
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.
|
|
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
|
|
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
|
}
|