appstore-api-mcp 1.16.2 → 1.16.3

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,18 @@ 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.3] - 2026-10-08
8
+
9
+ ### Fixed
10
+ - **Wrong US price for products with a price change** (affects `list_purchasable_products`
11
+ and the PPP base price in `preview_ppp_prices` / `apply_ppp_prices`). The US-price
12
+ lookup returned the *first* USA price row — usually the original base price —
13
+ instead of the price in effect today. A subscription raised from $0.99 to $2.99
14
+ reported $0.99, which also skewed every per-territory PPP recommendation derived
15
+ from it. Now selects the current effective price (latest `startDate` on or before
16
+ today; `null` = baseline; future-scheduled changes ignored) for both subscriptions
17
+ and IAPs. New pure `currentEffectivePrice()` helper, unit-tested.
18
+
7
19
  ## [1.16.2] - 2026-10-08
8
20
 
9
21
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appstore-api-mcp",
3
- "version": "1.16.2",
3
+ "version": "1.16.3",
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 ppId = price.relationships?.subscriptionPricePoint?.data?.id;
437
- const pp = ppMap.get(ppId);
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 null;
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) === "USA") {
456
- const pp = ppMap.get(ppId);
457
- if (pp) return Number(pp.attributes.customerPrice);
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 null;
463
+ return currentEffectivePrice(rows);
461
464
  }
462
465
 
463
466
  /** Parse raw price-point rows into { id, customerPrice, territory3 }, skipping bad ones. */
@@ -5738,7 +5741,7 @@ Some things a great App Store listing needs live OUTSIDE App Store Connect and c
5738
5741
  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
5742
 
5740
5743
  const server = new Server(
5741
- { name: "appstore-api-mcp", version: "1.16.2" },
5744
+ { name: "appstore-api-mcp", version: "1.16.3" },
5742
5745
  { capabilities: { tools: {} }, instructions: SERVER_INSTRUCTIONS },
5743
5746
  );
5744
5747
 
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();