@akoskomuves/appstoreconnect-mcp
v0.7.0
Published
Model Context Protocol server for the Apple App Store Connect API.
Downloads
1,061
Maintainers
Readme
appstoreconnect-mcp
A Model Context Protocol server for the Apple App Store Connect API. 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.
Install (zero-config)
npx @akoskomuves/appstoreconnect-mcp initThe wizard:
- Opens App Store Connect → Keys so you can download a
.p8(skipped if you already have one). - Copies the key to
~/.appstore/withchmod 600. - Asks for your Issuer ID and (auto-detected) Key ID.
- Verifies auth with a real API call before writing anything.
- 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:
npx @akoskomuves/appstoreconnect-mcp doctorManual 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:
{
"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:
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-mcpConfigure
Generate an App Store Connect API key at App Store Connect → Users and Access → Integrations → Keys. 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.
Tools
Apps
asc_list_apps— list apps (filter bybundleId)asc_get_app— fetch one app by ID
Subscriptions
asc_list_subscription_groups— groups for an appasc_list_subscriptions— auto-renewable subscriptions in a groupasc_list_subscription_prices— current price schedule per subscriptionasc_list_subscription_price_points— valid price points for a subscription in a territory. PassnearAmountto narrow the response to the nearest tiers around a target price.
Subscription pricing (writes)
asc_post_subscription_price— schedule a price change for one territoryasc_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 territoryasc_list_app_price_points— valid Apple price tiers for an app in a given territory (~600+ tiers per territory). PassnearAmount(target price) and optionalnearCount(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 nostartDate, and requires explicitacknowledgeReplacesAll: true. A separateacknowledgeDeletesScheduledIfBaseChangesack 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'sstartDate.
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 byinAppPurchaseTypeandstate. 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. SamenearAmount/nearCountnarrowing as the app and subscription price-point tools.asc_post_iap_price_schedule— replace the entire IAP price schedule (same whole-schedule replace semantics asasc_post_app_price_schedule:acknowledgeReplacesAll: true, base-territory entry with nostartDate, 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 noterritory) surfaces asTERR=(all)in the table.asc_get_subscription_introductory_offer— fetch one offer by ID.asc_post_subscription_introductory_offer— create an offer. ThreeofferModes:FREE_TRIAL(no price; omitpricePointId),PAY_AS_YOU_GO(charge the offer price each period fornumberOfPeriodsperiods),PAY_UP_FRONT(single charge for the whole duration). PassterritoryIdto 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 refusesPAY_*withoutpricePointId,PAY_AS_YOU_GOwithoutnumberOfPeriods, andendDate ≤ startDate— Apple's error is surfaced inline otherwise.asc_patch_subscription_introductory_offer— narrow update path: onlystartDate,endDate, andpricePointIdcan 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; PATCHendDateto 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 andofferCodecollisions, 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'smode: '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 for env vars. Built on Apple's official @apple/app-store-server-library.
asc_sign_promotional_offer_legacy— legacy ECDSA-concatenated signature used by StoreKit 1'sSKPaymentDiscountand the original StoreKit 2Product.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'sappTransactionId) is optional but strongly recommended.asc_sign_introductory_offer_eligibility— JWS v2 withaud="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 signalppp_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). PassresourceType: "subscription"(default) withsubscriptionId,resourceType: "app"withappIdfor paid apps,resourceType: "iap"withiapId,resourceType: "introductoryOffer"withsubscriptionIdplusofferMode/duration(andnumberOfPeriodsforPAY_AS_YOU_GO), orresourceType: "promotionalOffer"withsubscriptionIdplusofferMode/duration/promoOfferName/promoOfferCode(andnumberOfPeriodsforPAY_AS_YOU_GO).ppp_apply_proposal— recompute and apply the proposal against ASC after confirming via MCP elicitation (orconfirm: truefor unattended use). Refuses if any row drops by more thanmaxDropPct(default 90%); skips territories where ASC billing currency ≠ Apple Music currency.- For subscriptions: per-territory
subscriptionPricesPOSTs, paced atmaxConcurrency(default 2), retrying 429s automatically; existing subscribers grandfathered whenpreserveCurrentPrice: 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. RequiresacknowledgeDeletesScheduledIfBaseChanges: truewhen changing the base territory (Apple wipes pending scheduled changes on base-change). - For introductory offers: per-territory
subscriptionIntroductoryOffersPOSTs, paced atmaxConcurrency. 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_TRIALis rejected (no price to compute — useasc_post_subscription_introductory_offerwithterritoryIdomitted 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 asfailedin the result table. - For promotional offers: one atomic POST to
/v1/subscriptionPromotionalOfferscreates the offer + all per-territory PPP-snapped prices in a single request. Create-only — refuses ifofferCodecollides with an existing offer or the sub is at Apple's 10-offer cap.FREE_TRIALrejected (no price to compute). Same Δ-vs-current-sub-price reporting as intro offers.
- For subscriptions: per-territory
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 followslinks.nextand merges + dedupesincludedresources 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.requesthonoursRetry-Afterheaders 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(default 0.15) is a hard lower bound on per-territory drops as a fraction of the current price — guards against a stale index entry collapsing a price to near-zero. For a more conservative rebalance, pass 0.30 or 0.50. - Sanity ceiling on drops.
maxDropPct(default 90%) refuses to apply any run where a single row drops more than this. If you've ever seen Apple Music tank a market price aggressively, this catches the resulting outlier before you write it to ASC. - Refresh the snapshot when you care.
data/apple-music-prices.jsonis a hand-curated snapshot. Each entry is dated; the snapshot date is shown in proposal output. Pull request a refresh when Apple Music prices move and the project will fold it in.
PPP rebalancing skill
The examples/ppp-rebalance/ directory contains a Claude Code skill that wraps these tools into a Purchasing Power Parity workflow (dry-run → schedule → rollback) with the gotchas baked in.
mkdir -p ~/.claude/skills && \
ln -s "$PWD/examples/ppp-rebalance" ~/.claude/skills/ppp-rebalanceThen ask Claude: "Rebalance my subscription prices using the ppp-rebalance skill."
Roadmap
v0.1–v0.6 cover the full monetization-pricing surface: reads + writes + one-shot PPP rebalance across subscriptions, paid apps, in-app purchases, subscription introductory offers, and subscription promotional offers. The rest is fertile ground for LLM-driven ops because so much App Store work is judgment-heavy text — translations, review responses, pricing positioning — that a model can draft and a human approves.
| Phase | Domain | What it unlocks |
| --- | --- | --- |
| v0.1 ✓ | Apps · subscriptions · subscription pricing · PPP rebalance | Schedule per-territory price changes by purchasing power. |
| v0.2.0 ✓ | App pricing (non-subscription): list / list price points / replace schedule · PPP compute extended to apps | PPP dry-run against paid apps; manual apply via asc_post_app_price_schedule. |
| v0.3.0 ✓ | In-app purchases (v2): list / get / price schedule reads + writes | Same monetization surface for IAPs (consumables, non-consumables, non-renewing subs). Auto-renewables stay on the Subscriptions tools. |
| v0.4.0 ✓ | ppp_apply_proposal auto-apply for apps + IAPs · PPP for IAPs · nearAmount filter on price-point listings | One-shot PPP rebalance for every paid surface, not just subs. |
| v0.5.0 ✓ | Subscription introductory offers (free trial / pay-as-you-go / pay-up-front): list / get / post / patch / delete · PPP extended to intro offers | PPP-aware "first month" / "first three months" promos that adapt to local purchasing power instead of a literal $0.99 everywhere. |
| v0.6.0 ✓ | Subscription promotional offers (existing/lapsed subscribers): list / get / post / patch-prices / delete · PPP extended to promo offers (create-only, atomic single-POST) | Win-back campaigns with PPP-aware per-territory pricing. |
| v0.6.1 ✓ | Subscription offer signing: three signers (legacy ECDSA, JWS v2 promo, JWS v2 intro eligibility) covering every current Apple-supported format | StoreKit redemption end-to-end — promo offers from v0.6 are now usable in an iOS app, not just configurable in ASC. |
| v0.7 | Subscription offer codes: one-time-use bulk codes · custom (multi-use) codes · CSV export | Promo-code redemption campaigns (App Store Connect → "Offer codes"). |
| v0.8 | TestFlight: builds · beta groups · beta testers · build localizations · beta app review | "Invite these 30 testers to the new build with this test note in EN/ES/JA." |
| v0.9 | App version localizations · subscription localizations · IAP localizations | The biggest LLM win. Translate release notes into 35 locales using existing localizations as voice reference, present diff, push on approval. |
| v1.0 | Customer reviews (read · respond · filter by sentiment/version) | "Draft a response to every 1-star review on the latest version that mentions the export bug. Show me before posting." |
| v1.1 | Sales/trends · finance reports · app analytics | "Why did MRR drop in Brazil last week? Compare to the rebalance activation date." |
| v1.2+ | EU DMA · real-FX for currency-mismatch territories · screenshot uploads · ASO keyword analysis · custom product pages · A/B tests | Compliance, polish, and advanced surfaces. |
Out of scope (Fastlane / Xcode already do these well): provisioning profiles, certificates, devices, capabilities, Game Center config.
Think of this as the LLM companion for App Store Connect ops. Fastlane is for the build/release pipeline; this is for the post-release knowledge work — translation, pricing, customer feedback, analytics.
Each new domain is one file under src/domains/<name>.ts plus a register* call in src/index.ts. Contributions welcome — see CONTRIBUTING.md.
Develop
git clone https://github.com/akoskomuves/appstoreconnect-mcp.git
cd appstoreconnect-mcp
npm install
npm run dev # tsx watch mode
npm test
npm run buildSee CONTRIBUTING.md for the contributor flow (changesets, PR template, branch naming).
License
MIT © 2026 Akos Komuves
