appstore-api-mcp 1.16.2 → 1.16.4
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 +22 -0
- package/package.json +1 -1
- package/src/index.js +21 -14
- package/src/ppp.js +22 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,28 @@ 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.16.4] - 2026-10-08
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- `apply_ppp_prices` could never apply to an **in-app purchase**: it defaulted
|
|
11
|
+
`preserveCurrentPrice` to true and passed it into the resolver, which rejects
|
|
12
|
+
IAPs when that flag is set ("applies only to subscriptions"). The default is
|
|
13
|
+
now applied only when calling the subscription apply path (a no-op for IAPs),
|
|
14
|
+
after resolution, so IAP PPP pricing works. (Workaround on older versions:
|
|
15
|
+
pass `preserveCurrentPrice:false` for IAPs.)
|
|
16
|
+
|
|
17
|
+
## [1.16.3] - 2026-10-08
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- **Wrong US price for products with a price change** (affects `list_purchasable_products`
|
|
21
|
+
and the PPP base price in `preview_ppp_prices` / `apply_ppp_prices`). The US-price
|
|
22
|
+
lookup returned the *first* USA price row — usually the original base price —
|
|
23
|
+
instead of the price in effect today. A subscription raised from $0.99 to $2.99
|
|
24
|
+
reported $0.99, which also skewed every per-territory PPP recommendation derived
|
|
25
|
+
from it. Now selects the current effective price (latest `startDate` on or before
|
|
26
|
+
today; `null` = baseline; future-scheduled changes ignored) for both subscriptions
|
|
27
|
+
and IAPs. New pure `currentEffectivePrice()` helper, unit-tested.
|
|
28
|
+
|
|
7
29
|
## [1.16.2] - 2026-10-08
|
|
8
30
|
|
|
9
31
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appstore-api-mcp",
|
|
3
|
-
"version": "1.16.
|
|
3
|
+
"version": "1.16.4",
|
|
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
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
decodeTerritory,
|
|
33
33
|
validateCoefficients,
|
|
34
34
|
tierSummary,
|
|
35
|
+
currentEffectivePrice,
|
|
35
36
|
} from "./ppp.js";
|
|
36
37
|
import { detectOverlappingOffers } from "./subscriptions.js";
|
|
37
38
|
|
|
@@ -431,13 +432,16 @@ async function pppFetchUsPrice(product) {
|
|
|
431
432
|
const ppMap = new Map(
|
|
432
433
|
included.filter((i) => i.type === "subscriptionPricePoints").map((i) => [i.id, i]),
|
|
433
434
|
);
|
|
435
|
+
// Collect ALL USA price rows (original + every scheduled change) with their
|
|
436
|
+
// startDate, then pick the one effective today — not just the first row,
|
|
437
|
+
// which is usually the stale original base price.
|
|
438
|
+
const rows = [];
|
|
434
439
|
for (const price of data) {
|
|
435
440
|
if (price.relationships?.territory?.data?.id !== "USA") continue;
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
if (pp) return Number(pp.attributes.customerPrice);
|
|
441
|
+
const pp = ppMap.get(price.relationships?.subscriptionPricePoint?.data?.id);
|
|
442
|
+
if (pp) rows.push({ startDate: price.attributes?.startDate ?? null, amount: pp.attributes.customerPrice });
|
|
439
443
|
}
|
|
440
|
-
return
|
|
444
|
+
return currentEffectivePrice(rows);
|
|
441
445
|
}
|
|
442
446
|
const { data, included } = await client.getAllPages(
|
|
443
447
|
`/inAppPurchasePriceSchedules/${product.id}/manualPrices`,
|
|
@@ -449,15 +453,14 @@ async function pppFetchUsPrice(product) {
|
|
|
449
453
|
const ppMap = new Map(
|
|
450
454
|
included.filter((i) => i.type === "inAppPurchasePricePoints").map((i) => [i.id, i]),
|
|
451
455
|
);
|
|
456
|
+
const rows = [];
|
|
452
457
|
for (const price of data) {
|
|
453
|
-
if (price.attributes?.startDate != null) continue; // skip scheduled future prices
|
|
454
458
|
const ppId = price.relationships?.inAppPurchasePricePoint?.data?.id || "";
|
|
455
|
-
if (decodeTerritory(ppId)
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
}
|
|
459
|
+
if (decodeTerritory(ppId) !== "USA") continue;
|
|
460
|
+
const pp = ppMap.get(ppId);
|
|
461
|
+
if (pp) rows.push({ startDate: price.attributes?.startDate ?? null, amount: pp.attributes.customerPrice });
|
|
459
462
|
}
|
|
460
|
-
return
|
|
463
|
+
return currentEffectivePrice(rows);
|
|
461
464
|
}
|
|
462
465
|
|
|
463
466
|
/** Parse raw price-point rows into { id, customerPrice, territory3 }, skipping bad ones. */
|
|
@@ -3930,10 +3933,14 @@ ${a.teamId ? `<key>teamID</key><string>${a.teamId}</string>\n` : ""}<key>uploadS
|
|
|
3930
3933
|
error:
|
|
3931
3934
|
"Refused: apply_ppp_prices needs confirm:true. Run preview_ppp_prices, show the user the table, get explicit approval, then re-call with confirm:true.",
|
|
3932
3935
|
};
|
|
3933
|
-
//
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
+
// Resolve with the user's args as-is. Do NOT inject a default
|
|
3937
|
+
// preserveCurrentPrice here: the resolver rejects IAPs when that flag is
|
|
3938
|
+
// set, so defaulting it to true before resolve broke IAP applies.
|
|
3939
|
+
const r = await pppResolve(a);
|
|
3936
3940
|
if (r.error) return r;
|
|
3941
|
+
// Default preserveCurrentPrice to true for subscriptions only (safer for
|
|
3942
|
+
// existing subscribers); it is a no-op for IAPs.
|
|
3943
|
+
const preserved = a.preserveCurrentPrice === undefined ? true : a.preserveCurrentPrice;
|
|
3937
3944
|
const result = await pppApply(r.product, r.resolved, {
|
|
3938
3945
|
preserved,
|
|
3939
3946
|
startDate: a.startDate,
|
|
@@ -5738,7 +5745,7 @@ Some things a great App Store listing needs live OUTSIDE App Store Connect and c
|
|
|
5738
5745
|
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.`;
|
|
5739
5746
|
|
|
5740
5747
|
const server = new Server(
|
|
5741
|
-
{ name: "appstore-api-mcp", version: "1.16.
|
|
5748
|
+
{ name: "appstore-api-mcp", version: "1.16.4" },
|
|
5742
5749
|
{ capabilities: { tools: {} }, instructions: SERVER_INSTRUCTIONS },
|
|
5743
5750
|
);
|
|
5744
5751
|
|
package/src/ppp.js
CHANGED
|
@@ -168,6 +168,28 @@ export function decodeTerritory(pricePointId) {
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Pick the CURRENTLY-EFFECTIVE price from dated price rows.
|
|
173
|
+
* rows: [{ startDate: string|null, amount: number }]
|
|
174
|
+
* App Store Connect keeps every price a product has ever had: the original
|
|
175
|
+
* (startDate null) plus one row per scheduled change. The price customers pay
|
|
176
|
+
* today is the row with the LATEST startDate that is on or before today; a null
|
|
177
|
+
* startDate is the baseline (effective from the beginning). Future-dated rows
|
|
178
|
+
* (a scheduled upcoming change) are ignored. Returns the amount, or null.
|
|
179
|
+
*/
|
|
180
|
+
export function currentEffectivePrice(rows, today = new Date().toISOString().slice(0, 10)) {
|
|
181
|
+
let best = null;
|
|
182
|
+
for (const r of rows || []) {
|
|
183
|
+
const amount = Number(r.amount);
|
|
184
|
+
if (!Number.isFinite(amount)) continue;
|
|
185
|
+
const start = r.startDate || null;
|
|
186
|
+
if (start && start > today) continue; // skip not-yet-active scheduled prices
|
|
187
|
+
const key = start || ""; // null baseline sorts earliest
|
|
188
|
+
if (!best || key >= best.key) best = { key, amount };
|
|
189
|
+
}
|
|
190
|
+
return best ? best.amount : null;
|
|
191
|
+
}
|
|
192
|
+
|
|
171
193
|
/** The default coefficient + floor for each tier, for tool docs / previews. */
|
|
172
194
|
export function tierSummary() {
|
|
173
195
|
const seen = new Map();
|