MCP server for Apple's App Store Connect API: 1316 tools generated from the official OpenAPI spec.
The complete Model Context Protocol (MCP) server for Apple's App Store Connect API. 1316 tools. 100% coverage of every non-deprecated operation in Apple's official OpenAPI spec — TestFlight, Xcode Cloud, Game Center, App Clips, in-app purchases, subscriptions, review submissions, analytics, provisioning, and everything else Apple exposes. Works with Claude, Cursor, VS Code Copilot, Codex CLI, Gemini CLI, Windsurf, or any MCP client.
See also: google-play-developer-mcp — the same philosophy for the Google Play Console.
asc-mcp (zelentsov-dev)App Store Connect is huge. Apple's own OpenAPI spec currently lists 1303 operations across 203 resource groups — TestFlight, Xcode Cloud, Game Center, App Clips, in-app purchases, subscriptions, offer codes, review submissions, alternative distribution (EU DMA), analytics, metrics, privacy manifests, provisioning, users… the list goes on.
Every community MCP covers a slice. None covers everything.
app-store-connect-mcp does.
| Property | This server | Typical community MCP |
|---|---|---|
| App Store Connect API coverage | ✅ 1300+ tools (100% of spec v4.5.1) | Partial (usually 50–150 tools) |
| Xcode Cloud | ✅ all ciProducts / ciWorkflows / ciBuildRuns / ciBuildActions / ciArtifacts / ciIssues / ciTestResults | ❌ |
| Game Center | ✅ achievements, leaderboards, groups, matchmaking, vocabularies | ❌ |
| App Clips | ✅ default + advanced experiences, review details, header images | ❌ |
| Review Submissions v2 | ✅ | Often still on the legacy appStoreVersionSubmissions |
| Alternative distribution (EU DMA) | ✅ | ❌ |
| App data usages (privacy) | ✅ | ❌ |
| Multi-account (switch between dev accounts) | ✅ local registry + per-project ASC_ACCOUNT pin | Usually one |
| Credential safety | No .p8 content in repo, no keys in the process cache, strict .gitignore | Varies |
| Spec-driven — auto-regenerate on new spec | ✅ npm run codegen | ❌ |
If you drive App Store Connect with an AI assistant — submission automation, TestFlight management, IAP / subscription catalog, review triage, analytics, or Xcode Cloud orchestration — this gives the assistant the full toolbox in one install.
1316 tools total (as of spec v4.5.1):
accounts_* — local multi-account registry (add / list / switch / remove / update / current)auth_* — auth_status (readiness) and auth_revoke_cacheasset_upload_file — step 2 of the 3-step asset upload protocol (screenshots, previews, review attachments, IAP review screenshots, App Clip header images, Game Center images, routing app coverages)sales_reports_download — downloads and un-gzips sales reports, returns parsed TSVfinance_reports_download — same for finance reportsanalytics_report_instance_wait — poll until a report is COMPLETED/FAILEDci_build_run_wait — poll an Xcode Cloud build run to completionRun auth_status, then apps_get_collection to verify end-to-end.
Install from npm:
npm install -g apple-app-store-connect-mcp
Or run without installing:
npx -y apple-app-store-connect-mcp
About the package name. The package on npm is
apple-app-store-connect-mcp. After install, the command on your$PATHisapp-store-connect-mcp(the bin name) — that's what your MCP client config below points at.
Then add to your MCP client config (example, Claude Code):
{
"mcpServers": {
"app-store-connect": {
"command": "app-store-connect-mcp"
}
}
}
Finally, register your App Store Connect API key (details below):
accounts_add
name: my-app
keyId: AB12CD34EF
issuerId: 69a6de70-...
keyFile: /Users/you/.config/app-store-connect-mcp/AuthKey_AB12CD34EF.p8
AuthKey_<KEYID>.p8 file. You only get this download once. Store it outside any git repo, for example at ~/.config/app-store-connect-mcp/AuthKey_AB12CD34EF.p8 with chmod 600.Launch any MCP client that has the server configured, then call:
accounts_add
name: my-app
keyId: <the 10-char Key ID>
issuerId: <the UUID shown above the API keys list>
keyFile: /absolute/path/to/AuthKey_XXXXXXXXXX.p8
description: (optional)
auth_status
Should return { source: "accounts", tokenAcquired: true, expiresAt: ... }.
apps_get_collection
Lists the apps the key can see.
accounts_add name: client-a keyId: ... issuerId: ... keyFile: ...
accounts_add name: client-b keyId: ... issuerId: ... keyFile: ...
accounts_switch name: client-b
accounts_list
All subsequent calls target the currently-switched account.
ASC_ACCOUNT)currentAccount is a single global value in accounts.json, shared by every MCP client on
the machine. If you work on more than one App Store Connect account, that is a footgun: an
accounts_switch in one project silently retargets every other project, and an assistant in
project A can end up writing to account B's apps.
Pin the account per server process instead. Set ASC_ACCOUNT in the MCP client config:
{
"mcpServers": {
"app-store-connect": {
"command": "app-store-connect-mcp",
"env": { "ASC_ACCOUNT": "acme-app" }
}
}
}
With a pin in effect:
currentAccount happens to be.accounts_switch to any other account fails loudly instead of silently retargeting.accounts_remove refuses to delete the pinned account.auth_status and accounts_current report pinnedTo, so it is always visible which account
a session is on.If ASC_ACCOUNT names an account that isn't registered, calls fail with an explicit error rather
than falling back to some other account.
Give each project its own pin — then no project depends on the shared global at all.
For compatibility with older setups (e.g. zelentsov-dev/asc-mcp), the server also accepts:
ASC_KEY_ID="..."
ASC_ISSUER_ID="..."
ASC_PRIVATE_KEY_PATH="/absolute/path/to/AuthKey_XXX.p8"
If no account is registered via accounts_add, the server falls back to these env vars. accounts_* tools always take precedence.
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%/Claude/claude_desktop_config.json
{
"mcpServers": {
"app-store-connect": {
"command": "app-store-connect-mcp"
}
}
}
claude mcp add app-store-connect app-store-connect-mcp
Or edit ~/.claude.json:
{
"mcpServers": {
"app-store-connect": {
"command": "app-store-connect-mcp"
}
}
}
If you work on more than one App Store Connect account, put the server in each project's
.mcp.json instead and pin it — see Pinning one account per project:
{
"mcpServers": {
"app-store-connect": {
"command": "app-store-connect-mcp",
"env": { "ASC_ACCOUNT": "acme-app" }
}
}
}
Settings → MCP → Add new MCP server:
{
"app-store-connect": {
"command": "app-store-connect-mcp"
}
}
.vscode/mcp.json:
{
"servers": {
"app-store-connect": {
"type": "stdio",
"command": "app-store-connect-mcp"
}
}
}
Any MCP-compliant client accepts the same command entry — point it at the globally installed app-store-connect-mcp binary, or at node /path/to/dist/index.js when running from source.
git clone https://github.com/devinwang/app-store-connect-mcp.git
cd app-store-connect-mcp
npm install
npm run build
Then in your MCP client config:
{
"mcpServers": {
"app-store-connect": {
"command": "node",
"args": ["/absolute/path/to/app-store-connect-mcp/dist/index.js"]
}
}
}
accounts_switch name: my-app
auth_status
apps_get_collection
builds_get_collection
"filter[preReleaseVersion.version]": "1.2.0"
"filter[version]": "147"
beta_groups_get_collection
"filter[app]": <APP_ID>
"fields[betaGroups]": "name,publicLinkEnabled"
builds_beta_groups_create_to_many_relationship
id: <BUILD_ID>
body:
data:
- type: betaGroups
id: <BETA_GROUP_ID>
builds_update_instance
id: <BUILD_ID>
body:
data:
type: builds
id: <BUILD_ID>
attributes:
expired: false
app_screenshots_create_instance
body:
data:
type: appScreenshots
attributes:
fileName: "en-US-iphone-6_9-1.png"
fileSize: 2415122
relationships:
appScreenshotSet:
data: { type: appScreenshotSets, id: <SET_ID> }
# → response includes the new `data.id` (asset is now AWAITING_UPLOAD)
asset_upload_file
filePath: /abs/path/to/screenshot.png
assetType: appScreenshots # preferred: tool fetches uploadOperations
assetId: <SCREENSHOT_ID> # server-side (signature stays server-side)
# (legacy: pass `uploadOperations: [...]` from the create response instead)
app_screenshots_update_instance
id: <SCREENSHOT_ID>
body:
data:
type: appScreenshots
id: <SCREENSHOT_ID>
attributes:
uploaded: true
sourceFileChecksum: <MD5>
ci_build_runs_create_instance
body:
data:
type: ciBuildRuns
relationships:
workflow:
data: { type: ciWorkflows, id: <WORKFLOW_ID> }
# → response includes `data.id`
ci_build_run_wait
id: <BUILD_RUN_ID>
timeoutMs: 3600000
customer_reviews_get_collection
"filter[app]": <APP_ID>
"sort": "-createdDate"
"limit": 10
customer_review_responses_v1_create_instance
body:
data:
type: customerReviewResponses
attributes:
responseBody: "Thanks for the feedback — we fixed this crash in 1.2.1. Please give it another try!"
relationships:
review:
data: { type: customerReviews, id: <REVIEW_ID> }
sales_reports_download
"filter[frequency]": DAILY
"filter[reportType]": SALES
"filter[reportSubType]": SUMMARY
"filter[vendorNumber]": "12345678"
"filter[reportDate]": "2026-04-20"
Returns parsed rows. Add parse: false to get raw TSV.
subscription_groups_create_instance
body: { data: { type: subscriptionGroups, attributes: { referenceName: "Premium" }, relationships: { app: { data: { type: apps, id: <APP_ID> } } } } }
subscriptions_create_instance
body: { data: { type: subscriptions, attributes: { productId: "com.example.premium.monthly", name: "Premium Monthly", subscriptionPeriod: "ONE_MONTH" }, relationships: { group: { data: { type: subscriptionGroups, id: <GROUP_ID> } } } } }
subscription_prices_create_instance
body: { data: { type: subscriptionPrices, relationships: { subscription: { data: { type: subscriptions, id: <SUB_ID> } }, territory: { data: { type: territories, id: "USA" } }, subscriptionPricePoint: { data: { type: subscriptionPricePoints, id: <PRICE_POINT_ID> } } } } }
asc-mcp (zelentsov-dev)This server replaces github.com/zelentsov-dev/asc-mcp. Key differences:
zelentsov-dev/asc-mcp uses simplified names like apps_search, reviews_list, app_versions_submit_for_review. This server uses Apple's official operationId snake-cased — apps_get_collection, customer_reviews_get_collection, review_submissions_create_instance. There's no auto-translation: update any prompts or scripts that assumed the old names.ASC_KEY_ID, ASC_ISSUER_ID, ASC_PRIVATE_KEY_PATH) still work, so you don't need to move the .p8 to migrate. You'll get a one-time stderr warning recommending you register via accounts_add for multi-account support.company_* → accounts_*. Rename mental model: a "company" in the old server is an "account" here.A comprehensive name-by-name mapping is included in docs/MIGRATION.md.
This repo ships with zero credentials. You bring an App Store Connect API .p8; the server records only its path.
| Where | What |
|---|---|
~/.app-store-connect-mcp/accounts.json | Account names, key ids, issuer ids, key-file paths, descriptions. No secrets. Written with 0600 permissions, directory 0700. |
Wherever you placed your AuthKey_*.p8 | The actual private key. Never committed. Never copied into the server's memory longer than one JWT signing. |
| Repo contents | Code only. No fixtures, no example keys, no real key ids. |
| When | What |
|---|---|
| At JWT sign time | The .p8 content is readFileSync'd, passed to jwt.sign, and the buffer is fill(0)-ed and dereferenced immediately after. |
| Between calls | A signed JWT (20-minute lifetime) is cached in a local variable in token-cache.ts. This is in-memory only — it is never persisted to disk. Dropped on accounts_switch, accounts_update, accounts_remove, and auth_revoke_cache. |
| In tool outputs | Everything is passed through redact() before emission. JWT-like strings, PEM blocks, Bearer tokens, and long hex tokens get replaced with [REDACTED …] — defense-in-depth against any code path accidentally logging. |
.gitignore blocks*.p8, AuthKey_*, AuthKey_*.p8*.pem, *.p12, *.pfx, *.key, id_rsa*accounts.json, .app-store-connect-mcp/credentials/, keys/, secrets/.env, .env.* (keeps .env.example).p8 outside any git repo on your machine.Admin lets this MCP do anything including deleting store metadata. Restrict as appropriate.accounts_update --keyFile <newPath>.auth_status returns only the key id, issuer id, a boolean, and the JWT expiry timestamp. It never returns the JWT or the .p8 content.dist/.Apple logs every API call against your key id. If you ever suspect misuse, open App Store Connect → Users and Access → Integrations and revoke the key; any outstanding JWTs signed with it stop working immediately.
spec/app-store-connect-openapi.json). When Apple ships a new version, npm run spec:download && npm run codegen && npm run build is all it takes to update.operationId values converted to snake_case. This means the MCP names match the official REST reference, so when an LLM reads Apple's docs it can pick the right tool without translation.filter[name], fields[apps], limit[appStoreVersions]). Request bodies are z.record(z.unknown()) with a description pointing at the OpenAPI component schema — ASC validates them server-side and our error translator surfaces mistakes clearly. Generating full zod trees for every JSON:API envelope would balloon the server by 10× and add negligible value.401 becomes "JWT rejected — rotate or check clock skew", 403 becomes "key isn't authorized for this endpoint", 422 surfaces the exact source.pointer from the ASC response, 429 explains the per-team quota, 5xx points at https://developer.apple.com/system-status/.PRs welcome. Before you submit:
npm run typecheck + npm run build pass on Node 18 / 20 / 22 (CI also runs this)..p8, a real keyId, a real issuerId, or a real JWT in an issue, PR, fixture, or example.npm run spec:downloadnpm run codegen — regenerates src/tools/generated/ and spec/coverage-report.txtnpm run buildspec/coverage-report.txt diff — a delta of 10+ tools or a new top-level tag should get a CHANGELOG note.src/tools/overrides.ts and must have names that don't collide with any generated name.[DEPRECATED] prefix..gitignore, .p8 content never cached in process memory, in-memory-only JWT cache, universal output redaction.~/.app-store-connect-mcp/accounts.json (path storage only).ASC_KEY_ID / ASC_ISSUER_ID / ASC_PRIVATE_KEY_PATH env vars (drop-in replacement for zelentsov-dev/asc-mcp setups).npm run codegen.MIT — see LICENSE.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
npx -y apple-app-store-connect-mcpMerge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-devinwang-app-store-connect-mcp": {
"command": "npx",
"args": [
"-y",
"apple-app-store-connect-mcp"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.
Claude Desktop setup referenceapple-app-store-connect-mcpnpmio.github.devinwang/app-store-connect-mcp works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.