appstore-api-mcp 1.4.0 → 1.5.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,17 @@ 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.5.0] - 2026-06-02
8
+
9
+ ### Added
10
+ - **`apps_review_status`** — fleet review-status board: every app's current
11
+ version + state (waiting/in-review/rejected/ready) in one call, with a summary.
12
+ - **`submit_for_review`** — full submit flow (create submission → add version →
13
+ submit) and **`release_version`** for manually releasing an approved build.
14
+ - **`signing_health`** — flags certificates & provisioning profiles that are
15
+ expired/expiring (within `withinDays`) or invalid, across the account.
16
+ - **`update_in_app_purchase`** — edit an IAP's reference name / review note.
17
+
7
18
  ## [1.4.0] - 2026-06-02
8
19
 
9
20
  ### Added
package/README.md CHANGED
@@ -252,6 +252,10 @@ Full parameter reference: **[docs/TOOLS.md](docs/TOOLS.md)**.
252
252
  | 🛒 `list_in_app_purchases` / `get_app_price_schedule` / `list_available_territories` / `get_age_rating` | Catalog, pricing, territory availability, and age-rating. |
253
253
  | 🔏 `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. |
254
254
  | 🎮 `list_game_center_leaderboards` / `list_game_center_achievements` | Game Center leaderboards & achievements. |
255
+ | 🗂️ `apps_review_status` | **Fleet review board** — every app's current version + state (waiting/in-review/rejected/ready) in one call. |
256
+ | 🚀 `submit_for_review` / `release_version` | Submit a version to Apple review (full flow) and release an approved build. |
257
+ | 🔐 `signing_health` | Flag **certificates & profiles expiring soon** (or invalid) across the account — catches CI breakage early. |
258
+ | `update_in_app_purchase` | Update an IAP's reference name / review note. |
255
259
  | `raw_request` | Any method/path against the API — app previews, matchmaking, Xcode Cloud, anything not above. |
256
260
 
257
261
  > ⚙️ **Rate-limit aware:** the client automatically backs off and retries on
package/docs/TOOLS.md CHANGED
@@ -310,6 +310,34 @@ Invites a tester by email (emails a real person — confirm first).
310
310
  ### get_age_rating
311
311
  - `appId` **(required)** — the app's age-rating declaration (content descriptors).
312
312
 
313
+ ## Submission, release & fleet status
314
+
315
+ ### apps_review_status
316
+ Fleet review-status board across all (or selected) apps. Read-only.
317
+ - `appIds` — limit to these apps; `platform`; `limit`
318
+ - **Returns:** `{ summary: { apps, byState }, apps: [{ name, version, state, platform }] }`
319
+
320
+ ### submit_for_review
321
+ Submits a version to Apple review (create submission → add version → submit).
322
+ **Sends the app to review — confirm first.**
323
+ - `appId` **(required)**, `versionId` **(required)**, `platform` (default IOS)
324
+
325
+ ### release_version
326
+ Release an approved version waiting for manual release (PENDING_DEVELOPER_RELEASE).
327
+ **Makes it live — confirm first.**
328
+ - `versionId` **(required)**
329
+
330
+ ### update_in_app_purchase
331
+ - `inAppPurchaseId` **(required)**, `name`, `reviewNote`
332
+
333
+ ## Code-signing health
334
+
335
+ ### signing_health
336
+ Lists certificates and provisioning profiles, flagging expired/expiring (within
337
+ `withinDays`, default 30) and INVALID profiles. Read-only.
338
+ - `withinDays` — threshold in days
339
+ - **Returns:** `{ summary, certIssues, profileIssues }` (each with `daysLeft`).
340
+
313
341
  ## Provisioning & code signing
314
342
 
315
343
  ### list_bundle_ids
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appstore-api-mcp",
3
- "version": "1.4.0",
3
+ "version": "1.5.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
@@ -1588,6 +1588,216 @@ const tools = [
1588
1588
  },
1589
1589
  },
1590
1590
 
1591
+ // ---- Submission, release & fleet status ----
1592
+ {
1593
+ name: "apps_review_status",
1594
+ description:
1595
+ "Fleet review-status board: for all (or selected) apps, returns the most relevant App Store version and its state (WAITING_FOR_REVIEW, IN_REVIEW, PENDING_DEVELOPER_RELEASE, REJECTED, PROCESSING_FOR_APP_STORE, READY_FOR_SALE, …) plus an account summary. One call instead of opening every app. Read-only.",
1596
+ inputSchema: {
1597
+ type: "object",
1598
+ properties: {
1599
+ appIds: { type: "array", items: { type: "string" } },
1600
+ platform: { type: "string", description: "IOS, MAC_OS, TV_OS, VISION_OS" },
1601
+ limit: { type: "number", description: "Audit at most this many apps" },
1602
+ },
1603
+ },
1604
+ run: async (a) => {
1605
+ let apps = await client.getAll("/apps", { limit: 200 });
1606
+ if (a.appIds?.length) apps = apps.filter((x) => a.appIds.includes(x.id));
1607
+ if (a.limit) apps = apps.slice(0, a.limit);
1608
+ const IN_FLIGHT = new Set([
1609
+ "PREPARE_FOR_SUBMISSION",
1610
+ "WAITING_FOR_REVIEW",
1611
+ "IN_REVIEW",
1612
+ "PENDING_DEVELOPER_RELEASE",
1613
+ "PENDING_APPLE_RELEASE",
1614
+ "PROCESSING_FOR_APP_STORE",
1615
+ "METADATA_REJECTED",
1616
+ "REJECTED",
1617
+ "DEVELOPER_REJECTED",
1618
+ "INVALID_BINARY",
1619
+ "WAITING_FOR_EXPORT_COMPLIANCE",
1620
+ ]);
1621
+ const rows = await mapLimit(apps, 6, async (app) => {
1622
+ try {
1623
+ const q = { limit: 10 };
1624
+ if (a.platform) q["filter[platform]"] = a.platform;
1625
+ const vers = await client.getAll(
1626
+ `/apps/${app.id}/appStoreVersions`,
1627
+ q,
1628
+ );
1629
+ const chosen =
1630
+ vers.find((v) => IN_FLIGHT.has(v.attributes.appStoreState)) ||
1631
+ vers.find((v) => v.attributes.appStoreState === "READY_FOR_SALE") ||
1632
+ vers[0];
1633
+ return {
1634
+ appId: app.id,
1635
+ name: app.attributes.name,
1636
+ version: chosen?.attributes?.versionString,
1637
+ state: chosen?.attributes?.appStoreState || "NO_VERSION",
1638
+ platform: chosen?.attributes?.platform,
1639
+ };
1640
+ } catch (e) {
1641
+ return { appId: app.id, name: app.attributes.name, error: e.message.slice(0, 80) };
1642
+ }
1643
+ });
1644
+ const byState = {};
1645
+ for (const r of rows) {
1646
+ const s = r.state || (r.error ? "ERROR" : "unknown");
1647
+ byState[s] = (byState[s] || 0) + 1;
1648
+ }
1649
+ return {
1650
+ summary: { apps: rows.length, byState },
1651
+ apps: rows.sort((x, y) =>
1652
+ String(x.state).localeCompare(String(y.state)),
1653
+ ),
1654
+ };
1655
+ },
1656
+ },
1657
+ {
1658
+ name: "submit_for_review",
1659
+ description:
1660
+ "Submit an App Store version for Apple review. Runs the full flow: create a review submission, add the version, and submit it. NOTE: this sends the app to Apple review — confirm with the user first. platform default IOS.",
1661
+ inputSchema: {
1662
+ type: "object",
1663
+ properties: {
1664
+ appId: { type: "string" },
1665
+ versionId: { type: "string", description: "appStoreVersion id to submit" },
1666
+ platform: { type: "string", description: "IOS (default), MAC_OS, TV_OS, VISION_OS" },
1667
+ },
1668
+ required: ["appId", "versionId"],
1669
+ },
1670
+ run: async (a) => {
1671
+ const sub = await client.post("/reviewSubmissions", {
1672
+ data: {
1673
+ type: "reviewSubmissions",
1674
+ attributes: { platform: a.platform || "IOS" },
1675
+ relationships: { app: { data: { type: "apps", id: a.appId } } },
1676
+ },
1677
+ });
1678
+ const subId = sub.data.id;
1679
+ await client.post("/reviewSubmissionItems", {
1680
+ data: {
1681
+ type: "reviewSubmissionItems",
1682
+ relationships: {
1683
+ reviewSubmission: {
1684
+ data: { type: "reviewSubmissions", id: subId },
1685
+ },
1686
+ appStoreVersion: {
1687
+ data: { type: "appStoreVersions", id: a.versionId },
1688
+ },
1689
+ },
1690
+ },
1691
+ });
1692
+ const submitted = await client.patch(`/reviewSubmissions/${subId}`, {
1693
+ data: {
1694
+ type: "reviewSubmissions",
1695
+ id: subId,
1696
+ attributes: { submitted: true },
1697
+ },
1698
+ });
1699
+ return { reviewSubmissionId: subId, result: submitted };
1700
+ },
1701
+ },
1702
+ {
1703
+ name: "release_version",
1704
+ description:
1705
+ "Release an approved version that's waiting for manual release (state PENDING_DEVELOPER_RELEASE). NOTE: this makes the version live on the App Store — confirm with the user first.",
1706
+ inputSchema: {
1707
+ type: "object",
1708
+ properties: { versionId: { type: "string" } },
1709
+ required: ["versionId"],
1710
+ },
1711
+ run: async (a) =>
1712
+ client.post("/appStoreVersionReleaseRequests", {
1713
+ data: {
1714
+ type: "appStoreVersionReleaseRequests",
1715
+ relationships: {
1716
+ appStoreVersion: {
1717
+ data: { type: "appStoreVersions", id: a.versionId },
1718
+ },
1719
+ },
1720
+ },
1721
+ }),
1722
+ },
1723
+
1724
+ // ---- Code-signing health ----
1725
+ {
1726
+ name: "signing_health",
1727
+ description:
1728
+ "Code-signing health check: lists certificates and provisioning profiles, flagging any expired or expiring within `withinDays` (default 30) and any INVALID profiles. Catches silent CI breakage before it happens. Read-only.",
1729
+ inputSchema: {
1730
+ type: "object",
1731
+ properties: {
1732
+ withinDays: { type: "number", description: "Flag items expiring within N days (default 30)" },
1733
+ },
1734
+ },
1735
+ run: async (a) => {
1736
+ const within = a.withinDays ?? 30;
1737
+ const now = Date.now();
1738
+ const daysLeft = (d) =>
1739
+ d ? Math.round((new Date(d).getTime() - now) / 86400000) : null;
1740
+ const certs = await client.getAll("/certificates", { limit: 200 });
1741
+ const profs = await client.getAll("/profiles", { limit: 200 });
1742
+ const certRows = certs.map((c) => ({
1743
+ id: c.id,
1744
+ name: c.attributes.name,
1745
+ type: c.attributes.certificateType,
1746
+ expirationDate: c.attributes.expirationDate,
1747
+ daysLeft: daysLeft(c.attributes.expirationDate),
1748
+ }));
1749
+ const profRows = profs.map((p) => ({
1750
+ id: p.id,
1751
+ name: p.attributes.name,
1752
+ type: p.attributes.profileType,
1753
+ state: p.attributes.profileState,
1754
+ expirationDate: p.attributes.expirationDate,
1755
+ daysLeft: daysLeft(p.attributes.expirationDate),
1756
+ }));
1757
+ const certIssues = certRows.filter(
1758
+ (c) => c.daysLeft !== null && c.daysLeft <= within,
1759
+ );
1760
+ const profileIssues = profRows.filter(
1761
+ (p) =>
1762
+ p.state === "INVALID" ||
1763
+ (p.daysLeft !== null && p.daysLeft <= within),
1764
+ );
1765
+ return {
1766
+ summary: {
1767
+ certificates: certRows.length,
1768
+ profiles: profRows.length,
1769
+ certsExpiringOrExpired: certIssues.length,
1770
+ profilesInvalidOrExpiring: profileIssues.length,
1771
+ withinDays: within,
1772
+ },
1773
+ certIssues: certIssues.sort((x, y) => (x.daysLeft ?? 0) - (y.daysLeft ?? 0)),
1774
+ profileIssues: profileIssues.sort((x, y) => (x.daysLeft ?? 0) - (y.daysLeft ?? 0)),
1775
+ };
1776
+ },
1777
+ },
1778
+ {
1779
+ name: "update_in_app_purchase",
1780
+ description:
1781
+ "Update an in-app purchase product's editable fields (reference name and/or review note). Pass the inAppPurchasesV2 id.",
1782
+ inputSchema: {
1783
+ type: "object",
1784
+ properties: {
1785
+ inAppPurchaseId: { type: "string" },
1786
+ name: { type: "string", description: "Reference name" },
1787
+ reviewNote: { type: "string" },
1788
+ },
1789
+ required: ["inAppPurchaseId"],
1790
+ },
1791
+ run: async (a) => {
1792
+ const attributes = {};
1793
+ if (a.name !== undefined) attributes.name = a.name;
1794
+ if (a.reviewNote !== undefined) attributes.reviewNote = a.reviewNote;
1795
+ return client.patch(`/inAppPurchasesV2/${a.inAppPurchaseId}`, {
1796
+ data: { type: "inAppPurchasesV2", id: a.inAppPurchaseId, attributes },
1797
+ });
1798
+ },
1799
+ },
1800
+
1591
1801
  // ---- Generic escape hatch ----
1592
1802
  {
1593
1803
  name: "raw_request",