appstore-api-mcp 1.5.0 → 1.6.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 +14 -0
- package/README.md +21 -24
- package/docs/TOOLS.md +27 -0
- package/package.json +1 -1
- package/src/index.js +203 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@ 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.6.0] - 2026-06-02
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **`bulk_update_version_localizations`** — update listing copy across many
|
|
11
|
+
locales at once (creates missing ones); `dryRun` to preview.
|
|
12
|
+
- **`set_phased_release`** — control a version's 7-day gradual rollout (ACTIVE/PAUSE/COMPLETE).
|
|
13
|
+
- **Pricing:** `list_app_price_points` (find a territory's price points) and
|
|
14
|
+
`set_app_price` (set base price from a price point).
|
|
15
|
+
- **`list_app_store_version_experiments`** — Product Page Optimization A/B tests.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- README tools table: removed emojis from the tool-name column and moved them
|
|
19
|
+
into the description column.
|
|
20
|
+
|
|
7
21
|
## [1.5.0] - 2026-06-02
|
|
8
22
|
|
|
9
23
|
### Added
|
package/README.md
CHANGED
|
@@ -231,32 +231,29 @@ Full parameter reference: **[docs/TOOLS.md](docs/TOOLS.md)**.
|
|
|
231
231
|
| Tool | What it does |
|
|
232
232
|
| --- | --- |
|
|
233
233
|
| `list_apps` / `get_app` | Browse your apps |
|
|
234
|
-
| `list_app_infos` | Find the record holding name
|
|
235
|
-
| `
|
|
236
|
-
| `
|
|
237
|
-
| `
|
|
238
|
-
| `
|
|
239
|
-
| `
|
|
240
|
-
| `list_app_store_version_localizations` | Read description/keywords/etc. per locale |
|
|
241
|
-
| `get_app_store_version_localization` | Read one locale's listing copy |
|
|
242
|
-
| `update_app_store_version_localization` | Update **keywords, description, promo text, what's-new, URLs** |
|
|
243
|
-
| `create_app_store_version_localization` | Add a new locale to a version |
|
|
234
|
+
| `list_app_infos` / `list_app_info_localizations` | Find/read the record holding name, subtitle & privacy policy per locale |
|
|
235
|
+
| `update_app_info_localization` / `create_app_info_localization` | Update or add a locale's **name, subtitle, privacy policy** |
|
|
236
|
+
| `list_app_store_versions` / `create_app_store_version` | List versions and their states; start a new version |
|
|
237
|
+
| `list_app_store_version_localizations` / `get_app_store_version_localization` | Read description / keywords / etc. per locale |
|
|
238
|
+
| `update_app_store_version_localization` / `create_app_store_version_localization` | Update or add a locale's **keywords, description, promo text, what's-new, URLs** |
|
|
239
|
+
| `bulk_update_version_localizations` | 🌍 Update listing copy across **many locales at once** (creates missing ones); `dryRun` to preview |
|
|
244
240
|
| `list_screenshot_sets` / `create_screenshot_set` | Manage per-device screenshot sets |
|
|
245
241
|
| `list_screenshots` / `upload_screenshot` / `delete_screenshot` | Manage screenshots (upload handles the full reserve→upload→commit flow) |
|
|
246
|
-
|
|
|
247
|
-
|
|
|
248
|
-
|
|
|
249
|
-
|
|
|
250
|
-
|
|
|
251
|
-
|
|
|
252
|
-
|
|
|
253
|
-
|
|
|
254
|
-
|
|
|
255
|
-
|
|
|
256
|
-
|
|
|
257
|
-
|
|
|
258
|
-
| `
|
|
259
|
-
| `
|
|
242
|
+
| `get_screenshot` | 👁️ Fetch a live screenshot **as an image the agent can see** — review/compare what's on a listing |
|
|
243
|
+
| `audit_apps` | 🩺 **Fleet ASO audit** — scan all apps for missing subtitle/keywords/description, under-used keyword field, single-locale listings, missing screenshots. Read-only |
|
|
244
|
+
| `apps_review_status` | 🗂️ **Fleet review board** — every app's current version + state (waiting / in-review / rejected / ready) in one call |
|
|
245
|
+
| `submit_for_review` / `release_version` / `set_phased_release` | 🚀 Submit a version to Apple review (full flow), release an approved build, and control phased rollout |
|
|
246
|
+
| `get_sales_report` / `get_subscription_report` / `get_finance_report` | 📊 Units/downloads, proceeds, **subscriptions & retention**, earnings by region. Needs a Vendor Number + Admin/Finance/Sales key |
|
|
247
|
+
| `request_analytics_report` → `list_analytics_reports` → `list_analytics_report_instances` → `get_analytics_report_data` | 📈 The async Analytics Reports API — downloads, sessions, active devices, App Store engagement |
|
|
248
|
+
| `list_customer_reviews` / `reply_to_customer_review` | ⭐ Read reviews (filter by rating/territory, shows if you've replied) and post public replies |
|
|
249
|
+
| `list_builds` / `list_beta_groups` / `list_beta_testers` / `add_beta_tester` | ✈️ TestFlight — builds, beta groups, testers, and inviting testers |
|
|
250
|
+
| `list_in_app_purchases` / `update_in_app_purchase` | 🛒 In-app purchase products — list and edit name / review note |
|
|
251
|
+
| `list_app_price_points` / `set_app_price` / `get_app_price_schedule` / `list_available_territories` / `get_age_rating` | 💵 Pricing (find price points, set base price), territory availability, and age rating |
|
|
252
|
+
| `list_app_store_version_experiments` | 🧪 Product Page Optimization — list A/B tests (experiments) |
|
|
253
|
+
| `list_game_center_leaderboards` / `list_game_center_achievements` | 🎮 Game Center leaderboards & achievements |
|
|
254
|
+
| `signing_health` | 🔐 Flag **certificates & profiles expiring soon** (or invalid) across the account — catches CI breakage early |
|
|
255
|
+
| `list_bundle_ids` / `register_bundle_id` / `list_devices` / `register_device` / `list_certificates` / `create_certificate` / `revoke_certificate` / `list_profiles` / `create_profile` / `download_profile` / `delete_profile` | 🔏 **Provisioning & code signing** — bundle IDs, devices, certificates, and provisioning profiles |
|
|
256
|
+
| `raw_request` | 🧰 Any method/path against the API — app previews, matchmaking, Xcode Cloud, anything not above |
|
|
260
257
|
|
|
261
258
|
> ⚙️ **Rate-limit aware:** the client automatically backs off and retries on
|
|
262
259
|
> App Store Connect's `429` (hourly quota), so large sweeps (e.g. auditing 60+
|
package/docs/TOOLS.md
CHANGED
|
@@ -327,9 +327,36 @@ Release an approved version waiting for manual release (PENDING_DEVELOPER_RELEAS
|
|
|
327
327
|
**Makes it live — confirm first.**
|
|
328
328
|
- `versionId` **(required)**
|
|
329
329
|
|
|
330
|
+
### set_phased_release
|
|
331
|
+
Control a version's 7-day gradual rollout.
|
|
332
|
+
- `versionId` **(required)**, `state` **(required)** — `ACTIVE`, `PAUSE`, `COMPLETE`
|
|
333
|
+
|
|
330
334
|
### update_in_app_purchase
|
|
331
335
|
- `inAppPurchaseId` **(required)**, `name`, `reviewNote`
|
|
332
336
|
|
|
337
|
+
## Bulk localization
|
|
338
|
+
|
|
339
|
+
### bulk_update_version_localizations
|
|
340
|
+
Update listing copy across many locales in one call (creates missing locales).
|
|
341
|
+
The agent does the translating; this writes them.
|
|
342
|
+
- `versionId` **(required)**
|
|
343
|
+
- `locales` **(required)** — array of `{ locale, description?, keywords?, promotionalText?, whatsNew?, marketingUrl?, supportUrl? }`
|
|
344
|
+
- `dryRun` — preview every create/update without writing
|
|
345
|
+
|
|
346
|
+
## Pricing
|
|
347
|
+
|
|
348
|
+
### list_app_price_points
|
|
349
|
+
- `appId` **(required)**, `territory` **(required)** — each point has `customerPrice`, proceeds, and the `id` for `set_app_price`.
|
|
350
|
+
|
|
351
|
+
### set_app_price
|
|
352
|
+
Creates a new price schedule from a price point. **Changes live pricing — confirm first.**
|
|
353
|
+
- `appId` **(required)**, `baseTerritory` **(required)**, `pricePointId` **(required)**, `startDate` (YYYY-MM-DD, optional)
|
|
354
|
+
|
|
355
|
+
## Product Page Optimization
|
|
356
|
+
|
|
357
|
+
### list_app_store_version_experiments
|
|
358
|
+
- `appId` **(required)** — A/B tests with name, state, traffic proportion, start/end.
|
|
359
|
+
|
|
333
360
|
## Code-signing health
|
|
334
361
|
|
|
335
362
|
### signing_health
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appstore-api-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.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/index.js
CHANGED
|
@@ -1721,6 +1721,209 @@ const tools = [
|
|
|
1721
1721
|
}),
|
|
1722
1722
|
},
|
|
1723
1723
|
|
|
1724
|
+
{
|
|
1725
|
+
name: "set_phased_release",
|
|
1726
|
+
description:
|
|
1727
|
+
"Control a released version's phased (gradual 7-day) rollout. state: ACTIVE (start/resume), PAUSE, or COMPLETE (release to everyone now). Creates the phased release if one doesn't exist.",
|
|
1728
|
+
inputSchema: {
|
|
1729
|
+
type: "object",
|
|
1730
|
+
properties: {
|
|
1731
|
+
versionId: { type: "string" },
|
|
1732
|
+
state: { type: "string", description: "ACTIVE, PAUSE, or COMPLETE" },
|
|
1733
|
+
},
|
|
1734
|
+
required: ["versionId", "state"],
|
|
1735
|
+
},
|
|
1736
|
+
run: async (a) => {
|
|
1737
|
+
let pr = null;
|
|
1738
|
+
try {
|
|
1739
|
+
pr = await client.get(
|
|
1740
|
+
`/appStoreVersions/${a.versionId}/appStoreVersionPhasedRelease`,
|
|
1741
|
+
);
|
|
1742
|
+
} catch {
|
|
1743
|
+
/* none yet */
|
|
1744
|
+
}
|
|
1745
|
+
if (pr && pr.data && pr.data.id)
|
|
1746
|
+
return client.patch(`/appStoreVersionPhasedReleases/${pr.data.id}`, {
|
|
1747
|
+
data: {
|
|
1748
|
+
type: "appStoreVersionPhasedReleases",
|
|
1749
|
+
id: pr.data.id,
|
|
1750
|
+
attributes: { phasedReleaseState: a.state },
|
|
1751
|
+
},
|
|
1752
|
+
});
|
|
1753
|
+
return client.post(`/appStoreVersionPhasedReleases`, {
|
|
1754
|
+
data: {
|
|
1755
|
+
type: "appStoreVersionPhasedReleases",
|
|
1756
|
+
attributes: { phasedReleaseState: a.state },
|
|
1757
|
+
relationships: {
|
|
1758
|
+
appStoreVersion: {
|
|
1759
|
+
data: { type: "appStoreVersions", id: a.versionId },
|
|
1760
|
+
},
|
|
1761
|
+
},
|
|
1762
|
+
},
|
|
1763
|
+
});
|
|
1764
|
+
},
|
|
1765
|
+
},
|
|
1766
|
+
|
|
1767
|
+
// ---- Bulk localization ----
|
|
1768
|
+
{
|
|
1769
|
+
name: "bulk_update_version_localizations",
|
|
1770
|
+
description:
|
|
1771
|
+
"Update an App Store version's listing copy across MANY locales in one call — creating locales that don't exist yet. Pass an array of { locale, description?, keywords?, promotionalText?, whatsNew?, marketingUrl?, supportUrl? }. The agent does the translating; this writes them all. Set dryRun:true to preview every create/update without writing.",
|
|
1772
|
+
inputSchema: {
|
|
1773
|
+
type: "object",
|
|
1774
|
+
properties: {
|
|
1775
|
+
versionId: { type: "string" },
|
|
1776
|
+
locales: {
|
|
1777
|
+
type: "array",
|
|
1778
|
+
items: { type: "object", additionalProperties: true },
|
|
1779
|
+
description: "Array of { locale, ...fields } objects",
|
|
1780
|
+
},
|
|
1781
|
+
dryRun: { type: "boolean", description: "Preview without writing" },
|
|
1782
|
+
},
|
|
1783
|
+
required: ["versionId", "locales"],
|
|
1784
|
+
},
|
|
1785
|
+
run: async (a) => {
|
|
1786
|
+
const existing = await client.getAll(
|
|
1787
|
+
`/appStoreVersions/${a.versionId}/appStoreVersionLocalizations`,
|
|
1788
|
+
);
|
|
1789
|
+
const byLocale = Object.fromEntries(
|
|
1790
|
+
existing.map((x) => [x.attributes.locale, x]),
|
|
1791
|
+
);
|
|
1792
|
+
const FIELDS = [
|
|
1793
|
+
"description",
|
|
1794
|
+
"keywords",
|
|
1795
|
+
"promotionalText",
|
|
1796
|
+
"whatsNew",
|
|
1797
|
+
"marketingUrl",
|
|
1798
|
+
"supportUrl",
|
|
1799
|
+
];
|
|
1800
|
+
const results = [];
|
|
1801
|
+
for (const item of a.locales) {
|
|
1802
|
+
const { locale } = item;
|
|
1803
|
+
const attributes = {};
|
|
1804
|
+
for (const k of FIELDS)
|
|
1805
|
+
if (item[k] !== undefined) attributes[k] = item[k];
|
|
1806
|
+
const warnings = validateAttributes(attributes);
|
|
1807
|
+
const ex = byLocale[locale];
|
|
1808
|
+
if (a.dryRun) {
|
|
1809
|
+
results.push({
|
|
1810
|
+
locale,
|
|
1811
|
+
action: ex ? "update" : "create",
|
|
1812
|
+
changes: ex
|
|
1813
|
+
? buildDiff(ex.attributes, attributes)
|
|
1814
|
+
: Object.entries(attributes).map(([field, to]) => ({ field, to })),
|
|
1815
|
+
warnings,
|
|
1816
|
+
});
|
|
1817
|
+
continue;
|
|
1818
|
+
}
|
|
1819
|
+
if (ex) {
|
|
1820
|
+
await client.patch(
|
|
1821
|
+
`/appStoreVersionLocalizations/${ex.id}`,
|
|
1822
|
+
{ data: { type: "appStoreVersionLocalizations", id: ex.id, attributes } },
|
|
1823
|
+
);
|
|
1824
|
+
results.push({ locale, action: "updated", id: ex.id, warnings });
|
|
1825
|
+
} else {
|
|
1826
|
+
const created = await client.post(`/appStoreVersionLocalizations`, {
|
|
1827
|
+
data: {
|
|
1828
|
+
type: "appStoreVersionLocalizations",
|
|
1829
|
+
attributes: { locale, ...attributes },
|
|
1830
|
+
relationships: {
|
|
1831
|
+
appStoreVersion: {
|
|
1832
|
+
data: { type: "appStoreVersions", id: a.versionId },
|
|
1833
|
+
},
|
|
1834
|
+
},
|
|
1835
|
+
},
|
|
1836
|
+
});
|
|
1837
|
+
results.push({ locale, action: "created", id: created.data.id, warnings });
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
return { dryRun: !!a.dryRun, count: results.length, results };
|
|
1841
|
+
},
|
|
1842
|
+
},
|
|
1843
|
+
|
|
1844
|
+
// ---- Pricing ----
|
|
1845
|
+
{
|
|
1846
|
+
name: "list_app_price_points",
|
|
1847
|
+
description:
|
|
1848
|
+
"List available price points for an app in a territory — each has a customerPrice and your proceeds, plus the pricePointId to use with set_app_price.",
|
|
1849
|
+
inputSchema: {
|
|
1850
|
+
type: "object",
|
|
1851
|
+
properties: {
|
|
1852
|
+
appId: { type: "string" },
|
|
1853
|
+
territory: { type: "string", description: "3-letter code, e.g. USA" },
|
|
1854
|
+
limit: { type: "number", description: "Max (default 200)" },
|
|
1855
|
+
},
|
|
1856
|
+
required: ["appId", "territory"],
|
|
1857
|
+
},
|
|
1858
|
+
run: async (a) => {
|
|
1859
|
+
const data = await client.getAll(`/apps/${a.appId}/appPricePoints`, {
|
|
1860
|
+
"filter[territory]": a.territory,
|
|
1861
|
+
limit: Math.min(a.limit ?? 200, 200),
|
|
1862
|
+
});
|
|
1863
|
+
return data.map((x) => ({ id: x.id, ...x.attributes }));
|
|
1864
|
+
},
|
|
1865
|
+
},
|
|
1866
|
+
{
|
|
1867
|
+
name: "set_app_price",
|
|
1868
|
+
description:
|
|
1869
|
+
"Set an app's base price by creating a new price schedule from a price point (get one via list_app_price_points). NOTE: this changes LIVE pricing — confirm with the user. Optional startDate (YYYY-MM-DD) to schedule; omit for immediate.",
|
|
1870
|
+
inputSchema: {
|
|
1871
|
+
type: "object",
|
|
1872
|
+
properties: {
|
|
1873
|
+
appId: { type: "string" },
|
|
1874
|
+
baseTerritory: { type: "string", description: "3-letter code, e.g. USA" },
|
|
1875
|
+
pricePointId: { type: "string" },
|
|
1876
|
+
startDate: { type: "string", description: "YYYY-MM-DD (optional)" },
|
|
1877
|
+
},
|
|
1878
|
+
required: ["appId", "baseTerritory", "pricePointId"],
|
|
1879
|
+
},
|
|
1880
|
+
run: async (a) => {
|
|
1881
|
+
const lid = "new-manual-price";
|
|
1882
|
+
return client.post(`/appPriceSchedules`, {
|
|
1883
|
+
data: {
|
|
1884
|
+
type: "appPriceSchedules",
|
|
1885
|
+
relationships: {
|
|
1886
|
+
app: { data: { type: "apps", id: a.appId } },
|
|
1887
|
+
baseTerritory: {
|
|
1888
|
+
data: { type: "territories", id: a.baseTerritory },
|
|
1889
|
+
},
|
|
1890
|
+
manualPrices: { data: [{ type: "appPrices", id: lid }] },
|
|
1891
|
+
},
|
|
1892
|
+
},
|
|
1893
|
+
included: [
|
|
1894
|
+
{
|
|
1895
|
+
type: "appPrices",
|
|
1896
|
+
id: lid,
|
|
1897
|
+
attributes: a.startDate ? { startDate: a.startDate } : {},
|
|
1898
|
+
relationships: {
|
|
1899
|
+
appPricePoint: {
|
|
1900
|
+
data: { type: "appPricePoints", id: a.pricePointId },
|
|
1901
|
+
},
|
|
1902
|
+
},
|
|
1903
|
+
},
|
|
1904
|
+
],
|
|
1905
|
+
});
|
|
1906
|
+
},
|
|
1907
|
+
},
|
|
1908
|
+
|
|
1909
|
+
// ---- Product Page Optimization (A/B testing) ----
|
|
1910
|
+
{
|
|
1911
|
+
name: "list_app_store_version_experiments",
|
|
1912
|
+
description:
|
|
1913
|
+
"List Product Page Optimization A/B tests (App Store version experiments) for an app — name, state, traffic proportion, and start/end.",
|
|
1914
|
+
inputSchema: {
|
|
1915
|
+
type: "object",
|
|
1916
|
+
properties: { appId: { type: "string" } },
|
|
1917
|
+
required: ["appId"],
|
|
1918
|
+
},
|
|
1919
|
+
run: async (a) => {
|
|
1920
|
+
const data = await client.getAll(
|
|
1921
|
+
`/apps/${a.appId}/appStoreVersionExperimentsV2`,
|
|
1922
|
+
);
|
|
1923
|
+
return data.map((x) => ({ id: x.id, ...x.attributes }));
|
|
1924
|
+
},
|
|
1925
|
+
},
|
|
1926
|
+
|
|
1724
1927
|
// ---- Code-signing health ----
|
|
1725
1928
|
{
|
|
1726
1929
|
name: "signing_health",
|