Google Play Console MCP
Provides tools for managing Google Play Console apps, including releasing and updating app versions across tracks, monitoring Android vitals like crash and ANR rates, fetching earnings and acquisition reports, managing store listings and assets, and replying to user reviews.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Google Play Console MCP How much did we make in July, and where did installs come from?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Google Play Console MCP
An MCP server for Google Play Console — 170 tools across publishing, Android vitals, revenue, ASO and store assets, with a confirmation gate on every destructive write.
Connect Claude, Cursor, VS Code, Codex, Gemini CLI, Windsurf, or any Model Context Protocol client to your Google Play developer account.
Ship a release. Ask whether it is crashing. Find out what it earned. See what users searched to find it. Reply to a review. All of it from the same session, and none of the destructive parts without you saying yes first.
"Is the 4.2.1 rollout healthy?"
-> vitals freshness checked, crash rate by version code, worst issue's stack
trace, current rollout fraction — and a recommendation
"How much did we make in July, and where did the installs come from?"
-> earnings per currency, acquisitions by traffic source, top search terms
"Ship 4.2.2 to production at 10%."
-> a preview you have to approve before anything movesWhy this one
There are a dozen Google Play MCP servers. This is the only one that covers the whole surface, and the only one with a real test suite.
this | typical | |
Tools | 170 | 3–150 |
Google APIs covered | 3 + scraping + preflight | 1 |
Tests | 1,198 | 0 |
Branch coverage | 100% | none |
Confirmation gate on destructive writes | yes | no |
Audit log | yes | no |
Most servers wrap the Android Publisher API and stop. That API cannot tell you your crash rate, cannot tell you what you earned, and cannot tell you what people typed to find your app — those live in two other places entirely.
Related MCP server: Google Play Store MCP Server
The five planes
Plane | Source | What it answers |
Publishing | Android Publisher v3 | releases, tracks, listings, IAP, subscriptions, testers, users |
Android vitals | Play Developer Reporting v1beta1 | crash rate, ANR rate, slow start, wakelocks, error clusters, anomalies |
Revenue & acquisition | Cloud Storage bulk reports | earnings, installs, ratings, store conversion, search terms |
Public store | scraping | competitor listings, chart positions, keyword suggestions, review history |
Store assets | local | image preflight against Play's real rules, before upload |
Each needs different credentials, and the server tells you exactly which are missing and how to fix it.
Install
One line. It registers the server with the agents you pick:
uvx google-play-console-mcp installYou get a checklist of the agents found on your machine — Claude Code, Claude Desktop, Cursor, VS Code, Codex, Gemini CLI, Windsurf, Zed, Cline, Goose, OpenCode and 11 more — and it writes the right config file, in the right shape, for each one. No JSON to paste, no path to look up.
# pick agents up front and skip the prompts
uvx google-play-console-mcp install -a claude-code -a cursor -y
# read-only, with credentials wired in
uvx google-play-console-mcp install --read-only --credentials ~/sa-key.json
# see the exact command without running it
uvx google-play-console-mcp install --dry-runinstall keeps the server itself out of your way: uvx fetches it on demand,
so there is nothing to upgrade by hand. To keep a permanent copy instead:
uv tool install google-play-console-mcp # then: google-play-console-mcp install
pipx install google-play-console-mcp
pip install google-play-console-mcpThe wiring is done by add-mcp
(Apache-2.0), which tracks each agent's config format so this server does not
have to. It runs under npx; if you have no Node.js, install prints the exact
config block to paste instead of failing.
Add to your MCP client (.mcp.json, claude_desktop_config.json, …). VS Code
nests the same entry under servers, Zed under context_servers:
{
"mcpServers": {
"google-play-console": {
"command": "uvx",
"args": ["google-play-console-mcp"],
"env": {
"GOOGLE_APPLICATION_CREDENTIALS": "/path/to/service-account.json",
"GOOGLE_PLAY_MCP_REPORTS_BUCKET": "pubsite_prod_rev_0123456789"
}
}
}
}Credentials
Store the key in your OS keychain — macOS Keychain, Windows Credential Locker, or a Freedesktop Secret Service on Linux — instead of leaving it on disk or naming its path in an agent config file:
google-play-console-mcp credentials set service-account.json
google-play-console-mcp credentials show # what is configured; prints no secretsinstall --credentials key.json --keychain does both in one step: files the key
in the keychain and writes nothing about it into any config file.
Environment variables still work and take precedence — GOOGLE_APPLICATION_CREDENTIALS
(path) or GOOGLE_PLAY_STORE_CREDENTIALS (path or inline JSON) — which is what
you want in CI, in Docker, and anywhere a keychain does not exist.
User OAuth instead of a service account:
google-play-console-mcp credentials adc # prints the exact login commandIt authenticates with gcloud auth application-default login, so there is no
client secret or redirect to configure. It is off unless you set
GOOGLE_PLAY_MCP_USE_ADC=1 (or pass --use-adc): those credentials are
ambient machine state shared with every Google tool, and a server that can ship
a release should never quietly inherit whoever ran gcloud last.
Resolution order:
--credentials, then the environment variables, then the keychain, then ADC if enabled.
Setting up the service account
Service account — Google Cloud Console → enable the Google Play Android Developer API → create a service account → download the JSON key.
Invite it in Play Console → Users and permissions.
Grant these, which are separate and independently forgettable:
For | Grant |
Publishing | the app permissions you want the agent to have |
Android vitals | the |
Revenue & acquisition | the account-level permission "View app information and download bulk reports" |
The bulk-reports permission is account-level. An app-level grant does not reach the storage bucket, and this is the most common reason those tools 403.
Find the bucket in Play Console → Download reports → Statistics → Copy Cloud Storage URI. There is no API that discovers it.
Then ask the agent to run check_reports_access, or read the
gplay://config resource — both report what is configured and what each missing
piece needs.
Safety
This server can ship to production, delete every localized listing, and refund real money. So:
18 destructive tools are behind a two-step confirmation gate. The first call returns a preview — what changes, what it affects, how to undo it — and does nothing. Only a second call carrying that preview's token executes.
The token is an HMAC over a canonical fingerprint of the operation, keyed with a per-install secret the model never sees. It catches an invented token, a token from a different operation, a parameter changed between preview and confirmation, a reused token, and an expired one.
The threat model is not a hostile user. It is a language model that hallucinates confirmation tokens, replays old ones, adjusts a parameter between the preview and the confirmation, and will cheerfully report that you approved something you never saw.
halt_release is deliberately not gated. Stopping a bad rollout is the one
operation that reduces blast radius, and demanding a second round-trip during an
incident is itself the failure mode.
Production cannot go to 100% in one step. The staged-rollout policy caps the first step at 20%; reaching everyone requires the separately named widening tool. The ceiling can be tightened but not disabled.
Everything is audited — previewed, confirmed, rejected, executed, failed —
to ~/.google-play-mcp/audit.jsonl, with secrets scrubbed.
GOOGLE_PLAY_MCP_READ_ONLY=1 refuses every write outright, for exploratory
sessions.
See Write safety.
What it knows that you would otherwise learn the hard way
Screenshots must have no alpha channel. The icon must have one. A plain "PNG" export is RGBA — right for the icon, rejected for everything else.
preflight_store_imagecatches it before a sixty-image bake fails at upload.Play requires at least two screenshots per type per locale. A set of one is valid file by file and still blocks the release.
A subscription purchase left unacknowledged for three days is automatically refunded and revoked by Google. Silent revenue loss.
The API accepts a 50-character title; Play policy caps it at 30. It uploads fine and is rejected at review.
Vitals data lags by hours to days. A seven-day query that returns five days reads as an improvement rather than a gap, so freshness is a first-class tool.
Earnings for a month are not published until around the 5th of the next. Reporting "$0" before that reads as a collapse rather than an absence.
Earnings are never summed across currencies — 1000 JPY plus 10 EUR is a wrong answer that looks like a right one.
com.acme.freemust not collectcom.acme.freemium's revenue. Attribution stops at a package-segment boundary.A vitals query must be aligned to its aggregation period and must not run past the freshness. A window built the obvious way —
now()minus seven days — violates both and is rejected outright, so the client truncates the timestamps and clamps the end to the data that exists.The vitals API publishes no default metrics, rejects a query without them, and requires a dimension for some metric sets (
startType,reportType). The accepted combinations are filled in for you.
The failure log
Tool calls that fail are appended to ~/.local/state/google-play-console-mcp/tool-failures.jsonl
as one JSON object per line: timestamp, tool, outcome, detail, duration and the
(redacted) arguments.
This exists because an MCP client's logs record that a tool completed, not
what it answered. A tool that returns {"error": ...} — how this server reports
validation failures, missing configuration and partial successes — is written
down as a success, so the most common kind of failure leaves no trace anywhere
you would think to look. The log records both shapes: raised for an exception,
returned_error for an error-shaped payload.
Arguments whose names look sensitive (purchase_token, apiKey, anything
matching token/credential/secret/password) are redacted, long values truncated,
and the file rotates at 5 MB keeping one previous generation. Writing is
best-effort: a full disk can never fail a tool call.
GOOGLE_PLAY_MCP_AUDIT_LOG=/path/to/log.jsonl— write somewhere else.GOOGLE_PLAY_MCP_NO_AUDIT_LOG=1— switch it off.
# what has been failing, most frequent first
jq -r '.tool + " — " + .detail' ~/.local/state/google-play-console-mcp/tool-failures.jsonl \
| sort | uniq -c | sort -rnTools
170 tools. Full reference in docs/.
Area | Examples |
Releases |
|
Edits |
|
Listings |
|
Vitals |
|
Revenue |
|
Monetization |
|
Reviews |
|
ASO |
|
Assets |
|
Escape hatch |
|
Plus 2 prompts (publish_screenshots, release_health_check) and 4 resources.
Tested
1,198 tests · 100% line and branch coverage · 15 modules
ruff clean · mypy clean · every release gated at --cov-fail-under=100No live API call in the suite. Report parsing is tested against real UTF-16 bytes, image preflight against real PNG and JPEG headers.
Out of scope
Screenshot generation — use appscreens or any other tool, then preflight and upload here.
Licence
Keywords: google play console mcp, google play mcp server, android publisher api mcp, play developer api, model context protocol google play, android app release automation, android vitals mcp, play console api claude, aso mcp, google play earnings api, play store screenshots api, android release agent
Available Tools
170 toolsacknowledge_product_purchaseAcknowledge Product PurchaseA
Acknowledge an in-app product purchase.
Purchases not acknowledged within 3 days are automatically refunded.
Inputs: product_id (required), package_name (required), purchase_token (required), developer_payload. Returns: object
acknowledge_subscription_purchaseAcknowledge Subscription PurchaseA
Acknowledge a subscription purchase.
Google automatically refunds and revokes any subscription purchase that is not acknowledged within three days, so this must run for every new subscription purchase.
Inputs: package_name (required), purchase_token (required), subscription_id (required), developer_payload. Returns: object
activate_base_planActivate Base PlanA
Activate a subscription base plan, making it available to new subscribers.
Disabled in read-only mode.
Inputs: product_id (required), base_plan_id (required), package_name (required). Returns: object
activate_purchase_option_offerActivate Purchase Option OfferB
Activate a one-time product offer, making it available to eligible buyers.
Disabled in read-only mode.
Inputs: offer_id (required), product_id (required), package_name (required), purchase_option_id (required). Returns: object
activate_subscription_offerActivate Subscription OfferA
Activate a subscription offer, making it available to eligible subscribers.
Disabled in read-only mode.
Inputs: offer_id (required), product_id (required), base_plan_id (required), package_name (required). Returns: object
add_app_recovery_targetingAdd App Recovery TargetingA
Add targeting to an app recovery action.
Disabled in read-only mode.
Inputs: targeting (required), package_name (required), app_recovery_id (required). Returns: object
add_externally_hosted_apkAdd Externally Hosted ApkA
Register an APK hosted outside Play (enterprise distribution only).
Usable only by organisations enrolled in Play for Work private-channel distribution; a normal developer account receives a 403.
Inputs: package_name (required), externally_hosted_apk (required). Returns: object
android_publisher_invokeAndroid Publisher InvokeA
Call any Android Publisher v3 method directly, by dotted path.
An escape hatch for endpoints this server has not wrapped and for ones Google ships after this release. Prefer the dedicated tools: they validate input, normalise output and carry the safety rules. Use this when there is no dedicated tool.
Read methods run immediately. Anything that mutates is a two-step confirmed write, decided from the HTTP verb the API itself uses.
Inputs: method_path (required), params, confirm, confirmation_token. Returns: object
archive_subscriptionArchive SubscriptionA
Archive a subscription.
Play recommends archiving rather than deleting any subscription that has ever been purchased: deletion is rejected once purchases exist, and archiving preserves the record for existing subscribers.
Inputs: product_id (required), package_name (required). Returns: object
batch_delete_in_app_productsBatch Delete In App ProductsA
Delete multiple in-app products in a single operation.
Disabled in read-only mode.
Inputs: skus (required), package_name (required). Returns: object
batch_delete_one_time_productsBatch Delete One Time ProductsA
Delete multiple one-time products in a single operation.
Disabled in read-only mode.
Inputs: requests (required), package_name (required). Returns: object
batch_delete_purchase_option_offersBatch Delete Purchase Option OffersB
Delete multiple one-time product offers in a single operation.
Disabled in read-only mode.
Inputs: requests (required), product_id (required), package_name (required), purchase_option_id (required). Returns: object
batch_delete_purchase_optionsBatch Delete Purchase OptionsA
Delete multiple purchase options from a one-time product in a single operation.
Disabled in read-only mode.
Inputs: requests (required), product_id (required), package_name (required). Returns: object
batch_deployBatch DeployA
Deploy an app to multiple tracks in a single operation.
This is useful for deploying to internal and alpha tracks simultaneously, or for promoting to multiple testing tracks at once.
Inputs: tracks (required), file_path (required), package_name (required), confirm, +3 more. Returns: object
batch_get_in_app_productsBatch Get In App ProductsC
Get details for multiple in-app products at once.
Inputs: skus (required), package_name (required). Returns: result
batch_get_one_time_productsBatch Get One Time ProductsA
Get details for multiple one-time products at once.
Inputs: product_ids (required), package_name (required). Returns: result
batch_get_ordersBatch Get OrdersB
Get detailed information for multiple orders at once.
Inputs: order_ids (required), package_name (required). Returns: result
batch_get_purchase_option_offersBatch Get Purchase Option OffersA
Get details for multiple one-time product offers at once.
Inputs: requests (required), product_id (required), package_name (required), purchase_option_id (required). Returns: result
batch_get_subscription_offersBatch Get Subscription OffersB
Get details for multiple subscription offers in a single operation.
Inputs: requests (required), product_id (required), base_plan_id (required), package_name (required). Returns: result
batch_get_subscriptionsBatch Get SubscriptionsC
Get details for multiple subscription products at once.
Inputs: product_ids (required), package_name (required). Returns: result
batch_migrate_base_plan_pricesBatch Migrate Base Plan PricesB
Migrate prices for multiple base plans in a single operation.
Disabled in read-only mode.
Inputs: requests (required), product_id (required), package_name (required). Returns: object
batch_update_base_plan_statesBatch Update Base Plan StatesA
Activate or deactivate multiple base plans in a single operation.
Disabled in read-only mode.
Inputs: requests (required), product_id (required), package_name (required). Returns: result
batch_update_one_time_productsBatch Update One Time ProductsA
Update multiple one-time products in a single operation.
Disabled in read-only mode.
Inputs: requests (required), package_name (required). Returns: result
batch_update_purchase_option_offersBatch Update Purchase Option OffersB
Create or update multiple one-time product offers in a single operation.
Disabled in read-only mode.
Inputs: requests (required), product_id (required), package_name (required), purchase_option_id (required). Returns: result
batch_update_purchase_option_offer_statesBatch Update Purchase Option Offer StatesA
Activate, deactivate or cancel multiple one-time product offers in a single operation.
Disabled in read-only mode.
Inputs: requests (required), product_id (required), package_name (required), purchase_option_id (required). Returns: result
batch_update_purchase_option_statesBatch Update Purchase Option StatesB
Activate or deactivate multiple purchase options in a single operation.
Disabled in read-only mode.
Inputs: requests (required), product_id (required), package_name (required). Returns: result
batch_update_subscription_offersBatch Update Subscription OffersC
Update multiple subscription offers in a single operation.
Disabled in read-only mode.
Inputs: requests (required), product_id (required), base_plan_id (required), package_name (required). Returns: result
batch_update_subscription_offer_statesBatch Update Subscription Offer StatesA
Activate or deactivate multiple subscription offers in a single operation.
Disabled in read-only mode.
Inputs: requests (required), product_id (required), base_plan_id (required), package_name (required). Returns: result
batch_update_subscriptionsBatch Update SubscriptionsC
Update multiple subscription products in a single operation.
Disabled in read-only mode.
Inputs: requests (required), package_name (required). Returns: result
browse_store_chartBrowse Store ChartA
Browse a store chart: top free, top paid or top grossing.
Inputs: age, num, country, category, +2 more. Returns: object
cancel_app_recoveryCancel App RecoveryB
Cancel an app recovery action.
Disabled in read-only mode.
Inputs: package_name (required), app_recovery_id (required). Returns: object
cancel_purchase_option_offerCancel Purchase Option OfferA
Cancel a one-time product offer (e.g. a pre-order offer).
Disabled in read-only mode.
Inputs: offer_id (required), product_id (required), package_name (required), purchase_option_id (required). Returns: object
cancel_subscription_purchaseCancel Subscription PurchaseC
Cancel a subscription purchase.
Inputs: package_name (required), purchase_token (required), cancellation_type. Returns: object
check_aso_metadataCheck Aso MetadataA
Check listing text against Play POLICY limits, not just what the API accepts.
The distinction matters: the Android Publisher API accepts a 50-character title and Play then rejects the app at review, because policy caps titles at 30. A title between 31 and 50 characters is reported as a warning — it will upload successfully and fail review.
Use validate_listing_text for the API-level check before an upload; use this before deciding on the text.
Inputs: title, full_description, short_description. Returns: object
check_reports_accessCheck Reports AccessA
Verify the Play reports bucket is configured and readable. Run this first.
Installs, ratings, earnings and search terms come from a private Cloud Storage bucket rather than an API, and it needs separate setup: the bucket id in GOOGLE_PLAY_MCP_REPORTS_BUCKET, and the ACCOUNT-level Play Console permission "View app information and download bulk reports".
Returns: A diagnosis with the exact remedy when something is missing
Inputs: no named parameters. Returns: object
clone_listing_to_localeClone Listing To LocaleA
Copy a store listing's TEXT from one locale to another.
Copies title, descriptions and video. Images are not cloned — that means downloading and re-uploading every asset, a different operation with different failure modes.
A locale that already has a listing is left alone unless overwrite is set, so this cannot silently replace translated copy with the source language.
Inputs: package_name (required), source_language (required), target_language (required), confirm, +2 more. Returns: object
commit_editCommit EditA
Commit an edit, publishing everything staged in it.
Two-step. The preview is read back from Play — it lists the track releases, listing locales and binaries actually staged — because the content came from earlier calls rather than this one's arguments.
Inputs: edit_id (required), package_name (required), confirm, confirmation_token, +1 more. Returns: object
consume_product_purchaseConsume Product PurchaseA
Consume an in-app product purchase (for consumable products).
Marks the product as consumed so the user can purchase it again.
Inputs: product_id (required), package_name (required), purchase_token (required). Returns: object
convert_region_pricesConvert Region PricesA
Convert one source price into every Play region's price.
Returns Play's own recommended prices rather than a naive FX conversion — values are rounded to each region's local price conventions.
Inputs: package_name (required), price_micros (required), currency_code (required). Returns: object
create_app_recoveryCreate App RecoveryB
Create a draft app recovery action.
Disabled in read-only mode.
Inputs: recovery (required), package_name (required). Returns: object
create_device_tier_configCreate Device Tier ConfigB
Create a new device tier config.
Disabled in read-only mode.
Inputs: config (required), package_name (required), allow_unknown_devices. Returns: object
create_editCreate EditA
Open a draft edit and return its ID.
Nothing reaches users until commit_edit. Stage changes against the returned edit ID, inspect with describe_edit, then commit once.
Edits expire after about seven days. An abandoned one is harmless but worth discarding with delete_edit.
Inputs: package_name (required). Returns: object
create_external_transactionCreate External TransactionB
Create an external (alternative billing) transaction.
Inputs: transaction (required), package_name (required), external_transaction_id (required). Returns: object
create_grantCreate GrantB
Grant a user app-level access.
Disabled in read-only mode.
Inputs: email (required), grant (required), developer_id (required). Returns: object
create_in_app_productCreate In App ProductA
Create a new in-app product in the catalog.
Disabled in read-only mode.
Inputs: product (required), package_name (required). Returns: object
create_subscriptionCreate SubscriptionA
Create a new subscription product in the catalog.
Disabled in read-only mode.
Inputs: product_id (required), package_name (required), subscription (required), regions_version. Returns: object
create_subscription_offerCreate Subscription OfferC
Create a new subscription offer.
Disabled in read-only mode.
Inputs: offer (required), offer_id (required), product_id (required), base_plan_id (required), +2 more. Returns: object
create_system_apk_variantCreate System Apk VariantA
Create a system APK variant from an uploaded app bundle.
Disabled in read-only mode.
Inputs: variant (required), package_name (required), version_code (required). Returns: object
create_trackCreate TrackA
Create a custom closed-testing track.
The four built-in tracks (internal, alpha, beta, production) always exist. This creates an additional named track, which is how closed testing groups beyond the built-ins are set up.
Inputs: track (required), package_name (required), releases. Returns: object
create_userCreate UserA
Grant a user access to a developer account.
Disabled in read-only mode.
Inputs: user (required), developer_id (required). Returns: object
deactivate_base_planDeactivate Base PlanA
Deactivate a subscription base plan, making it unavailable to new subscribers.
Existing subscribers keep their subscription. Disabled in read-only mode.
Inputs: product_id (required), base_plan_id (required), package_name (required). Returns: object
deactivate_purchase_option_offerDeactivate Purchase Option OfferB
Deactivate a one-time product offer, making it unavailable to new buyers.
Disabled in read-only mode.
Inputs: offer_id (required), product_id (required), package_name (required), purchase_option_id (required). Returns: object
deactivate_subscription_offerDeactivate Subscription OfferA
Deactivate a subscription offer, making it unavailable to new subscribers.
Disabled in read-only mode.
Inputs: offer_id (required), product_id (required), base_plan_id (required), package_name (required). Returns: object
defer_subscription_purchaseDefer Subscription PurchaseB
Defer a subscription purchase's next renewal.
Inputs: etag (required), package_name (required), defer_duration (required), purchase_token (required). Returns: object
delete_all_imagesDelete All ImagesA
Delete all store-listing images for a language and image type; commit the edit.
Disabled in read-only mode. Two-step: preview, then confirm.
Inputs: language (required), image_type (required), package_name (required), confirm, +1 more. Returns: object
delete_all_listingsDelete All ListingsA
Delete every localized store listing for an app.
Destructive: the listing text for all locales is removed in one commit and the API offers no undo. Two-step: preview, then confirm.
Inputs: package_name (required), confirm, confirmation_token. Returns: object
delete_base_planDelete Base PlanA
Delete a subscription base plan.
Only inactive base plans with no active subscribers can be deleted. Disabled in read-only mode.
Inputs: product_id (required), base_plan_id (required), package_name (required). Returns: object
delete_editDelete EditA
Discard an open edit. Nothing staged in it takes effect.
Not gated: discarding a draft is the safe direction, and an agent that has to ask permission to clean up leaves edits open instead.
Inputs: edit_id (required), package_name (required). Returns: object
delete_grantDelete GrantC
Remove a user's app-level access.
Disabled in read-only mode.
Inputs: email (required), developer_id (required), package_name (required). Returns: object
delete_imageDelete ImageA
Delete a single store-listing image by ID and commit the edit.
Disabled in read-only mode.
Inputs: image_id (required), language (required), image_type (required), package_name (required). Returns: object
delete_in_app_productDelete In App ProductB
Delete an in-app product from the catalog.
Disabled in read-only mode.
Inputs: sku (required), package_name (required). Returns: object
delete_listingDelete ListingC
Delete one locale's store listing.
Inputs: language (required), package_name (required), confirm, confirmation_token. Returns: object
delete_one_time_productDelete One Time ProductA
Delete a one-time product from the catalog.
Disabled in read-only mode.
Inputs: product_id (required), package_name (required). Returns: object
delete_review_replyDelete Review ReplyA
Retract a developer reply to a review.
The Play API has no delete-reply endpoint. The reply is retracted by replying again with empty text, which clears the visible reply; the result records the mechanism so this is not mistaken for erasure from history.
Inputs: review_id (required), package_name (required), confirm, confirmation_token. Returns: object
delete_subscriptionDelete SubscriptionB
Delete a subscription product from the catalog.
Disabled in read-only mode.
Inputs: product_id (required), package_name (required). Returns: object
delete_subscription_offerDelete Subscription OfferA
Delete a subscription offer.
Only inactive offers with no active subscribers can be deleted. Disabled in read-only mode.
Inputs: offer_id (required), product_id (required), base_plan_id (required), package_name (required). Returns: object
delete_userDelete UserC
Remove a user's access to a developer account.
Disabled in read-only mode.
Inputs: email (required), developer_id (required), confirm, confirmation_token. Returns: object
deploy_appDeploy AppA
Deploy an APK or AAB file to a Play Store track.
Two-step: call with confirm=False first, show the returned preview to the user, and only call again with confirm=True and the preview's confirmation_token once they have approved it.
Inputs: track (required), file_path (required), package_name (required), confirm, +4 more. Returns: object
deploy_app_multilangDeploy App MultilangC
Deploy an APK or AAB file with multi-language release notes.
Inputs: track (required), file_path (required), package_name (required), release_notes (required), +3 more. Returns: object
deploy_app_recoveryDeploy App RecoveryC
Deploy an app recovery action to users.
Disabled in read-only mode.
Inputs: package_name (required), app_recovery_id (required), confirm, confirmation_token. Returns: object
describe_editDescribe EditA
Read back everything currently staged in an open edit.
Use before committing. Sections that cannot be read are listed under
unreadable rather than failing — an edit with no listings staged returns
404 for listings, which is information, not an error.
Inputs: edit_id (required), package_name (required). Returns: object
download_generated_apkDownload Generated ApkB
Download a single generated APK to a local file.
Inputs: download_id (required), package_name (required), version_code (required), destination_path (required). Returns: object
download_system_apk_variantDownload System Apk VariantA
Download a previously created system APK variant to a local file.
Inputs: variant_id (required), package_name (required), version_code (required), destination_path (required). Returns: object
get_app_detailsGet App DetailsB
Get app details including title, description, and developer info.
Inputs: package_name (required), language. Returns: object
get_country_availabilityGet Country AvailabilityB
Read which countries a track is distributed to.
Inputs: track (required), package_name (required). Returns: object
get_crashes_reportGet Crashes ReportA
Daily crash and ANR counts for one month.
Distinct from query_vitals: this is a raw daily count from the bulk report, while the Reporting API gives user-weighted rates against Google's bad-behaviour thresholds. Use query_vitals to judge release health.
Inputs: month (required), package_name (required), dimension. Returns: object
get_device_tier_configGet Device Tier ConfigC
Get a device tier config.
Inputs: package_name (required), device_tier_config_id (required). Returns: object
get_earnings_reportGet Earnings ReportA
Transaction-level earnings for one month, in merchant currency.
Totals are reported PER CURRENCY and never summed across currencies — adding 1000 JPY to 10 EUR would be a wrong answer that looks like a right one, and this server has no exchange rates.
Earnings for a month are published around the 5th of the following month. Before that the report does not exist, and the freshness block says so rather than reporting zero revenue.
Inputs: month (required), package_name. Returns: object
get_expansion_fileGet Expansion FileB
Get APK expansion file information.
Expansion files are used for large apps (especially games) that exceed the 100MB APK size limit.
Inputs: package_name (required), version_code (required), expansion_file_type. Returns: object
get_external_transactionGet External TransactionC
Get an external (alternative billing) transaction.
Inputs: package_name (required), external_transaction_id (required). Returns: object
get_in_app_productGet In App ProductB
Get details of a specific in-app product.
Inputs: sku (required), package_name (required). Returns: object
get_installs_reportGet Installs ReportA
Daily and active-device installs, uninstalls and upgrades for one month.
Inputs: month (required), package_name (required), dimension. Returns: object
get_keyword_suggestionsGet Keyword SuggestionsA
Play's own autocomplete suggestions for a keyword.
These are the terms Play believes users actually type, which is the closest thing to keyword volume the public store exposes. Pair with get_search_terms (bulk reports) to see which of them actually converted for your app.
Inputs: keyword (required), country, language. Returns: object
get_listingGet ListingC
Get store listing for a specific language.
Inputs: package_name (required), language. Returns: object
get_metric_set_metadataGet Metric Set MetadataA
Get metadata for one vitals metric set, including its freshness.
Inputs: metric_set (required), package_name (required). Returns: object
get_one_time_productGet One Time ProductC
Get details of a specific one-time product.
Inputs: product_id (required), package_name (required). Returns: object
get_orderGet OrderC
Get detailed order/transaction information.
Inputs: order_id (required), package_name (required). Returns: object
get_product_purchaseGet Product PurchaseB
Get the status of an in-app product purchase.
Inputs: product_id (required), package_name (required), purchase_token (required). Returns: object
get_product_purchase_v2Get Product Purchase V2A
Get the status of an in-app product purchase using the v2 API.
Unlike get_product_purchase, this identifies the purchase by token alone (no product ID) and returns line items and acknowledgement state.
Inputs: package_name (required), purchase_token (required). Returns: object
get_ratings_reportGet Ratings ReportB
Daily and total average rating for one month.
Inputs: month (required), package_name (required), dimension. Returns: object
get_release_filter_optionsGet Release Filter OptionsA
List the release dimensions usable as query filters for an app.
Use this to discover which tracks and version codes can appear in a query_vitals filter expression.
Inputs: package_name (required). Returns: object
get_releasesGet ReleasesA
Get release status for all tracks of an app.
Inputs: package_name (required). Returns: result
get_reviewGet ReviewA
Get a single user review by its ID.
Inputs: review_id (required), package_name (required), translation_language. Returns: object
get_reviewsGet ReviewsC
Get recent reviews for an app.
Inputs: package_name (required), max_results, translation_language. Returns: result
get_search_termsGet Search TermsB
What users searched to find the app, with acquisitions per term.
Pure ASO data available from no Play API — it exists only in the store performance CSV. Terms are ranked by acquisitions.
Inputs: month (required), package_name (required), top. Returns: object
get_store_conversion_reportGet Store Conversion ReportA
Store-listing visitors, acquisitions and visitor-to-install conversion rate.
The underlying rows also break down by SEARCH TERM and UTM campaign — this is the only place either is available anywhere in Play's APIs. Use get_search_terms for the search-term view specifically.
Inputs: month (required), package_name (required), dimension. Returns: object
get_store_listingGet Store ListingA
Read any app's public store listing — including a competitor's.
Inputs: app_id (required), country, language. Returns: object
get_store_performance_reportGet Store Performance ReportA
Store-listing acquisitions by traffic source or country.
Traffic sources are Play's own: Explore, Search, and Ads / referrals. For visitors and conversion rate use get_store_conversion_report.
Inputs: month (required), package_name (required), dimension. Returns: object
get_subscriptionGet SubscriptionA
Get details of a specific subscription product.
Inputs: product_id (required), package_name (required). Returns: object
get_subscription_offerGet Subscription OfferC
Get details of a specific subscription offer.
Inputs: offer_id (required), product_id (required), base_plan_id (required), package_name (required). Returns: object
get_subscriptions_reportGet Subscriptions ReportA
New, cancelled and active subscriptions by country for one month.
Inputs: month (required), package_name (required). Returns: object
get_subscription_statusGet Subscription StatusC
Get the status of a subscription purchase.
Inputs: package_name (required), purchase_token (required), subscription_id (required). Returns: object
get_system_apk_variantGet System Apk VariantB
Get a previously created system APK variant.
Inputs: variant_id (required), package_name (required), version_code (required). Returns: object
get_testersGet TestersC
Get testers for a specific testing track.
Inputs: track (required), package_name (required). Returns: object
get_vitals_freshnessGet Vitals FreshnessA
Report how current each vitals metric set's data is.
Call this before acting on a vitals number. Play data lags reality by hours to days, so a query covering "the last 7 days" can silently return five — and the absent days look like an improvement rather than a gap.
Inputs: package_name (required), metric_set. Returns: result
halt_releaseHalt ReleaseA
Halt a staged rollout. Takes effect immediately, no confirmation needed.
Deliberately NOT behind the confirmation gate. Halting is the one operation that makes things safer, and demanding a second round-trip during an incident is itself the failure mode. It is still written to the audit log.
Inputs: track (required), package_name (required), version_code (required). Returns: object
list_all_listingsList All ListingsB
List all store listings for all languages.
Inputs: package_name (required). Returns: result
list_apksList ApksB
List the APKs currently uploaded for an app.
Inputs: package_name (required). Returns: result
list_app_recoveriesList App RecoveriesB
List all app recovery actions for an app version.
Inputs: package_name (required), version_code (required). Returns: result
list_bundlesList BundlesA
List the Android App Bundles currently uploaded for an app.
Inputs: package_name (required). Returns: result
list_device_tier_configsList Device Tier ConfigsA
List all device tier configs for an app.
Inputs: package_name (required). Returns: result
list_generated_apksList Generated ApksA
List the APKs Google Play generated from an app bundle version.
Returns one entry per downloadable generated APK (split, standalone,
universal, asset pack slice, or recovery), each with a download ID that can
be passed to download_generated_apk.
Inputs: package_name (required), version_code (required). Returns: result
list_imagesList ImagesA
List the store-listing images for a language and image type.
Inputs: language (required), image_type (required), package_name (required). Returns: result
list_in_app_productsList In App ProductsB
List all in-app products for an app.
Inputs: package_name (required). Returns: result
list_locale_coverageList Locale CoverageB
Which locales have a store listing, and which targets are still missing.
Inputs: package_name (required), target_locales. Returns: object
list_metric_setsList Metric SetsA
List the vitals metric sets this server can query.
Returns: Each metric set's short key (use it as the metric_set argument), the underlying Play resource suffix, and a human-readable label
Inputs: no named parameters. Returns: result
list_one_time_productsList One Time ProductsA
List all one-time products for an app.
Inputs: package_name (required). Returns: result
list_purchase_option_offersList Purchase Option OffersC
List all offers for a one-time product purchase option.
Inputs: product_id (required), package_name (required), purchase_option_id (required). Returns: result
list_report_familiesList Report FamiliesA
List the bulk-report families this server can read.
Returns:
Each family's key, bucket prefix, available dimensions and description.
A family Google no longer publishes carries a discontinued note —
those return no rows for recent months by design, not by failure.
Inputs: no named parameters. Returns: result
list_reporting_appsList Reporting AppsA
List every app the service account can see through the Reporting API.
The Publisher API has no "list my apps" endpoint, so this is the only way to enumerate a portfolio from a service account.
Inputs: max_results. Returns: result
list_report_objectsList Report ObjectsA
List raw object names under a prefix in the reports bucket.
Diagnostic tool: use it when a report comes back empty to see what Play has actually published.
Inputs: prefix (required), max_results. Returns: object
list_store_enumsList Store EnumsA
The collection, category, sort and age values the store tools accept.
Returns: Valid values per argument
Inputs: no named parameters. Returns: object
list_store_image_specsList Store Image SpecsA
Play's requirements for every store-listing image type.
Returns: Per type: accepted formats, whether an alpha channel is required or forbidden, exact or bounded dimensions, aspect limit and size cap
Inputs: no named parameters. Returns: result
list_subscription_offersList Subscription OffersC
List all offers for a subscription base plan.
Inputs: product_id (required), base_plan_id (required), package_name (required). Returns: result
list_subscriptionsList SubscriptionsA
List all subscription products for an app.
Inputs: package_name (required). Returns: result
list_system_apk_variantsList System Apk VariantsB
List previously created system APK variants for an app bundle version.
Inputs: package_name (required), version_code (required). Returns: result
list_usersList UsersA
List users with access to a developer account.
Inputs: developer_id (required). Returns: result
list_vitals_anomaliesList Vitals AnomaliesB
List statistical anomalies Play detected across the vitals metric sets.
Inputs: package_name (required), max_results, filter_expression. Returns: result
list_voided_purchasesList Voided PurchasesB
List voided purchases (refunds, chargebacks).
Inputs: package_name (required), max_results. Returns: result
migrate_base_plan_pricesMigrate Base Plan PricesB
Migrate subscribers to the current base plan prices.
Disabled in read-only mode.
Inputs: request (required), product_id (required), base_plan_id (required), package_name (required). Returns: object
patch_expansion_filePatch Expansion FileA
Point a version's OBB at one already uploaded against another version.
Large apps reuse the same expansion file across builds. This associates an existing OBB with a new version code instead of re-uploading it. Uses the API's patch (partial) semantics.
Inputs: package_name (required), version_code (required), references_version (required), expansion_file_type (required). Returns: object
patch_in_app_productPatch In App ProductB
Partially update an existing in-app product.
Disabled in read-only mode.
Inputs: sku (required), product (required), package_name (required). Returns: object
patch_one_time_productPatch One Time ProductA
Create or update a one-time product (patch is create-or-update).
Disabled in read-only mode.
Inputs: product (required), product_id (required), update_mask (required), package_name (required), +1 more. Returns: object
patch_subscriptionPatch SubscriptionB
Partially update an existing subscription product.
Disabled in read-only mode.
Inputs: product_id (required), update_mask (required), package_name (required), subscription (required), +1 more. Returns: object
patch_subscription_offerPatch Subscription OfferB
Partially update an existing subscription offer.
Disabled in read-only mode.
Inputs: offer (required), offer_id (required), product_id (required), update_mask (required), +3 more. Returns: object
preflight_data_safety_csvPreflight Data Safety CsvA
Check a Data Safety CSV export before declaring it.
Play has no API to read the current Data Safety declaration, and set_data_safety replaces it wholesale, so this is the only point at which the file can be inspected. Play's own rejection arrives after the upload and does not name the row.
Structural faults are errors: wrong headers, a row that is not five fields, a Response value that is not exactly TRUE/FALSE/blank (a lowercase "true" is the signature of a spreadsheet re-save), a repeated response, or a single-choice question with two answers.
Everything conditional is a warning. Google adds and renames question IDs without publishing them, and most of a real export is conditional on answers elsewhere in it, so an unrecognised ID or an unanswered group means "look at this", never "stop".
Inputs: csv_path (required). Returns: object
preflight_screenshot_setPreflight Screenshot SetA
Check a whole screenshot set — every file, plus Play's count rules.
Play requires at least TWO screenshots per type per locale and caps at eight. A set of one is the common failure and it is invisible file by file, because each individual image is perfectly valid.
Inputs: image_paths (required), image_type. Returns: object
preflight_store_imagePreflight Store ImageA
Check one image against Play's rules before uploading it.
Reads the file's own header rather than trusting its extension — a .png that is really a JPEG is something export pipelines produce, and Play reads the bytes too.
Returns every problem at once rather than the first, so a fix round-trip handles all of them.
Inputs: image_path (required), image_type (required). Returns: object
promote_releasePromote ReleaseA
Promote a release from one track to another.
Two-step: preview first, then confirm with the preview's token.
Inputs: to_track (required), from_track (required), package_name (required), version_code (required), +3 more. Returns: object
query_vitalsQuery VitalsA
Query an app vitals metric set timeline.
Covers every vital Play exposes: crash rate, ANR rate, slow start, slow rendering, excessive wake-ups, stuck background wakelocks, low-memory-killer rate and error counts. Call list_metric_sets for the available keys and get_vitals_freshness to check the data is current before drawing conclusions.
Inputs: metric_set (required), package_name (required), metrics, days_back, +5 more. Returns: object
refund_external_transactionRefund External TransactionB
Refund an external (alternative billing) transaction.
Inputs: refund (required), package_name (required), external_transaction_id (required). Returns: object
refund_orderRefund OrderC
Refund an order, optionally revoking the user's entitlement.
Inputs: order_id (required), package_name (required), revoke, confirm, +1 more. Returns: object
reply_to_reviewReply To ReviewC
Reply to a user review.
Inputs: review_id (required), reply_text (required), package_name (required). Returns: object
revoke_subscription_purchaseRevoke Subscription PurchaseC
Revoke (refund) a subscription purchase.
Inputs: package_name (required), purchase_token (required), confirm, refund_type, +1 more. Returns: object
scrape_reviewsScrape ReviewsA
Read public reviews, going back further than the Reviews API can.
The authenticated get_reviews tool returns roughly ONE WEEK — that is the API's limit, not this server's. Scraping is the only way to read older reviews, and it works for competitors' apps too.
Inputs: app_id (required), num, sort, country, +1 more. Returns: object
search_error_issuesSearch Error IssuesA
Search grouped error issues — clusters of similar crashes or ANRs.
Inputs: package_name (required), order_by, days_back, max_results, +2 more. Returns: result
search_error_reportsSearch Error ReportsA
Search raw error reports — individual crashes and ANRs with stack traces.
Inputs: package_name (required), days_back, max_results, filter_expression. Returns: result
search_storeSearch StoreA
Search the public Play Store as a user in a given country would see it.
Store ranking is country- and language-specific, so those arguments change the answer rather than decorate it. Use this for competitor research and to see where an app ranks for a term.
Inputs: term (required), num, price, country, +1 more. Returns: object
set_data_safetySet Data SafetyA
Write the data safety labels declaration of an app.
Supply EITHER csv_path (preferred) OR safety_labels, not both.
There is no API to read the current declaration back, so the CSV can only come from Play Console > App content > Data safety > Export to CSV, and that exported file is the only thing that could ever undo this write. Export one before you start.
A csv_path is checked before anything is sent, and the file is read again on the confirm leg — so the approval is bound to the answers, not to the path.
Disabled in read-only mode. Two-step: preview, then confirm.
Inputs: package_name (required), confirm, csv_path, safety_labels, +1 more. Returns: object
update_app_detailsUpdate App DetailsA
Update app-level details: default language and store contact information.
Only the fields supplied are changed; the rest are read from the current details and written back unchanged, because the Play API replaces the whole resource on update.
Inputs: package_name (required), confirm, contact_email, contact_phone, +3 more. Returns: object
update_expansion_fileUpdate Expansion FileA
Set a version's OBB reference, replacing the whole resource.
Same effect as patch_expansion_file but with the API's update (full replace) semantics.
Inputs: package_name (required), version_code (required), references_version (required), expansion_file_type (required). Returns: object
update_grantUpdate GrantC
Update a user's app-level access.
Disabled in read-only mode.
Inputs: email (required), grant (required), update_mask (required), developer_id (required), +1 more. Returns: object
update_in_app_productUpdate In App ProductB
Update (replace) an existing in-app product.
Disabled in read-only mode.
Inputs: sku (required), product (required), package_name (required), auto_convert_missing_prices. Returns: object
update_listingUpdate ListingC
Update store listing for a specific language.
Inputs: language (required), package_name (required), title, video, +2 more. Returns: object
update_rolloutUpdate RolloutA
Update the rollout percentage for a staged release.
Use this to increase or decrease the percentage of users receiving an update. Set to 100 to complete the rollout. Two-step: preview, then confirm.
Inputs: track (required), package_name (required), version_code (required), rollout_percentage (required), +2 more. Returns: object
update_testersUpdate TestersC
Update testers for a specific testing track.
Inputs: track (required), package_name (required), google_groups (required). Returns: object
update_trackUpdate TrackB
Update a track's release configuration.
Inputs: track (required), package_name (required), releases. Returns: object
update_userUpdate UserC
Update a user's account access.
Disabled in read-only mode.
Inputs: user (required), email (required), update_mask (required), developer_id (required). Returns: object
upload_apkUpload ApkA
Upload an APK to a new edit and commit it.
Disabled in read-only mode.
Inputs: apk_path (required), package_name (required). Returns: object
upload_bundleUpload BundleA
Upload an Android App Bundle (.aab) to a new edit and commit it.
Disabled in read-only mode.
Inputs: bundle_path (required), package_name (required). Returns: object
upload_deobfuscation_fileUpload Deobfuscation FileA
Upload a deobfuscation (ProGuard mapping or native symbols) file.
Disabled in read-only mode.
Inputs: file_path (required), package_name (required), version_code (required), deobfuscation_file_type. Returns: object
upload_expansion_fileUpload Expansion FileA
Upload an APK expansion file (OBB) to a new edit and commit it.
Disabled in read-only mode.
Inputs: file_path (required), package_name (required), version_code (required), expansion_file_type. Returns: object
upload_imageUpload ImageA
Upload a store-listing image (PNG or JPEG) to a new edit and commit it.
Disabled in read-only mode.
Inputs: language (required), image_path (required), image_type (required), package_name (required). Returns: object
upload_internal_app_sharing_apkUpload Internal App Sharing ApkA
Upload an APK to internal app sharing.
Disabled in read-only mode.
Inputs: apk_path (required), package_name (required). Returns: object
upload_internal_app_sharing_bundleUpload Internal App Sharing BundleB
Upload an app bundle (.aab) to internal app sharing.
Disabled in read-only mode.
Inputs: bundle_path (required), package_name (required). Returns: object
validate_editValidate EditA
Dry-run an edit: open one, validate it, then discard it.
Nothing is committed. Most useful as a permissions probe before a deploy — a failure here means the service account cannot publish, which is better learned before a release than during one.
Inputs: package_name (required). Returns: object
validate_listing_textValidate Listing TextA
Validate store listing text lengths before updating.
Inputs: title, full_description, short_description. Returns: object
validate_open_editValidate Open EditA
Dry-run an open edit without committing or discarding it.
Unlike validate_edit, which opens and throws away its own edit, this checks one you are building.
Inputs: edit_id (required), package_name (required). Returns: object
validate_package_nameValidate Package NameA
Validate package name format before using it in other operations.
Inputs: package_name (required). Returns: object
validate_trackValidate TrackA
Validate track name before using it in deployment operations.
Inputs: track (required). Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 170 tools
Most tools map to a unique resource+action, and the descriptions work hard to separate near-neighbors like get_reviews vs scrape_reviews, get_product_purchase vs get_product_purchase_v2, and update_expansion_file vs patch_expansion_file. A few clusters—deploy_app/deploy_app_multilang/batch_deploy and the expansion-file operations—still require careful reading, so it is not perfectly unambiguous.
The overwhelming majority are lowercase snake_case verb_noun names with predictable get/list/create/update/delete/batch/upload/download prefixes. Minor exceptions like android_publisher_invoke and deploy_app_multilang keep it from a perfect 5, but the overall pattern is highly learnable.
170 tools is far beyond typical MCP norms and will heavily burden an agent's context and tool-selection space. The scope genuinely covers much of the Play Console, so it is not an extreme mismatch, but the count is still too high for comfortable use.
The set covers the full lifecycle for releases, monetization, subscriptions, orders, store listings, reviews, reporting, vitals, and user access. The android_publisher_invoke escape hatch also covers unwrapped endpoints, and the preflight/validation tools close operational dead ends.
Maintenance
Related MCP Connectors
Ship a new Android app from chat. AI runs Google Play Console listings, screenshots, releases, uplo…
AI-agent operations for App Store Connect and Google Play, with approval before live publishing.
AI-agent operations for App Store Connect and Google Play, with approval before live publishing.
Manage App Store and Google Play apps: listings, screenshots, builds, releases, reviews, metrics.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceIntegrates Google Play Store management tools with AI assistants to handle app listings, releases, and user reviews. It enables users to manage Play Console tasks like updating store information and retrieving performance statistics through natural language.305 npm11MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to automate Android release workflows via the Google Play Android Publisher API, including deploying builds, managing listings, and replying to reviews.40MIT
- AlicenseAqualityBmaintenanceEnables AI agents to manage Google Play Console reviews and Android app releases, including replying to reviews, promoting releases, managing staged rollouts, and checking crash/ANR vitals.4446 npm2MIT
- FlicenseBqualityCmaintenanceEnables AI agents to manage Google Play apps with scope-based permissions, including publishing internal releases, updating listings, replying to reviews, and promoting to production.118 npm-