Skip to main content
Glama
Google Play Console MCP

Google Play Console MCP

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.

CI PyPI Python Coverage License

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 moves

Why 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 install

You 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-run

install 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-mcp

The 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 secrets

install --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 command

It 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

  1. Service account — Google Cloud Console → enable the Google Play Android Developer API → create a service account → download the JSON key.

  2. Invite it in Play Console → Users and permissions.

  3. Grant these, which are separate and independently forgettable:

For

Grant

Publishing

the app permissions you want the agent to have

Android vitals

the playdeveloperreporting scope

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_image catches 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.free must not collect com.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 -rn

Tools

170 tools. Full reference in docs/.

Area

Examples

Releases

deploy_app promote_release update_rollout halt_release create_track

Edits

create_edit describe_edit validate_open_edit commit_edit delete_edit

Listings

update_listing list_locale_coverage clone_listing_to_locale upload_image

Vitals

query_vitals get_vitals_freshness search_error_issues list_vitals_anomalies

Revenue

get_earnings_report get_installs_report get_store_conversion_report get_search_terms

Monetization

create_subscription subscription_offers_* acknowledge_subscription_purchase refund_order

Reviews

get_reviews reply_to_review delete_review_reply scrape_reviews

ASO

search_store get_keyword_suggestions browse_store_chart check_aso_metadata

Assets

preflight_store_image preflight_screenshot_set list_store_image_specs

Escape hatch

android_publisher_invoke

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=100

No 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

Apache-2.0.


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 tools
acknowledge_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

    B3.2/5.0

    Scored across 170 tools

    Disambiguation4/5

    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.

    Naming Consistency4/5

    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.

    Tool Count2/5

    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.

    Completeness5/5

    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

    ActivityStale
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Integrates 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 npm
      11
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Enables 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.
      44
      46 npm
      2
      MIT
    • F
      license
      B
      quality
      C
      maintenance
      Enables AI agents to manage Google Play apps with scope-based permissions, including publishing internal releases, updating listings, replying to reviews, and promoting to production.
      11
      8 npm
      -