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 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).
@@ -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.
@@ -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.