Mağaza MCP
<div align="center">
# Mağaza MCP
**App Store Connect and Google Play, in one MCP server.**
Let your AI assistant manage both stores.
[](https://www.npmjs.com/package/magaza-mcp)
[](https://github.com/tunaarikaya/magaza-mcp/actions/workflows/ci.yml)
[](https://github.com/tunaarikaya/magaza-mcp/stargazers)
[](LICENSE)
[](https://nodejs.org)
**You don't type anything into a terminal to install this.**
Drop this line on your AI assistant, it handles the rest:
```
https://github.com/tunaarikaya/magaza-mcp — install this, read AGENTS.md
```
[Türkçe README](README.tr.md)
</div>
---
> **You:** How much is Nota Defteri's monthly subscription on App Store vs. Play in Turkey?
>
> **Assistant:** ₺129.99 on the App Store, ₺99.99 on Play. Play is noticeably
> cheaper — if you want the same price in both stores, you need to update the
> base plan on Play.
No other MCP server can answer that in a single call, because they all look at
one store. `magaza-mcp` sees both at once.
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/architecture-dark.svg">
<img src="docs/architecture-light.svg" alt="magaza-mcp runs on your machine between your AI assistant and the App Store Connect and Google Play APIs; keys stay in your Keychain." width="880">
</picture>
</p>
No server sits in the middle: the package runs on your own machine, talks
directly to Apple and Google, and your keys never leave your Keychain.
---
## 📚 Table of contents
| | |
| --- | --- |
| [🎯 Why this exists](#-why-this-exists) | The difference from single-store servers |
| [⚡ Setup](#-setup) | Have your assistant do it — you don't type commands |
| [🧑💻 Manual setup](#-manual-setup) | If you're not using an assistant |
| [🧩 Do the two stores mix up?](#-do-the-two-stores-mix-up) | No — here's why |
| [💬 What you can ask](#-what-you-can-ask) | Example prompts |
| [🚨 Account health](#-account-health) | Is your Apple account at risk? |
| [🧰 Tools](#-tools) | Full list of all 41 tools |
| [🌐 Full API access](#-full-api-access) | 1,440 operations, ~9,000 tokens |
| [🔑 Required permissions](#-required-permissions) | What Apple and Google require |
| [🔒 Security](#-security) | Keys, approval gates, telemetry |
| [🩺 Troubleshooting](#-troubleshooting) | Common errors |
---
## 🎯 Why this exists
Almost every MCP server on the market covers a single store. If you want
both, you install two separate servers, juggle two separate tool sets, and
manually stitch together any question that needs a comparison.
| Question | With single-store servers | With `magaza-mcp` |
| --- | --- | --- |
| "How much is this subscription in both stores?" | Two separate queries, manual comparison | One call, side by side |
| "User paid but didn't get premium, what's wrong?" | You figure out which store first | Scans both at once, ranks findings |
| "Where is what published?" | Two lists, manual matching | One list with iOS/Android pairs already matched |
The real win isn't more tools: **it's tools that can query both stores in the
same call, which is only possible once both are in one server.**
---
## ⚡ Setup
Tell your assistant (Claude Code, Antigravity, Cursor, Codex, Gemini CLI…):
```
https://github.com/tunaarikaya/magaza-mcp — install this, read AGENTS.md
```
The assistant reads [AGENTS.md](AGENTS.md) and handles the entire setup. All
you do is answer its questions:
```
You › https://github.com/tunaarikaya/magaza-mcp — install this, read AGENTS.md
Assistant › Looking for your keys on this machine...
Found two keys:
App Store : ~/Downloads/AuthKey_ABC123DEFG.p8 (Key ID ABC123DEFG)
Play : ~/.config/play/account.json (publish@project.iam...)
Want me to use these? If there's another file, tell me the path,
or copy the key and paste it — either works.
You › yes, use them
Assistant › I need the Issuer ID for App Store. It's the UUID above the key
list on App Store Connect → Users and Access → Integrations.
You › 69a6de70-0000-0000-0000-000000000000
Assistant › ✓ App Store Connect connected — 4 apps found
✓ Google Play connected — 3 apps found
✓ Registered in Claude Code and Antigravity settings
Restart Claude Code, then ask:
"List my apps across both stores"
```
**If it can't find your key**, it will ask you how to proceed: give it the
file path, copy the key to your clipboard, or — if you don't have a key yet —
it'll walk you through getting one step by step.
> [!IMPORTANT]
> **The assistant never sees the contents of your key.** It runs the setup,
> but the commands take the file's **path**, read its contents themselves, and
> write it straight into the Keychain. Even when you paste the key via
> clipboard (`pbpaste | …`), the content flows through a pipe and never enters
> the assistant's context. AGENTS.md tells the assistant this explicitly.
After setup, restart any open applications — MCP servers only load at
startup.
<details>
<summary><b>Supported clients and config files</b></summary>
<br>
| Client | Config file |
| --- | --- |
| Claude Code | `~/.claude.json` |
| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Antigravity (IDE + CLI) | `~/.gemini/config/mcp_config.json` |
| Cursor | `~/.cursor/mcp.json` |
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |
| Codex | `~/.codex/config.toml` |
Your existing config is left untouched — only the `magaza-mcp` entry is
added or updated, a `.magaza-mcp-yedek` backup is taken before every write,
and the write itself is atomic.
</details>
---
## 🧑💻 Manual setup
If you're not using an assistant, a wizard does the same job:
```bash
npx magaza-mcp kur
```
<details>
<summary><b>What does the wizard ask?</b></summary>
<br>
```
Mağaza MCP setup
App Store Connect + Google Play, in one setup
1. Which stores should we connect?
App Store Connect (iOS / macOS)? [Y/n] y
Google Play (Android)? [Y/n] y
2. App Store Connect key
Key ID: ABC123DEFG
Issuer ID: 00000000-0000-0000-0000-000000000000
Path to .p8 file: ~/Downloads/AuthKey_ABC123DEFG.p8
Connecting to Apple... ✓ 4 apps found
✓ Key saved (macOS Keychain)
3. Google Play service account
Path to service account JSON: ~/.config/play/account.json
Connecting to Google... ✓ 3 apps found
✓ Key saved (macOS Keychain)
4. Which clients should it be installed to?
Claude Code? [Y/n] y
Antigravity (IDE + CLI)? [Y/n] y
Cursor? [y/N] n
Setup complete.
```
| # | Question | Note |
| --- | --- | --- |
| 1 | Which stores? | App Store, Play, or both |
| 2 | App Store key | Key ID, Issuer ID, `.p8` path — verified with a real request to Apple before saving |
| 3 | Play service account | Path to the JSON key — also verified against Google |
| 4 | Which clients? | Clients detected on your machine come pre-checked |
| 5 | Read-only? | No by default |
</details>
**Keys are never written to the config file.** On macOS they're saved to the
Keychain; the config file only gets which stores are enabled (`MAGAZALAR`).
On systems without Keychain access, it falls back to a file with `0600`
permissions.
<details>
<summary><b>All commands</b></summary>
<br>
```bash
npx magaza-mcp tara # Search the machine for .p8 and service account keys
npx magaza-mcp anahtar ... # Verify a key and write it to the vault (below)
npx magaza-mcp kaydet # Register the server in clients' configs
npx magaza-mcp durum # Show what's connected (--json for machine-readable)
npx magaza-mcp araclar # List installed tools (✎ = can modify data)
npx magaza-mcp kur # Manual setup wizard
npx magaza-mcp surum # Print version
npx magaza-mcp yardim # Help
```
Registering a key (the command-line equivalent of the wizard's Q&A step):
```bash
npx magaza-mcp anahtar --apple-p8 ~/Downloads/AuthKey_ABC123DEFG.p8 \
--issuer-id 69a6de70-0000-0000-0000-000000000000
npx magaza-mcp anahtar --play-json ~/.config/play/account.json
pbpaste | npx magaza-mcp anahtar --play-json - # from clipboard, no file
npx magaza-mcp anahtar --sil appstore # remove from the vault
```
If `--key-id` isn't given, it's read from the filename. A key is never
written to the vault without being verified against Apple/Google with a real
request; if verification fails, the existing key in the vault is left
untouched.
</details>
---
## 🧩 Do the two stores mix up?
No — and that's not an accident, it's the design itself.
**1. Tool names are separated by prefix.** Every tool going to App Store
starts with `appstore__`, every tool going to Play starts with `play__`. A
tool never talks to both APIs. There's no shared name, so there's nothing to
mix up.
**2. Tools for the store you didn't pick are never loaded.** On startup, the
server reads the `MAGAZALAR` variable and builds the tool list from it — the
other store's tools aren't even loaded into memory, aren't shown to the
model, and cost zero tokens.
| Setup | Tools loaded |
| --- | --- |
| Both stores | 41 |
| App Store only | 22 |
| Play only | 19 |
| Read-only (both stores) | 27 |
**3. Cross-store tools only exist when both stores are enabled.** In a
single-store setup they're never created at all — they'd have no meaning.
**4. The dispatch tools know their scope too.** The `magaza` parameter on
`magaza__endpoint_ara` and `magaza__cagir` is an enum that only accepts the
stores you've set up. If you only installed Play, the model can't make a call
with `magaza: "appstore"` — the schema doesn't allow it.
---
## 💬 What you can ask
The app names in the examples are made up; you'd use your own app's name.
**Both stores at once**
- *"List my apps across both stores — where's each one published?"*
- *"What's Nota Defteri's monthly subscription on App Store vs. Play in Turkey? Is there a difference?"*
- *"A user says they paid but didn't get premium. Check the purchase setup in both stores."*
**App Store**
- *"Which of my versions is stuck in review?"*
- *"Did yesterday's TestFlight build finish processing?"*
- *"Summarize the 1- and 2-star reviews from the last week — what's the most common complaint?"*
- *"Update the 'What's New' text for the version in preparation."*
**Google Play**
- *"Which version is on the production channel and what percentage of users does it reach?"*
- *"Has the crash rate gone up over the last 14 days?"*
- *"Is this purchase token valid, is the subscription still active?"*
- *"Draft a polite reply to this review, but show me first."*
---
## 🚨 Account health
Apple terminates developer accounts every year for spam, copycat apps and
review manipulation — and many developers find out only when the email
arrives. Most of them never knew they were doing anything wrong: the same
screenshots in five apps, "Instagram" in the keywords, someone else's
copyright left over from a template.
`appstore__hesap_sagligi` scans the whole account and scores the risk from
0 to 100:
```
You › Is my developer account safe?
Assistant › ACCOUNT RISK: 72/100 🔴 HIGH
Clones / spam (4.3) ██████████ critical (3)
Metadata & trademarks (2.3/5.2) █████░░░░░ high (4)
Reviews & money (5.6/3.1) ██░░░░░░░░ low (2)
Review history & account ██████░░░░ medium (3)
1. [CRITICAL] Same screenshots in 6 apps
2. [HIGH] Photo Pro: "instagram" in the keywords
3. [HIGH] 3 apps carry someone else's copyright
…
```
| Section | What it looks for |
| --- | --- |
| Clones / spam | Identical screenshots across apps, near-identical names and descriptions, `app1, app2…` bundle IDs, saturated categories, foreign copyright, many apps in a short time |
| Metadata & trademarks | Brand names (Apple, Instagram, ChatGPT…) in names and keywords, "best / #1 / free" in the name, keyword stuffing, Android mentions, missing privacy or support URL, media downloaders, fake-function apps |
| Reviews & money | Scam / refund complaints, 5-star bursts, copy-paste reviews, misleading review replies, refund rate, apps with no downloads, overpriced weekly subscriptions, rejected IAPs |
| Review history & account | Rejections, reviews stuck for days, apps Apple removed, apps not updated in 3+ years, EU trader status, missing declarations (content rights, encryption, demo account, review notes, age rating), too many admins |
Every finding comes with the Apple guideline, why it's risky and the steps
to fix it.
| Parameter | What it does |
| --- | --- |
| `uygulama` | Scan a single app (Apple id, bundle ID or name). Clone detection needs the whole account. |
| `bolum` | Only these sections: `klon`, `metadata`, `yorum_para`, `inceleme_hesap` |
| `derin` | All languages and 1,000 reviews per app (slower). Default: primary language + en-US, 200 reviews |
| `satici_no` | Vendor number — adds the refund rate and 12-month download checks |
**Fixing.** Findings marked automatic can be fixed with
`appstore__hesap_duzelt`. It always shows the change first and only writes
with `onayla=true`:
```
appstore__hesap_duzelt(bulgular: ["anahtar-marka:1234567:en-US"])
→ preview: keywords "photo,instagram,edit" → "photo,edit"
appstore__hesap_duzelt(bulgular: ["anahtar-marka:1234567:en-US"], onayla: true)
→ applied
```
Findings that need a value (privacy URL, copyright, demo account) take it
through `degerler`. If there's no editable version, it tells you instead of
creating one. Heavy moves — pulling an app from sale, demoting an admin —
are only explained, never done.
**Quiet fixes.** The advice never pushes you to knock on Apple's door: issues
are fixed on your side, ideally in your next regular update; reporting
reviews, messaging App Review unprompted or volunteering explanations is not
recommended. If Apple asks, answer briefly and honestly. Every finding comes
with a "don't" list — the reflexes that draw attention.
> **What it can't see.** The API doesn't expose Apple's internal flags,
> rejection reasons or Resolution Center messages, or links to other
> accounts (same device, card or freelancer — the most common unexplained
> termination). So the tool reports risk signals, not a verdict, and says
> what it couldn't check.
Ask things like:
- *"Is my developer account safe?"*
- *"Why is my review taking so long?"*
- *"Fix the keyword problems you found, but show me first."*
---
## 🧰 Tools
The prefix tells you which store a tool goes to. Tools marked **✎** modify
data and won't run without `onayla=true`.
<details open>
<summary><b>Both stores at once — 3 tools</b> · <i>the heart of the project</i></summary>
<br>
These tools only load when both stores are connected.
| Tool | What it does |
| --- | --- |
| `magaza__genel_bakis` | Collects apps from both stores into one list; puts the iOS/Android pair of the same product side by side, and separates out apps that only exist in one store. |
| `magaza__abonelik_karsilastir` | Compares the same app's subscriptions across both stores: product IDs, durations, and prices in the requested country. Converts the country code to whichever format each store expects. |
| `magaza__iap_teshis` | Diagnoses "paid but no premium" issues: are the products live, do they have a price in that country, is the plan open to new subscribers; if you give it a Play purchase token it verifies that too, and writes up its findings as a list. |
</details>
<details>
<summary><b>App Store Connect — 19 tools</b> (<code>appstore__</code>)</summary>
<br>
| Tool | What it does |
| --- | --- |
| `appstore__uygulamalar` | Lists the apps on the account: name, bundle ID, SKU, primary language, and the `id` other tools need. |
| `appstore__surumler` | Shows an app's App Store versions and their status (in preparation, in review, live, rejected). |
| `appstore__buildler` | Lists TestFlight builds, their processing status, and expiry dates. |
| `appstore__yorumlar` | Fetches customer reviews, newest first; filterable by rating and country, or only the ones without a reply yet. Shows the existing developer response if there is one. |
| `appstore__yorum_yanitla` ✎ | Writes a developer response to a review (published after Apple's moderation). |
| `appstore__abonelikler` | Lists subscription groups and the subscriptions in them: product ID, duration, status. |
| `appstore__abonelik_fiyatlari` | Fetches a subscription's customer price and developer proceeds, country by country. |
| `appstore__iap_urunler` | Lists one-time in-app purchase products. |
| `appstore__testflight_gruplari` | Shows TestFlight beta groups, how many testers are in each, public invite links, and quotas. |
| `appstore__metin_guncelle` ✎ | Updates the store text of the version in preparation: release notes, description, keywords, promotional text. |
| `appstore__satis_raporu` | Downloads the sales/download report as TSV: daily, weekly, monthly, or yearly; sales, pre-order, installs, subscriptions, subscription events, subscribers, and offer code reports. |
| `appstore__abonelik_fiyat_ayarla` ✎ | Sets a subscription's price in **every country at once**: give one price (e.g. USA 9.99), Apple's equalization table fills in all ~175 countries and they're written in a single request. Add country-specific prices on top (`{"TUR": "349.99"}`), or change only those countries and leave the rest alone. |
| `appstore__fiyat_analizi` | Regional pricing suggestion, writes nothing: starts from Apple's equalized price and applies a softened discount based on each country's purchasing power (World Bank). Never raises a price, never cuts more than half, leaves rich countries alone. The output plugs straight into `abonelik_fiyat_ayarla`. |
| `appstore__iap_fiyat_ayarla` ✎ | Sets a one-time purchase's price from a single base country; Apple fills in the rest. |
| `appstore__abonelik_ulkeleri` ✎ | Makes a subscription available in every country (or the ones you list) in one request. |
| `appstore__abonelik_kur` ✎ | Creates a subscription from scratch behind one approval: group → subscription → localized names → availability → prices in all countries. |
| `appstore__yas_derecelendirme` ✎ | Reads or fills in the age rating questionnaire (Apple's 2025 questions). Finds the editable app info by itself; only the answers you give change. |
| `appstore__hesap_sagligi` | Scans the whole developer account for termination, removal and long-review risk: a 0–100 score, findings tied to Apple guidelines, why each one is risky and how to fix it. See [Account health](#-account-health). |
| `appstore__hesap_duzelt` ✎ | Fixes findings from `hesap_sagligi` by id: shows current → new first, applies with `onayla=true`. Never creates a version, removes an app or changes roles. |
</details>
<details>
<summary><b>Google Play — 16 tools</b> (<code>play__</code>)</summary>
<br>
| Tool | What it does |
| --- | --- |
| `play__uygulamalar` | Lists the apps the service account can access; this is where you get the package name other tools need. |
| `play__kanallar` | Shows release channels (internal, alpha, beta, production) and the versions on each, with user rollout percentages. Requires the service account to have release-management permission. |
| `play__yorumlar` | Fetches user reviews, translating them if asked. The Play API only returns roughly the last week. |
| `play__yorum_yanitla` ✎ | Writes a developer response to a review (350 characters max). |
| `play__abonelikler` | Lists subscriptions and their base plans: duration, status, number of priced countries. Includes archived ones if asked. |
| `play__abonelik_fiyatlari` | Fetches a base plan's prices country by country, and whether it's open to new subscribers; also checks the "rest of world" fallback price for countries not listed individually. |
| `play__urunler` | Lists one-time products; tries both the new (`oneTimeProducts`) and legacy (`inappproducts`) models. |
| `play__satin_alma_dogrula` | Verifies a purchase token: subscription status, acknowledgment status, expiry date, whether it's a test purchase. |
| `play__iade_edilenler` | Lists cancelled, refunded, or charged-back purchases; turns the refund reason and source into something readable. Google only returns the last 30 days. |
| `play__cokme_orani` | Android vitals: crash and ANR rates (including the user-perceived rates Play uses for its bad-behavior thresholds), whether a threshold is exceeded, and the most frequent crash clusters with stack traces. Can be narrowed to one version code. |
| `play__surum_yayinla` ✎ | Uploads an AAB/APK and releases it to a track in one approval: upload → assign to track → release notes → commit. Give a percentage for a staged rollout (e.g. production 10%). If anything fails, nothing is committed. |
| `play__yayin_yonet` ✎ | Manages a track: change the rollout %, halt, resume, complete, promote a release to another track (internal → production), or update only the release notes. The preview shows the track before and after. |
| `play__listeleme_guncelle` ✎ | Reads or updates store listing text (title, short and full description, video) for every language in one edit. |
| `play__gorsel_yukle` ✎ | Uploads screenshots, the icon, or the feature graphic, optionally replacing the existing ones. |
| `play__abonelik_fiyat_ayarla` ✎ | Sets a base plan's price in every country at once from a USD price (Google's own conversion), with optional country-specific prices on top, or changes only the countries you list. |
| `play__fiyat_analizi` | Regional pricing suggestion for Play, writes nothing: same purchasing-power logic as the App Store version, rounded to natural-looking local prices. |
</details>
<details>
<summary><b>Full API access — 3 tools</b> (<code>magaza__</code>)</summary>
<br>
| Tool | What it does |
| --- | --- |
| `magaza__endpoint_ara` | Searches for an endpoint across all connected stores' APIs; returns the operation name, HTTP method, path, and parameters. |
| `magaza__cagir` ✎ | Runs the operation you found. Fills in path parameters automatically; operations that modify data won't run without `onayla=true`. |
| `magaza__sema` | Shows what an operation's request body should look like; used before POST/PATCH calls. |
`magaza__cagir` is marked ✎ here because it can also run operations that
modify data. In `npx magaza-mcp araclar` output it doesn't show ✎ though:
since it's also the single gateway to every read endpoint in the catalog, it
isn't dropped from the list in read-only mode — only write operations through
it are blocked.
</details>
---
## 🌐 Full API access
The curated tools cover most day-to-day work. For everything else, the server
carries the **entire** API surface of both stores:
| Source | Operations |
| --- | --- |
| App Store Connect API v4.5 | 1,270 |
| Android Publisher API v3 | 145 |
| Play Developer Reporting API v1beta1 | 25 |
| **Total** | **1,440** |
When the curated tools aren't enough, the model first finds the operation it
needs with `magaza__endpoint_ara`, then runs it with `magaza__cagir`.
### Why aren't they all separate tools?
MCP tool definitions get loaded into context **on every request** — the
installed tool list is paid for again on every message, even if you don't use
any of them.
| | Tools | Paid on every message |
| --- | --- | --- |
| **magaza-mcp** | 41 | **~9,000 tokens** |
| If all were separate tools | 1,440 | ~210,000 tokens |
> [!NOTE]
> Nothing is locked away: all 1,440 operations are reachable, but the ones
> you don't use cost nothing. There's no "should I turn this feature on, it
> costs tokens" setting either — there's nothing to turn on.
The catalogs are generated from Apple's and Google's **official
specifications**: Apple's published App Store Connect OpenAPI file, and
Google's Android Publisher and Play Developer Reporting discovery documents.
There's no hand-written endpoint list. The 159 operations Apple marks as
deprecated aren't dropped from the catalog — some of them are the only way to
reach that capability — but their summaries start with `[ESKİMİŞ]`
(deprecated) and are pushed down in search results.
---
## 🔑 Required permissions
**App Store Connect** — The **App Manager** role is enough when creating the
key; Admin isn't required. Exceptions: user/access management endpoints and
some analytics reports need higher privileges.
**Google Play** — Both steps are required:
1. The service account must be invited from **Play Console → Users and
permissions** and granted access to the relevant apps.
2. The **Android Publisher API** and **Play Developer Reporting API** must be
enabled on the Cloud project. App listing and crash metrics come from the
Reporting API, so the second one is genuinely required too.
> [!WARNING]
> Contract, tax, and banking information **cannot be read with any API key.**
> Apple never exposes this data to the API at all; only the Account Holder can
> see it in the dashboard.
---
## 🔒 Security
- **Keys live in the Keychain.** The Apple `.p8` and Google service account
JSON are stored in the macOS Keychain; never written to config files,
environment variables, or the repo.
- **The assistant never sees the key, even when it runs the setup.** The
`anahtar` command takes the file's path, reads the content itself, and
writes it straight to the vault; a key given via clipboard also flows
through a pipe. The key's content never appears in any command's output —
`durum` only prints the last four characters of the Key ID, `tara` only
prints file paths.
- **Operations that modify data require approval.** Write tools don't run on
the first call: they return what would happen and the expected request
body; the operation only runs once the user approves and it's called again
with `onayla=true`. This is enforced **server-side** — it doesn't depend on
the client's approval UI.
- **Read-only mode.** `--salt-okunur` (or `SALT_OKUNUR=1`) removes every
write-capable tool from the list entirely: the model can't see them or call
them. 41 tools become 27. `magaza__cagir` stays in the list because it's
also the single gateway for read endpoints in the catalog — but it refuses
write operations when read-only mode is on.
- **Path parameters are validated.** IDs passed to tools are encoded before
entering a URL; path-traversal attempts like `.` and `..` are rejected. For
absolute URLs the target host is validated, so no request can carry a token
to anywhere other than Apple or Google.
- **Clients can see which tools modify data.** The tool list ships with MCP
`annotations` (`readOnlyHint`, `destructiveHint`).
- **Package provenance is verifiable.** Every version published to npm is
built inside GitHub Actions, and npm generates a Sigstore-signed provenance
attestation: the **Provenance** section on the npm page shows which commit
in this repo the tarball you downloaded was built from. No publish token is
stored in the repo; GitHub issues a short-lived, signed identity on every
release (trusted publishing), so there's no publish key to steal either.
- **No telemetry.** No analytics, error reports, or usage data are sent.
Network traffic only goes to `api.appstoreconnect.apple.com`,
`androidpublisher.googleapis.com`, `playdeveloperreporting.googleapis.com`,
and `oauth2.googleapis.com` for tokens. The one exception is
the two `fiyat_analizi` tools, which read public purchasing-power figures from
`api.worldbank.org` (no key, nothing of yours is sent). There's no server in
the middle; data flows directly between your machine and Apple/Google.
---
## 🩺 Troubleshooting
<details>
<summary><b>Apple returns 401</b></summary>
<br>
The Key ID, Issuer ID, and `.p8` file might not belong together — all three
need to be from the same key. If they match, check the system clock: the
signed JWT lives 20 minutes, and a token produced on a machine with a drifted
clock gets rejected by Apple. Also make sure the `.p8` file is an **App Store
Connect API key** — StoreKit or push keys don't work here.
</details>
<details>
<summary><b>Apple returns 403</b></summary>
<br>
The key's role isn't sufficient for that operation. App Manager covers most
things; user management and some reports need more.
Worth knowing upfront: **contract, tax, and banking information can't be read
with any API key.** A 403 here isn't a misconfiguration, it can't be fixed.
</details>
<details>
<summary><b>Play returns 403 on the <code>inappproducts</code> endpoint</b></summary>
<br>
The app has moved to Google's new product model; the legacy `inappproducts`
endpoint is now closed and `oneTimeProducts` should be used instead. The
`play__urunler` tool handles this itself: it tries the new endpoint first,
falls back to the legacy one if needed, and tells you in the output which
model it used.
</details>
<details>
<summary><b>Play returns 403 in general</b></summary>
<br>
The service account may not have been invited to the app in Play Console.
After inviting it, permissions can take a few minutes to propagate. If the
invite is already in place, verify that Android Publisher API and Play
Developer Reporting API are enabled on the Cloud project.
</details>
<details>
<summary><b>Why does "list my Play apps" come from the Reporting API?</b></summary>
<br>
Because the Android Publisher API **has no app-listing endpoint** — Google
never published one. Since no Publisher call can be made without knowing the
package name, the list is pulled from the Play Developer Reporting API's
`apps:search` endpoint instead. That's why the Reporting API needs to be
enabled not just for crash metrics, but for basic usage too.
</details>
<details>
<summary><b>Why is Play's version/channel info sometimes delayed?</b></summary>
<br>
On Play, channel information can only be read from within an "edit session."
The read tools open this session themselves, read from it, and leave it
without committing — so it creates no changes — but it does mean two extra
HTTP calls.
</details>
---
## 🛠 Development
```bash
git clone https://github.com/tunaarikaya/magaza-mcp.git
cd magaza-mcp
npm install
npm run build # Compile TypeScript
npm run kontrol # Type-check (tsc --noEmit)
npm run dev # Compile in watch mode
```
To regenerate the tool catalogs from the specs:
```bash
node scripts/uret-katalog.mjs
```
This reads the three spec files under `spec/` and rewrites the catalogs under
`src/katalog/`. The operation list is never hand-edited.
---
## 🤝 Similar projects
There are good projects in this space that came before this one. If your
needs are limited to a single store, we genuinely recommend looking at them:
- **[Heimdall](https://github.com/erayendes/app-store-connect-mcp)** — Covers
the entire App Store Connect API with 890 tools, with a profile system that
lets you narrow the tool set down. The deepest project in this space if
you're only interested in iOS.
- **[app-store-connect-mcp-server](https://github.com/JoshuaRileyDev/app-store-connect-mcp-server)**
— The first one. Established the idea of putting App Store Connect behind
an MCP server; everyone who came after owes something to it.
- **[google-play-developer-mcp](https://github.com/devinwang/google-play-developer-mcp)**
— A comprehensive, mature server on the Play side. More than enough if
you're only publishing on Android.
Our difference: all of these look at a single store. `magaza-mcp` combines
both in one setup and offers tools that can compare both stores in the same
call — exactly what a single-store server can't do.
---
## 📄 License
MIT — see [LICENSE](LICENSE) for details. See
[THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md) for third-party sources.
App Store, TestFlight, App Store Connect, Google Play, and Play Console are
trademarks of their respective owners. This is an independent open-source
project; it is not affiliated with, endorsed, sponsored, or supported by
Apple Inc. or Google LLC.
TDQS
Scored across 27 tools
The appstore__/play__ namespaces plus the magaza__ cross-store tools make most purposes clearly distinct (list versions vs. list builds vs. list reviews vs. reply to review). The only real overlap is the meta-tool trio (endpoint_ara, cagir, sema) which can reach any operation that the specific tools already wrap, so an agent could occasionally pick cagir where a dedicated tool would be better. Descriptions mostly resolve this by describing a clear workflow (find → schema → call).
All names are lowercase snake_case with a consistent platform prefix (magaza__, appstore__, play__), which gives a strong predictable grouping. The internal pattern mixes pure-resource nouns (uygulamalar, surumler, yorumlar) with resource_action forms (yorum_yanitla, metin_guncelle, satin_alma_dogrula), a minor deviation but still readable and internally consistent within each namespace.
At 27 tools the set is heavy, sitting just above the 25-tool threshold, though the surface spans two independent platforms (App Store + Play) across ~10 resource types each, which explains most of the count. The three generic meta-tools (endpoint_ara, cagir, sema) partially overlap with the dedicated wrappers, so a handful of tools could arguably be collapsed. Borderline but largely justified by the domain breadth.
Coverage is strong: apps, versions, builds, reviews (with replies), subscriptions, IAP products, pricing, sales reports, purchase verification, refunds, crash rate, plus cross-store comparison and purchase diagnosis. Dedicated gaps exist (no Play metadata update, no build upload/submit-for-review tool), but magaza__cagir/endpoint_ara/sema provide an escape hatch to the full App Store Connect and Play APIs, so agents are unlikely to hit a dead end.