asc-mcp-pro 0.1.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/LICENSE +21 -0
- package/README.md +241 -0
- package/build/auth.d.ts +8 -0
- package/build/auth.js +44 -0
- package/build/auth.js.map +1 -0
- package/build/client.d.ts +21 -0
- package/build/client.js +91 -0
- package/build/client.js.map +1 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +47 -0
- package/build/index.js.map +1 -0
- package/build/lib/jsonapi-write.d.ts +32 -0
- package/build/lib/jsonapi-write.js +18 -0
- package/build/lib/jsonapi-write.js.map +1 -0
- package/build/lib/jsonapi.d.ts +27 -0
- package/build/lib/jsonapi.js +27 -0
- package/build/lib/jsonapi.js.map +1 -0
- package/build/lib/locales.d.ts +24 -0
- package/build/lib/locales.js +66 -0
- package/build/lib/locales.js.map +1 -0
- package/build/lib/pagination.d.ts +11 -0
- package/build/lib/pagination.js +23 -0
- package/build/lib/pagination.js.map +1 -0
- package/build/lib/projections.d.ts +8 -0
- package/build/lib/projections.js +51 -0
- package/build/lib/projections.js.map +1 -0
- package/build/lib/upload.d.ts +38 -0
- package/build/lib/upload.js +133 -0
- package/build/lib/upload.js.map +1 -0
- package/build/tools/apps.d.ts +3 -0
- package/build/tools/apps.js +160 -0
- package/build/tools/apps.js.map +1 -0
- package/build/tools/builds.d.ts +3 -0
- package/build/tools/builds.js +158 -0
- package/build/tools/builds.js.map +1 -0
- package/build/tools/iap.d.ts +3 -0
- package/build/tools/iap.js +501 -0
- package/build/tools/iap.js.map +1 -0
- package/build/tools/ping.d.ts +3 -0
- package/build/tools/ping.js +12 -0
- package/build/tools/ping.js.map +1 -0
- package/build/tools/pricing.d.ts +3 -0
- package/build/tools/pricing.js +150 -0
- package/build/tools/pricing.js.map +1 -0
- package/build/tools/privacy.d.ts +3 -0
- package/build/tools/privacy.js +6 -0
- package/build/tools/privacy.js.map +1 -0
- package/build/tools/provisioning.d.ts +3 -0
- package/build/tools/provisioning.js +343 -0
- package/build/tools/provisioning.js.map +1 -0
- package/build/tools/registry.d.ts +3 -0
- package/build/tools/registry.js +35 -0
- package/build/tools/registry.js.map +1 -0
- package/build/tools/reports.d.ts +3 -0
- package/build/tools/reports.js +177 -0
- package/build/tools/reports.js.map +1 -0
- package/build/tools/reviews.d.ts +3 -0
- package/build/tools/reviews.js +51 -0
- package/build/tools/reviews.js.map +1 -0
- package/build/tools/runner.d.ts +3 -0
- package/build/tools/runner.js +174 -0
- package/build/tools/runner.js.map +1 -0
- package/build/tools/screenshots.d.ts +3 -0
- package/build/tools/screenshots.js +228 -0
- package/build/tools/screenshots.js.map +1 -0
- package/build/tools/testflight.d.ts +3 -0
- package/build/tools/testflight.js +360 -0
- package/build/tools/testflight.js.map +1 -0
- package/build/tools/types.d.ts +7 -0
- package/build/tools/types.js +2 -0
- package/build/tools/types.js.map +1 -0
- package/build/tools/users.d.ts +3 -0
- package/build/tools/users.js +65 -0
- package/build/tools/users.js.map +1 -0
- package/build/tools/versions.d.ts +3 -0
- package/build/tools/versions.js +443 -0
- package/build/tools/versions.js.map +1 -0
- package/build/tools/workflows.d.ts +3 -0
- package/build/tools/workflows.js +633 -0
- package/build/tools/workflows.js.map +1 -0
- package/examples/localize.workflow.json +30 -0
- package/examples/release.workflow.json +49 -0
- package/examples/testflight.workflow.json +21 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rsalim0
|
|
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,241 @@
|
|
|
1
|
+
# asc-mcp
|
|
2
|
+
|
|
3
|
+
An MCP server that exposes **191 tools** spanning the entire App Store Connect API: apps, versions, builds, TestFlight, IAP & subscriptions, pricing, screenshots, certificates, reports — plus high-level **workflow macros** (`release_next_version`, `submission_health`, `aso_audit`, `crash_triage`, etc.) and a **declarative workflow runner** that executes JSON manifests.
|
|
4
|
+
|
|
5
|
+
Built so you can do *"translate my what's-new to all locales and push"* or *"submit the latest build to External Testers"* from a Claude conversation instead of clicking through ASC for 40 minutes.
|
|
6
|
+
|
|
7
|
+
[](https://cursor.com/en/install-mcp?name=asc-mcp-pro&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImFzYy1tY3AtcHJvIl0sImVudiI6eyJBUFBfU1RPUkVfQ09OTkVDVF9LRVlfSUQiOiJZT1VSX0tFWV9JRCIsIkFQUF9TVE9SRV9DT05ORUNUX0lTU1VFUl9JRCI6IllPVVJfSVNTVUVSX0lEIiwiQVBQX1NUT1JFX0NPTk5FQ1RfUDhfUEFUSCI6Ii9hYnNvbHV0ZS9wYXRoL3RvL0F1dGhLZXkucDgifX0%3D)
|
|
8
|
+
|
|
9
|
+
> The button opens Cursor with a pre-filled install dialog. After install, edit `~/.cursor/mcp.json` and replace `YOUR_KEY_ID`, `YOUR_ISSUER_ID`, and the `.p8` path with your real values — see [Getting an App Store Connect API key](#getting-an-app-store-connect-api-key) below.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
### Option A — npm (recommended for end users)
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
claude mcp add appstore-connect \
|
|
19
|
+
-e APP_STORE_CONNECT_KEY_ID=YOUR_KEY_ID \
|
|
20
|
+
-e APP_STORE_CONNECT_ISSUER_ID=YOUR_ISSUER_ID \
|
|
21
|
+
-e APP_STORE_CONNECT_P8_PATH=/absolute/path/to/AuthKey_XXXXXX.p8 \
|
|
22
|
+
-- npx -y asc-mcp-pro
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Option B — install directly from GitHub
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
claude mcp add appstore-connect \
|
|
29
|
+
-e APP_STORE_CONNECT_KEY_ID=YOUR_KEY_ID \
|
|
30
|
+
-e APP_STORE_CONNECT_ISSUER_ID=YOUR_ISSUER_ID \
|
|
31
|
+
-e APP_STORE_CONNECT_P8_PATH=/absolute/path/to/AuthKey_XXXXXX.p8 \
|
|
32
|
+
-- npx -y github:rsalim0/asc-mcp
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Option C — local clone (for hacking on the code)
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
git clone https://github.com/rsalim0/asc-mcp.git
|
|
39
|
+
cd asc-mcp
|
|
40
|
+
npm install
|
|
41
|
+
npm run build
|
|
42
|
+
|
|
43
|
+
claude mcp add appstore-connect \
|
|
44
|
+
-e APP_STORE_CONNECT_KEY_ID=YOUR_KEY_ID \
|
|
45
|
+
-e APP_STORE_CONNECT_ISSUER_ID=YOUR_ISSUER_ID \
|
|
46
|
+
-e APP_STORE_CONNECT_P8_PATH=/absolute/path/to/AuthKey_XXXXXX.p8 \
|
|
47
|
+
-- node $(pwd)/build/index.js
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Add `APP_STORE_CONNECT_VENDOR_NUMBER=XXXXXXXXXX` to enable sales / finance report tools (find your vendor number under *Payments and Financial Reports* in App Store Connect).
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Other MCP clients
|
|
55
|
+
|
|
56
|
+
MCP is a standard protocol — this server works with **any** MCP client, not just Claude Code. The config snippet is the same shape everywhere:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"command": "npx",
|
|
61
|
+
"args": ["-y", "asc-mcp-pro"],
|
|
62
|
+
"env": {
|
|
63
|
+
"APP_STORE_CONNECT_KEY_ID": "YOUR_KEY_ID",
|
|
64
|
+
"APP_STORE_CONNECT_ISSUER_ID": "YOUR_ISSUER_ID",
|
|
65
|
+
"APP_STORE_CONNECT_P8_PATH": "/absolute/path/to/AuthKey.p8"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Drop it into your client's MCP config file:
|
|
71
|
+
|
|
72
|
+
| Client | Config file | How to slot it in |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)<br>`%APPDATA%\Claude\claude_desktop_config.json` (Windows) | Under `"mcpServers": { "asc-mcp-pro": { …snippet… } }` |
|
|
75
|
+
| **Cursor** | `~/.cursor/mcp.json` | Under `"mcpServers": { "asc-mcp-pro": { …snippet… } }` (or use the button above) |
|
|
76
|
+
| **Windsurf** | `~/.codeium/windsurf/mcp_config.json` | Under `"mcpServers": { "asc-mcp-pro": { …snippet… } }` |
|
|
77
|
+
| **Zed** | Zed settings (`~/.config/zed/settings.json`) | Under `"context_servers": { "asc-mcp-pro": { …snippet… } }` |
|
|
78
|
+
| **Continue** (VS Code / JetBrains) | `.continue/config.json` | See [Continue MCP docs](https://docs.continue.dev/customization/mcp-tools) — same `command/args/env` shape |
|
|
79
|
+
| **OpenAI Codex CLI** | `~/.codex/config.toml` | TOML form (see below) |
|
|
80
|
+
|
|
81
|
+
### Codex CLI (TOML form)
|
|
82
|
+
|
|
83
|
+
Codex uses TOML, not JSON. Add this to `~/.codex/config.toml`:
|
|
84
|
+
|
|
85
|
+
```toml
|
|
86
|
+
[mcp_servers.asc_mcp_pro]
|
|
87
|
+
command = "npx"
|
|
88
|
+
args = ["-y", "asc-mcp-pro"]
|
|
89
|
+
env = { APP_STORE_CONNECT_KEY_ID = "YOUR_KEY_ID", APP_STORE_CONNECT_ISSUER_ID = "YOUR_ISSUER_ID", APP_STORE_CONNECT_P8_PATH = "/absolute/path/to/AuthKey.p8" }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
(Note: Codex config keys must be valid TOML identifiers, so the table name uses `asc_mcp_pro` with underscores rather than hyphens.)
|
|
93
|
+
|
|
94
|
+
After editing the config file, restart your client.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Getting an App Store Connect API key
|
|
99
|
+
|
|
100
|
+
1. Go to https://appstoreconnect.apple.com/access/integrations/api
|
|
101
|
+
2. Click **+** to generate a new key. Role: **App Manager** (or **Admin** if you need full provisioning access).
|
|
102
|
+
3. Note the **Key ID** (10 chars, e.g. `7Y3QAAD5HX`) and **Issuer ID** (UUID).
|
|
103
|
+
4. **Download API Key** — saves `AuthKey_<KEYID>.p8`. ⚠️ One-time download — keep it safe.
|
|
104
|
+
5. Pass all three to the `claude mcp add` command above.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Quick wins
|
|
109
|
+
|
|
110
|
+
Once installed, try in Claude Code:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
list my apps
|
|
114
|
+
audit the latest version of <app name> for submission readiness
|
|
115
|
+
draft what's new from git log v1.2.0..HEAD in ~/code/myapp
|
|
116
|
+
find the newest valid build for <app name> and add it to my "Beta" group
|
|
117
|
+
list reviews I haven't responded to yet for <app name>
|
|
118
|
+
show me the keyword overlap and gaps for <app name>'s latest version
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Tool catalog (191 tools)
|
|
124
|
+
|
|
125
|
+
| Group | Tools | Notable |
|
|
126
|
+
|---|---:|---|
|
|
127
|
+
| `asc_ping` | 1 | Smoke test |
|
|
128
|
+
| `asc_apps_*` | 11 | List/get/update apps + app info localizations |
|
|
129
|
+
| `asc_versions_*` | 28 | Version CRUD, localizations, submission, phased release, review details, age rating |
|
|
130
|
+
| `asc_review_submissions_*` | 4 | v2 review submission API (bundles version + IAPs) |
|
|
131
|
+
| `asc_builds_*` | 12 | Builds + beta build localizations + export compliance |
|
|
132
|
+
| `asc_testflight_*` | 25 | Groups, testers (single + bulk), public links, beta review, feedback |
|
|
133
|
+
| `asc_reviews_*` | 3 | Customer reviews + developer responses |
|
|
134
|
+
| `asc_iap_*` | 8 | IAP CRUD + localizations + review submission |
|
|
135
|
+
| `asc_subscription*` / `asc_subscriptions_*` | 17 | Groups, subscriptions, prices, intro + promotional offers, localizations |
|
|
136
|
+
| `asc_pricing_*` | 6 | Price schedules, territories, availability, pre-orders |
|
|
137
|
+
| `asc_reports_*` / `asc_analytics_*` | 6 | Sales/finance (gzip TSV parsed), async analytics flow |
|
|
138
|
+
| `asc_users_*` / `asc_user_invitations_*` | 4 | Team management |
|
|
139
|
+
| `asc_bundle_*` / `asc_certificates_*` / `asc_profiles_*` / `asc_devices_*` | 14 | Signing assets |
|
|
140
|
+
| `asc_screenshots_*` / `asc_previews_*` | 17 | Sets, single upload, **folder matrix upload**, reorder, delete |
|
|
141
|
+
| `asc_workflow_*` | 14 | Macros (see below) + declarative runner |
|
|
142
|
+
|
|
143
|
+
### Workflow macros (the payoff)
|
|
144
|
+
|
|
145
|
+
- **`asc_workflow_release_next_version`** — copy metadata from prior version, attach latest build, set release type. Caller pushes "what's new" separately.
|
|
146
|
+
- **`asc_workflow_whats_new_from_git`** — `git log <since>..HEAD` → clean release-note bullets.
|
|
147
|
+
- **`asc_workflow_localize_metadata`** — push a translations map across locales (calling model does the translation inline).
|
|
148
|
+
- **`asc_workflow_testflight_quick_ship`** — newest valid build → groups → "what to test" → optional beta submit.
|
|
149
|
+
- **`asc_workflow_submission_health`** — pre-submit audit: locales complete, screenshots present, export compliance, review details, build attached, age rating.
|
|
150
|
+
- **`asc_workflow_aso_audit`** — keyword duplicates, unused chars, words wasted in description, optional competitor diff.
|
|
151
|
+
- **`asc_workflow_build_lifecycle`** — all builds w/ days-to-expiry, flag <7 days.
|
|
152
|
+
- **`asc_workflow_crash_triage`** — top diagnostic signatures grouped across recent builds.
|
|
153
|
+
- **`asc_workflow_id_resolver`** — bundle id / name / sku → canonical appId + latest version/build.
|
|
154
|
+
- **`asc_workflow_review_responses`** — list unanswered reviews.
|
|
155
|
+
- **`asc_workflow_metadata_sync`** — pull metadata to a JSON object or push from one.
|
|
156
|
+
- **`asc_workflow_analytics_oneshot`** — create analytics request + poll until ready.
|
|
157
|
+
|
|
158
|
+
### Declarative workflow runner
|
|
159
|
+
|
|
160
|
+
`asc_workflow_run` executes JSON manifests with templated values:
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"name": "Cut next App Store version",
|
|
165
|
+
"vars": { "appId": "1234", "newVersion": "1.2.3" },
|
|
166
|
+
"steps": [
|
|
167
|
+
{
|
|
168
|
+
"name": "create",
|
|
169
|
+
"tool": "asc_workflow_release_next_version",
|
|
170
|
+
"input": { "appId": "${vars.appId}", "platform": "IOS", "newVersionString": "${vars.newVersion}" }
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"name": "audit",
|
|
174
|
+
"tool": "asc_workflow_submission_health",
|
|
175
|
+
"input": { "versionId": "${steps.create.newVersionId}" }
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Run it via `asc_workflow_run({ workflowPath: "/abs/path/to/release.workflow.json" })`. Use `dryRun: true` to validate + propagate dry-run to every step. Examples in `examples/`.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Safety
|
|
186
|
+
|
|
187
|
+
- Every write tool supports `dryRun: true` → returns `{ method, path, body }` without sending.
|
|
188
|
+
- App Store char limits enforced before request (name 30, subtitle 30, keywords 100, promo 170, description 4000, what's-new 4000).
|
|
189
|
+
- The runner propagates `dryRun` to every step automatically when set.
|
|
190
|
+
- Stdio server logs only to stderr — never stdout (preserves JSON-RPC framing).
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Development
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
npm install
|
|
198
|
+
npm run typecheck # tsc --noEmit
|
|
199
|
+
npm run build # compiles to ./build/
|
|
200
|
+
npm run dev # tsx src/index.ts (for iterating)
|
|
201
|
+
npm test # runs scripts/test-all.ts against real ASC — requires .env
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The test runner exercises every registered tool (reads as-is, writes with `dryRun: true`) and reports a pass/fail/skip matrix.
|
|
205
|
+
|
|
206
|
+
### Project layout
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
src/
|
|
210
|
+
├── index.ts # stdio bootstrap
|
|
211
|
+
├── auth.ts # ES256 JWT generation + caching
|
|
212
|
+
├── client.ts # HTTP client, JSON:API error normalization
|
|
213
|
+
├── lib/
|
|
214
|
+
│ ├── jsonapi.ts # JSON:API flatten helpers
|
|
215
|
+
│ ├── jsonapi-write.ts # PATCH/POST body builders + dryRun gate
|
|
216
|
+
│ ├── pagination.ts # cursor walker
|
|
217
|
+
│ ├── projections.ts # default sparse fields per resource
|
|
218
|
+
│ ├── locales.ts # locale codes + char limits
|
|
219
|
+
│ └── upload.ts # 3-step asset reservation flow
|
|
220
|
+
└── tools/
|
|
221
|
+
├── registry.ts # aggregates all tools
|
|
222
|
+
├── ping.ts apps.ts versions.ts builds.ts testflight.ts reviews.ts
|
|
223
|
+
├── iap.ts pricing.ts reports.ts users.ts provisioning.ts privacy.ts
|
|
224
|
+
├── screenshots.ts workflows.ts runner.ts
|
|
225
|
+
└── types.ts
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Known limitations
|
|
231
|
+
|
|
232
|
+
- **App Privacy nutrition labels** are not exposed by Apple's public API. Edit them in the ASC web UI.
|
|
233
|
+
- **Power & Performance metrics** endpoint was removed from the public API.
|
|
234
|
+
- **Feedback submissions** (screenshots/crashes) can only be accessed by id (no listing endpoint).
|
|
235
|
+
- **Analytics reports** are async — `analytics_create_request` returns immediately; the reports are generated over the following minutes/hours. Use `asc_workflow_analytics_oneshot` to poll.
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## License
|
|
240
|
+
|
|
241
|
+
MIT — see [LICENSE](./LICENSE).
|
package/build/auth.d.ts
ADDED
package/build/auth.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import jwt from "jsonwebtoken";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
export function loadCredentialsFromEnv() {
|
|
4
|
+
const keyId = process.env.APP_STORE_CONNECT_KEY_ID;
|
|
5
|
+
const issuerId = process.env.APP_STORE_CONNECT_ISSUER_ID;
|
|
6
|
+
const keyPath = process.env.APP_STORE_CONNECT_P8_PATH;
|
|
7
|
+
const inlineKey = process.env.APP_STORE_CONNECT_P8;
|
|
8
|
+
const vendorNumber = process.env.APP_STORE_CONNECT_VENDOR_NUMBER;
|
|
9
|
+
if (!keyId)
|
|
10
|
+
throw new Error("APP_STORE_CONNECT_KEY_ID is required");
|
|
11
|
+
if (!issuerId)
|
|
12
|
+
throw new Error("APP_STORE_CONNECT_ISSUER_ID is required");
|
|
13
|
+
let privateKey;
|
|
14
|
+
if (inlineKey) {
|
|
15
|
+
privateKey = inlineKey.replace(/\\n/g, "\n");
|
|
16
|
+
}
|
|
17
|
+
else if (keyPath) {
|
|
18
|
+
privateKey = readFileSync(keyPath, "utf8");
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
throw new Error("APP_STORE_CONNECT_P8_PATH or APP_STORE_CONNECT_P8 is required");
|
|
22
|
+
}
|
|
23
|
+
return { keyId, issuerId, privateKey, vendorNumber };
|
|
24
|
+
}
|
|
25
|
+
let cachedToken = null;
|
|
26
|
+
export function generateToken(creds) {
|
|
27
|
+
const now = Math.floor(Date.now() / 1000);
|
|
28
|
+
if (cachedToken && cachedToken.expiresAt > now + 60) {
|
|
29
|
+
return cachedToken.token;
|
|
30
|
+
}
|
|
31
|
+
const expiresIn = 20 * 60;
|
|
32
|
+
const token = jwt.sign({
|
|
33
|
+
iss: creds.issuerId,
|
|
34
|
+
iat: now,
|
|
35
|
+
exp: now + expiresIn,
|
|
36
|
+
aud: "appstoreconnect-v1",
|
|
37
|
+
}, creds.privateKey, {
|
|
38
|
+
algorithm: "ES256",
|
|
39
|
+
header: { alg: "ES256", kid: creds.keyId, typ: "JWT" },
|
|
40
|
+
});
|
|
41
|
+
cachedToken = { token, expiresAt: now + expiresIn };
|
|
42
|
+
return token;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,cAAc,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AASvC,MAAM,UAAU,sBAAsB;IACpC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;IACnD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC;IACzD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC;IACtD,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;IACnD,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAC;IAEjE,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IACpE,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAE1E,IAAI,UAAkB,CAAC;IACvB,IAAI,SAAS,EAAE,CAAC;QACd,UAAU,GAAG,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC/C,CAAC;SAAM,IAAI,OAAO,EAAE,CAAC;QACnB,UAAU,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC7C,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,KAAK,CACb,+DAA+D,CAChE,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC;AACvD,CAAC;AAED,IAAI,WAAW,GAAgD,IAAI,CAAC;AAEpE,MAAM,UAAU,aAAa,CAAC,KAAqB;IACjD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC1C,IAAI,WAAW,IAAI,WAAW,CAAC,SAAS,GAAG,GAAG,GAAG,EAAE,EAAE,CAAC;QACpD,OAAO,WAAW,CAAC,KAAK,CAAC;IAC3B,CAAC;IAED,MAAM,SAAS,GAAG,EAAE,GAAG,EAAE,CAAC;IAC1B,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CACpB;QACE,GAAG,EAAE,KAAK,CAAC,QAAQ;QACnB,GAAG,EAAE,GAAG;QACR,GAAG,EAAE,GAAG,GAAG,SAAS;QACpB,GAAG,EAAE,oBAAoB;KAC1B,EACD,KAAK,CAAC,UAAU,EAChB;QACE,SAAS,EAAE,OAAO;QAClB,MAAM,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE;KACvD,CACF,CAAC;IAEF,WAAW,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,GAAG,SAAS,EAAE,CAAC;IACpD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type AscCredentials } from "./auth.js";
|
|
2
|
+
export type QueryValue = string | number | boolean | string[] | undefined;
|
|
3
|
+
export interface RequestOptions {
|
|
4
|
+
method?: string;
|
|
5
|
+
query?: Record<string, QueryValue>;
|
|
6
|
+
body?: unknown;
|
|
7
|
+
rawBody?: BodyInit;
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
expectBinary?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface AscError extends Error {
|
|
12
|
+
status?: number;
|
|
13
|
+
details?: unknown;
|
|
14
|
+
}
|
|
15
|
+
export declare class AscClient {
|
|
16
|
+
private creds;
|
|
17
|
+
constructor(creds: AscCredentials);
|
|
18
|
+
get vendorNumber(): string | undefined;
|
|
19
|
+
buildUrl(path: string, query?: Record<string, QueryValue>): URL;
|
|
20
|
+
request<T = unknown>(path: string, options?: RequestOptions): Promise<T>;
|
|
21
|
+
}
|
package/build/client.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { generateToken } from "./auth.js";
|
|
2
|
+
const BASE_URL = "https://api.appstoreconnect.apple.com/v1";
|
|
3
|
+
export class AscClient {
|
|
4
|
+
creds;
|
|
5
|
+
constructor(creds) {
|
|
6
|
+
this.creds = creds;
|
|
7
|
+
}
|
|
8
|
+
get vendorNumber() {
|
|
9
|
+
return this.creds.vendorNumber;
|
|
10
|
+
}
|
|
11
|
+
buildUrl(path, query) {
|
|
12
|
+
const url = new URL(path.startsWith("http") ? path : `${BASE_URL}${path}`);
|
|
13
|
+
if (!query)
|
|
14
|
+
return url;
|
|
15
|
+
for (const [k, v] of Object.entries(query)) {
|
|
16
|
+
if (v === undefined || v === null)
|
|
17
|
+
continue;
|
|
18
|
+
if (Array.isArray(v)) {
|
|
19
|
+
if (v.length === 0)
|
|
20
|
+
continue;
|
|
21
|
+
url.searchParams.set(k, v.join(","));
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
url.searchParams.set(k, String(v));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return url;
|
|
28
|
+
}
|
|
29
|
+
async request(path, options = {}) {
|
|
30
|
+
const url = this.buildUrl(path, options.query);
|
|
31
|
+
const token = generateToken(this.creds);
|
|
32
|
+
const headers = {
|
|
33
|
+
Authorization: `Bearer ${token}`,
|
|
34
|
+
...(options.headers ?? {}),
|
|
35
|
+
};
|
|
36
|
+
if (options.body !== undefined && !options.rawBody) {
|
|
37
|
+
headers["Content-Type"] = headers["Content-Type"] ?? "application/json";
|
|
38
|
+
}
|
|
39
|
+
const res = await fetch(url, {
|
|
40
|
+
method: options.method ?? "GET",
|
|
41
|
+
headers,
|
|
42
|
+
body: options.rawBody ?? (options.body ? JSON.stringify(options.body) : undefined),
|
|
43
|
+
});
|
|
44
|
+
if (options.expectBinary) {
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
const text = await res.text().catch(() => "");
|
|
47
|
+
throw makeError(res.status, res.statusText, text);
|
|
48
|
+
}
|
|
49
|
+
return (await res.arrayBuffer());
|
|
50
|
+
}
|
|
51
|
+
const text = await res.text();
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
throw makeError(res.status, res.statusText, text);
|
|
54
|
+
}
|
|
55
|
+
if (!text)
|
|
56
|
+
return undefined;
|
|
57
|
+
try {
|
|
58
|
+
return JSON.parse(text);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return text;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function makeError(status, statusText, body) {
|
|
66
|
+
let detail = body;
|
|
67
|
+
let parsed;
|
|
68
|
+
try {
|
|
69
|
+
parsed = JSON.parse(body);
|
|
70
|
+
if (typeof parsed === "object" &&
|
|
71
|
+
parsed !== null &&
|
|
72
|
+
"errors" in parsed &&
|
|
73
|
+
Array.isArray(parsed.errors)) {
|
|
74
|
+
const errs = parsed.errors;
|
|
75
|
+
detail = errs
|
|
76
|
+
.map((e) => {
|
|
77
|
+
const where = e.source?.pointer ?? e.source?.parameter ?? "";
|
|
78
|
+
return [e.code, e.title, e.detail, where].filter(Boolean).join(" — ");
|
|
79
|
+
})
|
|
80
|
+
.join("; ");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// not JSON, keep raw
|
|
85
|
+
}
|
|
86
|
+
const err = new Error(`App Store Connect API ${status} ${statusText}: ${detail}`);
|
|
87
|
+
err.status = status;
|
|
88
|
+
err.details = parsed ?? body;
|
|
89
|
+
return err;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/build/index.d.ts
ADDED
package/build/index.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// IMPORTANT: stdio MCP servers must never write to stdout. console.log corrupts
|
|
3
|
+
// the JSON-RPC frame. Always use console.error (which writes to stderr).
|
|
4
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
8
|
+
import { AscClient } from "./client.js";
|
|
9
|
+
import { loadCredentialsFromEnv } from "./auth.js";
|
|
10
|
+
import { allTools } from "./tools/registry.js";
|
|
11
|
+
async function main() {
|
|
12
|
+
const creds = loadCredentialsFromEnv();
|
|
13
|
+
const client = new AscClient(creds);
|
|
14
|
+
const tools = allTools(client);
|
|
15
|
+
const toolMap = new Map(tools.map((t) => [t.name, t]));
|
|
16
|
+
const server = new Server({ name: "appstore-connect-mcp", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
17
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
18
|
+
tools: tools.map((t) => ({
|
|
19
|
+
name: t.name,
|
|
20
|
+
description: t.description,
|
|
21
|
+
inputSchema: zodToJsonSchema(t.inputSchema, { target: "openApi3" }),
|
|
22
|
+
})),
|
|
23
|
+
}));
|
|
24
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
25
|
+
const tool = toolMap.get(req.params.name);
|
|
26
|
+
if (!tool)
|
|
27
|
+
throw new Error(`Unknown tool: ${req.params.name}`);
|
|
28
|
+
const parsed = tool.inputSchema.parse(req.params.arguments ?? {});
|
|
29
|
+
const result = await tool.handler(parsed);
|
|
30
|
+
return {
|
|
31
|
+
content: [
|
|
32
|
+
{
|
|
33
|
+
type: "text",
|
|
34
|
+
text: typeof result === "string" ? result : JSON.stringify(result, null, 2),
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
const transport = new StdioServerTransport();
|
|
40
|
+
await server.connect(transport);
|
|
41
|
+
console.error(`appstore-connect-mcp ready (${tools.length} tools)`);
|
|
42
|
+
}
|
|
43
|
+
main().catch((err) => {
|
|
44
|
+
console.error("Fatal error:", err);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
});
|
|
47
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { AscClient } from "../client.js";
|
|
2
|
+
export interface JsonApiBody {
|
|
3
|
+
data: {
|
|
4
|
+
type: string;
|
|
5
|
+
id?: string;
|
|
6
|
+
attributes?: Record<string, unknown>;
|
|
7
|
+
relationships?: Record<string, {
|
|
8
|
+
data: {
|
|
9
|
+
type: string;
|
|
10
|
+
id: string;
|
|
11
|
+
} | Array<{
|
|
12
|
+
type: string;
|
|
13
|
+
id: string;
|
|
14
|
+
}> | null;
|
|
15
|
+
}>;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export declare function patchBody(type: string, id: string, attributes?: Record<string, unknown>, relationships?: JsonApiBody["data"]["relationships"]): JsonApiBody;
|
|
19
|
+
export declare function postBody(type: string, attributes?: Record<string, unknown>, relationships?: JsonApiBody["data"]["relationships"]): JsonApiBody;
|
|
20
|
+
export declare function singleRel(type: string, id: string): {
|
|
21
|
+
readonly data: {
|
|
22
|
+
readonly type: string;
|
|
23
|
+
readonly id: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
export interface DryRunResult {
|
|
27
|
+
dryRun: true;
|
|
28
|
+
method: string;
|
|
29
|
+
path: string;
|
|
30
|
+
body?: unknown;
|
|
31
|
+
}
|
|
32
|
+
export declare function maybeSend<T>(client: AscClient, dryRun: boolean | undefined, method: "POST" | "PATCH" | "DELETE", path: string, body?: unknown): Promise<T | DryRunResult>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function patchBody(type, id, attributes, relationships) {
|
|
2
|
+
return { data: { type, id, attributes, relationships } };
|
|
3
|
+
}
|
|
4
|
+
export function postBody(type, attributes, relationships) {
|
|
5
|
+
return { data: { type, attributes, relationships } };
|
|
6
|
+
}
|
|
7
|
+
export function singleRel(type, id) {
|
|
8
|
+
return { data: { type, id } };
|
|
9
|
+
}
|
|
10
|
+
export async function maybeSend(client, dryRun, method, path, body) {
|
|
11
|
+
if (dryRun)
|
|
12
|
+
return { dryRun: true, method, path, body };
|
|
13
|
+
return client.request(path, {
|
|
14
|
+
method,
|
|
15
|
+
body,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=jsonapi-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonapi-write.js","sourceRoot":"","sources":["../../src/lib/jsonapi-write.ts"],"names":[],"mappings":"AAcA,MAAM,UAAU,SAAS,CACvB,IAAY,EACZ,EAAU,EACV,UAAoC,EACpC,aAAoD;IAEpD,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,UAAU,EAAE,aAAa,EAAE,EAAE,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,QAAQ,CACtB,IAAY,EACZ,UAAoC,EACpC,aAAoD;IAEpD,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,aAAa,EAAE,EAAE,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,EAAU;IAChD,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,EAAW,CAAC;AACzC,CAAC;AASD,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,MAAiB,EACjB,MAA2B,EAC3B,MAAmC,EACnC,IAAY,EACZ,IAAc;IAEd,IAAI,MAAM;QAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACxD,OAAO,MAAM,CAAC,OAAO,CAAI,IAAI,EAAE;QAC7B,MAAM;QACN,IAAI;KACL,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface JsonApiResource {
|
|
2
|
+
type: string;
|
|
3
|
+
id: string;
|
|
4
|
+
attributes?: Record<string, unknown>;
|
|
5
|
+
relationships?: Record<string, {
|
|
6
|
+
data?: {
|
|
7
|
+
type: string;
|
|
8
|
+
id: string;
|
|
9
|
+
} | Array<{
|
|
10
|
+
type: string;
|
|
11
|
+
id: string;
|
|
12
|
+
}>;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
export interface JsonApiResponse<T = JsonApiResource> {
|
|
16
|
+
data: T | T[];
|
|
17
|
+
included?: JsonApiResource[];
|
|
18
|
+
links?: {
|
|
19
|
+
next?: string;
|
|
20
|
+
self?: string;
|
|
21
|
+
};
|
|
22
|
+
meta?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
/** Flatten a single JSON:API resource into `{ id, type, ...attributes, _rel: {...} }`. */
|
|
25
|
+
export declare function flatten(resource: JsonApiResource): Record<string, unknown>;
|
|
26
|
+
/** Flatten a list response. */
|
|
27
|
+
export declare function flattenList(resp: JsonApiResponse<JsonApiResource>): Record<string, unknown>[];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Flatten a single JSON:API resource into `{ id, type, ...attributes, _rel: {...} }`. */
|
|
2
|
+
export function flatten(resource) {
|
|
3
|
+
const out = {
|
|
4
|
+
id: resource.id,
|
|
5
|
+
type: resource.type,
|
|
6
|
+
...(resource.attributes ?? {}),
|
|
7
|
+
};
|
|
8
|
+
if (resource.relationships) {
|
|
9
|
+
const rels = {};
|
|
10
|
+
for (const [name, rel] of Object.entries(resource.relationships)) {
|
|
11
|
+
if (!rel.data)
|
|
12
|
+
continue;
|
|
13
|
+
rels[name] = Array.isArray(rel.data)
|
|
14
|
+
? rel.data.map((d) => d.id)
|
|
15
|
+
: rel.data.id;
|
|
16
|
+
}
|
|
17
|
+
if (Object.keys(rels).length > 0)
|
|
18
|
+
out._rel = rels;
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
/** Flatten a list response. */
|
|
23
|
+
export function flattenList(resp) {
|
|
24
|
+
const data = Array.isArray(resp.data) ? resp.data : [resp.data];
|
|
25
|
+
return data.map(flatten);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=jsonapi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonapi.js","sourceRoot":"","sources":["../../src/lib/jsonapi.ts"],"names":[],"mappings":"AAiBA,0FAA0F;AAC1F,MAAM,UAAU,OAAO,CAAC,QAAyB;IAC/C,MAAM,GAAG,GAA4B;QACnC,EAAE,EAAE,QAAQ,CAAC,EAAE;QACf,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,GAAG,CAAC,QAAQ,CAAC,UAAU,IAAI,EAAE,CAAC;KAC/B,CAAC;IACF,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC;QAC3B,MAAM,IAAI,GAA4B,EAAE,CAAC;QACzC,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;YACjE,IAAI,CAAC,GAAG,CAAC,IAAI;gBAAE,SAAS;YACxB,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;gBAClC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3B,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAClB,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;YAAE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;IACpD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,+BAA+B;AAC/B,MAAM,UAAU,WAAW,CACzB,IAAsC;IAEtC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChE,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Store locale codes. Apple uses a fixed list — this is the canonical set
|
|
3
|
+
* the API accepts for App Store Version Localizations as of 2026.
|
|
4
|
+
* Source: https://developer.apple.com/documentation/appstoreconnectapi/applocalecodes
|
|
5
|
+
*/
|
|
6
|
+
export declare const APP_STORE_LOCALES: readonly ["ar-SA", "ca", "cs", "da", "de-DE", "el", "en-AU", "en-CA", "en-GB", "en-US", "es-ES", "es-MX", "fi", "fr-CA", "fr-FR", "he", "hi", "hr", "hu", "id", "it", "ja", "ko", "ms", "nl-NL", "no", "pl", "pt-BR", "pt-PT", "ro", "ru", "sk", "sv", "th", "tr", "uk", "vi", "zh-Hans", "zh-Hant"];
|
|
7
|
+
export type AppStoreLocale = (typeof APP_STORE_LOCALES)[number];
|
|
8
|
+
/**
|
|
9
|
+
* Character limits enforced by App Store Connect on metadata fields,
|
|
10
|
+
* keyed by the JSON:API attribute name.
|
|
11
|
+
*/
|
|
12
|
+
export declare const METADATA_CHAR_LIMITS: {
|
|
13
|
+
readonly name: 30;
|
|
14
|
+
readonly subtitle: 30;
|
|
15
|
+
readonly keywords: 100;
|
|
16
|
+
readonly promotionalText: 170;
|
|
17
|
+
readonly description: 4000;
|
|
18
|
+
readonly whatsNew: 4000;
|
|
19
|
+
readonly privacyPolicyText: 4000;
|
|
20
|
+
readonly marketingUrl: 255;
|
|
21
|
+
readonly supportUrl: 255;
|
|
22
|
+
readonly privacyPolicyUrl: 255;
|
|
23
|
+
};
|
|
24
|
+
export declare function isValidLocale(code: string): code is AppStoreLocale;
|