appstore-api-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +20 -0
- package/CHANGELOG.md +20 -0
- package/LICENSE +21 -0
- package/README.md +265 -0
- package/docs/CLIENTS.md +167 -0
- package/docs/SECURITY.md +57 -0
- package/docs/SETUP.md +105 -0
- package/docs/TOOLS.md +239 -0
- package/package.json +53 -0
- package/src/client.js +162 -0
- package/src/index.js +815 -0
package/.env.example
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# App Store Connect API credentials
|
|
2
|
+
# Create a key at: App Store Connect → Users and Access → Integrations → App Store Connect API
|
|
3
|
+
# See docs/SETUP.md for the full walkthrough.
|
|
4
|
+
|
|
5
|
+
# Key ID (short alphanumeric, shown next to the key)
|
|
6
|
+
ASC_KEY_ID=
|
|
7
|
+
|
|
8
|
+
# Issuer ID (UUID, shown at the top of the Integrations page)
|
|
9
|
+
ASC_ISSUER_ID=
|
|
10
|
+
|
|
11
|
+
# Provide the .p8 private key ONE of these three ways:
|
|
12
|
+
|
|
13
|
+
# 1) Absolute path to the downloaded .p8 file (simplest)
|
|
14
|
+
ASC_PRIVATE_KEY_PATH=/absolute/path/to/AuthKey_XXXXXXXXXX.p8
|
|
15
|
+
|
|
16
|
+
# 2) ...or the raw PEM contents (keep the newlines)
|
|
17
|
+
# ASC_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
|
|
18
|
+
|
|
19
|
+
# 3) ...or base64 of the .p8 file (no newline headaches): base64 -i AuthKey_XXXX.p8
|
|
20
|
+
# ASC_PRIVATE_KEY_BASE64=
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/) and the project uses
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [1.0.0] - 2026-06-02
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Initial release.
|
|
11
|
+
- App browsing: `list_apps`, `get_app`.
|
|
12
|
+
- App info localizations (name, subtitle, privacy policy): list / update / create.
|
|
13
|
+
- App Store versions: list / create.
|
|
14
|
+
- Version localizations (description, keywords, promotional text, what's-new, URLs): list / get / update / create.
|
|
15
|
+
- Screenshots: list/create sets, list/upload/delete screenshots (full reserve→upload→commit flow).
|
|
16
|
+
- `audit_apps` — fleet-wide ASO/listing health check across all apps, read-only, with an account summary.
|
|
17
|
+
- Dry-run mode (`dryRun: true`) on the update tools — preview a field-by-field diff with length/limit checks before writing.
|
|
18
|
+
- `raw_request` escape hatch covering the entire App Store Connect API.
|
|
19
|
+
- ES256 JWT auth with three key-input methods: file path, raw PEM, base64.
|
|
20
|
+
- Multi-client setup guide (`docs/CLIENTS.md`) — Claude, Cursor, Cline, Windsurf, VS Code, Zed, Continue, and custom agents.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sviatoslav Fil
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# App Store Connect MCP
|
|
2
|
+
|
|
3
|
+
An [MCP](https://modelcontextprotocol.io) server that lets **any AI agent** read
|
|
4
|
+
and edit your **App Store Connect** apps in plain language — keywords,
|
|
5
|
+
descriptions, titles, subtitles, promotional text, what's-new, screenshots,
|
|
6
|
+
versions — plus a raw-request tool that reaches the **entire**
|
|
7
|
+
[App Store Connect API](https://developer.apple.com/documentation/appstoreconnectapi).
|
|
8
|
+
|
|
9
|
+
> Ask things like *"update the keywords for my app to X, Y, Z"*,
|
|
10
|
+
> *"show the English description for MyApp"*, or
|
|
11
|
+
> *"upload these screenshots to the 6.7-inch set"* — the agent calls the right tools.
|
|
12
|
+
|
|
13
|
+
- 🤖 **Works with any MCP client** — Claude Code/Desktop, Cursor, Cline, Windsurf, VS Code (agent mode), Zed, Continue, and custom agents on the MCP SDKs. Standard stdio server, no client-specific code. → [docs/CLIENTS.md](docs/CLIENTS.md)
|
|
14
|
+
- ✅ **One-line install** via `npx` — no clone, no build
|
|
15
|
+
- ✅ **Credentials stay on your machine** — calls go straight to Apple, nothing is proxied
|
|
16
|
+
- ✅ **Slim, well-described tool set** + a `raw_request` escape hatch for the whole API
|
|
17
|
+
- 🛡️ **Dry-run mode** — preview any metadata change (old→new + length checks) before writing
|
|
18
|
+
- 🚀 **Fleet audit** — one call health-checks **all** your apps for ASO gaps (built for indies with many apps)
|
|
19
|
+
- ✅ MIT licensed, actively maintained
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Table of contents
|
|
24
|
+
|
|
25
|
+
- [Quick start](#quick-start)
|
|
26
|
+
- [Supported clients](#supported-clients) → full guide in [docs/CLIENTS.md](docs/CLIENTS.md)
|
|
27
|
+
- [Getting your API key](#getting-your-api-key) → full guide in [docs/SETUP.md](docs/SETUP.md)
|
|
28
|
+
- [Configuration](#configuration)
|
|
29
|
+
- [Tools](#tools) → full reference in [docs/TOOLS.md](docs/TOOLS.md)
|
|
30
|
+
- [Common workflows](#common-workflows)
|
|
31
|
+
- [Security](#security) → details in [docs/SECURITY.md](docs/SECURITY.md)
|
|
32
|
+
- [Troubleshooting](#troubleshooting)
|
|
33
|
+
- [Development](#development)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Quick start
|
|
38
|
+
|
|
39
|
+
**Requirements:** Node.js ≥ 18 and an Apple Developer account with an
|
|
40
|
+
[App Store Connect API key](#getting-your-api-key).
|
|
41
|
+
|
|
42
|
+
### Claude Code (CLI)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
claude mcp add appstore-api \
|
|
46
|
+
--env ASC_KEY_ID=YOUR_KEY_ID \
|
|
47
|
+
--env ASC_ISSUER_ID=YOUR_ISSUER_ID \
|
|
48
|
+
--env ASC_PRIVATE_KEY_PATH=/absolute/path/to/AuthKey_XXXXXXXXXX.p8 \
|
|
49
|
+
-- npx -y appstore-api-mcp
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Add `--scope user` to make it available in **every** project (default is the
|
|
53
|
+
current project only).
|
|
54
|
+
|
|
55
|
+
### Any other MCP client (Cursor, Cline, Windsurf, VS Code, Zed, Continue, …)
|
|
56
|
+
|
|
57
|
+
Almost every client uses this same block (Claude Desktop config path:
|
|
58
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"appstore-api": {
|
|
64
|
+
"command": "npx",
|
|
65
|
+
"args": ["-y", "appstore-api-mcp"],
|
|
66
|
+
"env": {
|
|
67
|
+
"ASC_KEY_ID": "YOUR_KEY_ID",
|
|
68
|
+
"ASC_ISSUER_ID": "YOUR_ISSUER_ID",
|
|
69
|
+
"ASC_PRIVATE_KEY_PATH": "/absolute/path/to/AuthKey_XXXXXXXXXX.p8"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Restart the client, then ask it to *"list my App Store apps"* to confirm it works.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Supported clients
|
|
81
|
+
|
|
82
|
+
This is a standard stdio MCP server — it works with **any MCP-compatible agent**.
|
|
83
|
+
The command (`npx -y appstore-api-mcp`) and the three `ASC_*` env vars are always
|
|
84
|
+
the same; only each client's config format/location differs.
|
|
85
|
+
|
|
86
|
+
| Client | Where to configure |
|
|
87
|
+
| --- | --- |
|
|
88
|
+
| **Claude Code** | `claude mcp add …` (see above) |
|
|
89
|
+
| **Claude Desktop** | `claude_desktop_config.json` → `mcpServers` |
|
|
90
|
+
| **Cursor** | `.cursor/mcp.json` → `mcpServers` |
|
|
91
|
+
| **Cline** (VS Code) | `cline_mcp_settings.json` → `mcpServers` |
|
|
92
|
+
| **Windsurf** | `~/.codeium/windsurf/mcp_config.json` → `mcpServers` |
|
|
93
|
+
| **VS Code** (agent mode) | `.vscode/mcp.json` → `servers` (note: not `mcpServers`) |
|
|
94
|
+
| **Zed** | `settings.json` → `context_servers` |
|
|
95
|
+
| **Continue** | `~/.continue/config.yaml` → `mcpServers` |
|
|
96
|
+
| **Custom agent** (MCP SDK / Agents SDK / LangChain) | spawn the stdio command with the env vars |
|
|
97
|
+
|
|
98
|
+
Copy-paste config snippets for each are in **[docs/CLIENTS.md](docs/CLIENTS.md)**.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Getting your API key
|
|
103
|
+
|
|
104
|
+
1. Go to **[App Store Connect](https://appstoreconnect.apple.com) → Users and Access → Integrations → App Store Connect API**.
|
|
105
|
+
2. Click **+** to generate a key. Give it a name and the **least privilege** role
|
|
106
|
+
you need — **App Manager** is enough to edit metadata, keywords and
|
|
107
|
+
screenshots. (Avoid **Admin** unless you truly need it.)
|
|
108
|
+
3. Note the **Issuer ID** (top of the page) and the **Key ID** (next to the key).
|
|
109
|
+
4. **Download the `.p8` file** — you can only download it once. Store it somewhere safe.
|
|
110
|
+
|
|
111
|
+
Full walkthrough with screenshots-worth of detail: **[docs/SETUP.md](docs/SETUP.md)**.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Configuration
|
|
116
|
+
|
|
117
|
+
All configuration is via environment variables.
|
|
118
|
+
|
|
119
|
+
| Variable | Required | Description |
|
|
120
|
+
| --- | --- | --- |
|
|
121
|
+
| `ASC_KEY_ID` | yes | The key's ID (short alphanumeric) |
|
|
122
|
+
| `ASC_ISSUER_ID` | yes | The issuer UUID from the Integrations page |
|
|
123
|
+
| `ASC_PRIVATE_KEY_PATH` | one of these three | Absolute path to the `.p8` file |
|
|
124
|
+
| `ASC_PRIVATE_KEY` | one of these three | The raw PEM contents of the key |
|
|
125
|
+
| `ASC_PRIVATE_KEY_BASE64` | one of these three | Base64 of the `.p8` (`base64 -i AuthKey.p8`) — easiest for env vars |
|
|
126
|
+
|
|
127
|
+
See [.env.example](.env.example) for a copy-paste template.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Tools
|
|
132
|
+
|
|
133
|
+
A compact set of high-level tools, plus `raw_request` for everything else.
|
|
134
|
+
Full parameter reference: **[docs/TOOLS.md](docs/TOOLS.md)**.
|
|
135
|
+
|
|
136
|
+
| Tool | What it does |
|
|
137
|
+
| --- | --- |
|
|
138
|
+
| `list_apps` / `get_app` | Browse your apps |
|
|
139
|
+
| `list_app_infos` | Find the record holding name/subtitle/privacy localizations |
|
|
140
|
+
| `list_app_info_localizations` | Read name, subtitle, privacy policy per locale |
|
|
141
|
+
| `update_app_info_localization` | Update **name, subtitle, privacy policy** |
|
|
142
|
+
| `create_app_info_localization` | Add a new locale's name/subtitle |
|
|
143
|
+
| `list_app_store_versions` | List versions and their states |
|
|
144
|
+
| `create_app_store_version` | Start a new version to prepare for submission |
|
|
145
|
+
| `list_app_store_version_localizations` | Read description/keywords/etc. per locale |
|
|
146
|
+
| `get_app_store_version_localization` | Read one locale's listing copy |
|
|
147
|
+
| `update_app_store_version_localization` | Update **keywords, description, promo text, what's-new, URLs** |
|
|
148
|
+
| `create_app_store_version_localization` | Add a new locale to a version |
|
|
149
|
+
| `list_screenshot_sets` / `create_screenshot_set` | Manage per-device screenshot sets |
|
|
150
|
+
| `list_screenshots` / `upload_screenshot` / `delete_screenshot` | Manage screenshots (upload handles the full reserve→upload→commit flow) |
|
|
151
|
+
| 🚀 `audit_apps` | **Fleet health check** — scan all (or selected) apps for missing subtitle/keywords/description, under-used keyword field, single-locale listings, missing screenshots, and more. Read-only. |
|
|
152
|
+
| `raw_request` | Any method/path against the API — previews, pricing, TestFlight, IAP, reviews, analytics, sales reports, … |
|
|
153
|
+
|
|
154
|
+
> 🛡️ **Dry-run:** the `update_*` tools accept `dryRun: true` — they return a field-by-field
|
|
155
|
+
> diff (old → new) with length/limit checks and **write nothing**. Drop the flag to apply.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Common workflows
|
|
160
|
+
|
|
161
|
+
### Update keywords or description
|
|
162
|
+
|
|
163
|
+
1. `list_apps` → get the app id
|
|
164
|
+
2. `list_app_store_versions` (filter `PREPARE_FOR_SUBMISSION`) → the editable version id
|
|
165
|
+
3. `list_app_store_version_localizations` → the locale's localization id
|
|
166
|
+
4. `update_app_store_version_localization` with `keywords` and/or `description`
|
|
167
|
+
|
|
168
|
+
In practice you just ask: *"set the keywords for MyApp to a, b, c"* and the model
|
|
169
|
+
chains these for you.
|
|
170
|
+
|
|
171
|
+
### Update the app name or subtitle
|
|
172
|
+
|
|
173
|
+
`list_app_infos` → `list_app_info_localizations` → `update_app_info_localization`.
|
|
174
|
+
|
|
175
|
+
### Upload screenshots
|
|
176
|
+
|
|
177
|
+
`list_app_store_version_localizations` → `list_screenshot_sets`
|
|
178
|
+
(or `create_screenshot_set`) → `upload_screenshot` with an absolute image path.
|
|
179
|
+
|
|
180
|
+
### Audit your whole portfolio (the indie superpower)
|
|
181
|
+
|
|
182
|
+
Just ask: *"Audit all my apps for ASO gaps."* One `audit_apps` call returns a
|
|
183
|
+
ranked report — which apps are missing keywords, subtitles, descriptions, or
|
|
184
|
+
under-using the 100-char keyword field — plus an account-wide summary. Add
|
|
185
|
+
`checkScreenshots: true` to also flag apps with no screenshots.
|
|
186
|
+
|
|
187
|
+
```jsonc
|
|
188
|
+
// returns { summary: { appsAudited, appsWithIssues, issuesByType, … }, findings: [ … ] }
|
|
189
|
+
{ "name": "audit_apps", "arguments": { "checkScreenshots": false } }
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Preview before you write (dry-run)
|
|
193
|
+
|
|
194
|
+
Every `update_*` tool takes `dryRun: true`. You get the exact diff and length
|
|
195
|
+
checks, and **nothing is written** — so you (or the model) can confirm first:
|
|
196
|
+
|
|
197
|
+
```jsonc
|
|
198
|
+
{ "name": "update_app_store_version_localization",
|
|
199
|
+
"arguments": { "localizationId": "…", "keywords": "todo,tasks,planner", "dryRun": true } }
|
|
200
|
+
// → { dryRun:true, changes:[{ field:"keywords", from:"…", to:"…", newLength:18, limit:100, exceedsLimit:false }], warnings:[] }
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Anything else
|
|
204
|
+
|
|
205
|
+
Use `raw_request`, e.g. read customer reviews:
|
|
206
|
+
`GET /apps/{id}/customerReviews`. The whole API surface is reachable this way.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Field limits (enforced by Apple)
|
|
211
|
+
|
|
212
|
+
| Field | Limit |
|
|
213
|
+
| --- | --- |
|
|
214
|
+
| App name | 30 chars |
|
|
215
|
+
| Subtitle | 30 chars |
|
|
216
|
+
| Keywords (comma-separated, combined) | 100 chars |
|
|
217
|
+
| Promotional text | 170 chars |
|
|
218
|
+
| Description / What's New | 4000 chars |
|
|
219
|
+
|
|
220
|
+
Metadata only **saves on a version in an editable state** (e.g.
|
|
221
|
+
`PREPARE_FOR_SUBMISSION`) — except **promotional text**, which can change live.
|
|
222
|
+
Edits go to the **draft**; they go public only after you submit and Apple approves.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Security
|
|
227
|
+
|
|
228
|
+
- Your API key is **powerful** — it can rewrite your live store listings. Treat it like a password.
|
|
229
|
+
- Credentials are read from env vars and used **only** to talk to Apple directly. Nothing is sent anywhere else.
|
|
230
|
+
- **Never commit your `.p8` or credentials.** This repo's `.gitignore` and npm `files` whitelist are set up to prevent that.
|
|
231
|
+
- Use a **dedicated, least-privilege key** (App Manager), and revoke it anytime in App Store Connect.
|
|
232
|
+
|
|
233
|
+
More: **[docs/SECURITY.md](docs/SECURITY.md)**.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Troubleshooting
|
|
238
|
+
|
|
239
|
+
| Symptom | Fix |
|
|
240
|
+
| --- | --- |
|
|
241
|
+
| `ASC_KEY_ID is required` etc. | An env var is missing — recheck your config. |
|
|
242
|
+
| `401`/`403` errors | Wrong issuer/key id, wrong `.p8`, or the key's role lacks permission. |
|
|
243
|
+
| `409` on metadata update | The version isn't in an editable state — create/select a `PREPARE_FOR_SUBMISSION` version. |
|
|
244
|
+
| `npx` can't find the package | Ensure Node ≥ 18 and that the package name is published/correct. |
|
|
245
|
+
| Tools don't appear | MCP servers load at client startup — restart the client / start a new session. |
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Development
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
git clone https://github.com/fil-technology/appstore-api-mcp.git
|
|
253
|
+
cd appstore-api-mcp
|
|
254
|
+
npm install
|
|
255
|
+
cp .env.example .env # fill in your credentials
|
|
256
|
+
node src/index.js # runs the server on stdio (Ctrl-C to stop)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The server is plain ES modules, no build step. Source:
|
|
260
|
+
- `src/index.js` — tool definitions + MCP wiring
|
|
261
|
+
- `src/client.js` — JWT (ES256) auth, request/paging helpers, asset upload
|
|
262
|
+
|
|
263
|
+
## License
|
|
264
|
+
|
|
265
|
+
MIT © Sviatoslav Fil — see [LICENSE](LICENSE).
|
package/docs/CLIENTS.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Using with different MCP clients
|
|
2
|
+
|
|
3
|
+
This is a standard [Model Context Protocol](https://modelcontextprotocol.io)
|
|
4
|
+
server over **stdio** — it works with **any** MCP-compatible agent, not just
|
|
5
|
+
Claude. The server itself contains nothing client-specific; only the *config
|
|
6
|
+
format and file location* differ per client.
|
|
7
|
+
|
|
8
|
+
Everything below runs the same command:
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
npx -y appstore-api-mcp
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
with these three env vars (see [SETUP.md](SETUP.md) to get them):
|
|
15
|
+
|
|
16
|
+
| Variable | Value |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `ASC_KEY_ID` | your key id |
|
|
19
|
+
| `ASC_ISSUER_ID` | your issuer id |
|
|
20
|
+
| `ASC_PRIVATE_KEY_PATH` | absolute path to your `.p8` (or use `ASC_PRIVATE_KEY` / `ASC_PRIVATE_KEY_BASE64`) |
|
|
21
|
+
|
|
22
|
+
> Tip: most clients use the identical `{ "mcpServers": { … } }` block shown for
|
|
23
|
+
> Claude Desktop. Where a client differs (VS Code, Zed), it's called out below.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Claude Code (CLI)
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
claude mcp add appstore-api \
|
|
31
|
+
--scope user \
|
|
32
|
+
--env ASC_KEY_ID=YOUR_KEY_ID \
|
|
33
|
+
--env ASC_ISSUER_ID=YOUR_ISSUER_ID \
|
|
34
|
+
--env ASC_PRIVATE_KEY_PATH=/absolute/path/to/AuthKey.p8 \
|
|
35
|
+
-- npx -y appstore-api-mcp
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Claude Desktop
|
|
39
|
+
|
|
40
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) /
|
|
41
|
+
`%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"appstore-api": {
|
|
47
|
+
"command": "npx",
|
|
48
|
+
"args": ["-y", "appstore-api-mcp"],
|
|
49
|
+
"env": {
|
|
50
|
+
"ASC_KEY_ID": "YOUR_KEY_ID",
|
|
51
|
+
"ASC_ISSUER_ID": "YOUR_ISSUER_ID",
|
|
52
|
+
"ASC_PRIVATE_KEY_PATH": "/absolute/path/to/AuthKey.p8"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Cursor
|
|
60
|
+
|
|
61
|
+
Project-level `.cursor/mcp.json` (or global `~/.cursor/mcp.json`) — same shape:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcpServers": {
|
|
66
|
+
"appstore-api": {
|
|
67
|
+
"command": "npx",
|
|
68
|
+
"args": ["-y", "appstore-api-mcp"],
|
|
69
|
+
"env": {
|
|
70
|
+
"ASC_KEY_ID": "YOUR_KEY_ID",
|
|
71
|
+
"ASC_ISSUER_ID": "YOUR_ISSUER_ID",
|
|
72
|
+
"ASC_PRIVATE_KEY_PATH": "/absolute/path/to/AuthKey.p8"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Cline (VS Code extension)
|
|
80
|
+
|
|
81
|
+
Open the Cline MCP settings (`cline_mcp_settings.json`) and add the same
|
|
82
|
+
`mcpServers` entry as above.
|
|
83
|
+
|
|
84
|
+
## Windsurf (Codeium)
|
|
85
|
+
|
|
86
|
+
`~/.codeium/windsurf/mcp_config.json` — same `mcpServers` shape as Claude Desktop.
|
|
87
|
+
|
|
88
|
+
## VS Code (native MCP / Copilot agent mode)
|
|
89
|
+
|
|
90
|
+
VS Code uses a `servers` key (note: **not** `mcpServers`). Project file
|
|
91
|
+
`.vscode/mcp.json`:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"servers": {
|
|
96
|
+
"appstore-api": {
|
|
97
|
+
"command": "npx",
|
|
98
|
+
"args": ["-y", "appstore-api-mcp"],
|
|
99
|
+
"env": {
|
|
100
|
+
"ASC_KEY_ID": "YOUR_KEY_ID",
|
|
101
|
+
"ASC_ISSUER_ID": "YOUR_ISSUER_ID",
|
|
102
|
+
"ASC_PRIVATE_KEY_PATH": "/absolute/path/to/AuthKey.p8"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Zed
|
|
110
|
+
|
|
111
|
+
In `settings.json`, Zed uses `context_servers` with a nested `command` object:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"context_servers": {
|
|
116
|
+
"appstore-api": {
|
|
117
|
+
"command": {
|
|
118
|
+
"path": "npx",
|
|
119
|
+
"args": ["-y", "appstore-api-mcp"],
|
|
120
|
+
"env": {
|
|
121
|
+
"ASC_KEY_ID": "YOUR_KEY_ID",
|
|
122
|
+
"ASC_ISSUER_ID": "YOUR_ISSUER_ID",
|
|
123
|
+
"ASC_PRIVATE_KEY_PATH": "/absolute/path/to/AuthKey.p8"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Continue
|
|
132
|
+
|
|
133
|
+
In `~/.continue/config.yaml`:
|
|
134
|
+
|
|
135
|
+
```yaml
|
|
136
|
+
mcpServers:
|
|
137
|
+
- name: appstore-api
|
|
138
|
+
command: npx
|
|
139
|
+
args: ["-y", "appstore-api-mcp"]
|
|
140
|
+
env:
|
|
141
|
+
ASC_KEY_ID: YOUR_KEY_ID
|
|
142
|
+
ASC_ISSUER_ID: YOUR_ISSUER_ID
|
|
143
|
+
ASC_PRIVATE_KEY_PATH: /absolute/path/to/AuthKey.p8
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Any other MCP client / custom agent
|
|
147
|
+
|
|
148
|
+
Point your client at a stdio server with:
|
|
149
|
+
|
|
150
|
+
- **command:** `npx`
|
|
151
|
+
- **args:** `["-y", "appstore-api-mcp"]`
|
|
152
|
+
- **env:** the three `ASC_*` variables
|
|
153
|
+
|
|
154
|
+
Or, if your client launches binaries by path, install globally
|
|
155
|
+
(`npm i -g appstore-api-mcp`) and run `appstore-api-mcp` directly. For SDK-based
|
|
156
|
+
agents (e.g. the Python/TypeScript MCP SDKs, OpenAI Agents SDK, LangChain MCP
|
|
157
|
+
adapters), spawn the same stdio command and pass the env vars through.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
### Notes that apply to every client
|
|
162
|
+
|
|
163
|
+
- The server speaks MCP over **stdio** — no ports, no network server to manage.
|
|
164
|
+
- It needs **Node.js ≥ 18** on the machine running the client.
|
|
165
|
+
- Credentials are read from the env you supply and used **only** to call Apple directly.
|
|
166
|
+
- After editing a client's config, **restart the client** (or start a new session)
|
|
167
|
+
so it picks up the server.
|
package/docs/SECURITY.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
This server talks to Apple on your behalf using an App Store Connect API key.
|
|
4
|
+
That key can **read and modify your live App Store presence**. Treat it with the
|
|
5
|
+
same care as a password.
|
|
6
|
+
|
|
7
|
+
## What the key can do
|
|
8
|
+
|
|
9
|
+
Depending on the role you assign, the key can edit metadata and keywords, upload
|
|
10
|
+
screenshots, manage TestFlight, change pricing, respond to reviews, and download
|
|
11
|
+
sales/finance data. Scope it down.
|
|
12
|
+
|
|
13
|
+
## Principle of least privilege
|
|
14
|
+
|
|
15
|
+
- Create a **dedicated key** just for this server — don't reuse one.
|
|
16
|
+
- Give it the **lowest role** that covers your tasks. **App Manager** is enough for
|
|
17
|
+
metadata, keywords, and screenshots. Avoid **Admin** unless you truly need it.
|
|
18
|
+
- You can **revoke** the key at any time in App Store Connect → Users and Access →
|
|
19
|
+
Integrations. Revoking is instant and breaks only this integration.
|
|
20
|
+
|
|
21
|
+
## Where credentials live
|
|
22
|
+
|
|
23
|
+
- Credentials are read from **environment variables** at startup.
|
|
24
|
+
- They are used **only** to mint a short-lived (≤20 min) JWT and call
|
|
25
|
+
`api.appstoreconnect.apple.com` **directly**. Nothing is proxied through any
|
|
26
|
+
third party, and nothing is logged to disk by this server.
|
|
27
|
+
- The JWT is held in memory and regenerated as needed.
|
|
28
|
+
|
|
29
|
+
## Never commit secrets
|
|
30
|
+
|
|
31
|
+
This project is set up to keep secrets out of git and npm:
|
|
32
|
+
|
|
33
|
+
- `.gitignore` excludes `secrets/`, `*.p8`, `.env`, `*.key`.
|
|
34
|
+
- `package.json` uses a `files` allow-list, so **only** `src/`, docs, README,
|
|
35
|
+
LICENSE, and `.env.example` are published to npm — even if a key sits in the
|
|
36
|
+
folder, `npm publish` won't include it.
|
|
37
|
+
|
|
38
|
+
**Before committing or publishing, always verify:**
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
git status # no .p8, no .env, no secrets/
|
|
42
|
+
npm pack --dry-run # review the file list — there must be NO key files
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
If a key is ever exposed (committed, pasted, shared), **revoke it immediately**
|
|
46
|
+
and generate a new one — that's a 60-second operation and the only safe response.
|
|
47
|
+
|
|
48
|
+
## Handling the .p8 file
|
|
49
|
+
|
|
50
|
+
- Store it outside any repo (e.g. `~/.appstoreconnect/`), `chmod 600`.
|
|
51
|
+
- Prefer `ASC_PRIVATE_KEY_PATH`. For containers/CI where files are awkward, use
|
|
52
|
+
`ASC_PRIVATE_KEY_BASE64` injected as a secret — not committed to the repo.
|
|
53
|
+
|
|
54
|
+
## Reporting a vulnerability
|
|
55
|
+
|
|
56
|
+
Found a security issue in this server? Please open a private report / security
|
|
57
|
+
advisory on the GitHub repository rather than a public issue.
|
package/docs/SETUP.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Setup guide
|
|
2
|
+
|
|
3
|
+
Step-by-step from zero to a working server.
|
|
4
|
+
|
|
5
|
+
## 1. Prerequisites
|
|
6
|
+
|
|
7
|
+
- **Node.js ≥ 18** — check with `node --version`. Install from [nodejs.org](https://nodejs.org) or `brew install node`.
|
|
8
|
+
- An **Apple Developer Program** membership with access to App Store Connect.
|
|
9
|
+
- A role that can create API keys: **Account Holder** or **Admin** creates the key;
|
|
10
|
+
the key itself can carry a lower role (see below).
|
|
11
|
+
|
|
12
|
+
## 2. Create an App Store Connect API key
|
|
13
|
+
|
|
14
|
+
1. Sign in to [App Store Connect](https://appstoreconnect.apple.com).
|
|
15
|
+
2. Go to **Users and Access** → **Integrations** tab → **App Store Connect API**.
|
|
16
|
+
3. Copy the **Issuer ID** shown near the top — this is your `ASC_ISSUER_ID`
|
|
17
|
+
(a UUID like `12345678-90ab-cdef-1234-567890abcdef`).
|
|
18
|
+
4. Click the **+** button to create a new key.
|
|
19
|
+
- **Name:** something like `mcp-metadata`.
|
|
20
|
+
- **Access (role):** choose the **least privilege** that covers your use:
|
|
21
|
+
- **App Manager** — edit metadata, keywords, screenshots, versions, TestFlight. ✅ Recommended.
|
|
22
|
+
- **Developer** — narrower; may not allow all edits.
|
|
23
|
+
- **Admin** — full control. Only if you genuinely need it.
|
|
24
|
+
5. Click **Generate**. You'll now see the key listed with a **Key ID** — that's your `ASC_KEY_ID`.
|
|
25
|
+
6. Click **Download API Key** to get the `AuthKey_XXXXXXXXXX.p8` file.
|
|
26
|
+
> ⚠️ You can only download this **once**. Save it somewhere safe and backed up.
|
|
27
|
+
> If you lose it, revoke the key and create a new one.
|
|
28
|
+
|
|
29
|
+
## 3. Store the key safely
|
|
30
|
+
|
|
31
|
+
Pick a stable location outside any git repo, e.g.:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
mkdir -p ~/.appstoreconnect
|
|
35
|
+
mv ~/Downloads/AuthKey_XXXXXXXXXX.p8 ~/.appstoreconnect/
|
|
36
|
+
chmod 600 ~/.appstoreconnect/AuthKey_XXXXXXXXXX.p8
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 4. Add the server to your MCP client
|
|
40
|
+
|
|
41
|
+
### Claude Code
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
claude mcp add appstore-connect \
|
|
45
|
+
--scope user \
|
|
46
|
+
--env ASC_KEY_ID=YOUR_KEY_ID \
|
|
47
|
+
--env ASC_ISSUER_ID=YOUR_ISSUER_ID \
|
|
48
|
+
--env ASC_PRIVATE_KEY_PATH=$HOME/.appstoreconnect/AuthKey_XXXXXXXXXX.p8 \
|
|
49
|
+
-- npx -y appstore-api-mcp
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`--scope user` makes it available in all projects. Drop it to scope to the current project only.
|
|
53
|
+
|
|
54
|
+
### Claude Desktop
|
|
55
|
+
|
|
56
|
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"mcpServers": {
|
|
61
|
+
"appstore-connect": {
|
|
62
|
+
"command": "npx",
|
|
63
|
+
"args": ["-y", "appstore-api-mcp"],
|
|
64
|
+
"env": {
|
|
65
|
+
"ASC_KEY_ID": "YOUR_KEY_ID",
|
|
66
|
+
"ASC_ISSUER_ID": "YOUR_ISSUER_ID",
|
|
67
|
+
"ASC_PRIVATE_KEY_PATH": "/Users/you/.appstoreconnect/AuthKey_XXXXXXXXXX.p8"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Then fully quit and reopen Claude Desktop.
|
|
75
|
+
|
|
76
|
+
### Cursor / Windsurf / other clients
|
|
77
|
+
|
|
78
|
+
Use the same `command` / `args` / `env` shape in that client's MCP settings file.
|
|
79
|
+
|
|
80
|
+
## 5. Verify
|
|
81
|
+
|
|
82
|
+
Start a new chat / session and ask: **"List my App Store Connect apps."**
|
|
83
|
+
You should get your app list back. If not, see [Troubleshooting](#troubleshooting) below.
|
|
84
|
+
|
|
85
|
+
## Alternative: passing the key without a file path
|
|
86
|
+
|
|
87
|
+
If your environment makes file paths awkward (containers, CI), base64-encode the key:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
base64 -i AuthKey_XXXXXXXXXX.p8 # macOS
|
|
91
|
+
base64 -w0 AuthKey_XXXXXXXXXX.p8 # Linux
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Then set `ASC_PRIVATE_KEY_BASE64` instead of `ASC_PRIVATE_KEY_PATH`.
|
|
95
|
+
|
|
96
|
+
## Troubleshooting
|
|
97
|
+
|
|
98
|
+
- **`401 NOT_AUTHORIZED` / `403`** — issuer ID or key ID doesn't match the `.p8`,
|
|
99
|
+
or the key's role lacks permission for that action. Double-check all three.
|
|
100
|
+
- **`ASC_*_is required`** — an env var didn't reach the process. In Claude Desktop,
|
|
101
|
+
confirm the JSON is valid and you restarted the app.
|
|
102
|
+
- **`409 CONFLICT` on a metadata update** — the App Store version isn't editable.
|
|
103
|
+
Create or select a version in `PREPARE_FOR_SUBMISSION` state.
|
|
104
|
+
- **Token/clock errors** — JWTs are time-based; make sure your system clock is correct.
|
|
105
|
+
- **`npx` fails to resolve the package** — verify Node ≥ 18 and the published package name.
|