# App Store Connect [Health: Active]

**Category:** 💻 Developer Tools  
**Repository:** https://github.com/forgeopslabs/appstore-mcp  
**GitHub Stars:** 2  
**Views:** 0  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/app-store-connect-2

## Description
MCP server for the Apple App Store Connect API - 101 tools for the full app lifecycle.

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `npx` (confidence: high):

```json
"mcpServers": {
  "app-store-connect": {
    "command": "npx",
    "args": ["-y","@modelcontextprotocol/inspector"]
  }
}
```

## Documentation & README

# appstore-mcp

An [MCP](https://modelcontextprotocol.io) server, written in Rust, that exposes
the **Apple App Store Connect API** to AI agents. It covers the full product
lifecycle — apps & metadata, in-app purchases, subscriptions and their offers,
pricing & availability, App Store versions, App Review submission, TestFlight,
provisioning & signing, asset uploads, promoted purchases, customer reviews,
phased release, users & access, in-app events, Xcode Cloud, and analytics
reports — across **114 tools**, and can reach *any* other App Store Connect
endpoint through two generic JSON:API tools.

Built on the official [`rmcp`](https://crates.io/crates/rmcp) SDK over stdio.

📖 **[Full tool reference → docs/TOOLS.md](https://github.com/forgeopslabs/appstore-mcp/blob/HEAD/docs/TOOLS.md)** — every tool's purpose
and parameters.

Every tool is labelled with MCP annotations, so a client can tell `list_apps`
from `remove_user`. You can serve only the domains you need (`ASC_TOOLS`) or only
the tools that cannot write (`ASC_READ_ONLY`) — see
[Choosing which tools to serve](#choosing-which-tools-to-serve).

## Design: hybrid coverage

The App Store Connect API has hundreds of endpoints but is uniformly
[JSON:API](https://jsonapi.org). Rather than a tool per endpoint, this server is
**hybrid**:

- **Curated tools** (112) for the common, multi-step, or error-prone workflows —
  apps & metadata, IAPs, subscriptions & offers, versions, pricing, availability,
  App Review submission, TestFlight, provisioning, asset uploads, promoted
  purchases, customer reviews, phased release, users, in-app events, Xcode Cloud,
  analytics reports, and custom product pages.
- **Two generic escape-hatch tools** — `appstore_request` and `appstore_list` —
  that can call *any* endpoint with raw JSON:API documents.

## Tools

| Group | Tools |
|------|-------|
| **Generic** | `appstore_request`, `appstore_list` |
| **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` |
| **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` |
| **Subscriptions** | `list_subscription_groups`, `create_subscription_group`, `create_subscription`, `update_subscription`, `create_subscription_localization`, `set_subscription_price` |
| **Versions & metadata** | `list_app_store_versions`, `create_app_store_version`, `create_version_localization`, `update_version_localization` |
| **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` |
| **Pricing** | `list_territories`, `list_iap_price_points`, `list_subscription_price_points` |
| **Availability** | `set_iap_availability`, `set_subscription_availability`, `set_app_availability` |
| **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` |
| **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` |
| **Assets** | `upload_app_screenshot`, `upload_app_preview`, `create_screenshot_set`, `create_preview_set`, `delete_screenshot_set`, `delete_preview_set`, `reorder_screenshots` |
| **Subscription offers** | `create_introductory_offer`, `create_promotional_offer`, `create_winback_offer`, `list_winback_offers` |
| **Offer codes** | `create_offer_code`, `generate_one_time_use_codes`, `create_custom_offer_code`, `list_offer_codes` |
| **Promoted purchases** | `create_promoted_purchase`, `update_promoted_purchase`, `set_promoted_purchase_order`, `list_promoted_purchases` |
| **Customer reviews** | `list_customer_reviews`, `respond_to_review`, `delete_review_response` |
| **Phased release** | `start_phased_release`, `update_phased_release` |
| **Users & access** | `list_users`, `invite_user`, `update_user`, `remove_user` |
| **In-app events** | `create_app_event`, `create_app_event_localization`, `upload_app_event_screenshot` |
| **Xcode Cloud** | `list_ci_products`, `list_ci_workflows`, `start_ci_build`, `get_ci_build_run`, `list_ci_build_actions` |
| **Analytics reports** | `request_analytics_report`, `list_analytics_reports`, `list_analytics_report_instances`, `list_analytics_report_segments`, `download_analytics_segment` |
| **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` |

See **[docs/TOOLS.md](https://github.com/forgeopslabs/appstore-mcp/blob/HEAD/docs/TOOLS.md)** for each tool's description and parameters. Custom product page
images are uploaded with the existing `upload_app_screenshot` / `upload_app_preview` tools.

## Install

Prebuilt binaries for **macOS (universal), Linux (x86-64), and Windows (x86-64)**
are attached to every [GitHub Release](https://github.com/forgeopslabs/appstore-mcp/releases).
Pick the channel for your client; all of them need credentials (see [Credentials](#credentials)).

### Claude Desktop — one-click bundle

Download `appstore-mcp.mcpb` from the latest release and open it with Claude Desktop
(**Settings → Extensions → Install Extension…**, or drag the file onto the window).
It prompts for your **Issuer ID**, **Key ID**, and **.p8 key file**. The bundle ships
all three platforms' binaries and selects the right one automatically.

### Claude Code — plugin marketplace

```text
/plugin marketplace add forgeopslabs/appstore-mcp
/plugin install appstore-mcp@forgeopslabs
```

The plugin launches the `appstore-mcp` binary from your `PATH`, so install it first —
download the binary for your OS from the [latest release](https://github.com/forgeopslabs/appstore-mcp/releases/latest)
and put it on your `PATH`, or `cargo install --git https://github.com/forgeopslabs/appstore-mcp`.
Set `ASC_ISSUER_ID`, `ASC_KEY_ID`, and `ASC_PRIVATE_KEY_PATH` in the environment you start
Claude Code from.

### Codex

Codex configures MCP servers directly (no marketplace). With `appstore-mcp` on your `PATH`:

```bash
codex mcp add appstore \
  --env ASC_ISSUER_ID=... --env ASC_KEY_ID=... \
  --env ASC_PRIVATE_KEY_PATH=/path/AuthKey_XXXXXX.p8 \
  -- appstore-mcp
```

or in `~/.codex/config.toml`:

```toml
[mcp_servers.appstore]
command = "appstore-mcp"
args = []
env = { ASC_ISSUER_ID = "...", ASC_KEY_ID = "...", ASC_PRIVATE_KEY_PATH = "/path/AuthKey_XXXXXX.p8" }
```

### MCP Registry

Published as `io.github.forgeopslabs/appstore-mcp` (metadata in
[`server.json`](https://github.com/forgeopslabs/appstore-mcp/blob/HEAD/server.json)) so any MCP-aware client can discover it.

### From source

```bash
cargo build --release    # -> target/release/appstore-mcp
```

## Credentials

Generate a **Team Key** in App Store Connect → *Users and Access → Integrations →
App Store Connect API*, and download the `.p8` file. Then set:

| Variable | Required | Description |
|----------|----------|-------------|
| `ASC_ISSUER_ID` | ✅ | Issuer UUID shown above the keys table. |
| `ASC_KEY_ID` | ✅ | The API key's Key ID. |
| `ASC_PRIVATE_KEY` | one of | Inline `.p8` PEM contents. |
| `ASC_PRIVATE_KEY_PATH` | one of | Path to the downloaded `.p8` file. |
| `ASC_BASE_URL` | optional | Override the API origin. |
| `ASC_LOG` | optional | Log filter (to stderr). Default `info`. |

See [`.env.example`](https://github.com/forgeopslabs/appstore-mcp/blob/HEAD/.env.example). The server authenticates each request with a
short-lived **ES256 JWT** signed by your key (cached and refreshed automatically).

> The server starts even without credentials so a client can list its tools;
> tool calls then return an actionable configuration error until creds are set.

## Choosing which tools to serve

A hundred tool definitions cost context in every session, and a client that sees
one flat list can't tell a read from a delete. Two knobs fix that:

| Variable | Default | Description |
|----------|---------|-------------|
| `ASC_TOOLS` | all | Comma-separated tool groups to serve, or the preset `core`. |
| `ASC_READ_ONLY` | `0` | Serve only tools that cannot modify the account. |

```bash
ASC_TOOLS=core                     # 41 tools: generic, apps, versions, assets, testflight, submission
ASC_TOOLS=testflight,provisioning  # just what a build-distribution agent needs
ASC_READ_ONLY=1                    # 35 read-only tools; writes are withheld entirely
```

Groups: `generic`, `apps`, `iap`, `subscriptions`, `versions`, `pricing`,
`availability`, `submission`, `testflight`, `provisioning`, `assets`, `offers`,
`offer-codes`, `promotions`, `reviews`, `users`, `events`, `xcode-cloud`,
`analytics`, `custom-product-pages` — plus `all` and `core`. An unrecognised name
is warned about on stderr and serves nothing rather than quietly falling back to
everything.

In read-only mode `appstore_request` is kept but refuses any method other than
`GET`, so the escape hatch still reaches endpoints without a curated tool without
becoming a way around the restriction.

Every served tool advertises MCP annotations (`readOnlyHint`, `destructiveHint`,
`idempotentHint`), which clients use to decide what needs confirming. Nine tools
are marked destructive: the seven `delete_*`/`remove_*` tools, `expire_build`,
`disable_bundle_id_capability`, and `appstore_request` (which can reach any
`DELETE` endpoint).

## Tuning

Defaults are chosen so a tool call can't hang and a single response can't swamp
an agent's context. All of these are optional.

| Variable | Default | Description |
|----------|---------|-------------|
| `ASC_TIMEOUT_SECS` | `60` | Whole-request timeout. `0` disables. |
| `ASC_CONNECT_TIMEOUT_SECS` | `10` | Connect timeout. `0` disables. |
| `ASC_TRANSFER_TIMEOUT_SECS` | `300` | Timeout for asset uploads and report downloads. |
| `ASC_MAX_RETRIES` | `3` | Retries after the first attempt. `0` disables. |
| `ASC_MAX_RESPONSE_BYTES` | `60000` | Tool-result size cap. `0` disables. |
| `ASC_COMPACT_RESPONSES` | `1` | Strip redundant JSON:API links from responses. |

**Retries.** A `429` is replayed for any method, since Apple rejected the request
without applying it. A `5xx` or a mid-flight timeout is replayed only for
`GET`/`PATCH`/`PUT`/`DELETE` — never `POST`, which could otherwise create a
duplicate resource (and Apple permanently reserves identifiers like a product
ID). Backoff is exponential with jitter and honours `Retry-After`.

**Response shaping.** Results are serialized compactly — indented JSON measured
1.72× the bytes for identical content, so the same budget now carries about 40%
more of the data you asked for. Per-resource `self` links and link-only
relationships are stripped: no addressable content is lost, and `links.next`
survives for pagination. If a response still exceeds the budget, `included` is
dropped first, then trailing `data` items, and the result carries a `_truncated`
key saying what went missing and how to narrow the query. Note that following
`links.next` after a trim would skip the dropped items — re-request with a
smaller `limit` instead.

## Build & run

```bash
cargo build --release
ASC_ISSUER_ID=... ASC_KEY_ID=... ASC_PRIVATE_KEY_PATH=/path/AuthKey_XXX.p8 \
  ./target/release/appstore-mcp
```

The server speaks MCP over **stdio**. Logs go to **stderr**; stdout is the
protocol channel.

### Use with an MCP client

Example client config (e.g. Claude Desktop's `mcpServers`):

```json
{
  "mcpServers": {
    "appstore": {
      "command": "/absolute/path/to/appstore-mcp/target/release/appstore-mcp",
      "env": {
        "ASC_ISSUER_ID": "00000000-0000-0000-0000-000000000000",
        "ASC_KEY_ID": "ABCD123456",
        "ASC_PRIVATE_KEY_PATH": "/absolute/path/to/AuthKey_ABCD123456.p8"
      }
    }
  }
}
```

### Inspect with the MCP Inspector

```bash
npx @modelcontextprotocol/inspector ./target/release/appstore-mcp
```

## Usage notes

- **IDs are opaque.** List/get first to resolve app, IAP, subscription, set, and
  price-point IDs, then pass them to create/update tools.
- **Pricing needs a price point.** Use `list_iap_price_points` /
  `list_subscription_price_points` to get the `id` for `set_iap_price_schedule` /
  `set_subscription_price`.
- **Asset uploads** (`upload_*`) take a local file path and run the full reserve →
  chunked upload → MD5 commit flow in one call. The file is streamed, so peak
  memory is one chunk rather than the size of the asset, and a chunk that fails
  is retried on its own. Screenshots/previews require an existing
  `appScreenshotSet` / `appPreviewSet`; create those with the generic tools if
  needed.
- **Pagination.** `appstore_list` returns one page by default. Pass
  `max_pages` (up to 20) to follow `links.next` and merge the pages into one
  result — `meta.hasMore` tells you whether anything is left.
- **Analytics data.** `request_analytics_report` → `list_analytics_reports` →
  `list_analytics_report_instances` → `list_analytics_report_segments` gets you a
  presigned segment URL; `download_analytics_segment` fetches it, gunzips it, and
  returns the rows as JSON. Apple can take up to 48 hours to generate the first
  report for a new request.
- **Anything not listed** is reachable via `appstore_request` (raw method + path +
  JSON:API body) or `appstore_list` (paginated GET). Example:
  `appstore_request { "method": "GET", "path": "/v1/apps/123/customerReviews" }`.
- **Not covered:** sales/finance report endpoints return gzipped TSV (not JSON:API)
  and are out of scope for these tools.

## Limitations (enforced by Apple)

- **You cannot create an app via the API.** The `apps` resource only allows GET and
  UPDATE — `POST /v1/apps` returns `403 FORBIDDEN_ERROR`. Create the app record in
  the [App Store Connect website](https://appstoreconnect.apple.com) (*Apps → ➕ →
  New App*); you can pre-create its bundle ID with `create_bundle_id`. All other
  tools operate on an existing app.
- A deleted in-app purchase's `productId` is permanently reserved by Apple and
  cannot be reused.

## Development

```bash
cargo test                              # 200+ tests, no network or credentials needed
cargo clippy --all-targets -- -D warnings
cargo fmt --check
```

Tests come in three layers: pure unit tests for request-body builders, retry
decisions, and response shaping; [`wiremock`](https://crates.io/crates/wiremock)
tests that drive the real HTTP client against a mock API (retries, timeouts,
pagination, the three-step upload protocol, segment downloads); and
`tests/tool_surface.rs`, which asserts the invariants of what a client actually
sees — unique names, real descriptions, object schemas, correct annotations, and
that `ASC_TOOLS`/`ASC_READ_ONLY` withhold exactly what they claim to.

The minimum supported Rust version is **1.88**, checked by its own CI job.

Regenerate the tool reference after adding/changing tools (needs the release
binary; no credentials required):

```bash
cargo build --release && python3 scripts/gen_tools_doc.py   # rewrites docs/TOOLS.md
```

### Live integration tests

`scripts/integration_test.py` drives the compiled server against the real API.
Read-only by default; `--write` adds a self-cleaning IAP lifecycle.

```bash
cargo build --release
# Credentials via env (ASC_ISSUER_ID/ASC_KEY_ID/ASC_PRIVATE_KEY_PATH) or local
# appstore-connect.txt + AuthKey_*.p8 in the repo root (both gitignored).
python3 scripts/integration_test.py --app <APP_ID>          # read-only sweep
python3 scripts/integration_test.py --app <APP_ID> --write  # + write lifecycle
```

## License

MIT

