App Store Connect MCP
by ravenKaisar
README.md
# App Store Connect MCP
Node.js and TypeScript MCP server for App Store Connect. It supports multiple Apple accounts and can run either as a self-hosted remote MCP gateway or as a local `npx` stdio MCP server.
## Features
- Multiple named App Store Connect accounts
- Account-prefixed MCP tools, so one server can expose several Apple accounts safely
- ES256 App Store Connect JWT generation and caching
- Remote Streamable HTTP MCP endpoint at `/mcp`
- Bearer-token protection for remote HTTP mode
- Local stdio mode for `npx` usage
- Dockerfile and Docker Compose support
- Secret-safe logging and responses
- Zod validation for account configuration and tool inputs
## Modes
| Mode | Best For | Credentials Location | MCP Transport |
| --- | --- | --- | --- |
| Remote HTTP gateway | Teams, shared servers, centralized access | Mounted server-side JSON file | Streamable HTTP |
| Local stdio package | Individual developers or local automation | MCP client environment variable | stdio |
Use remote HTTP mode when developers should not receive Apple private keys. Use local stdio mode only when the machine running the MCP client is allowed to hold Apple credentials.
## Architecture
```text
Remote HTTP mode:
MCP Client
|
| HTTPS /mcp
| Authorization: Bearer server-token
v
App Store Connect MCP Gateway
|
| ES256 Apple JWT
v
App Store Connect API
```
```text
Local stdio mode:
MCP Client
|
| npx @kaisar/mcp-appstore-connect
| APPLE_ACCOUNTS=base64-json
v
Local MCP Server
|
| ES256 Apple JWT
v
App Store Connect API
```
## Apple Account Configuration
Create an `apple_accounts.json` file:
```json
[
{
"id": "production",
"keyId": "ABC123DEFG",
"issuerId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"privateKey": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
"vendorNumber": "12345678"
},
{
"id": "client_a",
"keyId": "XYZ123DEFG",
"issuerId": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
"privateKey": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
}
]
```
Fields:
- `id`: local account name used to generate MCP tool prefixes
- `keyId`: App Store Connect API key ID
- `issuerId`: App Store Connect issuer ID
- `privateKey`: `.p8` private key content, with newlines preserved or escaped as `\n`
- `vendorNumber`: optional Sales and Trends vendor number
The config does not support `defaultBundleId` or `defaultAppStoreId`. Pass app IDs explicitly to tools.
Account IDs can contain spaces or symbols. Tool prefixes are generated by lowercasing the account ID and replacing unsafe characters with `_`. Prefixes must be unique.
Example:
```text
"Client A" -> client_a
"Production" -> production
```
## Tool Names
The server exposes one set of tools per account. Tool names use:
```text
account_prefix__tool_name
```
Examples:
```text
production__list_apps
production__get_builds
production__get_sales_data
client_a__list_beta_groups
client_a__get_customer_reviews
```
The server also exposes:
```text
list_gateway_accounts
```
This returns configured account IDs and generated tool prefixes only. It never returns Apple keys, issuer IDs, private keys, or vendor numbers.
## Remote HTTP Gateway
Remote mode runs a web server with:
- `GET /healthz`
- `POST /mcp`
Required environment variables:
```bash
APPLE_ACCOUNTS_FILE=/run/secrets/apple_accounts.json
MCP_SERVER_TOKEN=replace-with-long-random-token
```
`APPLE_ACCOUNTS_JSON` is intentionally not supported in remote HTTP mode. Use a mounted file so Apple credentials are not baked into the image or visible in process environment dumps.
### Docker Compose
Create local files:
```bash
mkdir -p secrets
cp .env.example .env
```
Add Apple credentials to:
```text
secrets/apple_accounts.json
```
Set the gateway token in `.env`:
```env
MCP_SERVER_TOKEN=replace-with-long-random-token
```
Start the server:
```bash
docker compose up --build
```
Health check:
```bash
curl http://localhost:3000/healthz
```
Unauthorized MCP requests return `401`:
```bash
curl -i -X POST http://localhost:3000/mcp
```
Manual initialize request:
```bash
curl -i http://localhost:3000/mcp \
-H "Authorization: Bearer replace-with-long-random-token" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
--data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0.0"}}}'
```
### Remote MCP Client Config
Use this pattern for clients that support remote Streamable HTTP MCP servers:
```json
{
"servers": {
"appstore-connect": {
"type": "http",
"url": "https://example.com/mcp",
"headers": {
"Authorization": "Bearer replace-with-long-random-token"
}
}
}
}
```
Developers using remote mode only need the URL and gateway token. They do not need Apple API credentials.
## Local npx stdio
Local stdio mode runs directly from npm:
```bash
npx -y @kaisar/mcp-appstore-connect
```
It requires `APPLE_ACCOUNTS`, a base64-encoded copy of `apple_accounts.json`.
Generate the value:
```bash
node -e "process.stdout.write(Buffer.from(require('fs').readFileSync('secrets/apple_accounts.json')).toString('base64'))"
```
MCP client config:
```json
{
"mcpServers": {
"appstore-connect": {
"command": "npx",
"args": ["-y", "@kaisar/mcp-appstore-connect"],
"env": {
"APPLE_ACCOUNTS": "base64-encoded-apple-accounts-json"
}
}
}
}
```
Base64 is not encryption. Anyone who can read this MCP client config can decode the Apple credentials.
## Development
Install dependencies:
```bash
pnpm install
```
Build:
```bash
pnpm run build
```
Run tests:
```bash
pnpm test
```
Run remote HTTP mode locally:
```bash
APPLE_ACCOUNTS_FILE="./secrets/apple_accounts.json" \
MCP_SERVER_TOKEN="replace-with-long-random-token" \
pnpm run dev
```
Run stdio mode locally:
```bash
APPLE_ACCOUNTS="base64-encoded-apple-accounts-json" pnpm run dev:stdio
```
Validate mounted-file configuration:
```bash
APPLE_ACCOUNTS_FILE="./secrets/apple_accounts.json" pnpm run validate:config
```
Validate base64 configuration:
```bash
APPLE_ACCOUNTS="base64-encoded-apple-accounts-json" pnpm run validate:config
```
Validation prints only account IDs, tool prefixes, private key validity, and key type. It does not print private key content.
## Security
- Do not commit real `apple_accounts.json` files.
- Do not commit `.env` files.
- Do not bake Apple private keys into Docker images.
- Use HTTPS for remote HTTP deployments.
- Use a long random `MCP_SERVER_TOKEN`.
- Rotate `MCP_SERVER_TOKEN` when access changes.
- Treat the base64 `APPLE_ACCOUNTS` value as secret material.
- Give App Store Connect API keys the minimum Apple role needed for the tools users will call.
- Review logs before sharing them. Logs are designed to avoid secrets, but operational logs can still reveal account IDs and request timing.
## Supported Tool Areas
The server focuses on read-oriented App Store Connect operations, including:
- apps and app metadata
- builds and TestFlight resources
- beta groups and beta feedback
- customer reviews and review summaries
- app versions, screenshots, previews, and localizations
- in-app purchases, subscriptions, offers, prices, and promoted purchases
- app availability and pricing
- analytics, sales, finance, and performance reports
- App Store Connect users
## Troubleshooting
`Set APPLE_ACCOUNTS_FILE to the mounted apple_accounts.json path`
Remote HTTP mode needs `APPLE_ACCOUNTS_FILE` and a readable mounted JSON file.
`Set APPLE_ACCOUNTS to base64-encoded apple_accounts.json content`
Local stdio mode needs `APPLE_ACCOUNTS`.
`Set MCP_SERVER_TOKEN`
Remote HTTP mode needs a gateway token.
`Unauthorized`
The MCP client bearer token does not match `MCP_SERVER_TOKEN`.
`JWT generation failed`
The private key is not a valid App Store Connect `.p8` EC private key. Re-copy the complete key, including the `BEGIN PRIVATE KEY` and `END PRIVATE KEY` lines.
Sales report errors mentioning `reportSubType` or `vendorNumber`
Sales and Trends reports require Apple report filters and a numeric Vendor Number. Pass `vendorNumber` in the tool call or configure it in the account entry.
App lookup errors for values like `com.example.app`
Most App Store Connect API app endpoints expect the numeric app resource ID returned by `list_apps`, not the bundle ID.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues