Changelog
All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
Security
- The published package metadata now requires
urllib3>=2.8.0andauthlib>=1.9.1, as it already didpyjwt>=2.15.1. 0.7.0's lockfile and Docker image already had these versions, butpip install play-store-mcpcould keep an older, vulnerable copy that was already installed.
Planned
- Consolidate and reduce the MCP tool surface (now 117 tools) by grouping related operations, to lower per-request tool-list overhead — with no planned loss of functionality.
0.7.0 - 2026-10-29
Breaking — code-mode is now enabled by default. Clients that expect the full 117-tool list (e.g. anything enumerating tools by name) will instead see three meta-tools (
search/get_schema/execute) unlessCODE_MODE=0is set. See Changed below.
This is the first published release since 0.6.1. It also ships 0.6.3 and 0.6.4, which were never tagged (0.6.2, also untagged, is listed separately below).
Security
- Blocked SSRF via a spoofed
token_uriin credential JSON. Credentials passed in theX-Google-Credentials/X-Google-Credentials-Base64headers (accepted on any request) could name an arbitrarytoken_uri, which google-auth POSTs to on token refresh. Credentials are now rejected unlesstoken_uriis Google's ownhttps://oauth2.googleapis.com/token. - Upgraded
pyjwt2.13.0 → 2.15.1 (14 known vulnerabilities, 2 critical, fixed; declared floor raised to>=2.15.1so published metadata carries the fix),urllib32.7.0 → 2.8.0 (3 known vulnerabilities), andauthlib1.7.2 → 1.9.1 (7 known vulnerabilities in 1.8.0, 1 critical). - The server warns at startup if
PLAY_STORE_MCP_ADMIN_TOKENis set but shorter than 16 characters, and the docs warn that--host 0.0.0.0exposes the unauthenticated/mcpendpoint.
Added
- Opt-in tool-failure log. Set
PLAY_STORE_MCP_FAILURE_LOG=1to record each failed tool call (raised errors, argument-validation failures, and results withsuccess: falseor anerror) to a JSON-lines file atPLAY_STORE_MCP_FAILURE_LOG_PATH(default$XDG_STATE_HOME/play-store-mcp/tool-failures.jsonl). MCP clients log such calls as successes, so this is the only record of why they failed. Off by default for every transport. Secret-named arguments are redacted (including from error text), long values are truncated, and the file is0600and rotates at 5MB. With code-mode on, calls insideexecuteare recorded under their own tool names. The idea came from abd3lraouf-studios/google-play-console-mcp and was implemented independently here. list_subscriptionsgainsinclude_regional_prices(see Changed).
Changed
- Breaking:
CODE_MODEnow defaults to enabled (was opt-in/default-off). SetCODE_MODE=0(orfalse/no/off, case-insensitive) to opt out and get the classic 117-tool list back. Theexecutemeta-tool's sandbox (Monty) is now a base dependency — not an optional extra — so every install path (pip install play-store-mcp,uvx play-store-mcp, Docker) works out of the box with no separate install step. Read-only enforcement still applies inside the sandbox. list_subscriptionsno longer returns each base plan's per-region price table by default (see Fixed); passinclude_regional_prices=Truefor it.- The Docker image now runs on Python 3.15 (
python:3.15-alpine, was 3.14).
Fixed
get_reviewsandget_reviewfailed on every real response with a pydanticstring_typeerror: the Reviews API sendsandroidOsVersionas an integer SDK level (e.g.34), whileReview.android_versionis a string. It is now converted; a missing value is stillNone. Reported and fixed by @j4velin (#178).deploy_app,promote_release,halt_release,update_rollout,update_listingandupdate_testersraised instead of returning their documented failure result when setting up the edit failed (bad package name, missing permission, edit quota, transient API errors).- A pooled keep-alive connection that Google closed while idle made calls fail
with
BrokenPipeErroror an SSL EOF, and kept failing: httplib2 leaves a connection whose send failed in its pool, so the next call reused the dead socket. Transport errors now drop the pool so the next request reconnects, and are retried on the same terms as a 500/503 (idempotent requests only, plus edit creation, where a duplicate uncommitted edit is harmless). This mostly hit long-running HTTP deployments. The bug was reported in abd3lraouf-studios/google-play-console-mcp and fixed independently here. list_subscriptionsreturned every base plan's full per-region price table (regionalConfigs, ~170 regions each), so a handful of subscriptions exceeded a tool result's size limit. One subscription with two base plans was already ~40KB. Each base plan now reportsregionalConfigCountinstead, cutting that to ~1.2KB;include_regional_prices=Truerestores the full table, andget_subscriptionstill returns one product's prices. The bug was reported in abd3lraouf-studios/google-play-console-mcp and fixed independently here.- Concurrent first tool calls could each build their own API client (a race in the lazy client setup); the client is now built once.
Dependencies
fastmcp4.0.2 → 4.1.4,mcp2.1.1 → 2.3.0, the code-mode sandboxpydantic-monty0.0.18 → 1.4.0,starlette1.3.1 → 1.10.0,google-api-python-client2.198.0 → 2.201.0,google-auth2.55.1 → 2.62.0,cryptography50.0.0 → 50.0.2, and the rest of the lockfile.- Container: newer base image digest, plus
apk upgradein the runtime stage to pick up Alpine fixes that the base image hasn't yet. - CI:
uv0.13.6; GitHub Actions updated (allgithub/codeql-actionrefs pinned to one version and grouped in Dependabot); runners onubuntu-26.04with Python 3.14.9; Grype/Trivy suppress only no-fix-yet CVEs, using each scanner's native mechanism (state-based or with an expiry).
[0.6.2] - 2026-09-05
Fixed
stable's copied CI/security workflows (ci,codeql,sonarcloud,snyk,safety,dependency-review,qlty-coverage,docker) still filtered onbranches: [main]— a leftover from copying them frommain— so none of them ever actually ran on a PR or push targetingstable. Retargeted all eight tobranches: [stable]. (docsandscorecarddeliberately left unchanged:docsdeploys to the single shared GitHub Pages site andscorecardisn't a PR gate on either branch, so both need their own ownership decision rather than a mechanical fix here.)Dockerfilestill used the pre-uv.lock-aware build pattern (uv build --wheel+uv pip installinto a fresh venv), already fixed onmain: that pattern re-resolves dependencies instead of honoring the lockfile, and can fail to reproduce a pinuv.lockalready settled on. Portedmain's fix (uv sync --frozen --no-editablestraight into the runtime venv path). Verified with a real localdocker build+ boot.
0.6.1 - 2026-09-04
Added
release.ymlnow has aVerify Version & Changeloggate that runs before any test/build/publish step: it fails the whole release ifpyproject.toml's version doesn't exactly match the pushed tag, or ifCHANGELOG.mdhas no matching## [X.Y.Z]heading. Complements the PR-timeVersion Bump Check(which only runs on PRs intostable) by also guarding the tag-push trigger itself — nothing stops a tag from being pushed independently of a PR. Verified live: a deliberately mismatched test tag failed at this gate and never reached the Publish to PyPI step.
0.6.0 - 2026-09-04
Upgrades to FastMCP 4 and the new MCP 2026-07-28 spec, now that
FastMCP 4 has reached a stable GA release (4.0.0, 4.0.1, 4.0.2).
Changed
- Bumped
fastmcpfrom the<4.0cap to>=4.0.0,<5.0, which migrates to MCP Python SDK v2 and the new MCP 2026-07-28 spec (stateless protocol core; no more mandatoryinitialize/Mcp-Session-Id). FastMCP 4 negotiates the best mutual protocol era per client by default, so existing clients still on the older handshake keep working unchanged — verified: full unit suite (728 tests), lint, mypy, both transports (stdioandstreamable-http) boot and complete a legacyinitialize→notifications/initialized→tools/listround trip, the live read-only integration suite against a real Play Console app, and the Docker build all pass unmodified. This was soaked onmainfirst (see the nightly-only beta trial and this GA follow-up) before promotion here.
0.5.1 - 2026-08-19
Added
- CI now requires a version bump and a matching
CHANGELOG.mdentry on every pull request intostable, so a dependency or config change can no longer slip onto the production branch without a corresponding release.
Dependencies
- Capped
fastmcpto<4.0(was open-ended>=3.4.2). FastMCP 4 targets the new, backward-incompatible MCP 2026-07-28 spec (stateless protocol core, no moreinitialize/Mcp-Session-Id) and is still in beta upstream. This keeps Dependabot from silently proposing/merging a major protocol jump; the migration will be a deliberate, manually-verified upgrade instead.
0.5.0 - 2026-08-14
Adds opt-in code-mode, migrates the server onto the standalone fastmcp
package, hardens shared-client concurrency, and removes the non-functional Vitals
tools.
Breaking — Vitals tools removed.
get_vitals_overviewandget_vitals_metricsno longer exist (see Removed); they returned placeholder data and never called an API.
Added
- Experimental code-mode (opt-in): set
CODE_MODE=1to expose tools through FastMCP's code-mode transform (search/get_schema/executemeta-tools with a sandboxed executor) instead of the full tool list, reducing per-request tool-list token overhead. Off by default; requires theplay-store-mcp[code-mode]extra (Monty sandbox) for theexecutetool. This is the first step of the tool-surface reduction noted under Planned.
Changed
- Migrated the server framework from the official MCP SDK's
FastMCP(mcp.server.fastmcp) to the standalonefastmcppackage (v3). Behavior is unchanged — all 117 tools, the/healthand/credentialsroutes, per-request header credentials, admin-token auth, read-only mode, and DNS-rebinding protection (PLAY_STORE_MCP_DISABLE_DNS_REBINDING) are preserved. This unblocks the upcoming code-mode capability, which lives only infastmcp. - Breaking: APK/AAB downloads are now always confined to a directory —
there is no "write anywhere" mode. The base directory is
PLAY_STORE_MCP_DOWNLOAD_DIRwhen set, otherwise the server's current working directory; adestination_paththat resolves outside it is rejected. On network transports (--transport sse/streamable-http), settingPLAY_STORE_MCP_DOWNLOAD_DIRis recommended but not required — the server logs a warning (rather than refusing to start) when it is unset and falls back to the working directory. Point it at a writable directory on cloud/hosted deployments (e.g./tmp/play-store-downloadson Render), where the working directory may be read-only.
Removed
- Breaking: removed the non-functional
get_vitals_overviewandget_vitals_metricstools. They never called an API and returned hardcoded placeholder data; Android Vitals requires the separate Play Developer Reporting API, which is out of scope for this server.
Fixed
get_order/batch_get_ordersnow read the real v3Orderresource: product IDs fromlineItems[].productId(exposed asproduct_ids/line_items) and status from thestatestring enum. Previously they read non-existent top-levelproductId/purchaseStatefields, so those values were always null against the live API and order state was lost.list_in_app_productsnow followstokenPagination.nextPageTokeninstead of returning only the first page (apps with many SKUs were silently truncated).get_reviewsandlist_voided_purchasesnow paginate tomax_resultsacross pages viatokenPagination, rather than returning a single page.delete_subscription_offernow returns the parentproduct_idinstead of mislabeling the deletedoffer_idasproduct_id.- Media downloads (
download_generated_apk/download_system_apk_variant) now acquire the client's transport lock per chunk, closing a gap in the shared-client thread-safety fix: a download concurrent with another call on the shared client no longer races on the non-thread-safehttplib2transport (which could corrupt the downloaded file or raiseResponseNotReady). - The shared (env /
/credentials) client now serializes its HTTP transport with a per-client lock, so concurrent tool calls under network transports no longer race on the non-thread-safehttplib2connection (which could interleave requests or deliver a response to the wrong caller). Per-request header-auth clients each get their own client and stay fully concurrent.
Security
- APK/AAB downloads (
download_generated_apk,download_system_apk_variant) now write to a temporary file and atomically rename on success, so a failed or unauthorized download can no longer truncate an existing file or leave a partial one at the destination. - Download-destination confinement lives in
PlayStoreClientand applies to both the temporary.partfile and the final file: every destination is canonicalized and verified to stay within the (always-present) base directory before anything is written, closing the path-traversal / arbitrary-file-overwrite vector (SonarCloudS2083). Downloads are always confined —PLAY_STORE_MCP_DOWNLOAD_DIRwhen set, otherwise the working directory. - Documented that the server-side credential fallback
(
GOOGLE_PLAY_STORE_CREDENTIALS//credentials) is a process-global client shared by every request that omits a credential header; multi-tenant deployments should leave it unset so a missing header fails closed rather than running under a shared identity. - Recommend pairing code-mode with
--read-only/PLAY_STORE_MCP_READ_ONLY=1unless writes are needed: oneexecutecan invoke up to 50 tool calls (including mutations) behind a single approval. Read-only enforcement still applies inside the sandbox.
Dependencies
- Bumped
pyasn10.6.3 → 0.6.4 (CVE-2026-59885, CVE-2026-59886) andcryptography49.0.0 → 50.0.0 (PYSEC-2026-3552) — HIGH-severity advisories in transitive dependencies (viagoogle-auth/pyjwt[crypto]).pip-auditclean.
[0.4.0] - 2026-07-02
Major feature expansion: grows from ~24 to 119 MCP tools, adding broad coverage of the Google Play Developer API, plus reliability/security hardening and a full dependency refresh.
Note — write endpoints are beta. The new write/mutating tools in this release are covered by unit tests (mocked), but only read-only paths have been exercised against the live Play API. Treat create/update/patch/delete/upload/ purchase-action/migrate tools as beta and open an issue for any problems. Run with
--read-only/PLAY_STORE_MCP_READ_ONLY=1to disable all write operations.Note — tool count. 119 tools is a large surface for a single MCP server: it increases per-request token usage and some clients cap/truncate large tool lists. A follow-up release will reduce this.
Added
- Purchases & orders: in-app product purchases (
get/acknowledge/consume); purchase management (refund_order,cancel/defer/revoke_subscription_purchase,get_product_purchase_v2);get_review,batch_get_orders. - Monetization catalog: in-app products, subscriptions, subscription base plans (incl. price migration), subscription offers, one-time products, and one-time product purchase options & offers.
- Artifacts & uploads: edit upload pipeline (APKs, app bundles, deobfuscation and expansion files); store-listing images; generated APK list + download; system APK variants; internal app sharing uploads.
- Account & configuration: external transactions (alternative billing); device tier configs; app data safety labels; app recovery actions; Play Console account access (users & grants).
- Read-only mode:
--read-only/PLAY_STORE_MCP_READ_ONLYdisables all write operations.
Changed
- Transient errors (429/500/503) are retried with exponential backoff on real API calls, and the retry is idempotency-aware — non-idempotent (POST) mutations are not retried on an ambiguous 5xx, to avoid duplicate side effects.
/credentialsendpoint hardened: optionalPLAY_STORE_MCP_ADMIN_TOKEN(constant-time bearer check) for deployments behind a reverse proxy; blocking credential validation moved off the event loop.- Consistent error contract: read methods raise
PlayStoreClientErrorinstead of leaking rawHttpError, and edit transactions are always cleaned up on failure. - List endpoints now follow
nextPageToken— fixes silent truncation (including the account-access user list). - CI: PyPI publish gated on tests/lint/type-check; least-privilege Docker workflow
permissions; pinned
uv.
Fixed
list_app_recoveriesnow sends the API-requiredversionCode(previously rejected).__version__is single-sourced from package metadata (was a stale0.2.0).- Case-insensitive
.aabdetection. - Subscription
start_time/expiry_timepopulated from the v2 response.
Security
pyjwt[crypto]>=2.12.0is now a declared dependency so the CVE-2026-32597 fix reaches installs, not just the lockfile.- Credential-update error responses no longer leak exception text.
Dependencies
- Refreshed all dependencies to latest, including the majors mypy 2.x and protobuf 7.x. Validated: 697 tests / 100% branch coverage, ruff/mypy clean, pip-audit clean, and a live read-only API smoke.
0.3.0 - 2026-06-19
Security hardening, dependency upgrades, and CI improvements.
Added
- Configurable DNS-rebinding protection via the
PLAY_STORE_MCP_DISABLE_DNS_REBINDINGenvironment variable (for cloud / reverse-proxy deployments).
Changed
- Upgraded
mcp1.26.0 → 1.28.0 andcryptography46.0.7 → 49.0.0. - Hardened CI workflows, suppressed scanner false positives, and addressed code-review findings.
[0.2.0] and earlier
See the GitHub Releases page.