shipstores 0.1.0__tar.gz

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.
Files changed (33) hide show
  1. shipstores-0.1.0/.github/ISSUE_TEMPLATE/bug.yml +12 -0
  2. shipstores-0.1.0/.github/ISSUE_TEMPLATE/console-flow-broke.yml +19 -0
  3. shipstores-0.1.0/.github/ISSUE_TEMPLATE/new-tool.yml +11 -0
  4. shipstores-0.1.0/.github/pull_request_template.md +10 -0
  5. shipstores-0.1.0/.github/workflows/ci.yml +12 -0
  6. shipstores-0.1.0/.github/workflows/publish.yml +15 -0
  7. shipstores-0.1.0/.gitignore +17 -0
  8. shipstores-0.1.0/.python-version +1 -0
  9. shipstores-0.1.0/CHANGELOG.md +10 -0
  10. shipstores-0.1.0/CONTRIBUTING.md +35 -0
  11. shipstores-0.1.0/LICENSE +21 -0
  12. shipstores-0.1.0/PKG-INFO +205 -0
  13. shipstores-0.1.0/README.md +184 -0
  14. shipstores-0.1.0/SECURITY.md +5 -0
  15. shipstores-0.1.0/assets/banner-dark.png +0 -0
  16. shipstores-0.1.0/assets/banner-light.png +0 -0
  17. shipstores-0.1.0/assets/social-preview.png +0 -0
  18. shipstores-0.1.0/config.example.toml +18 -0
  19. shipstores-0.1.0/docs/apple-subscriptions.md +29 -0
  20. shipstores-0.1.0/pyproject.toml +35 -0
  21. shipstores-0.1.0/src/shipstores/__init__.py +3 -0
  22. shipstores-0.1.0/src/shipstores/apple.py +222 -0
  23. shipstores-0.1.0/src/shipstores/apple_console.py +132 -0
  24. shipstores-0.1.0/src/shipstores/apple_review.py +149 -0
  25. shipstores-0.1.0/src/shipstores/browser.py +296 -0
  26. shipstores-0.1.0/src/shipstores/config.py +101 -0
  27. shipstores-0.1.0/src/shipstores/data_safety.py +129 -0
  28. shipstores-0.1.0/src/shipstores/eas.py +107 -0
  29. shipstores-0.1.0/src/shipstores/play.py +276 -0
  30. shipstores-0.1.0/src/shipstores/play_console.py +361 -0
  31. shipstores-0.1.0/src/shipstores/server.py +2090 -0
  32. shipstores-0.1.0/src/shipstores/signing.py +105 -0
  33. shipstores-0.1.0/uv.lock +1587 -0
