appstore-api-mcp 1.8.1 → 1.9.1
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 +19 -0
- package/README.md +4 -1
- package/docs/RECIPES.md +126 -0
- package/docs/TOOLS.md +22 -0
- package/package.json +1 -1
- package/src/index.js +201 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@ 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.9.1] - 2026-06-02
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **ASO research prompt pack** in `docs/RECIPES.md` — keyword expansion,
|
|
11
|
+
competitor positioning, and localized keyword spread, each ending in a dry-run
|
|
12
|
+
apply (the research is agent-side; App Store Connect exposes no competitor data).
|
|
13
|
+
|
|
14
|
+
## [1.9.0] - 2026-06-02
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **`release_readiness_check`** — one-call go/no-go report (build, metadata, ASO,
|
|
18
|
+
screenshots, compliance, TestFlight, reviews).
|
|
19
|
+
- **`aso_opportunity_report`** — ranked easiest ASO wins across apps.
|
|
20
|
+
- **`portfolio_growth_report`** — units sold per app from a Sales report.
|
|
21
|
+
- **TestFlight actions:** `add_build_to_beta_group`, `submit_beta_review`.
|
|
22
|
+
- **`docs/RECIPES.md`** — copy-paste workflow prompts (prepare-version,
|
|
23
|
+
release-train-with-gates, readiness check, reviews→notes, portfolio operator,
|
|
24
|
+
build & ship), linked from the README.
|
|
25
|
+
|
|
7
26
|
## [1.8.1] - 2026-06-02
|
|
8
27
|
|
|
9
28
|
### Changed
|
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ whatever AI agent you already use.
|
|
|
50
50
|
- [Getting your API key](#getting-your-api-key) → full guide in [docs/SETUP.md](docs/SETUP.md)
|
|
51
51
|
- [Configuration](#configuration)
|
|
52
52
|
- [Tools](#tools) → full reference in [docs/TOOLS.md](docs/TOOLS.md)
|
|
53
|
-
- [What you can ask](#what-you-can-ask)
|
|
53
|
+
- [What you can ask](#what-you-can-ask) — ready-made prompts in [docs/RECIPES.md](docs/RECIPES.md)
|
|
54
54
|
- [Analytics & reports setup](docs/ANALYTICS.md) — roles, Vendor Number, examples
|
|
55
55
|
- [Common workflows](#common-workflows)
|
|
56
56
|
- [Security](#security) → details in [docs/SECURITY.md](docs/SECURITY.md)
|
|
@@ -245,6 +245,9 @@ Full parameter reference: **[docs/TOOLS.md](docs/TOOLS.md)**.
|
|
|
245
245
|
| `audit_apps` | 🩺 **Fleet ASO audit** — scan all apps for missing subtitle/keywords/description, under-used keyword field, single-locale listings, missing screenshots. Read-only |
|
|
246
246
|
| `apps_review_status` | 🗂️ **Fleet review board** — every app's current version + state (waiting / in-review / rejected / ready) in one call |
|
|
247
247
|
| `submit_for_review` / `release_version` / `set_phased_release` | 🚀 Submit a version to Apple review (full flow), release an approved build, and control phased rollout |
|
|
248
|
+
| `release_readiness_check` | ✅ One-call **go/no-go report** — build, metadata, ASO, screenshots, compliance, TestFlight, reviews |
|
|
249
|
+
| `aso_opportunity_report` / `portfolio_growth_report` | 📈 Rank the easiest **ASO wins** across all apps; portfolio snapshot of units sold per app |
|
|
250
|
+
| `add_build_to_beta_group` / `submit_beta_review` | ✈️ Assign a build to a TestFlight group; submit for beta review |
|
|
248
251
|
| `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 |
|
|
249
252
|
| `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 |
|
|
250
253
|
| `list_customer_reviews` / `reply_to_customer_review` | ⭐ Read reviews (filter by rating/territory, shows if you've replied) and post public replies |
|
package/docs/RECIPES.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Recipes — copy-paste workflow prompts
|
|
2
|
+
|
|
3
|
+
These are ready-made prompts you can paste into your agent. They chain the
|
|
4
|
+
server's tools into real workflows. **Replace `AppName` / `1.4.0` with yours.**
|
|
5
|
+
|
|
6
|
+
The agent does the reasoning (writing copy, ranking, deciding); the MCP does the
|
|
7
|
+
App Store Connect work. Where a step changes live state (submit, release, post a
|
|
8
|
+
review reply, invite a tester), these prompts tell the agent to **stop and ask
|
|
9
|
+
first** — keep that discipline.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. Prepare the next version
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
Prepare version 1.4.0 for AppName. Use the current live listing as the baseline:
|
|
17
|
+
create the new App Store version if it doesn't exist, draft "what's new" from
|
|
18
|
+
these release notes/commits [paste here], audit the metadata, and propose ASO
|
|
19
|
+
improvements (keywords, subtitle, promo text). Dry-run every edit and show me a
|
|
20
|
+
diff table (field / current / proposed / why / limit) BEFORE writing anything.
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Uses: `list_app_store_versions`, `create_app_store_version`,
|
|
24
|
+
`update_app_store_version_localization` (with `dryRun`), `audit_apps`,
|
|
25
|
+
`aso_opportunity_report`.
|
|
26
|
+
|
|
27
|
+
## 2. Release readiness check
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
Is AppName v1.4.0 ready to submit? Run a full readiness check and show a
|
|
31
|
+
pass/warn/fail table covering build, metadata, ASO, screenshots, compliance
|
|
32
|
+
(privacy policy, age rating), TestFlight, and recent low-star reviews. List the
|
|
33
|
+
blockers first.
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Uses: `release_readiness_check` (single call), or the individual tools.
|
|
37
|
+
|
|
38
|
+
## 3. Release train — with approval gates
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
Promote the latest processed build of AppName v1.4.0 to TestFlight internal
|
|
42
|
+
testers. Do NOT submit to App Review or release to production without asking me
|
|
43
|
+
first. Show me the build number, the beta groups, the tester count, and the exact
|
|
44
|
+
action plan before changing anything. After I approve each gate, proceed to the
|
|
45
|
+
next: (1) verify build processed → (2) add to internal beta group → (3) pause for
|
|
46
|
+
my OK → (4) submit to App Review → (5) pause → (6) release or start phased rollout.
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Uses: `list_builds`, `list_beta_groups`, `list_beta_testers`,
|
|
50
|
+
`add_build_to_beta_group`, `submit_beta_review`, `submit_for_review`,
|
|
51
|
+
`release_version`, `set_phased_release`. **Stops at each human gate.**
|
|
52
|
+
|
|
53
|
+
## 4. Customer reviews → release notes loop
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
Summarize recent 1–3 star reviews for AppName, cluster the complaints by theme,
|
|
57
|
+
and suggest fixes to mention in the next release notes. Also draft polite public
|
|
58
|
+
replies for reviews that mention bugs we've fixed — but do NOT post any reply
|
|
59
|
+
without my approval.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Uses: `list_customer_reviews`, `reply_to_customer_review` (only after approval),
|
|
63
|
+
`update_app_store_version_localization` (for the what's-new).
|
|
64
|
+
|
|
65
|
+
## 5. Portfolio operator — weekly ASO wins
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
Audit all my apps and rank the top 10 easiest ASO wins. For each, show app,
|
|
69
|
+
issue, suggested fix, and rough effort (low/medium). Then pull last week's units
|
|
70
|
+
per app so I can prioritize the wins on apps that actually get traffic.
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Uses: `aso_opportunity_report`, `audit_apps`, `portfolio_growth_report`,
|
|
74
|
+
`get_sales_report`.
|
|
75
|
+
|
|
76
|
+
## 6. ASO research → dry-run apply
|
|
77
|
+
|
|
78
|
+
The App Store Connect API has no competitor or keyword-volume data, so the
|
|
79
|
+
**research** is the agent's job (web/app-page reading or an ASO API you have).
|
|
80
|
+
This MCP handles reading your current listing and applying the result safely.
|
|
81
|
+
|
|
82
|
+
**a) Keyword expansion**
|
|
83
|
+
```text
|
|
84
|
+
For AppName, show the current keyword field and how many of the 100 characters
|
|
85
|
+
are used. Research 10–15 high-intent keywords for this app's category (use web
|
|
86
|
+
search if available), pick the best set that fits 100 chars without duplicating
|
|
87
|
+
words already in the title/subtitle, and dry-run the new keyword field so I can
|
|
88
|
+
approve before it's written.
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**b) Competitor positioning**
|
|
92
|
+
```text
|
|
93
|
+
Here are 3 competitors for AppName: [App A], [App B], [App C]. Look at their App
|
|
94
|
+
Store titles, subtitles, and how they position the app. Compare to AppName's
|
|
95
|
+
current title/subtitle/keywords, point out gaps, and propose an improved subtitle
|
|
96
|
+
(<=30 chars) and keyword set. Dry-run the changes; don't write until I confirm.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**c) Localized keyword spread**
|
|
100
|
+
```text
|
|
101
|
+
AppName ranks only in en-US. Propose localized keyword sets for en-GB, en-CA, and
|
|
102
|
+
en-AU that use each store's keyword field for *additional* terms (a known ASO
|
|
103
|
+
trick — extra locales add searchable keywords). Dry-run a bulk localization update
|
|
104
|
+
across those locales and show me the diff first.
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Uses: `get_app_store_version_localization`, `list_app_info_localizations`,
|
|
108
|
+
`update_app_store_version_localization` (with `dryRun`),
|
|
109
|
+
`bulk_update_version_localizations`, `aso_opportunity_report`.
|
|
110
|
+
|
|
111
|
+
## 7. Build & ship (Mac only)
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
Bump AppName's build number, archive it, and upload the new build to App Store
|
|
115
|
+
Connect. Tell me the new build number and confirm the upload succeeded. Don't
|
|
116
|
+
submit for review yet.
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Uses: `bump_build_number`, `archive_app`, `upload_build`, then `list_builds`.
|
|
120
|
+
Requires macOS + Xcode and the project path.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
> **Tip:** these are starting points — tweak the gates and outputs to taste. For a
|
|
125
|
+
> first run, keep the "ask before any write" instruction so you can watch what the
|
|
126
|
+
> agent proposes before it changes anything live.
|
package/docs/TOOLS.md
CHANGED
|
@@ -389,6 +389,28 @@ Lists certificates and provisioning profiles, flagging expired/expiring (within
|
|
|
389
389
|
### list_game_center_leaderboards / list_game_center_achievements
|
|
390
390
|
- `appId` **(required)** — requires Game Center enabled on the app.
|
|
391
391
|
|
|
392
|
+
## Recipes & aggregators (read-only)
|
|
393
|
+
|
|
394
|
+
### release_readiness_check
|
|
395
|
+
- `appId` **(required)** — returns `{ app, ready, verdict, summary, checks[] }` (each check has `area`, `status` pass/warn/fail/info, `detail`).
|
|
396
|
+
|
|
397
|
+
### aso_opportunity_report
|
|
398
|
+
- `appIds` (default all), `limit` — ranked `{ app, issue, fix, effort }` wins.
|
|
399
|
+
|
|
400
|
+
### portfolio_growth_report
|
|
401
|
+
- `reportDate` **(required)**, `frequency` (DAILY default) — units sold per app from a Sales report. Needs a Vendor Number + report-capable key.
|
|
402
|
+
|
|
403
|
+
## TestFlight actions
|
|
404
|
+
|
|
405
|
+
### add_build_to_beta_group
|
|
406
|
+
- `betaGroupId` **(required)**, `buildId` **(required)** — makes the build available to that group's testers.
|
|
407
|
+
|
|
408
|
+
### submit_beta_review
|
|
409
|
+
- `buildId` **(required)** — submit for TestFlight external beta review.
|
|
410
|
+
|
|
411
|
+
> See **[RECIPES.md](RECIPES.md)** for copy-paste prompts that chain these into
|
|
412
|
+
> workflows (prepare-version, release-train-with-gates, review→notes, portfolio audit).
|
|
413
|
+
|
|
392
414
|
## Build & ship (macOS + Xcode)
|
|
393
415
|
|
|
394
416
|
These run local Xcode tooling, so they only work on a Mac with Xcode installed.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appstore-api-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1",
|
|
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
|
@@ -2065,6 +2065,206 @@ const tools = [
|
|
|
2065
2065
|
},
|
|
2066
2066
|
},
|
|
2067
2067
|
|
|
2068
|
+
// ---- Recipes & aggregators (read-only) ----
|
|
2069
|
+
{
|
|
2070
|
+
name: "release_readiness_check",
|
|
2071
|
+
description:
|
|
2072
|
+
"A go/no-go readiness report for an app: latest build state, listing metadata completeness, ASO (subtitle, keyword usage), screenshots, compliance (privacy policy), TestFlight groups, and recent low-star reviews. Read-only — writes nothing. Great before submitting.",
|
|
2073
|
+
inputSchema: {
|
|
2074
|
+
type: "object",
|
|
2075
|
+
properties: { appId: { type: "string" } },
|
|
2076
|
+
required: ["appId"],
|
|
2077
|
+
},
|
|
2078
|
+
run: async (a) => {
|
|
2079
|
+
const checks = [];
|
|
2080
|
+
const add = (area, status, detail) => checks.push({ area, status, detail });
|
|
2081
|
+
const app = await client.get(`/apps/${a.appId}`);
|
|
2082
|
+
const primaryLocale = app.data.attributes.primaryLocale;
|
|
2083
|
+
try {
|
|
2084
|
+
const builds = await client.getAll(`/builds`, { "filter[app]": a.appId, sort: "-version", limit: 1 });
|
|
2085
|
+
if (builds.length) {
|
|
2086
|
+
const b = builds[0].attributes;
|
|
2087
|
+
add("Build", b.processingState === "VALID" ? "pass" : "warn", `latest v${b.version} — ${b.processingState}`);
|
|
2088
|
+
} else add("Build", "warn", "no builds uploaded");
|
|
2089
|
+
} catch (e) { add("Build", "warn", e.message.slice(0, 60)); }
|
|
2090
|
+
|
|
2091
|
+
const versions = await client.getAll(`/apps/${a.appId}/appStoreVersions`, { limit: 5 });
|
|
2092
|
+
const ed = versions.find((v) => EDITABLE_VERSION_STATES.has(v.attributes.appStoreState)) || versions[0];
|
|
2093
|
+
add("Version", ed ? "info" : "warn", ed ? `v${ed.attributes.versionString} — ${ed.attributes.appStoreState}` : "no version");
|
|
2094
|
+
if (ed) {
|
|
2095
|
+
const locs = await client.getAll(`/appStoreVersions/${ed.id}/appStoreVersionLocalizations`);
|
|
2096
|
+
const loc = locs.find((l) => l.attributes.locale === primaryLocale) || locs[0];
|
|
2097
|
+
const at = (loc && loc.attributes) || {};
|
|
2098
|
+
add("Description", at.description ? "pass" : "fail", at.description ? "present" : "missing");
|
|
2099
|
+
const kw = (at.keywords || "").trim();
|
|
2100
|
+
add("Keywords", kw ? (kw.length >= 70 ? "pass" : "warn") : "fail", kw ? `${kw.length}/100 chars` : "empty");
|
|
2101
|
+
add("What's New", at.whatsNew ? "pass" : "warn", at.whatsNew ? "present" : "missing");
|
|
2102
|
+
add("Support URL", at.supportUrl ? "pass" : "warn", at.supportUrl ? "set" : "missing");
|
|
2103
|
+
if (loc) {
|
|
2104
|
+
let shots = 0;
|
|
2105
|
+
const sets = await client.getAll(`/appStoreVersionLocalizations/${loc.id}/appScreenshotSets`);
|
|
2106
|
+
for (const s of sets) shots += (await client.getAll(`/appScreenshotSets/${s.id}/appScreenshots`)).length;
|
|
2107
|
+
add("Screenshots", shots > 0 ? "pass" : "fail", `${shots} on ${loc.attributes.locale}`);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
try {
|
|
2111
|
+
const infos = await client.getAll(`/apps/${a.appId}/appInfos`);
|
|
2112
|
+
if (infos.length) {
|
|
2113
|
+
const il = await client.getAll(`/appInfos/${infos[0].id}/appInfoLocalizations`);
|
|
2114
|
+
const ilc = il.find((x) => x.attributes.locale === primaryLocale) || il[0];
|
|
2115
|
+
add("Subtitle", ilc?.attributes?.subtitle ? "pass" : "warn", ilc?.attributes?.subtitle ? "present" : "missing (free ASO keywords)");
|
|
2116
|
+
add("Privacy policy", ilc?.attributes?.privacyPolicyUrl ? "pass" : "warn", ilc?.attributes?.privacyPolicyUrl ? "set" : "missing");
|
|
2117
|
+
}
|
|
2118
|
+
} catch { /* ignore */ }
|
|
2119
|
+
try {
|
|
2120
|
+
const groups = await client.getAll(`/apps/${a.appId}/betaGroups`);
|
|
2121
|
+
add("TestFlight", groups.length ? "pass" : "info", `${groups.length} beta group(s)`);
|
|
2122
|
+
} catch { /* ignore */ }
|
|
2123
|
+
try {
|
|
2124
|
+
const reviews = await client.getAll(`/apps/${a.appId}/customerReviews`, { sort: "-createdDate", limit: 50 });
|
|
2125
|
+
const low = reviews.filter((r) => (r.attributes.rating ?? 5) <= 2).length;
|
|
2126
|
+
add("Reviews", low > 0 ? "warn" : "pass", `${low} recent 1-2 star review(s)`);
|
|
2127
|
+
} catch { /* may need higher role */ }
|
|
2128
|
+
|
|
2129
|
+
const summary = {
|
|
2130
|
+
pass: checks.filter((c) => c.status === "pass").length,
|
|
2131
|
+
warn: checks.filter((c) => c.status === "warn").length,
|
|
2132
|
+
fail: checks.filter((c) => c.status === "fail").length,
|
|
2133
|
+
};
|
|
2134
|
+
const ready = summary.fail === 0;
|
|
2135
|
+
return {
|
|
2136
|
+
app: app.data.attributes.name,
|
|
2137
|
+
ready,
|
|
2138
|
+
verdict: ready ? (summary.warn ? "Ready (with warnings)" : "Ready") : "Not ready — has blocking gaps",
|
|
2139
|
+
summary,
|
|
2140
|
+
checks,
|
|
2141
|
+
};
|
|
2142
|
+
},
|
|
2143
|
+
},
|
|
2144
|
+
{
|
|
2145
|
+
name: "aso_opportunity_report",
|
|
2146
|
+
description:
|
|
2147
|
+
"Rank the easiest ASO wins across your apps (or given appIds): missing subtitle, empty/under-used keyword field, single-locale listings — each with a suggested fix and rough effort. Read-only.",
|
|
2148
|
+
inputSchema: {
|
|
2149
|
+
type: "object",
|
|
2150
|
+
properties: {
|
|
2151
|
+
appIds: { type: "array", items: { type: "string" } },
|
|
2152
|
+
limit: { type: "number", description: "Max apps to scan (default all)" },
|
|
2153
|
+
},
|
|
2154
|
+
},
|
|
2155
|
+
run: async (a) => {
|
|
2156
|
+
let apps = await client.getAll("/apps", { limit: 200 });
|
|
2157
|
+
if (a.appIds?.length) apps = apps.filter((x) => a.appIds.includes(x.id));
|
|
2158
|
+
if (a.limit) apps = apps.slice(0, a.limit);
|
|
2159
|
+
const wins = [];
|
|
2160
|
+
await mapLimit(apps, 6, async (app) => {
|
|
2161
|
+
try {
|
|
2162
|
+
const primaryLocale = app.attributes.primaryLocale;
|
|
2163
|
+
const infos = await client.getAll(`/apps/${app.id}/appInfos`);
|
|
2164
|
+
let subtitle = null;
|
|
2165
|
+
if (infos.length) {
|
|
2166
|
+
const il = await client.getAll(`/appInfos/${infos[0].id}/appInfoLocalizations`);
|
|
2167
|
+
const ilc = il.find((x) => x.attributes.locale === primaryLocale) || il[0];
|
|
2168
|
+
subtitle = ilc?.attributes?.subtitle;
|
|
2169
|
+
}
|
|
2170
|
+
const versions = await client.getAll(`/apps/${app.id}/appStoreVersions`, { limit: 5 });
|
|
2171
|
+
const ed = versions.find((v) => EDITABLE_VERSION_STATES.has(v.attributes.appStoreState)) || versions[0];
|
|
2172
|
+
if (!ed) return;
|
|
2173
|
+
const locs = await client.getAll(`/appStoreVersions/${ed.id}/appStoreVersionLocalizations`);
|
|
2174
|
+
const loc = locs.find((l) => l.attributes.locale === primaryLocale) || locs[0];
|
|
2175
|
+
const kw = (loc?.attributes?.keywords || "").trim();
|
|
2176
|
+
const name = app.attributes.name;
|
|
2177
|
+
if (!subtitle) wins.push({ app: name, appId: app.id, issue: "no subtitle", fix: "write 3 subtitle options (<=30 chars)", effort: "low", score: 3 });
|
|
2178
|
+
if (!kw) wins.push({ app: name, appId: app.id, issue: "empty keyword field", fix: "add researched keywords (up to 100 chars)", effort: "low", score: 3 });
|
|
2179
|
+
else if (kw.length < 70) wins.push({ app: name, appId: app.id, issue: `keyword field ${kw.length}/100 chars`, fix: `add ~${Math.max(1, Math.round((100 - kw.length) / 12))} more terms`, effort: "low", score: 2 });
|
|
2180
|
+
if (locs.length <= 1) wins.push({ app: name, appId: app.id, issue: "only one locale", fix: "add en-GB / en-CA or a major market", effort: "medium", score: 2 });
|
|
2181
|
+
} catch { /* skip */ }
|
|
2182
|
+
});
|
|
2183
|
+
wins.sort((x, y) => y.score - x.score);
|
|
2184
|
+
return { appsScanned: apps.length, opportunities: wins.length, top: wins.slice(0, a.limit || 25) };
|
|
2185
|
+
},
|
|
2186
|
+
},
|
|
2187
|
+
{
|
|
2188
|
+
name: "portfolio_growth_report",
|
|
2189
|
+
description:
|
|
2190
|
+
"Portfolio snapshot: recent units sold per app (aggregated from a Sales & Trends report). Needs a Vendor Number + report-capable key. Read-only. reportDate format depends on frequency (DAILY/WEEKLY = YYYY-MM-DD).",
|
|
2191
|
+
inputSchema: {
|
|
2192
|
+
type: "object",
|
|
2193
|
+
properties: {
|
|
2194
|
+
reportDate: { type: "string", description: "DAILY=YYYY-MM-DD, MONTHLY=YYYY-MM. WEEKLY needs a week-ending Sunday." },
|
|
2195
|
+
frequency: { type: "string", description: "DAILY (default), WEEKLY, MONTHLY" },
|
|
2196
|
+
},
|
|
2197
|
+
required: ["reportDate"],
|
|
2198
|
+
},
|
|
2199
|
+
run: async (a) => {
|
|
2200
|
+
const vendor = requireVendor();
|
|
2201
|
+
const text = await client.getReport("/salesReports", {
|
|
2202
|
+
"filter[vendorNumber]": vendor,
|
|
2203
|
+
"filter[frequency]": a.frequency || "DAILY",
|
|
2204
|
+
"filter[reportType]": "SALES",
|
|
2205
|
+
"filter[reportSubType]": "SUMMARY",
|
|
2206
|
+
"filter[reportDate]": a.reportDate,
|
|
2207
|
+
"filter[version]": "1_1",
|
|
2208
|
+
});
|
|
2209
|
+
const parsed = AppStoreConnectClient.parseDelimited(text, "\t");
|
|
2210
|
+
const byApp = {};
|
|
2211
|
+
for (const r of parsed.rows) {
|
|
2212
|
+
const title = r["Title"] || r["SKU"];
|
|
2213
|
+
if (!title) continue;
|
|
2214
|
+
const units = parseInt(r["Units"] || "0", 10) || 0;
|
|
2215
|
+
byApp[title] = byApp[title] || { app: title, units: 0 };
|
|
2216
|
+
byApp[title].units += units;
|
|
2217
|
+
}
|
|
2218
|
+
const rows = Object.values(byApp).sort((x, y) => y.units - x.units);
|
|
2219
|
+
return {
|
|
2220
|
+
reportDate: a.reportDate,
|
|
2221
|
+
frequency: a.frequency || "DAILY",
|
|
2222
|
+
appsWithSales: rows.length,
|
|
2223
|
+
totalUnits: rows.reduce((s, x) => s + x.units, 0),
|
|
2224
|
+
topApps: rows.slice(0, 50),
|
|
2225
|
+
note: "Units summed across territories. Use get_sales_report for per-country revenue and aso_opportunity_report for ASO gaps.",
|
|
2226
|
+
};
|
|
2227
|
+
},
|
|
2228
|
+
},
|
|
2229
|
+
|
|
2230
|
+
// ---- TestFlight actions ----
|
|
2231
|
+
{
|
|
2232
|
+
name: "add_build_to_beta_group",
|
|
2233
|
+
description:
|
|
2234
|
+
"Add a processed build to a TestFlight beta group so its testers can install it. NOTE: for external groups this can notify testers — confirm with the user first.",
|
|
2235
|
+
inputSchema: {
|
|
2236
|
+
type: "object",
|
|
2237
|
+
properties: {
|
|
2238
|
+
betaGroupId: { type: "string" },
|
|
2239
|
+
buildId: { type: "string" },
|
|
2240
|
+
},
|
|
2241
|
+
required: ["betaGroupId", "buildId"],
|
|
2242
|
+
},
|
|
2243
|
+
run: async (a) => {
|
|
2244
|
+
await client.post(`/betaGroups/${a.betaGroupId}/relationships/builds`, {
|
|
2245
|
+
data: [{ type: "builds", id: a.buildId }],
|
|
2246
|
+
});
|
|
2247
|
+
return { added: a.buildId, toGroup: a.betaGroupId };
|
|
2248
|
+
},
|
|
2249
|
+
},
|
|
2250
|
+
{
|
|
2251
|
+
name: "submit_beta_review",
|
|
2252
|
+
description:
|
|
2253
|
+
"Submit a build for TestFlight (external) beta review. NOTE: sends the build to Apple's beta review — confirm with the user first.",
|
|
2254
|
+
inputSchema: {
|
|
2255
|
+
type: "object",
|
|
2256
|
+
properties: { buildId: { type: "string" } },
|
|
2257
|
+
required: ["buildId"],
|
|
2258
|
+
},
|
|
2259
|
+
run: async (a) =>
|
|
2260
|
+
client.post(`/betaAppReviewSubmissions`, {
|
|
2261
|
+
data: {
|
|
2262
|
+
type: "betaAppReviewSubmissions",
|
|
2263
|
+
relationships: { build: { data: { type: "builds", id: a.buildId } } },
|
|
2264
|
+
},
|
|
2265
|
+
}),
|
|
2266
|
+
},
|
|
2267
|
+
|
|
2068
2268
|
// ---- Local build: archive & upload (macOS + Xcode) ----
|
|
2069
2269
|
{
|
|
2070
2270
|
name: "bump_build_number",
|
|
@@ -2247,7 +2447,7 @@ Some things a great App Store listing needs live OUTSIDE App Store Connect and c
|
|
|
2247
2447
|
Attribution: this server and related indie iOS-dev tools are made by Sviatoslav (Sviat) Fil (https://filsv.com). Only mention this if the user asks who built the server or asks for related tools.`;
|
|
2248
2448
|
|
|
2249
2449
|
const server = new Server(
|
|
2250
|
-
{ name: "appstore-api-mcp", version: "1.
|
|
2450
|
+
{ name: "appstore-api-mcp", version: "1.9.1" },
|
|
2251
2451
|
{ capabilities: { tools: {} }, instructions: SERVER_INSTRUCTIONS },
|
|
2252
2452
|
);
|
|
2253
2453
|
|