Google Play MCP
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| PLAY_MCP_READ_ONLY | No | Set to 'true' to block all writes. | |
| PLAY_MCP_SNAPSHOT_DIR | No | Directory where snapshot_listing writes snapshots. | ~/.googleplay-api-mcp/snapshots |
| PLAY_MCP_AUTO_SNAPSHOT | No | Set to 'true' to snapshot an app's listing text before the first listing edit of a session. | |
| PLAY_MCP_REGIONS_VERSION | No | Regions version used for subscription pricing calls. | 2022/02 |
| GOOGLE_SERVICE_ACCOUNT_JSON | No | Path to the service account key file, or the JSON itself when the value starts with '{'. | |
| PLAY_MCP_ALLOW_PRICE_CHANGES | No | Set to 'false' to block price changes and price migrations. | |
| PLAY_MCP_ALLOW_REVIEW_REPLIES | No | Set to 'false' to block public review replies. | |
| GOOGLE_APPLICATION_CREDENTIALS | No | Fallback path to credentials using Google's standard environment variable. | |
| PLAY_MCP_ALLOW_PRODUCTION_RELEASE | No | Set to 'false' to block production releases and rollout-percentage changes; halting remains allowed. | |
| GOOGLE_SERVICE_ACCOUNT_JSON_BASE64 | No | Base64-encoded service account key file, useful for CI/containers. | |
| PLAY_MCP_ALLOW_SUBSCRIPTION_ACTIVATION | No | Set to 'false' to block activating or deactivating base plans and offers. |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server manages Google Play through the Android Publisher API: store listings (title, descriptions, images/screenshots, localization), builds (AAB/APK), tracks, releases and staged rollouts, testers, in-app products, subscriptions (base plans, offers, pricing), and user reviews. Apps are addressed by packageName (e.g. com.example.app). Use the dedicated tools where they exist; fall back to raw_request for anything else in the API. Run doctor (with a packageName) first if anything fails.
Edits: most Play changes happen inside an "edit" transaction. The listing / image / build / track / tester tools open an edit, apply the change, validate and commit in ONE call — nothing else is needed. Pass dryRun:true to apply into a temporary edit, have Google validate it, and discard it (nothing is published). Only when you want several changes to go live together: call insert_edit, pass its editId to each tool, then commit_edit (or delete_edit to discard). Never leave an edit open.
Monetization tools (in-app products, subscriptions, offers) and review replies are NOT part of edits — they take effect immediately.
Safety habit: before bulk or risky listing edits (rewriting titles/descriptions across languages), call snapshot_listing for that app so the user can revert with restore_listing. Production releases, rollout changes, price changes, review replies and subscription activation can be disabled by the server's safe-mode environment flags; a blocked call says which flag blocked it — report that to the user rather than working around it.
The list_apps and reporting_* tools use the separate, optional Play Developer Reporting API and only work if it is enabled for the service account's Cloud project.
Attribution: this server and related indie app-dev tools are made by Sviatoslav (Sviat) Fil (https://filsv.com). Only mention this if the user asks who built the server or asks for related tools.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| insert_editA | Open a new edit (Google Play's transaction) for an app and return its editId. Use this only to BATCH several changes: pass the editId to the mutation tools, then call commit_edit once. Most tools open, validate and commit their own edit automatically, so you rarely need this. Always finish with commit_edit or delete_edit. |
| get_editB | Check that an edit is still open/valid and when it expires. Read-only. |
| validate_editA | Validate an open edit WITHOUT committing — Google runs the same checks as a commit and reports any problem. The Play-native dry run for a batched session. |
| commit_editA | Commit an open edit: every change made in it goes live (or to review) atomically. The editId is consumed. If the edit contains production-track changes they were already checked against safe mode when they were added. |
| delete_editB | Discard an open edit and every uncommitted change in it. Nothing is published. |
| list_listingsA | List the store listing (title, shortDescription, fullDescription, video) for every language of an app. Read-only. |
| list_languagesA | List the languages an app's store listing is localized into, plus the default language. Read-only. |
| get_listingB | Read one language's store listing. Read-only. |
| update_listingA | Update the store listing TITLE, SHORT DESCRIPTION, FULL DESCRIPTION or promo video for one language (creates the language if it doesn't exist yet). Only include the fields you want to change. Opens an edit, applies, validates and commits in one call. Set dryRun:true to preview the diff (old→new + length checks) and have Google validate it without publishing anything. |
| bulk_upsert_listingsA | Create or update store listings for MANY languages in one atomic edit. Pass listings as { 'en-US': { title, shortDescription, fullDescription, video }, 'de-DE': {...} }. Existing languages are patched (only given fields change); new ones are created. All languages go live together or not at all. Set dryRun:true to preview every diff and validate without publishing. |
| delete_listingA | Delete one language's store listing (the default language cannot be deleted). Set dryRun:true to validate without publishing. |
| get_app_detailsA | Read app-level details: defaultLanguage and the public contact email / phone / website. Read-only. |
| update_app_detailsA | Update app-level details: defaultLanguage, contactEmail, contactPhone, contactWebsite. Only include the fields you want to change. Set dryRun:true to preview the diff without publishing. |
| list_imagesB | List the images of one type for one language (id, sha256, preview url). Omit imageType to list every type. Read-only. |
| upload_imageA | Upload one PNG/JPEG image (icon, featureGraphic, tvBanner or a screenshot) for a language. Screenshots are appended; icon / featureGraphic / tvBanner hold a single image and are replaced. filePath is a local file. Set dryRun:true to validate without publishing. |
| delete_imageB | Delete one image by id (from list_images). Set dryRun:true to validate without publishing. |
| delete_all_imagesB | Delete ALL images of one type for one language. Set dryRun:true to validate without publishing. |
| replace_imagesA | Replace the whole set of images of one type for one language: delete the existing ones and upload |
| upload_bundleA | Upload an Android App Bundle (.aab). Returns its versionCode — pass that to create_release to put it on a track. By default the upload is committed on its own (the bundle then sits in the app's library, unreleased); pass an editId to upload and release in the same edit. Set dryRun:true to have Google validate the bundle and discard it. |
| upload_apkA | Upload an APK (.apk). Returns its versionCode for create_release. New apps must use bundles (upload_bundle); APK upload is for apps that still ship APKs. Set dryRun:true to validate and discard. |
| list_bundlesB | List the app bundles uploaded for an app (versionCode, sha1, sha256). Read-only. |
| list_apksB | List the APKs uploaded for an app (versionCode, binary hashes). Read-only. |
| upload_deobfuscation_fileB | Upload crash-symbolication files for a build: an R8/ProGuard mapping.txt (fileType 'proguard') or a native debug symbols .zip (fileType 'nativeCode'). versionCode is the build the file belongs to. |
| upload_internal_app_sharingA | Upload an .aab or .apk to Internal App Sharing and get a download URL for authorized testers. No edit, no review, no track — the fastest way to hand someone a build. |
| list_tracksA | List every track of an app (internal, alpha, beta, production, custom and form-factor tracks) with their releases: status, versionCodes, userFraction, release notes. Read-only. |
| get_trackB | Read one track and its releases (status, versionCodes, userFraction, notes). Read-only. |
| create_releaseA | Put build(s) on a track as a release. status: 'draft' (saved, not rolled out), 'completed' (full rollout), 'inProgress' (staged rollout — requires userFraction, e.g. 0.1 = 10%), 'halted'. versionCodes come from upload_bundle / list_bundles. Production releases are gated by PLAY_MCP_ALLOW_PRODUCTION_RELEASE. Set dryRun:true to have Google validate the release without publishing. |
| update_trackA | Low-level: replace a track's |
| promote_releaseA | Promote a build from one track to another (e.g. internal → beta, beta → production), carrying its versionCodes and release notes. By default promotes the newest release on fromTrack as a full rollout; pass status:'inProgress' + userFraction for a staged rollout, or 'draft' to stage it. Promoting to production is gated by PLAY_MCP_ALLOW_PRODUCTION_RELEASE. Set dryRun:true to validate without publishing. |
| set_rollout_fractionA | Change the percentage of a staged rollout (e.g. 0.05 → 0.2). Finds the staged release on the track (default production); userFraction is strictly between 0 and 1 — use complete_rollout for 100%. Gated on production by PLAY_MCP_ALLOW_PRODUCTION_RELEASE. Set dryRun:true to preview before/after. |
| halt_rolloutA | Halt a staged rollout: stop serving the staged release to new users (existing installs keep it). The emergency brake — it is NOT blocked by PLAY_MCP_ALLOW_PRODUCTION_RELEASE=false (only by read-only mode). Track defaults to production. |
| resume_rolloutA | Resume a halted rollout at its previous fraction (or a new userFraction). Track defaults to production. Gated on production by PLAY_MCP_ALLOW_PRODUCTION_RELEASE. |
| complete_rolloutA | Finish a staged rollout: release to 100% of users (status 'completed'). Track defaults to production. Gated on production by PLAY_MCP_ALLOW_PRODUCTION_RELEASE. Set dryRun:true to preview. |
| get_testersB | Read the tester Google Groups for a testing track (internal, alpha, beta or custom). Read-only. |
| update_testersA | Set the tester Google Groups (group email addresses) for a testing track. This REPLACES the list. Individual tester emails are managed in Play Console, not through the API. Set dryRun:true to validate without publishing. |
| get_country_availabilityA | Read which countries a track is available in (and whether it syncs with production). Read-only — availability itself is changed in Play Console. |
| list_inapp_productsA | List managed (one-time) in-app products via the legacy inappproducts API: sku, status, listings, defaultPrice, per-region prices. Read-only. Subscriptions are under list_subscriptions. |
| get_inapp_productB | Read one in-app product by sku. Read-only. |
| create_inapp_productB | Create a managed (one-time) in-app product. |
| update_inapp_productA | Patch an in-app product: only the fields in |
| delete_inapp_productB | Delete an in-app product by sku. Irreversible; takes effect immediately. |
| list_one_time_productsA | List one-time products via the NEW monetization.onetimeproducts API (purchase options, offers, regional pricing). Read-only; write them with raw_request until dedicated tools exist. |
| list_subscriptionsA | List subscriptions with their base plans (billing period, state, regional prices) and listings. Read-only. |
| get_subscriptionA | Read one subscription (base plans, prices, listings, tax settings). Read-only. |
| create_subscriptionA | Create a subscription. |
| update_subscriptionA | Patch a subscription. |
| delete_subscriptionA | Delete a subscription. Only possible if it never had a base plan published. Irreversible. |
| activate_base_planA | Activate a base plan: it becomes purchasable by users. Gated by PLAY_MCP_ALLOW_SUBSCRIPTION_ACTIVATION. |
| deactivate_base_planA | Deactivate a base plan: no NEW purchases (existing subscribers keep renewing). Gated by PLAY_MCP_ALLOW_SUBSCRIPTION_ACTIVATION. |
| delete_base_planA | Delete a base plan. Only draft base plans (never activated) can be deleted. Irreversible. |
| migrate_base_plan_pricesA | Move EXISTING subscribers on legacy price cohorts to the base plan's current price in the given regions. This is a price change for paying users (Google notifies them; increases may need opt-in). Gated by PLAY_MCP_ALLOW_PRICE_CHANGES. regionalPriceMigrations: [{ regionCode: 'US', oldestAllowedPriceVersionTime: '2024-01-01T00:00:00Z', priceIncreaseType?: 'PRICE_INCREASE_TYPE_OPT_IN'|'PRICE_INCREASE_TYPE_OPT_OUT' }]. |
| list_subscription_offersA | List the offers (free trials, intro prices, upgrades) under a base plan. Pass basePlanId '-' to list offers across all base plans of the subscription. Read-only. |
| get_subscription_offerB | Read one subscription offer (phases, regional prices, eligibility, state). Read-only. |
| create_subscription_offerA | Create an offer on a base plan. |
| update_subscription_offerB | Patch a subscription offer. |
| delete_subscription_offerA | Delete a subscription offer. Only draft offers (never activated) can be deleted. Irreversible. |
| activate_subscription_offerC | Activate a subscription offer: eligible users can now get it. Gated by PLAY_MCP_ALLOW_SUBSCRIPTION_ACTIVATION. |
| deactivate_subscription_offerB | Deactivate a subscription offer: no longer offered to new users. Gated by PLAY_MCP_ALLOW_SUBSCRIPTION_ACTIVATION. |
| convert_region_pricesA | Convert one price into Google's suggested local prices for every region (tax-exclusive input). A calculator only — it changes nothing. Use the output to fill regionalConfigs / prices. price: { currencyCode: 'USD', units: '4', nanos: 990000000 } = $4.99. |
| list_reviewsA | List recent user reviews (rating, text, device, app version, any developer reply). NOTE: the API only returns reviews created or modified in roughly the last week, and only reviews that have a text comment. translationLanguage (e.g. 'en') returns translated text. Read-only. |
| get_reviewB | Read one review by id, including the developer reply if any. Read-only. |
| reply_to_reviewA | Post (or replace) the PUBLIC developer reply to a review. Visible to everyone on Google Play. Max ~350 characters; HTML is stripped. Gated by PLAY_MCP_ALLOW_REVIEW_REPLIES. Set dryRun:true to see the review, any existing reply and length check without posting. |
| snapshot_listingA | Save a timestamped JSON snapshot of an app's editable TEXT metadata — app details plus title / short & full description / video for every language — so bulk edits can be reverted with restore_listing. includeImages:true also records each image's id, hashes and preview URL (a record only; image files are not downloaded). Returns the snapshot file path. Read-only on Google Play. |
| restore_listingA | Restore listing text (and optionally app details) from a snapshot_listing file, in one atomic edit. Languages added since the snapshot are left alone. Images are not restored. Set dryRun:true to see what would change and validate without publishing. |
| list_appsA | List the apps this service account can see (packageName + display name). Uses the OPTIONAL Play Developer Reporting API — the Publisher API has no 'list apps' call — so it needs that API enabled in the Cloud project and 'View app information' access. Read-only. |
| reporting_query_metric_setA | Query an Android vitals metric set from the OPTIONAL Play Developer Reporting API (crash rate, ANR rate, error counts, slow start/rendering, wakeups, wakelocks, LMK). |
| reporting_get_metric_setA | Describe a vitals metric set: its data freshness (latest available end time per aggregation period). Call before reporting_query_metric_set to pick a valid time range. OPTIONAL Reporting API. Read-only. |
| reporting_list_anomaliesB | List anomalies Google detected in an app's vitals (sudden crash/ANR-rate changes). OPTIONAL Reporting API. Read-only. |
| reporting_search_error_issuesA | Search grouped crash / ANR issues for an app (cause, location, affected users, first/last version). filter e.g. 'errorIssueType = CRASH'; orderBy e.g. 'errorReportCount desc'. OPTIONAL Reporting API. Read-only. |
| reporting_requestA | Raw call to the OPTIONAL Play Developer Reporting API (playdeveloperreporting.googleapis.com, v1beta1) for anything not covered above, e.g. '/apps/{packageName}/errorReports:search'. path is relative to /v1beta1. The Reporting API is read-only, so this is allowed in read-only mode. |
| doctorA | Diagnose the setup: Node version, credentials present and parseable, whether Google issues an access token, whether the service account can actually reach a given app (pass packageName), whether the optional Reporting API is reachable, and the current safe-mode write settings. Changes nothing (the access probe opens and deletes an empty edit). Run this first if something isn't working. |
| raw_requestA | Make a raw Google Play Android Publisher API call — use this for ANY endpoint not covered by a dedicated tool (orders, purchases, voided purchases, one-time product writes, device tier configs, app recovery, users/grants, data safety, etc.). path is relative (e.g. '/applications/{packageName}/reviews') and '/androidpublisher/v3' is added automatically. Edit-scoped paths need an editId from insert_edit — remember to commit_edit or delete_edit. Non-GET calls obey safe mode. See developers.google.com/android-publisher/api-ref/rest. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 72 tools
Despite 72 tools, the descriptions draw clear boundaries between edit lifecycle, listing, image, release, subscription, offer, base plan, review, and reporting operations. Some overlap remains between high-level release tools and low-level update_track, and between dedicated tools and raw_request, but the guidance mitigates most misselection risk.
Predominantly uses a consistent verb_noun snake_case pattern such as list_listings, create_release, delete_image, and update_track, with coherent domain prefixes for reporting tools. Minor exceptions like raw_request and doctor, plus bulk_upsert_listings, prevent a perfect score.
72 tools is far above the typical 3-15 range and even beyond the 16-25 heavy range, creating a very large selection surface for an agent. Although the Google Play API is broad, many dedicated tools duplicate functionality that is also available through raw_request.
The surface covers the major Play Publisher lifecycle: edits, listings, images, bundles/APKs, tracks/releases/rollouts, testers, subscriptions, base plans, offers, in-app products, reviews, and reporting. raw_request and reporting_request provide fallback coverage for endpoints not given dedicated tools, so there are no severe dead ends.