@@ -0,0 +1,12 @@
1
+ name: Bug
2
+ description: Something is wrong in a tool that uses the public APIs
3
+ labels: [bug]
4
+ body:
5
+ - type: input
6
+ id: tool
7
+ attributes: {label: Tool}
8
+ validations: {required: true}
9
+ - type: textarea
10
+ id: details
11
+ attributes: {label: What happened, description: Input you passed and the error. Redact keys and ids.}
12
+ validations: {required: true}
@@ -0,0 +1,19 @@
1
+ name: Console flow broke
2
+ description: A tool that drives App Store Connect or Play Console stopped working
3
+ labels: [console-breakage]
4
+ body:
5
+ - type: input
6
+ id: tool
7
+ attributes: {label: Tool, placeholder: apple_set_app_privacy}
8
+ validations: {required: true}
9
+ - type: dropdown
10
+ id: store
11
+ attributes: {label: Store, options: [App Store Connect, Google Play Console]}
12
+ validations: {required: true}
13
+ - type: input
14
+ id: lang
15
+ attributes: {label: Console language, placeholder: en-US}
16
+ - type: textarea
17
+ id: error
18
+ attributes: {label: What happened, description: Error output and what you saw in the console. Blur account names.}
19
+ validations: {required: true}
@@ -0,0 +1,11 @@
1
+ name: New tool
2
+ description: A store step you still do by hand
3
+ labels: [new-tool]
4
+ body:
5
+ - type: textarea
6
+ id: step
7
+ attributes: {label: Which step, description: What you click today, in which store.}
8
+ validations: {required: true}
9
+ - type: textarea
10
+ id: api
11
+ attributes: {label: API or endpoint, description: Public API docs link, or the console request you saw in DevTools (redact ids).}
@@ -0,0 +1,10 @@
1
+ ## What
2
+
3
+ ## Tested against
4
+ - [ ] Real store (which one, test app)
5
+ - [ ] Only offline (compile/import)
6
+
7
+ ## Checklist
8
+ - [ ] Publishing tools say "External action" in the docstring
9
+ - [ ] No credentials, real account ids or personal data
10
+ - [ ] README "Hard-won lessons" updated if this fixes a store quirk
@@ -0,0 +1,12 @@
1
+ name: CI
2
+ on: [push, pull_request]
3
+ jobs:
4
+ check:
5
+ runs-on: ubuntu-latest
6
+ steps:
7
+ - uses: actions/checkout@v4
8
+ - uses: astral-sh/setup-uv@v6
9
+ - run: uv sync
10
+ - run: uv run --with ruff ruff check src --select E9,F
11
+ - run: uv run python -W error -c "import glob; [compile(open(f).read(), f, 'exec') for f in glob.glob('src/shipstores/*.py')]"
12
+ - run: uv run python -c "import asyncio; from shipstores.server import mcp; print(len(asyncio.run(mcp.list_tools())), 'tools')"
@@ -0,0 +1,15 @@
1
+ name: Publish to PyPI
2
+ on:
3
+ release:
4
+ types: [published]
5
+ jobs:
6
+ publish:
7
+ runs-on: ubuntu-latest
8
+ environment: pypi
9
+ permissions:
10
+ id-token: write
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: astral-sh/setup-uv@v6
14
+ - run: uv build
15
+ - run: uv publish --trusted-publishing always
@@ -0,0 +1,17 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # Credentials live in ~/.config/shipstores, never in the repo
13
+ .env
14
+ *.p8
15
+ *service-account*.json
16
+ *play-ops*.json
17
+ config.toml
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 — first public release
4
+
5
+ 60 tools. Install with `uvx shipstores`.
6
+
7
+ - App Store Connect: builds, versions, listing, screenshots, subscriptions, age rating, categories, price, availability, privacy label, review submission and cancellation, TestFlight internal invites.
8
+ - App Review: read rejection messages, reply with attachments and resubmit the rejected version.
9
+ - Google Play: bundles, tracks, listing, screenshots, contact details, signing, App content declarations and data safety via console automation.
10
+ - Expo/EAS: start, poll, download and submit builds (iOS submit via altool).
@@ -0,0 +1,35 @@
1
+ # Contributing
2
+
3
+ Thanks for helping! Store consoles change every few months, so real-world fixes are the most valuable contributions.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ uv sync
9
+ uv run python -c "import asyncio; from shipstores.server import mcp; print(len(asyncio.run(mcp.list_tools())), 'tools')"
10
+ ```
11
+
12
+ You don't need store credentials to work on most of the code. To try tools against real stores, see "Credentials" in the README and use a test app.
13
+
14
+ ## What we love to merge
15
+
16
+ - **A quirk you hit in production**: a rejection reason, an undocumented API rule, a console change. Add the fix *and* a line in the README's "Hard-won lessons".
17
+ - **New tools** for steps you still do by hand (open an issue first with the store endpoint or console flow).
18
+ - **Console labels in other languages**: console automation matches UI text literally (`pt-BR` and `en-US` today). Adding your console language is a great first PR.
19
+ - Docs, examples, workflows for other stacks (Flutter, native Xcode/Gradle, Capacitor).
20
+
21
+ ## Guidelines
22
+
23
+ - One tool = one store operation. Tools that publish or submit must say so in the first line of their docstring ("External action").
24
+ - Tool docstrings are what the agent reads: be precise about preconditions, side effects and the next step.
25
+ - Never commit credentials, account ids, bundle ids of real apps or screenshots with personal data.
26
+ - Keep console automation in its own module (`apple_console.py`, `apple_review.py`, `play_console.py`) so breakage stays contained.
27
+ - Run before opening a PR:
28
+ ```bash
29
+ uv run ruff check src
30
+ uv run python -W error -c "import glob; [compile(open(f).read(), f, 'exec') for f in glob.glob('src/shipstores/*.py')]"
31
+ ```
32
+
33
+ ## Reporting a broken console flow
34
+
35
+ Open a "Console flow broke" issue with the tool name, the store, your console language and the error. Screenshots help — blur account names.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matheus Fidelis
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.
@@ -0,0 +1,205 @@
1
+ Metadata-Version: 2.5
2
+ Name: shipstores
3
+ Version: 0.1.0
4
+ Summary: MCP server to ship iOS and Android apps from AI agents: App Store Connect, Google Play Console and Expo EAS
5
+ Project-URL: Homepage, https://github.com/FidelisMM/shipstores
6
+ Project-URL: Issues, https://github.com/FidelisMM/shipstores/issues
7
+ Author: Matheus Fidelis
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: android,app-store-connect,claude,eas,expo,fastlane-alternative,google-play,ios,mcp,model-context-protocol
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Topic :: Software Development :: Build Tools
14
+ Requires-Python: >=3.12
15
+ Requires-Dist: fastmcp>=3.0
16
+ Requires-Dist: google-auth>=2.57.0
17
+ Requires-Dist: httpx>=0.28.1
18
+ Requires-Dist: pyjwt[crypto]>=2.13.0
19
+ Requires-Dist: requests>=2.34.2
20
+ Description-Content-Type: text/markdown
21
+
22
+ <p align="center">
23
+ <picture>
24
+ <source media="(prefers-color-scheme: dark)" srcset="assets/banner-dark.png">
25
+ <img alt="shipstores — ship iOS and Android apps from your AI agent" src="assets/banner-light.png" width="100%">
26
+ </picture>
27
+ </p>
28
+
29
+ <p align="center">
30
+ <a href="LICENSE"><img alt="MIT license" src="https://img.shields.io/badge/license-MIT-7C6CFF"></a>
31
+ <img alt="Python 3.12+" src="https://img.shields.io/badge/python-3.12+-3776AB?logo=python&logoColor=white">
32
+ <img alt="MCP server" src="https://img.shields.io/badge/MCP-server-C04CFD">
33
+ <img alt="Works with Claude Code" src="https://img.shields.io/badge/works%20with-Claude%20Code-D97757">
34
+ <a href="https://github.com/FidelisMM/shipstores/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/FidelisMM/shipstores/actions/workflows/ci.yml/badge.svg"></a>
35
+ <a href="https://github.com/FidelisMM/shipstores/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/FidelisMM/shipstores?style=flat&color=7C6CFF"></a>
36
+ </p>
37
+
38
+ <p align="center">
39
+ <a href="#quick-start">Quick start</a> ·
40
+ <a href="#tools">60 tools</a> ·
41
+ <a href="#workflows">Workflows</a> ·
42
+ <a href="#hard-won-lessons">Hard-won lessons</a> ·
43
+ <a href="CONTRIBUTING.md">Contributing</a>
44
+ </p>
45
+
46
+ ---
47
+
48
+ **shipstores** drives App Store Connect and Google Play Console end to end — builds, store listings, screenshots, privacy labels, review submission and even replying to App Review — so Claude (or any MCP client) can take an app from `eas build` to "Waiting for Review" without you clicking through two consoles.
49
+
50
+ > Built because publishing was always the slowest part of shipping an app. Every quirk documented below cost at least one rejected build to discover.
51
+
52
+ ```mermaid
53
+ flowchart LR
54
+ A["🤖 Claude / any MCP client"] -->|"tools"| M["shipstores"]
55
+ M -->|"public API"| ASC["App Store Connect"]
56
+ M -->|"public API"| GP["Google Play Developer API"]
57
+ M -->|"eas-cli"| EAS["Expo EAS builds"]
58
+ M -.->|"console automation<br/>(dedicated browser profile)"| C["Privacy label · availability<br/>App Review replies · Play App content"]
59
+ ```
60
+
61
+ ## Why
62
+
63
+ Publishing a mobile app is ~40 manual steps across two consoles, half of which have no public API. AI agents can write your app, but they stall at the store. This server closes that gap:
64
+
65
+ | | App Store Connect | Google Play |
66
+ |---|---|---|
67
+ | Upload build, versions, listing, screenshots | ✅ API | ✅ API |
68
+ | Subscriptions / in-app purchases | ✅ API | — |
69
+ | Age rating, categories, price, URLs | ✅ API | ✅ API |
70
+ | **Privacy label & country availability** | ✅ console's internal API | ✅ Data safety via console |
71
+ | **Read the rejection & reply to App Review (with video)** | ✅ console | — |
72
+ | **"App content" declarations** (11 forms, no API) | — | ✅ console automation |
73
+ | Create the app record | 🧭 opens the right form, tells you what to type | 🧭 same |
74
+ | Expo / EAS builds | ✅ start, poll, download, submit | ✅ |
75
+
76
+ Neither store lets anyone create an app record through an API, so `*_create_app_form` opens the right page in a logged-in browser and returns the exact values to fill. Everything else is automated.
77
+
78
+ ## Quick start
79
+
80
+ Requirements: Python 3.12+, [uv](https://docs.astral.sh/uv/), an App Store Connect API key and/or a Google Play service account. Xcode command line tools for iOS uploads (`xcrun altool`).
81
+
82
+ Add it to Claude Code (no clone needed, `uvx` fetches and runs it):
83
+
84
+ ```bash
85
+ claude mcp add shipstores \
86
+ -e ASC_KEY_ID=ABC123XYZ \
87
+ -e ASC_ISSUER_ID=00000000-0000-0000-0000-000000000000 \
88
+ -e ASC_PRIVATE_KEY_PATH=~/.config/shipstores/AuthKey_ABC123XYZ.p8 \
89
+ -e PLAY_SERVICE_ACCOUNT_PATH=~/.config/shipstores/play-service-account.json \
90
+ -- uvx --from git+https://github.com/FidelisMM/shipstores shipstores
91
+ ```
92
+
93
+ Then ask your agent: *"run store_doctor"*. It checks both credentials with real calls and detects your Apple Team ID for you.
94
+
95
+ ### Credentials
96
+
97
+ | Variable | Required | What it is |
98
+ |---|---|---|
99
+ | `ASC_KEY_ID`, `ASC_ISSUER_ID` | iOS | App Store Connect → Users and Access → Integrations → API key (role **Admin** or **App Manager**) |
100
+ | `ASC_PRIVATE_KEY_PATH` | iOS | the `.p8` file (default `~/.config/shipstores/AuthKey_<KEY_ID>.p8`) |
101
+ | `APPLE_TEAM_ID` | no | detected by `store_doctor` from any bundle ID |
102
+ | `PLAY_SERVICE_ACCOUNT_PATH` | Android | service account JSON invited in Play Console with release permissions |
103
+ | `PLAY_DEVELOPER_ID` | console forms | the number after `/developers/` in the Play Console URL |
104
+
105
+ Values can also live in `~/.config/shipstores/config.toml` — see [`config.example.toml`](config.example.toml). Nothing secret is ever written to the repo.
106
+
107
+ ### Console features (optional)
108
+
109
+ Privacy labels, availability, App Review replies and Play "App content" forms run through the store consoles' own web endpoints in a dedicated, logged-in browser profile, driven by [browser-harness](https://github.com/browser-use/browser-harness). Log in once:
110
+
111
+ ```bash
112
+ uvx --from git+https://github.com/FidelisMM/shipstores python -m shipstores.browser login # opens a window; sign in to both consoles (2FA included)
113
+ ```
114
+
115
+ > ⚠️ These features use undocumented console endpoints (the same family fastlane's Spaceship relies on). They work today and are isolated behind small modules, but Apple or Google can change them without notice. PRs that keep them working are very welcome.
116
+
117
+ ## Tools
118
+
119
+ **Diagnostics** — `store_doctor`, `store_browser_session`, `store_audit_identity`
120
+
121
+ **Apple: build & release** — `apple_list_apps`, `apple_register_bundle_id`, `apple_create_app_form`, `apple_upload_build`, `apple_list_builds`, `apple_create_version`, `apple_list_versions`, `apple_set_version_string`, `apple_attach_build`, `apple_submit_for_review`, `apple_resubmit_for_review`, `apple_cancel_submission`, `apple_testflight_invite`
122
+
123
+ **Apple: listing & app setup** — `apple_update_listing`, `apple_upload_screenshots`, `apple_set_app_info` (subtitle, URLs, categories, copyright, content rights), `apple_set_age_rating`, `apple_set_free_price`, `apple_set_availability`, `apple_set_app_privacy`, `apple_review_details`, `apple_set_review_details`
124
+
125
+ **Apple: App Review** — `apple_review_messages` (read the rejection and its guideline), `apple_reply_review` (answer with text + attachments; iPhone HEVC videos are converted to H.264)
126
+
127
+ **Apple: subscriptions** — `apple_create_subscription_group`, `apple_create_subscription`, `apple_list_price_points`, `apple_set_subscription_price`, `apple_set_subscription_availability`, `apple_create_intro_offer`, `apple_list_subscriptions`, `apple_upload_subscription_screenshot`
128
+
129
+ **Google Play** — `play_create_app_form`, `play_track_status`, `play_upload_bundle`, `play_promote_release`, `play_update_listing`, `play_upload_screenshots`, `play_list_screenshots`, `play_contact_details`, `play_set_contact_details`, `play_signing_sha1`, `play_submission_status`, `play_submit_for_review`
130
+
131
+ **Play Console forms** — `play_content_status`, `play_content_open`, `play_content_options`, `play_content_answer`, `play_content_save`, `play_data_safety_fill`, `play_data_safety_export`, `play_data_safety_import`
132
+
133
+ **Expo / EAS** — `eas_build_list`, `eas_build_start`, `eas_build_status`, `eas_build_download`, `eas_submit`
134
+
135
+ Tools that publish or submit (`apple_submit_for_review`, `apple_resubmit_for_review`, `apple_reply_review`, `apple_cancel_submission`, `apple_set_*`, `apple_testflight_invite`, `play_upload_bundle`, `play_promote_release`, `play_upload_screenshots`) say so in their description, so the agent confirms with you first.
136
+
137
+ ## Workflows
138
+
139
+ **New iOS app**
140
+ ```
141
+ apple_register_bundle_id → apple_create_app_form → [fill in the browser]
142
+ → apple_list_apps → eas_build_start / apple_upload_build → apple_list_builds (wait for VALID)
143
+ → apple_attach_build → apple_update_listing → apple_set_app_info → apple_set_age_rating
144
+ → apple_set_free_price → apple_set_availability → apple_set_app_privacy
145
+ → apple_upload_screenshots → apple_set_review_details → apple_submit_for_review
146
+ ```
147
+
148
+ **New Android app**
149
+ ```
150
+ play_create_app_form → [fill in the browser] → play_upload_bundle (track=internal)
151
+ → play_update_listing → play_content_* / play_data_safety_fill → play_promote_release (production)
152
+ ```
153
+
154
+ **Rejected with "Guideline 2.1 – Information Needed"** (standard for new developer accounts)
155
+ ```
156
+ apple_review_messages → record the iPhone walkthrough → apple_set_review_details (answers in Notes)
157
+ → apple_reply_review (answers + video) → apple_resubmit_for_review
158
+ ```
159
+
160
+ ## Hard-won lessons
161
+
162
+ The part people bookmark. Each one cost a rejected build or a lost afternoon.
163
+
164
+ **App Store Connect**
165
+ - **New developer accounts get "2.1 Information Needed" on the first submission**, regardless of app quality. Apple wants a screen recording from a *physical* device that starts at app launch from the Home Screen and shows login, the main flow and account deletion, plus purpose, access instructions, external services, regional differences and regulated-industry info — in the reply *and* in the review Notes. Replying is not enough: the version stays "Rejected" until you resubmit it (`apple_resubmit_for_review`, or "Update Review" on the version page).
166
+ - **Health apps must ship from an organization account** (Guideline 5.1.1(ix)). An app rejected on an individual account can't be transferred (transfers need a released version): it becomes a new app with a new bundle ID.
167
+ - **You can't learn your Team ID from the API until a bundle ID exists**; then it's the bundle's `seedId`.
168
+ - **Privacy label records need category + purpose + protection in the same record.** Separate records are accepted one by one, then publishing fails with *"An app data usage is missing a category/purpose or data protection type"*.
169
+ - **Availability needs every territory in the payload**, each flagged available or not — and the public API returns 409 anyway; the console endpoint works.
170
+ - **Cancelling a submission also cancels its subscriptions, and that can't be undone by API.** Re-adding them requires the console (details in [`docs/apple-subscriptions.md`](docs/apple-subscriptions.md)).
171
+ - **An app with an auto-renewable subscription is auto-rejected (3.1.2) without a working Terms of Use link** in the listing. The standard Apple EULA link fixes it without a new build.
172
+ - **`xcrun altool` won't take a key path argument**, but it honors `API_PRIVATE_KEYS_DIR`.
173
+ - **Screenshots belong to a version on iOS** (a released version won't accept new ones — create the next version) but to the listing on Play (live immediately).
174
+
175
+ **Expo / EAS**
176
+ - `eas submit --non-interactive` refuses App Store Connect API keys; this server downloads the `.ipa` and uploads with `altool` instead.
177
+ - `eas build:view` rejects `--non-interactive` ("Nonexistent flag") — a classic source of silent failures in wrappers.
178
+ - APNs keys are per team. After moving an app to another Apple team, a push key from the old team silently stops delivering (`InvalidProviderToken`).
179
+
180
+ **Google Play**
181
+ - The 11 "App content" declarations have no API. The Material radio `<input>` sits ~15px above the visible circle; clicking the center focuses but doesn't select. The accessibility tree is the source of truth.
182
+ - Play Console app and developer ids only appear in console URLs; no API resolves them from a package name.
183
+
184
+ ## Contributing
185
+
186
+ Contributions are what make this useful for everyone — new stores' quirks change monthly. See [CONTRIBUTING.md](CONTRIBUTING.md). Good first issues are labeled [`good first issue`](https://github.com/FidelisMM/shipstores/labels/good%20first%20issue).
187
+
188
+ ### Contributors
189
+
190
+ <a href="https://github.com/FidelisMM/shipstores/graphs/contributors">
191
+ <img alt="Contributors" src="https://contrib.rocks/image?repo=FidelisMM/shipstores" />
192
+ </a>
193
+
194
+ ## Star history
195
+
196
+ <a href="https://star-history.com/#FidelisMM/shipstores&Date">
197
+ <picture>
198
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=FidelisMM/shipstores&type=Date&theme=dark" />
199
+ <img alt="Star history chart" src="https://api.star-history.com/svg?repos=FidelisMM/shipstores&type=Date" width="600" />
200
+ </picture>
201
+ </a>
202
+
203
+ ## License
204
+
205
+ [MIT](LICENSE) © Matheus Fidelis. Not affiliated with Apple or Google; App Store Connect and Google Play are trademarks of their owners.
@@ -0,0 +1,184 @@
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="assets/banner-dark.png">
4
+ <img alt="shipstores — ship iOS and Android apps from your AI agent" src="assets/banner-light.png" width="100%">
5
+ </picture>
6
+ </p>
7
+
8
+ <p align="center">
9
+ <a href="LICENSE"><img alt="MIT license" src="https://img.shields.io/badge/license-MIT-7C6CFF"></a>
10
+ <img alt="Python 3.12+" src="https://img.shields.io/badge/python-3.12+-3776AB?logo=python&logoColor=white">
11
+ <img alt="MCP server" src="https://img.shields.io/badge/MCP-server-C04CFD">
12
+ <img alt="Works with Claude Code" src="https://img.shields.io/badge/works%20with-Claude%20Code-D97757">
13
+ <a href="https://github.com/FidelisMM/shipstores/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/FidelisMM/shipstores/actions/workflows/ci.yml/badge.svg"></a>
14
+ <a href="https://github.com/FidelisMM/shipstores/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/FidelisMM/shipstores?style=flat&color=7C6CFF"></a>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="#quick-start">Quick start</a> ·
19
+ <a href="#tools">60 tools</a> ·
20
+ <a href="#workflows">Workflows</a> ·
21
+ <a href="#hard-won-lessons">Hard-won lessons</a> ·
22
+ <a href="CONTRIBUTING.md">Contributing</a>
23
+ </p>
24
+
25
+ ---
26
+
27
+ **shipstores** drives App Store Connect and Google Play Console end to end — builds, store listings, screenshots, privacy labels, review submission and even replying to App Review — so Claude (or any MCP client) can take an app from `eas build` to "Waiting for Review" without you clicking through two consoles.
28
+
29
+ > Built because publishing was always the slowest part of shipping an app. Every quirk documented below cost at least one rejected build to discover.
30
+
31
+ ```mermaid
32
+ flowchart LR
33
+ A["🤖 Claude / any MCP client"] -->|"tools"| M["shipstores"]
34
+ M -->|"public API"| ASC["App Store Connect"]
35
+ M -->|"public API"| GP["Google Play Developer API"]
36
+ M -->|"eas-cli"| EAS["Expo EAS builds"]
37
+ M -.->|"console automation<br/>(dedicated browser profile)"| C["Privacy label · availability<br/>App Review replies · Play App content"]
38
+ ```
39
+
40
+ ## Why
41
+
42
+ Publishing a mobile app is ~40 manual steps across two consoles, half of which have no public API. AI agents can write your app, but they stall at the store. This server closes that gap:
43
+
44
+ | | App Store Connect | Google Play |
45
+ |---|---|---|
46
+ | Upload build, versions, listing, screenshots | ✅ API | ✅ API |
47
+ | Subscriptions / in-app purchases | ✅ API | — |
48
+ | Age rating, categories, price, URLs | ✅ API | ✅ API |
49
+ | **Privacy label & country availability** | ✅ console's internal API | ✅ Data safety via console |
50
+ | **Read the rejection & reply to App Review (with video)** | ✅ console | — |
51
+ | **"App content" declarations** (11 forms, no API) | — | ✅ console automation |
52
+ | Create the app record | 🧭 opens the right form, tells you what to type | 🧭 same |
53
+ | Expo / EAS builds | ✅ start, poll, download, submit | ✅ |
54
+
55
+ Neither store lets anyone create an app record through an API, so `*_create_app_form` opens the right page in a logged-in browser and returns the exact values to fill. Everything else is automated.
56
+
57
+ ## Quick start
58
+
59
+ Requirements: Python 3.12+, [uv](https://docs.astral.sh/uv/), an App Store Connect API key and/or a Google Play service account. Xcode command line tools for iOS uploads (`xcrun altool`).
60
+
61
+ Add it to Claude Code (no clone needed, `uvx` fetches and runs it):
62
+
63
+ ```bash
64
+ claude mcp add shipstores \
65
+ -e ASC_KEY_ID=ABC123XYZ \
66
+ -e ASC_ISSUER_ID=00000000-0000-0000-0000-000000000000 \
67
+ -e ASC_PRIVATE_KEY_PATH=~/.config/shipstores/AuthKey_ABC123XYZ.p8 \
68
+ -e PLAY_SERVICE_ACCOUNT_PATH=~/.config/shipstores/play-service-account.json \
69
+ -- uvx --from git+https://github.com/FidelisMM/shipstores shipstores
70
+ ```
71
+
72
+ Then ask your agent: *"run store_doctor"*. It checks both credentials with real calls and detects your Apple Team ID for you.
73
+
74
+ ### Credentials
75
+
76
+ | Variable | Required | What it is |
77
+ |---|---|---|
78
+ | `ASC_KEY_ID`, `ASC_ISSUER_ID` | iOS | App Store Connect → Users and Access → Integrations → API key (role **Admin** or **App Manager**) |
79
+ | `ASC_PRIVATE_KEY_PATH` | iOS | the `.p8` file (default `~/.config/shipstores/AuthKey_<KEY_ID>.p8`) |
80
+ | `APPLE_TEAM_ID` | no | detected by `store_doctor` from any bundle ID |
81
+ | `PLAY_SERVICE_ACCOUNT_PATH` | Android | service account JSON invited in Play Console with release permissions |
82
+ | `PLAY_DEVELOPER_ID` | console forms | the number after `/developers/` in the Play Console URL |
83
+
84
+ Values can also live in `~/.config/shipstores/config.toml` — see [`config.example.toml`](config.example.toml). Nothing secret is ever written to the repo.
85
+
86
+ ### Console features (optional)
87
+
88
+ Privacy labels, availability, App Review replies and Play "App content" forms run through the store consoles' own web endpoints in a dedicated, logged-in browser profile, driven by [browser-harness](https://github.com/browser-use/browser-harness). Log in once:
89
+
90
+ ```bash
91
+ uvx --from git+https://github.com/FidelisMM/shipstores python -m shipstores.browser login # opens a window; sign in to both consoles (2FA included)
92
+ ```
93
+
94
+ > ⚠️ These features use undocumented console endpoints (the same family fastlane's Spaceship relies on). They work today and are isolated behind small modules, but Apple or Google can change them without notice. PRs that keep them working are very welcome.
95
+
96
+ ## Tools
97
+
98
+ **Diagnostics** — `store_doctor`, `store_browser_session`, `store_audit_identity`
99
+
100
+ **Apple: build & release** — `apple_list_apps`, `apple_register_bundle_id`, `apple_create_app_form`, `apple_upload_build`, `apple_list_builds`, `apple_create_version`, `apple_list_versions`, `apple_set_version_string`, `apple_attach_build`, `apple_submit_for_review`, `apple_resubmit_for_review`, `apple_cancel_submission`, `apple_testflight_invite`
101
+
102
+ **Apple: listing & app setup** — `apple_update_listing`, `apple_upload_screenshots`, `apple_set_app_info` (subtitle, URLs, categories, copyright, content rights), `apple_set_age_rating`, `apple_set_free_price`, `apple_set_availability`, `apple_set_app_privacy`, `apple_review_details`, `apple_set_review_details`
103
+
104
+ **Apple: App Review** — `apple_review_messages` (read the rejection and its guideline), `apple_reply_review` (answer with text + attachments; iPhone HEVC videos are converted to H.264)
105
+
106
+ **Apple: subscriptions** — `apple_create_subscription_group`, `apple_create_subscription`, `apple_list_price_points`, `apple_set_subscription_price`, `apple_set_subscription_availability`, `apple_create_intro_offer`, `apple_list_subscriptions`, `apple_upload_subscription_screenshot`
107
+
108
+ **Google Play** — `play_create_app_form`, `play_track_status`, `play_upload_bundle`, `play_promote_release`, `play_update_listing`, `play_upload_screenshots`, `play_list_screenshots`, `play_contact_details`, `play_set_contact_details`, `play_signing_sha1`, `play_submission_status`, `play_submit_for_review`
109
+
110
+ **Play Console forms** — `play_content_status`, `play_content_open`, `play_content_options`, `play_content_answer`, `play_content_save`, `play_data_safety_fill`, `play_data_safety_export`, `play_data_safety_import`
111
+
112
+ **Expo / EAS** — `eas_build_list`, `eas_build_start`, `eas_build_status`, `eas_build_download`, `eas_submit`
113
+
114
+ Tools that publish or submit (`apple_submit_for_review`, `apple_resubmit_for_review`, `apple_reply_review`, `apple_cancel_submission`, `apple_set_*`, `apple_testflight_invite`, `play_upload_bundle`, `play_promote_release`, `play_upload_screenshots`) say so in their description, so the agent confirms with you first.
115
+
116
+ ## Workflows
117
+
118
+ **New iOS app**
119
+ ```
120
+ apple_register_bundle_id → apple_create_app_form → [fill in the browser]
121
+ → apple_list_apps → eas_build_start / apple_upload_build → apple_list_builds (wait for VALID)
122
+ → apple_attach_build → apple_update_listing → apple_set_app_info → apple_set_age_rating
123
+ → apple_set_free_price → apple_set_availability → apple_set_app_privacy
124
+ → apple_upload_screenshots → apple_set_review_details → apple_submit_for_review
125
+ ```
126
+
127
+ **New Android app**
128
+ ```
129
+ play_create_app_form → [fill in the browser] → play_upload_bundle (track=internal)
130
+ → play_update_listing → play_content_* / play_data_safety_fill → play_promote_release (production)
131
+ ```
132
+
133
+ **Rejected with "Guideline 2.1 – Information Needed"** (standard for new developer accounts)
134
+ ```
135
+ apple_review_messages → record the iPhone walkthrough → apple_set_review_details (answers in Notes)
136
+ → apple_reply_review (answers + video) → apple_resubmit_for_review
137
+ ```
138
+
139
+ ## Hard-won lessons
140
+
141
+ The part people bookmark. Each one cost a rejected build or a lost afternoon.
142
+
143
+ **App Store Connect**
144
+ - **New developer accounts get "2.1 Information Needed" on the first submission**, regardless of app quality. Apple wants a screen recording from a *physical* device that starts at app launch from the Home Screen and shows login, the main flow and account deletion, plus purpose, access instructions, external services, regional differences and regulated-industry info — in the reply *and* in the review Notes. Replying is not enough: the version stays "Rejected" until you resubmit it (`apple_resubmit_for_review`, or "Update Review" on the version page).
145
+ - **Health apps must ship from an organization account** (Guideline 5.1.1(ix)). An app rejected on an individual account can't be transferred (transfers need a released version): it becomes a new app with a new bundle ID.
146
+ - **You can't learn your Team ID from the API until a bundle ID exists**; then it's the bundle's `seedId`.
147
+ - **Privacy label records need category + purpose + protection in the same record.** Separate records are accepted one by one, then publishing fails with *"An app data usage is missing a category/purpose or data protection type"*.
148
+ - **Availability needs every territory in the payload**, each flagged available or not — and the public API returns 409 anyway; the console endpoint works.
149
+ - **Cancelling a submission also cancels its subscriptions, and that can't be undone by API.** Re-adding them requires the console (details in [`docs/apple-subscriptions.md`](docs/apple-subscriptions.md)).
150
+ - **An app with an auto-renewable subscription is auto-rejected (3.1.2) without a working Terms of Use link** in the listing. The standard Apple EULA link fixes it without a new build.
151
+ - **`xcrun altool` won't take a key path argument**, but it honors `API_PRIVATE_KEYS_DIR`.
152
+ - **Screenshots belong to a version on iOS** (a released version won't accept new ones — create the next version) but to the listing on Play (live immediately).
153
+
154
+ **Expo / EAS**
155
+ - `eas submit --non-interactive` refuses App Store Connect API keys; this server downloads the `.ipa` and uploads with `altool` instead.
156
+ - `eas build:view` rejects `--non-interactive` ("Nonexistent flag") — a classic source of silent failures in wrappers.
157
+ - APNs keys are per team. After moving an app to another Apple team, a push key from the old team silently stops delivering (`InvalidProviderToken`).
158
+
159
+ **Google Play**
160
+ - The 11 "App content" declarations have no API. The Material radio `<input>` sits ~15px above the visible circle; clicking the center focuses but doesn't select. The accessibility tree is the source of truth.
161
+ - Play Console app and developer ids only appear in console URLs; no API resolves them from a package name.
162
+
163
+ ## Contributing
164
+
165
+ Contributions are what make this useful for everyone — new stores' quirks change monthly. See [CONTRIBUTING.md](CONTRIBUTING.md). Good first issues are labeled [`good first issue`](https://github.com/FidelisMM/shipstores/labels/good%20first%20issue).
166
+
167
+ ### Contributors
168
+
169
+ <a href="https://github.com/FidelisMM/shipstores/graphs/contributors">
170
+ <img alt="Contributors" src="https://contrib.rocks/image?repo=FidelisMM/shipstores" />
171
+ </a>
172
+
173
+ ## Star history
174
+
175
+ <a href="https://star-history.com/#FidelisMM/shipstores&Date">
176
+ <picture>
177
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=FidelisMM/shipstores&type=Date&theme=dark" />
178
+ <img alt="Star history chart" src="https://api.star-history.com/svg?repos=FidelisMM/shipstores&type=Date" width="600" />
179
+ </picture>
180
+ </a>
181
+
182
+ ## License
183
+
184
+ [MIT](LICENSE) © Matheus Fidelis. Not affiliated with Apple or Google; App Store Connect and Google Play are trademarks of their owners.
@@ -0,0 +1,5 @@
1
+ # Security
2
+
3
+ This server handles App Store Connect API keys and Google Play service accounts. It never stores or transmits them anywhere except to Apple and Google.
4
+
5
+ If you find a vulnerability (for example, a way for a tool to leak credentials or act on an app the user didn't ask for), please **do not open a public issue**. Report it privately through GitHub's "Report a vulnerability" (Security tab) and we'll respond within a few days.
Binary file
Binary file
@@ -0,0 +1,18 @@
1
+ # Copy to ~/.config/shipstores/config.toml (or point SHIPSTORES_CONFIG at it).
2
+ # Environment variables with the same meaning always win over this file.
3
+ # Never commit the real file: it holds paths to your keys.
4
+
5
+ [apple]
6
+ key_id = "ABC123XYZ" # ASC_KEY_ID
7
+ issuer_id = "00000000-0000-0000-0000-000000000000" # ASC_ISSUER_ID
8
+ private_key_path = "~/.config/shipstores/AuthKey_ABC123XYZ.p8"
9
+ # team_id = "ABCDE12345" # optional: store_doctor detects it
10
+
11
+ [play]
12
+ service_account_path = "~/.config/shipstores/play-service-account.json"
13
+ developer_id = "1234567890123456789" # number after /developers/ in the Play Console URL
14
+
15
+ # Needed only for the Play Console form tools (App content, data safety):
16
+ # packageName = number after /app/ in the Play Console URL
17
+ [play.console_app_ids]
18
+ "com.example.myapp" = "4970000000000000000"
@@ -0,0 +1,29 @@
1
+ # Subscriptions and review submissions on App Store Connect
2
+
3
+ What it cost to learn while shipping an app with auto-renewable subscriptions.
4
+
5
+ **The metadata bot rejects for a missing EULA without looking at the binary.** An app with an auto-renewable subscription needs a working Terms of Use link in the store listing; without it you get `3.1.2 Business: Payments - Subscriptions` minutes after submitting. No new build needed: `apple_update_listing` with a subscription block in the description (plan name, price, period, renewal, how to cancel) plus Apple's standard EULA link fixes it.
6
+
7
+ ```
8
+ Terms of Use (EULA): https://www.apple.com/legal/internet-services/itunes/dev/stdeula/
9
+ ```
10
+
11
+ A custom EULA is the other path: instead of the link, the text goes in the License Agreement field (`/v1/apps/{id}/endUserLicenseAgreement`, `null` when using Apple's standard one).
12
+
13
+ **A version lives in one submission at a time.** Resubmitting a rejected version returns `409 ITEM_PART_OF_ANOTHER_SUBMISSION` while the old submission exists. Cancel with `PATCH /v1/reviewSubmissions/{id}` and `{"canceled": true}` (`apple_cancel_submission`); the state stays `CANCELING` for ~15 s before the version is free.
14
+
15
+ **Cancelling the submission burns the subscription with it, and the API can't bring it back.** The `subscriptionVersion` becomes `DEVELOPER_REJECTED`, and from then on:
16
+
17
+ - `POST /v1/subscriptionSubmissions` answers *"has no pending version for submission"*;
18
+ - editing localization, price or `reviewNote` does **not** create a new version;
19
+ - adding it to a submission (the valid relationship is `subscriptionVersion`, not `subscription`) fails with `STATE_ERROR.ENTITY_STATE_INVALID`.
20
+
21
+ Only the console recreates the subscription items (App Store Connect → Distribution → App Review), and they land in a separate **draft** submission. That draft won't submit alone — *"add an app version for the selected platform"*: in-app purchases always travel with a version. If the version is already in another submission, regroup:
22
+
23
+ 1. cancel the submission that only has the version and wait for it to leave `CANCELING`;
24
+ 2. `POST /v1/reviewSubmissionItems` with `appStoreVersion` pointing at the draft;
25
+ 3. `PATCH` the draft with `{"submitted": true}`.
26
+
27
+ You end with one submission holding three items: the version, the subscription group and the subscription.
28
+
29
+ **`apple_submit_for_review` sends only the version.** Subscriptions need their own `subscriptionVersion` item in the same `reviewSubmission`, created before `submitted: true`.