{
  "markdown": "# appstore-mcp\n\nAn [MCP](https://modelcontextprotocol.io) server, written in Rust, that exposes\nthe **Apple App Store Connect API** to AI agents. It covers the full product\nlifecycle — apps & metadata, in-app purchases, subscriptions and their offers,\npricing & availability, App Store versions, App Review submission, TestFlight,\nprovisioning & signing, asset uploads, promoted purchases, customer reviews,\nphased release, users & access, in-app events, Xcode Cloud, and analytics\nreports — across **114 tools**, and can reach *any* other App Store Connect\nendpoint through two generic JSON:API tools.\n\nBuilt on the official [`rmcp`](https://crates.io/crates/rmcp) SDK over stdio.\n\n📖 **[Full tool reference → docs/TOOLS.md](docs/TOOLS.md)** — every tool's purpose\nand parameters.\n\nEvery tool is labelled with MCP annotations, so a client can tell `list_apps`\nfrom `remove_user`. You can serve only the domains you need (`ASC_TOOLS`) or only\nthe tools that cannot write (`ASC_READ_ONLY`) — see\n[Choosing which tools to serve](#choosing-which-tools-to-serve).\n\n## Design: hybrid coverage\n\nThe App Store Connect API has hundreds of endpoints but is uniformly\n[JSON:API](https://jsonapi.org). Rather than a tool per endpoint, this server is\n**hybrid**:\n\n- **Curated tools** (112) for the common, multi-step, or error-prone workflows —\n  apps & metadata, IAPs, subscriptions & offers, versions, pricing, availability,\n  App Review submission, TestFlight, provisioning, asset uploads, promoted\n  purchases, customer reviews, phased release, users, in-app events, Xcode Cloud,\n  analytics reports, and custom product pages.\n- **Two generic escape-hatch tools** — `appstore_request` and `appstore_list` —\n  that can call *any* endpoint with raw JSON:API documents.\n\n## Tools\n\n| Group | Tools |\n|------|-------|\n| **Generic** | `appstore_request`, `appstore_list` |\n| **Apps & metadata** | `list_apps`, `get_app`, `update_app`, `list_app_infos`, `update_app_info`, `set_age_rating`, `create_app_info_localization`, `update_app_info_localization` |\n| **In-app purchases (v2)** | `list_in_app_purchases`, `create_in_app_purchase`, `update_in_app_purchase`, `delete_in_app_purchase`, `create_iap_localization`, `set_iap_price_schedule`, `upload_iap_review_screenshot` |\n| **Subscriptions** | `list_subscription_groups`, `create_subscription_group`, `create_subscription`, `update_subscription`, `create_subscription_localization`, `set_subscription_price` |\n| **Versions & metadata** | `list_app_store_versions`, `create_app_store_version`, `create_version_localization`, `update_version_localization` |\n| **App Review submission** | `create_review_submission`, `add_review_submission_item`, `submit_review_submission`, `list_review_submissions`, `submit_in_app_purchase`, `set_app_review_detail`, `create_app_encryption_declaration`, `assign_build_encryption_declaration` |\n| **Pricing** | `list_territories`, `list_iap_price_points`, `list_subscription_price_points` |\n| **Availability** | `set_iap_availability`, `set_subscription_availability`, `set_app_availability` |\n| **TestFlight** | `list_builds`, `list_beta_groups`, `create_beta_group`, `add_beta_tester`, `submit_build_for_beta_review`, `set_build_test_notes`, `set_build_beta_detail`, `set_beta_app_review_detail`, `expire_build`, `add_build_to_beta_group` |\n| **Provisioning & signing** | `list_bundle_ids`, `create_bundle_id`, `enable_bundle_id_capability`, `disable_bundle_id_capability`, `list_certificates`, `create_certificate`, `list_devices`, `register_device`, `list_profiles`, `create_profile` |\n| **Assets** | `upload_app_screenshot`, `upload_app_preview`, `create_screenshot_set`, `create_preview_set`, `delete_screenshot_set`, `delete_preview_set`, `reorder_screenshots` |\n| **Subscription offers** | `create_introductory_offer`, `create_promotional_offer`, `create_winback_offer`, `list_winback_offers` |\n| **Offer codes** | `create_offer_code`, `generate_one_time_use_codes`, `create_custom_offer_code`, `list_offer_codes` |\n| **Promoted purchases** | `create_promoted_purchase`, `update_promoted_purchase`, `set_promoted_purchase_order`, `list_promoted_purchases` |\n| **Customer reviews** | `list_customer_reviews`, `respond_to_review`, `delete_review_response` |\n| **Phased release** | `start_phased_release`, `update_phased_release` |\n| **Users & access** | `list_users`, `invite_user`, `update_user`, `remove_user` |\n| **In-app events** | `create_app_event`, `create_app_event_localization`, `upload_app_event_screenshot` |\n| **Xcode Cloud** | `list_ci_products`, `list_ci_workflows`, `start_ci_build`, `get_ci_build_run`, `list_ci_build_actions` |\n| **Analytics reports** | `request_analytics_report`, `list_analytics_reports`, `list_analytics_report_instances`, `list_analytics_report_segments`, `download_analytics_segment` |\n| **Custom product pages** | `list_custom_product_pages`, `get_custom_product_page`, `create_custom_product_page`, `update_custom_product_page`, `delete_custom_product_page`, `list_custom_product_page_versions`, `create_custom_product_page_version`, `list_custom_product_page_localizations`, `create_custom_product_page_localization`, `update_custom_product_page_localization`, `create_cpp_screenshot_set`, `create_cpp_preview_set` |\n\nSee **[docs/TOOLS.md](docs/TOOLS.md)** for each tool's description and parameters. Custom product page\nimages are uploaded with the existing `upload_app_screenshot` / `upload_app_preview` tools.\n\n## Install\n\nPrebuilt binaries for **macOS (universal), Linux (x86-64), and Windows (x86-64)**\nare attached to every [GitHub Release](https://github.com/forgeopslabs/appstore-mcp/releases).\nPick the channel for your client; all of them need credentials (see [Credentials](#credentials)).\n\n### Claude Desktop — one-click bundle\n\nDownload `appstore-mcp.mcpb` from the latest release and open it with Claude Desktop\n(**Settings → Extensions → Install Extension…**, or drag the file onto the window).\nIt prompts for your **Issuer ID**, **Key ID**, and **.p8 key file**. The bundle ships\nall three platforms' binaries and selects the right one automatically.\n\n### Claude Code — plugin marketplace\n\n```text\n/plugin marketplace add forgeopslabs/appstore-mcp\n/plugin install appstore-mcp@forgeopslabs\n```\n\nThe plugin launches the `appstore-mcp` binary from your `PATH`, so install it first —\ndownload the binary for your OS from the [latest release](https://github.com/forgeopslabs/appstore-mcp/releases/latest)\nand put it on your `PATH`, or `cargo install --git https://github.com/forgeopslabs/appstore-mcp`.\nSet `ASC_ISSUER_ID`, `ASC_KEY_ID`, and `ASC_PRIVATE_KEY_PATH` in the environment you start\nClaude Code from.\n\n### Codex\n\nCodex configures MCP servers directly (no marketplace). With `appstore-mcp` on your `PATH`:\n\n```bash\ncodex mcp add appstore \\\n  --env ASC_ISSUER_ID=... --env ASC_KEY_ID=... \\\n  --env ASC_PRIVATE_KEY_PATH=/path/AuthKey_XXXXXX.p8 \\\n  -- appstore-mcp\n```\n\nor in `~/.codex/config.toml`:\n\n```toml\n[mcp_servers.appstore]\ncommand = \"appstore-mcp\"\nargs = []\nenv = { ASC_ISSUER_ID = \"...\", ASC_KEY_ID = \"...\", ASC_PRIVATE_KEY_PATH = \"/path/AuthKey_XXXXXX.p8\" }\n```\n\n### MCP Registry\n\nPublished as `io.github.forgeopslabs/appstore-mcp` (metadata in\n[`server.json`](server.json)) so any MCP-aware client can discover it.\n\n### From source\n\n```bash\ncargo build --release    # -> target/release/appstore-mcp\n```\n\n## Credentials\n\nGenerate a **Team Key** in App Store Connect → *Users and Access → Integrations →\nApp Store Connect API*, and download the `.p8` file. Then set:\n\n| Variable | Required | Description |\n|----------|----------|-------------|\n| `ASC_ISSUER_ID` | ✅ | Issuer UUID shown above the keys table. |\n| `ASC_KEY_ID` | ✅ | The API key's Key ID. |\n| `ASC_PRIVATE_KEY` | one of | Inline `.p8` PEM contents. |\n| `ASC_PRIVATE_KEY_PATH` | one of | Path to the downloaded `.p8` file. |\n| `ASC_BASE_URL` | optional | Override the API origin. |\n| `ASC_LOG` | optional | Log filter (to stderr). Default `info`. |\n\nSee [`.env.example`](.env.example). The server authenticates each request with a\nshort-lived **ES256 JWT** signed by your key (cached and refreshed automatically).\n\n> The server starts even without credentials so a client can list its tools;\n> tool calls then return an actionable configuration error until creds are set.\n\n## Choosing which tools to serve\n\nA hundred tool definitions cost context in every session, and a client that sees\none flat list can't tell a read from a delete. Two knobs fix that:\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `ASC_TOOLS` | all | Comma-separated tool groups to serve, or the preset `core`. |\n| `ASC_READ_ONLY` | `0` | Serve only tools that cannot modify the account. |\n\n```bash\nASC_TOOLS=core                     # 41 tools: generic, apps, versions, assets, testflight, submission\nASC_TOOLS=testflight,provisioning  # just what a build-distribution agent needs\nASC_READ_ONLY=1                    # 35 read-only tools; writes are withheld entirely\n```\n\nGroups: `generic`, `apps`, `iap`, `subscriptions`, `versions`, `pricing`,\n`availability`, `submission`, `testflight`, `provisioning`, `assets`, `offers`,\n`offer-codes`, `promotions`, `reviews`, `users`, `events`, `xcode-cloud`,\n`analytics`, `custom-product-pages` — plus `all` and `core`. An unrecognised name\nis warned about on stderr and serves nothing rather than quietly falling back to\neverything.\n\nIn read-only mode `appstore_request` is kept but refuses any method other than\n`GET`, so the escape hatch still reaches endpoints without a curated tool without\nbecoming a way around the restriction.\n\nEvery served tool advertises MCP annotations (`readOnlyHint`, `destructiveHint`,\n`idempotentHint`), which clients use to decide what needs confirming. Nine tools\nare marked destructive: the seven `delete_*`/`remove_*` tools, `expire_build`,\n`disable_bundle_id_capability`, and `appstore_request` (which can reach any\n`DELETE` endpoint).\n\n## Tuning\n\nDefaults are chosen so a tool call can't hang and a single response can't swamp\nan agent's context. All of these are optional.\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `ASC_TIMEOUT_SECS` | `60` | Whole-request timeout. `0` disables. |\n| `ASC_CONNECT_TIMEOUT_SECS` | `10` | Connect timeout. `0` disables. |\n| `ASC_TRANSFER_TIMEOUT_SECS` | `300` | Timeout for asset uploads and report downloads. |\n| `ASC_MAX_RETRIES` | `3` | Retries after the first attempt. `0` disables. |\n| `ASC_MAX_RESPONSE_BYTES` | `60000` | Tool-result size cap. `0` disables. |\n| `ASC_COMPACT_RESPONSES` | `1` | Strip redundant JSON:API links from responses. |\n\n**Retries.** A `429` is replayed for any method, since Apple rejected the request\nwithout applying it. A `5xx` or a mid-flight timeout is replayed only for\n`GET`/`PATCH`/`PUT`/`DELETE` — never `POST`, which could otherwise create a\nduplicate resource (and Apple permanently reserves identifiers like a product\nID). Backoff is exponential with jitter and honours `Retry-After`.\n\n**Response shaping.** Results are serialized compactly — indented JSON measured\n1.72× the bytes for identical content, so the same budget now carries about 40%\nmore of the data you asked for. Per-resource `self` links and link-only\nrelationships are stripped: no addressable content is lost, and `links.next`\nsurvives for pagination. If a response still exceeds the budget, `included` is\ndropped first, then trailing `data` items, and the result carries a `_truncated`\nkey saying what went missing and how to narrow the query. Note that following\n`links.next` after a trim would skip the dropped items — re-request with a\nsmaller `limit` instead.\n\n## Build & run\n\n```bash\ncargo build --release\nASC_ISSUER_ID=... ASC_KEY_ID=... ASC_PRIVATE_KEY_PATH=/path/AuthKey_XXX.p8 \\\n  ./target/release/appstore-mcp\n```\n\nThe server speaks MCP over **stdio**. Logs go to **stderr**; stdout is the\nprotocol channel.\n\n### Use with an MCP client\n\nExample client config (e.g. Claude Desktop's `mcpServers`):\n\n```json\n{\n  \"mcpServers\": {\n    \"appstore\": {\n      \"command\": \"/absolute/path/to/appstore-mcp/target/release/appstore-mcp\",\n      \"env\": {\n        \"ASC_ISSUER_ID\": \"00000000-0000-0000-0000-000000000000\",\n        \"ASC_KEY_ID\": \"ABCD123456\",\n        \"ASC_PRIVATE_KEY_PATH\": \"/absolute/path/to/AuthKey_ABCD123456.p8\"\n      }\n    }\n  }\n}\n```\n\n### Inspect with the MCP Inspector\n\n```bash\nnpx @modelcontextprotocol/inspector ./target/release/appstore-mcp\n```\n\n## Usage notes\n\n- **IDs are opaque.** List/get first to resolve app, IAP, subscription, set, and\n  price-point IDs, then pass them to create/update tools.\n- **Pricing needs a price point.** Use `list_iap_price_points` /\n  `list_subscription_price_points` to get the `id` for `set_iap_price_schedule` /\n  `set_subscription_price`.\n- **Asset uploads** (`upload_*`) take a local file path and run the full reserve →\n  chunked upload → MD5 commit flow in one call. The file is streamed, so peak\n  memory is one chunk rather than the size of the asset, and a chunk that fails\n  is retried on its own. Screenshots/previews require an existing\n  `appScreenshotSet` / `appPreviewSet`; create those with the generic tools if\n  needed.\n- **Pagination.** `appstore_list` returns one page by default. Pass\n  `max_pages` (up to 20) to follow `links.next` and merge the pages into one\n  result — `meta.hasMore` tells you whether anything is left.\n- **Analytics data.** `request_analytics_report` → `list_analytics_reports` →\n  `list_analytics_report_instances` → `list_analytics_report_segments` gets you a\n  presigned segment URL; `download_analytics_segment` fetches it, gunzips it, and\n  returns the rows as JSON. Apple can take up to 48 hours to generate the first\n  report for a new request.\n- **Anything not listed** is reachable via `appstore_request` (raw method + path +\n  JSON:API body) or `appstore_list` (paginated GET). Example:\n  `appstore_request { \"method\": \"GET\", \"path\": \"/v1/apps/123/customerReviews\" }`.\n- **Not covered:** sales/finance report endpoints return gzipped TSV (not JSON:API)\n  and are out of scope for these tools.\n\n## Limitations (enforced by Apple)\n\n- **You cannot create an app via the API.** The `apps` resource only allows GET and\n  UPDATE — `POST /v1/apps` returns `403 FORBIDDEN_ERROR`. Create the app record in\n  the [App Store Connect website](https://appstoreconnect.apple.com) (*Apps → ➕ →\n  New App*); you can pre-create its bundle ID with `create_bundle_id`. All other\n  tools operate on an existing app.\n- A deleted in-app purchase's `productId` is permanently reserved by Apple and\n  cannot be reused.\n\n## Development\n\n```bash\ncargo test                              # 200+ tests, no network or credentials needed\ncargo clippy --all-targets -- -D warnings\ncargo fmt --check\n```\n\nTests come in three layers: pure unit tests for request-body builders, retry\ndecisions, and response shaping; [`wiremock`](https://crates.io/crates/wiremock)\ntests that drive the real HTTP client against a mock API (retries, timeouts,\npagination, the three-step upload protocol, segment downloads); and\n`tests/tool_surface.rs`, which asserts the invariants of what a client actually\nsees — unique names, real descriptions, object schemas, correct annotations, and\nthat `ASC_TOOLS`/`ASC_READ_ONLY` withhold exactly what they claim to.\n\nThe minimum supported Rust version is **1.88**, checked by its own CI job.\n\nRegenerate the tool reference after adding/changing tools (needs the release\nbinary; no credentials required):\n\n```bash\ncargo build --release && python3 scripts/gen_tools_doc.py   # rewrites docs/TOOLS.md\n```\n\n### Live integration tests\n\n`scripts/integration_test.py` drives the compiled server against the real API.\nRead-only by default; `--write` adds a self-cleaning IAP lifecycle.\n\n```bash\ncargo build --release\n# Credentials via env (ASC_ISSUER_ID/ASC_KEY_ID/ASC_PRIVATE_KEY_PATH) or local\n# appstore-connect.txt + AuthKey_*.p8 in the repo root (both gitignored).\npython3 scripts/integration_test.py --app <APP_ID>          # read-only sweep\npython3 scripts/integration_test.py --app <APP_ID> --write  # + write lifecycle\n```\n\n## License\n\nMIT\n",
  "bytes": 16105,
  "sha": "fc8514064bd6426fc82bc030c37257d17ab504011d50e51fe468d4f3e81ce3b3",
  "repo_slug": "forgeopslabs/appstore-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_forgeopslabs_appstore_mcp_a7a23eb9/readme"
}