# Appstoreconnect Mcp

> MCP server for App Store Connect — rebalance subscription prices across ~175 countries via Purchasing Power Parity, schedule per-territory price changes, manage subscriptions from a chat.

- **Type:** MCP server
- **Install:** `agentstack add mcp-akoskomuves-appstoreconnect-mcp`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [akoskomuves](https://agentstack.voostack.com/s/akoskomuves)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [akoskomuves](https://github.com/akoskomuves)
- **Source:** https://github.com/akoskomuves/appstoreconnect-mcp
- **Website:** https://www.npmjs.com/package/@akoskomuves/appstoreconnect-mcp

## Install

```sh
agentstack add mcp-akoskomuves-appstoreconnect-mcp
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# appstoreconnect-mcp

[](https://www.npmjs.com/package/@akoskomuves/appstoreconnect-mcp)
[](https://github.com/akoskomuves/appstoreconnect-mcp/actions/workflows/ci.yml)
[](LICENSE)

A [Model Context Protocol](https://modelcontextprotocol.io) server for the [Apple App Store Connect API](https://developer.apple.com/documentation/appstoreconnectapi). Drives apps, subscriptions, pricing, and more from any MCP-compatible client (Claude Code, Claude Desktop, Cursor, Windsurf).

The first published surface is **subscription pricing** — including a Purchasing Power Parity rebalance flow that's already been used to schedule 120 production price changes across 65 territories on a real iOS app. New ASC domains (TestFlight, sales, screenshots, IAPs) are designed to plug in one file at a time; see [Roadmap](#roadmap).

## Install (zero-config)

```sh
npx @akoskomuves/appstoreconnect-mcp init
```

The wizard:

1. Opens [App Store Connect → Keys](https://appstoreconnect.apple.com/access/integrations/api) so you can download a `.p8` (skipped if you already have one).
2. Copies the key to `~/.appstore/` with `chmod 600`.
3. Asks for your Issuer ID and (auto-detected) Key ID.
4. Verifies auth with a real API call before writing anything.
5. Detects which MCP clients you have installed — Claude Code, Claude Desktop, Cursor, Windsurf — and registers itself in the ones you pick.

When something looks off later, run a read-only diagnostic:

```sh
npx @akoskomuves/appstoreconnect-mcp doctor
```

### Manual install

If you'd rather wire it up by hand, add to `~/.claude.json` (Claude Code), `claude_desktop_config.json` (Claude Desktop), or your client's equivalent:

```json
{
  "mcpServers": {
    "appstoreconnect": {
      "command": "npx",
      "args": ["-y", "@akoskomuves/appstoreconnect-mcp"],
      "env": {
        "ASC_ISSUER_ID": "...",
        "ASC_KEY_ID": "...",
        "ASC_PRIVATE_KEY_PATH": "~/.appstore/AuthKey_XXXXXXXXXX.p8"
      }
    }
  }
}
```

Or via Claude Code's CLI:

```sh
claude mcp add appstoreconnect \
  -e ASC_ISSUER_ID=... \
  -e ASC_KEY_ID=... \
  -e ASC_PRIVATE_KEY_PATH=~/.appstore/AuthKey_XXXXXXXXXX.p8 \
  -- npx -y @akoskomuves/appstoreconnect-mcp
```

## Configure

Generate an App Store Connect API key at [App Store Connect → Users and Access → Integrations → Keys](https://appstoreconnect.apple.com/access/integrations/api). Pricing writes need the **Admin** role; read-only operations work with **App Manager**.

| Variable | What |
| --- | --- |
| `ASC_ISSUER_ID` | Issuer UUID from the Keys page |
| `ASC_KEY_ID` | 10-character Key ID |
| `ASC_PRIVATE_KEY_PATH` | Path to your downloaded `AuthKey_XXXXXXXXXX.p8` file (`~` is expanded) |

The `.p8` file is a private key — never commit it. Recommended: `~/.appstore/AuthKey_XXXXXXXXXX.p8` outside any repo.

### Optional: In-App Purchase signing key

Only needed for the `asc_sign_*` tools (subscription offer redemption signing). Issue a second key at App Store Connect → Users and Access → Integrations → **In-App Purchase** — this is a separate key from the ASC API key above, generated on a different tab of the same page.

| Variable | What |
| --- | --- |
| `ASC_IAP_ISSUER_ID` | Issuer UUID from the In-App Purchase keys tab (different from `ASC_ISSUER_ID`) |
| `ASC_IAP_KEY_ID` | 10-character Key ID for the IAP key |
| `ASC_IAP_PRIVATE_KEY_PATH` | Path to the IAP signing `.p8` (`~` is expanded) |

The server starts fine without these — only the `asc_sign_*` tools refuse with a setup message if they're missing. Set one or two but not all three and the server rejects with a clear error. Run `appstoreconnect-mcp doctor` to verify the key loads as a valid ES256 PKCS#8.

### Optional: vendor number (sales + finance reports)

Only used by `asc_get_sales_report` / `asc_get_finance_report`. Your vendor number is account-level, shown at [App Store Connect → Payments and Financial Reports](https://appstoreconnect.apple.com/itc/payments_and_financial_reports) next to your team name (a numeric string like `85123456`).

| Variable | What |
| --- | --- |
| `ASC_VENDOR_NUMBER` | Default vendor number for sales/finance report downloads |

Without it the two report tools still work — they just need `vendorNumber` passed per call (and their error message tells you where to find it). Note: downloading sales/finance reports requires an API key with the **Admin**, **Finance**, or **Sales** role.

## Tools

### Apps
- `asc_list_apps` — list apps (filter by `bundleId`)
- `asc_get_app` — fetch one app by ID

### Subscriptions
- `asc_list_subscription_groups` — groups for an app
- `asc_list_subscriptions` — auto-renewable subscriptions in a group
- `asc_list_subscription_prices` — current price schedule per subscription
- `asc_list_subscription_price_points` — valid price points for a subscription in a territory. Pass `nearAmount` to narrow the response to the nearest tiers around a target price.

### Subscription pricing (writes)
- `asc_post_subscription_price` — schedule a price change for one territory
- `asc_delete_subscription_price` — cancel a pending scheduled change

### App pricing (paid non-subscription apps)
- `asc_list_app_prices` — current price schedule for an app, splitting manual overrides from auto-derived prices and surfacing the base territory
- `asc_list_app_price_points` — valid Apple price tiers for an app in a given territory (~600+ tiers per territory). Pass `nearAmount` (target price) and optional `nearCount` (default 10) to narrow the response to the nearest tiers — Apple does not support a near-amount filter server-side, so the full list is still paginated but only the nearest tiers are surfaced.
- `asc_post_app_price_schedule` — replace the entire price schedule (whole-schedule replace, NOT a merge — matches Apple's API). Pre-flight refuses unless at least one entry targets the base territory with no `startDate`, and requires explicit `acknowledgeReplacesAll: true`. A separate `acknowledgeDeletesScheduledIfBaseChanges` ack is required when changing the base territory (Apple wipes pending scheduled changes on base-change). Apps have no grandfather mechanism — new schedules activate atomically at each entry's `startDate`.

### In-app purchases (consumables, non-consumables, non-renewing subs)
- `asc_list_iaps` — list IAPs for an app (v2 surface only — auto-renewable subscriptions are covered by the Subscriptions tools above). Filterable by `inAppPurchaseType` and `state`. If this returns zero rows for an app you know has IAPs, the IAPs may be legacy-only and need to be migrated in the App Store Connect web UI before they appear here.
- `asc_get_iap` — fetch a single IAP by ID.
- `asc_list_iap_prices` — current price schedule for an IAP (same shape as app prices: manual overrides + auto-derived + base territory).
- `asc_list_iap_price_points` — valid Apple price tiers for an IAP in a given territory. Same `nearAmount` / `nearCount` narrowing as the app and subscription price-point tools.
- `asc_post_iap_price_schedule` — replace the entire IAP price schedule (same whole-schedule replace semantics as `asc_post_app_price_schedule`: `acknowledgeReplacesAll: true`, base-territory entry with no `startDate`, base-change ack required). No grandfather mechanism — same as apps.

### Subscription introductory offers
Introductory offers target **new** subscribers — the discounted "first window" before the regular price kicks in.

- `asc_list_subscription_introductory_offers` — list intro offers (free trial / pay-as-you-go / pay-up-front) configured for a subscription, across territories. Apple's "all territories" wildcard (a single offer with no `territory`) surfaces as `TERR=(all)` in the table.
- `asc_get_subscription_introductory_offer` — fetch one offer by ID.
- `asc_post_subscription_introductory_offer` — create an offer. Three `offerMode`s: `FREE_TRIAL` (no price; omit `pricePointId`), `PAY_AS_YOU_GO` (charge the offer price each period for `numberOfPeriods` periods), `PAY_UP_FRONT` (single charge for the whole duration). Pass `territoryId` to target one market, or omit it for Apple's "all territories" wildcard (uses the literal price point in every market — no auto-FX). Server-side validation refuses `PAY_*` without `pricePointId`, `PAY_AS_YOU_GO` without `numberOfPeriods`, and `endDate ≤ startDate` — Apple's error is surfaced inline otherwise.
- `asc_patch_subscription_introductory_offer` — narrow update path: only `startDate`, `endDate`, and `pricePointId` can change after creation. To change mode / duration / periods, delete and re-create.
- `asc_delete_subscription_introductory_offer` — delete a pending or active offer. Apple refuses to delete one that is currently redeemable; PATCH `endDate` to today to stop it instead.

### Subscription promotional offers
Promotional offers target **existing or lapsed** subscribers — opposite eligibility from intro offers, set by the resource type itself (no per-offer flag). Apple caps active promo offers at 10 per subscription. After creation, only the per-territory prices can be edited — `name`, `offerCode`, `offerMode`, `duration`, and `numberOfPeriods` are immutable.

- `asc_list_subscription_promotional_offers` — list promo offers configured for a subscription.
- `asc_get_subscription_promotional_offer` — fetch a single offer, including its per-territory prices.
- `asc_list_subscription_promotional_offer_prices` — list per-territory price rows attached to an offer (territory + currency + amount + price-point ID).
- `asc_post_subscription_promotional_offer` — create an offer (`name` + `offerCode` + mode + duration + all per-territory prices) in one atomic POST. Pre-flights Apple's 10-offer cap and `offerCode` collisions, refusing with a clear remedy message instead of letting Apple 409.
- `asc_patch_subscription_promotional_offer_prices` — update the offer's per-territory prices. Apple's wire semantic is replace (the new prices array becomes the post-state, dropping any territory not listed); the tool's `mode: 'replace' | 'add' | 'remove'` parameter hides the footgun — `'add'` reads current prices and merges, `'remove'` reads and filters.
- `asc_delete_subscription_promotional_offer` — DELETE → 204.

### Subscription offer signing (in-app redemption)
The cryptographic signer that makes promo/intro offers redeemable in your iOS app via StoreKit. Uses a **separate** signing key from the ASC API key — issued at App Store Connect → Users and Access → Integrations → In-App Purchase. See the [optional config section](#optional-in-app-purchase-signing-key) for env vars. Built on Apple's official [`@apple/app-store-server-library`](https://github.com/apple/app-store-server-library-node).

- `asc_sign_promotional_offer_legacy` — legacy ECDSA-concatenated signature used by StoreKit 1's `SKPaymentDiscount` and the original StoreKit 2 `Product.PurchaseOption.promotionalOffer(offerID:keyID:nonce:signature:timestamp:)` API. Returns the base64 signature plus the nonce, timestamp, and keyId for the caller to pass to StoreKit. Auto-generates a UUID nonce and current timestamp; both overridable for testing.
- `asc_sign_promotional_offer` — JWS v2 format introduced at WWDC 2025 (back-deployed to iOS 15). Use with StoreKit 2's newer promotional-offer purchase options. Returns the JWS compact serialization directly. `transactionId` (the customer's `appTransactionId`) is optional but strongly recommended.
- `asc_sign_introductory_offer_eligibility` — JWS v2 with `aud="introductory-offer-eligibility"`. Lets you override StoreKit's default introductory-offer eligibility check (e.g. grant a returning customer another trial). New in WWDC 2025.

All signatures are valid for 24 hours from signing time — re-sign per redemption attempt rather than pre-signing and caching.

### Territories
- `asc_list_territories` — all 175 App Store territories

### PPP rebalancing
- `ppp_load_index` — return the bundled Apple Music Individual-plan price snapshot used as the PPP signal
- `ppp_compute_proposal` — compute a proposed per-territory price schedule (read-only dry-run; uses Apple Music ratios as implied PPP-FX, snaps to valid Apple price points, applies a configurable round strategy and floor). Pass `resourceType: "subscription"` (default) with `subscriptionId`, `resourceType: "app"` with `appId` for paid apps, `resourceType: "iap"` with `iapId`, `resourceType: "introductoryOffer"` with `subscriptionId` plus `offerMode` / `duration` (and `numberOfPeriods` for `PAY_AS_YOU_GO`), or `resourceType: "promotionalOffer"` with `subscriptionId` plus `offerMode` / `duration` / `promoOfferName` / `promoOfferCode` (and `numberOfPeriods` for `PAY_AS_YOU_GO`).
- `ppp_apply_proposal` — recompute and apply the proposal against ASC after confirming via MCP elicitation (or `confirm: true` for unattended use). Refuses if any row drops by more than `maxDropPct` (default 90%); skips territories where ASC billing currency ≠ Apple Music currency.
  - For **subscriptions**: per-territory `subscriptionPrices` POSTs, paced at `maxConcurrency` (default 2), retrying 429s automatically; existing subscribers grandfathered when `preserveCurrentPrice: true` (default).
  - For **apps** and **IAPs**: a single whole-schedule-replace POST (one HTTP call, atomic). Apps/IAPs have no grandfather mechanism — new prices activate at each entry's `startDate`. Requires `acknowledgeDeletesScheduledIfBaseChanges: true` when changing the base territory (Apple wipes pending scheduled changes on base-change).
  - For **introductory offers**: per-territory `subscriptionIntroductoryOffers` POSTs, paced at `maxConcurrency`. The Δ column compares the snapped offer price against the current regular sub price in that territory, so `-50%` means the offer is half off the sub. `FREE_TRIAL` is rejected (no price to compute — use `asc_post_subscription_introductory_offer` with `territoryId` omitted for a single global free trial). Intro offers are additions, not replacements — Apple returns 409 if an active offer already exists for a `(sub, territory)` cell, and those rows show as `failed` in the result table.
  - For **promotional offers**: one atomic POST to `/v1/subscriptionPromotionalOffers` creates the offer + all per-territory PPP-snapped prices in a single request. Create-only — refuses if `offerCode` collides with an existing offer or the sub is at Apple's 10-offer cap. `FREE_TRIAL` rejected (no price to compute). Same Δ-vs-current-sub-price reporting as intro offers.

### Response shape

Every list/get tool returns a compact text table by default — designed for an LLM to read without burning context. Every tool also accepts:

- `raw: true` — return the full JSON:API payload (`data`, `included`, `links`, `meta`) for debugging or advanced use.
- `maxItems: number` — cap auto-pagination (default 500–1000 depending on the tool). The MCP follows `links.next` and merges + dedupes `included` resources across pages.

Sparse fieldsets (`fields[type]=...`) are applied per tool to avoid pulling unused attributes. The whole 175-territory price schedule comes back in one paginated call (200/page) at roughly 1/10th the size of the unfiltered payload.

## Production behavior

A few details worth knowing before running `ppp_apply_proposal` against a live App Store Connect account:

- **Rate limit handling.** Apple throttles POST endpoints around 50/min. `client.request` honours `Retry-After` headers and falls back to exponential backoff (2s → 60s, capped, up to 6 retries). A 60-territory rebalance pacing through retries finishes in about 2 minutes wall time with zero manual intervention.
- **Currency-mismatch skip.** If the bundled Apple Music index lists a territory in one currency (say BHD) but ASC bills your subscription in another (USD), the PPP-FX ratio breaks dimensionally. The proposal marks those rows `currency-mismatch (asc=USD, am=BHD)` and excludes them from the apply set. Common in Gulf USD-billed markets (BHR, KWT, OMN). Set those manually if you want to.
- **Sanity floor.** `floorFactor` (

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [akoskomuves](https://github.com/akoskomuves)
- **Source:** [akoskomuves/appstoreconnect-mcp](https://github.com/akoskomuves/appstoreconnect-mcp)
- **License:** MIT
- **Homepage:** https://www.npmjs.com/package/@akoskomuves/appstoreconnect-mcp

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-akoskomuves-appstoreconnect-mcp
- Seller: https://agentstack.voostack.com/s/akoskomuves
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
