Google Play MCP
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 MCP upload app-release.aab and roll it out to 10% of production"
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 MCP
An MCP server for Google Play — the
Android sibling of appstore-api-mcp.
It lets an AI agent ship and manage Play apps through the official
Android Publisher API:
upload builds, manage tracks and staged rollouts, edit store listings and
screenshots, manage in-app products and subscriptions, and read and reply to
reviews.
Status: 0.1.0, not yet verified against the live API. Every endpoint path and parameter comes from Google's published API discovery document and the logic is unit-tested with a mocked network, but no tool has been run against a real Play Console account yet. Start with
doctoranddryRun, and see what's verified.
✨ What makes it stand out
Edits handled for you. Google Play changes happen inside an "edit" transaction. Each tool opens one, applies the change, validates and commits — and deletes it if anything fails. No dangling edits.
Real dry-run.
dryRun: trueapplies your change in a temporary edit, has Google validate it, then throws the edit away. You see the diff and the verdict; nothing is published.Batch when you want to. Open one edit, upload a build, create the release and swap screenshots in it, commit once — all live together.
Safe mode enforced by the server. Read-only mode, plus separate switches for production releases, price changes, review replies and subscription activation.
Full coverage. A
raw_requestescape hatch reaches anything without a dedicated tool — still subject to safe mode.
💬 Just ask
"Upload
app-release.aaband roll it out to 10% of production with these release notes." "Bump the production rollout to 50%." · "Halt the rollout." "Rewrite the short description in all 12 languages — show me the diff first." "Replace the German phone screenshots with the files in./shots/de." "What are this week's 1- and 2-star reviews? Draft replies." "Create a monthly subscription at $4.99 and convert the price for every region."
Table of contents
Related MCP server: play-console-mcp
Quick start
You need Node.js ≥ 18 and a Google Cloud service account key that has been granted access in Play Console (how).
Add this to your MCP client's config:
{
"mcpServers": {
"googleplay-api": {
"command": "npx",
"args": ["-y", "googleplay-api-mcp"],
"env": {
"GOOGLE_SERVICE_ACCOUNT_JSON": "/absolute/path/to/service-account.json"
}
}
}
}Or, with Claude Code:
# --scope user installs it for ALL your projects (recommended).
# Remove the --scope user line to install for the current project only.
claude mcp add googleplay-api \
--scope user \
--env GOOGLE_SERVICE_ACCOUNT_JSON="$HOME/.googleplay/service-account.json" \
-- npx -y googleplay-api-mcpThen ask: "Run the Google Play doctor for com.example.app."
Per-client instructions (Claude Desktop, Codex, Cursor, Windsurf, VS Code, Zed, Gemini CLI, …) are in docs/CLIENTS.md.
Getting your service account key
The short version — full walkthrough in docs/SETUP.md:
Google Cloud Console → pick or create a project → enable the Google Play Android Developer API.
IAM & Admin → Service Accounts → create one → Keys → Add key → JSON.
Play Console → Users and permissions → Invite new users → paste the service account's email → grant it the apps and permissions it needs.
Step 3 is the one people miss: until the service account is invited in Play
Console, every call returns 403.
Configuration
All configuration is by environment variable. Credentials are never accepted as tool arguments.
Variable | Purpose |
| Path to the service account key file — or the JSON itself (inline when the value starts with |
| Base64 of the key file (handy for CI/containers) |
| Fallback path (Google's standard variable) |
| Where |
|
|
| Regions version for subscription pricing calls (default |
Safe mode
Enforced by the server before a tool runs — not a suggestion to the model.
Variable | Effect |
| Block all writes |
| Block production releases and rollout-percentage changes (halting stays allowed) |
| Block price changes and price migrations |
| Block public review replies |
| Block activating/deactivating base plans and offers |
The four category flags default to allowed; set them to false to lock a
category. A blocked call fails with a message naming the flag. Details in
docs/TOOLS.md.
Tools
72 tools. Full reference with parameters: docs/TOOLS.md.
Group | Tools |
Edits |
|
Store listing |
|
Images |
|
Builds |
|
Tracks & releases |
|
Testers & availability |
|
In-app products |
|
Subscriptions |
|
Reviews |
|
Snapshots |
|
Reporting (optional API) |
|
Diagnostics |
|
Coming from the App Store server?
App Store Connect | Google Play | |
App identifier | numeric |
|
Credentials |
| one service account |
Safe-mode prefix |
|
|
Applying changes | each call is live | changes go through an edit; tools auto-commit |
Preview |
|
|
Phased release |
|
|
Keywords field | yes | none — Play indexes the title and descriptions |
Common workflows
Ship a build
upload_bundle { packageName, filePath: "/path/app-release.aab" } → versionCode 42
create_release { packageName, track: "internal", versionCodes: [42], status: "completed" }
promote_release { packageName, fromTrack: "internal", toTrack: "production", userFraction: 0.1 }
set_rollout_fraction { packageName, userFraction: 0.5 }
complete_rollout { packageName }Something wrong? halt_rollout { packageName }.
To upload and release atomically, wrap them in one edit: insert_edit, pass
editId to upload_bundle and create_release, then commit_edit.
Edit the listing
update_listing { packageName, language: "en-US", shortDescription: "…", dryRun: true }Review the diff and Google's validation, then run it again without dryRun.
For many languages at once use bulk_upsert_listings — one commit for all.
Replace screenshots
replace_images { packageName, language: "de-DE", imageType: "phoneScreenshots",
files: ["/shots/de/1.png", "/shots/de/2.png"] }Old ones are removed and new ones added in a single edit, so the listing never shows a half-replaced set.
Before a bulk rewrite
snapshot_listing { packageName } first; restore_listing { file } puts the
text back. Or set PLAY_MCP_AUTO_SNAPSHOT=true and it happens automatically.
Reviews
list_reviews { packageName, translationLanguage: "en" }, then
reply_to_review { packageName, reviewId, replyText, dryRun: true } to check
the reply before it goes public.
Anything else
raw_request { method, path, query?, body? } — path is relative to
/androidpublisher/v3, e.g. /applications/com.example.app/orders/GPA.1234.
Field limits
Field | Limit |
Title | 30 |
Short description | 80 |
Full description | 4000 |
Release notes (per language) | 500 |
Review reply | ~350 |
Over-limit text is flagged in _warnings and in the dry-run diff; Google
rejects it on validate/commit. The listing limits come from Play Console Help
rather than the API reference.
What's verified
From Google's discovery documents (
androidpublisherv3 andplaydeveloperreportingv1beta1, revision 2026-11-08): every endpoint path, HTTP method, query parameter, enum and response field the tools use.Unit-tested (
npm test, mocked network): service-account auth and token caching, error shaping, retry, paging, uploads, the edit lifecycle including failure cleanup, release/rollout rules, validation limits, safe-mode gating, and the tool registry.Needs live verification: everything, in the sense that no call has hit the real API. The specific open questions — large-bundle uploads, the validate-before-commit step, rollout body shape,
regionsVersion, and which Play Console permission each tool needs — are listed in docs/TOOLS.md.
The Reporting API tools are optional and limited to what that API offers: Android vitals, error issues and anomalies. It has no installs, ratings or revenue endpoints.
Security
The service account key can change your live store presence. Use a dedicated service account with the fewest permissions that work, keep the key file out of any repository, and turn on safe mode where you can. Credentials are read only from the environment, sent only to Google, and never logged. Full notes: docs/SECURITY.md.
Troubleshooting
Run doctor with a packageName first. The usual suspects:
403 on everything — the service account hasn't been invited in Play Console → Users and permissions (or the grant hasn't propagated yet).
Server won't start — your client is launching Node < 18.
list_reviewslooks empty — the API only returns reviews from about the last week, and only those with text.Commit fails mentioning
changesNotSentForReview— pass (or remove) that option as the message instructs.
More in docs/SETUP.md.
Development
git clone https://github.com/fil-technology/googleplay-api-mcp.git
cd googleplay-api-mcp
npm install
npm test # node --test, no network, no credentials
GOOGLE_SERVICE_ACCOUNT_JSON=/path/sa.json npm startLayout: src/index.js (tool registry + server), src/client.js (auth,
requests, paging, uploads), src/edits.js (edit lifecycle), src/releases.js
(release/rollout rules), src/guardrails.js (safe mode), src/validation.js
(limits + diff).
Releasing
Publishing runs in GitHub Actions on a v*.*.* tag, authenticated by npm
trusted publishing (OIDC) — there is no NPM_TOKEN anywhere.
One-time setup on npmjs.com (package → Settings → Trusted Publisher → GitHub
Actions): organization fil-technology, repository googleplay-api-mcp,
workflow npm-publish.yml. npm currently only lets you configure a trusted
publisher for a package that already exists, so the very first version may need
a manual npm publish --access public from a maintainer's machine; every
release after that is tag-driven.
npm version patch # bumps package.json + creates the tag
git push --follow-tags # the tag triggers the publish workflowThe workflow refuses to publish if the tag doesn't match package.json, and
skips versions already on npm.
License
MIT © Sviatoslav Fil
Available Tools
72 toolsactivate_base_planA
Activate a base plan: it becomes purchasable by users. Gated by PLAY_MCP_ALLOW_SUBSCRIPTION_ACTIVATION.
Inputs: productId (required), basePlanId (required), packageName (required), latencyTolerance.
activate_subscription_offerC
Activate a subscription offer: eligible users can now get it. Gated by PLAY_MCP_ALLOW_SUBSCRIPTION_ACTIVATION.
Inputs: offerId (required), productId (required), basePlanId (required), packageName (required), +1 more.
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.
Inputs: listings (required), packageName (required), dryRun, editId, +1 more.
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.
Inputs: editId (required), packageName (required), changesInReviewBehavior, changesNotSentForReview.
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.
Inputs: packageName (required), track, dryRun, editId, +2 more.
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.
Inputs: price (required), packageName (required), productTaxCategoryCode.
create_inapp_productB
Create a managed (one-time) in-app product. product is an InAppProduct: { sku, status: 'active'|'inactive', purchaseType: 'managedUser', defaultLanguage, listings: { 'en-US': { title, description } }, defaultPrice: { priceMicros: '990000', currency: 'USD' } }. autoConvertMissingPrices fills other regions from defaultPrice. Takes effect immediately (no edit).
Inputs: product (required), packageName (required), autoConvertMissingPrices.
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.
Inputs: track (required), status (required), packageName (required), versionCodes (required), +8 more.
create_subscriptionA
Create a subscription. subscription is a Subscription resource: { listings: [{ languageCode, title, benefits?, description? }], basePlans: [{ basePlanId, autoRenewingBasePlanType: { billingPeriodDuration: 'P1M' }, regionalConfigs: [{ regionCode: 'US', price: { currencyCode: 'USD', units: '4', nanos: 990000000 } }] }] }. New base plans start as DRAFT — activate them with activate_base_plan. Takes effect immediately (no edit).
Inputs: productId (required), packageName (required), subscription (required), regionsVersion.
create_subscription_offerA
Create an offer on a base plan. offer is a SubscriptionOffer: { phases: [{ recurrenceCount, duration: 'P1W', regionalConfigs: [{ regionCode, free: {} | price | relativeDiscount | absoluteDiscount }] }], regionalConfigs: [{ regionCode, newSubscriberAvailability: true }], targeting?, offerTags? }. New offers start as DRAFT — activate with activate_subscription_offer.
Inputs: offer (required), offerId (required), productId (required), basePlanId (required), +2 more.
deactivate_base_planA
Deactivate a base plan: no NEW purchases (existing subscribers keep renewing). Gated by PLAY_MCP_ALLOW_SUBSCRIPTION_ACTIVATION.
Inputs: productId (required), basePlanId (required), packageName (required), latencyTolerance.
deactivate_subscription_offerB
Deactivate a subscription offer: no longer offered to new users. Gated by PLAY_MCP_ALLOW_SUBSCRIPTION_ACTIVATION.
Inputs: offerId (required), productId (required), basePlanId (required), packageName (required), +1 more.
delete_all_imagesB
Delete ALL images of one type for one language. Set dryRun:true to validate without publishing.
Inputs: language (required), imageType (required), packageName (required), dryRun, +2 more.
delete_base_planA
Delete a base plan. Only draft base plans (never activated) can be deleted. Irreversible.
Inputs: productId (required), basePlanId (required), packageName (required).
delete_editB
Discard an open edit and every uncommitted change in it. Nothing is published.
Inputs: editId (required), packageName (required).
delete_imageB
Delete one image by id (from list_images). Set dryRun:true to validate without publishing.
Inputs: imageId (required), language (required), imageType (required), packageName (required), +3 more.
delete_inapp_productB
Delete an in-app product by sku. Irreversible; takes effect immediately.
Inputs: sku (required), packageName (required).
delete_listingA
Delete one language's store listing (the default language cannot be deleted). Set dryRun:true to validate without publishing.
Inputs: language (required), packageName (required), dryRun, editId, +1 more.
delete_subscriptionA
Delete a subscription. Only possible if it never had a base plan published. Irreversible.
Inputs: productId (required), packageName (required).
delete_subscription_offerA
Delete a subscription offer. Only draft offers (never activated) can be deleted. Irreversible.
Inputs: offerId (required), productId (required), basePlanId (required), packageName (required).
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.
Inputs: packageName.
get_app_detailsA
Read app-level details: defaultLanguage and the public contact email / phone / website. Read-only.
Inputs: packageName (required), editId.
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.
Inputs: track (required), packageName (required), editId.
get_editB
Check that an edit is still open/valid and when it expires. Read-only.
Inputs: editId (required), packageName (required).
get_inapp_productB
Read one in-app product by sku. Read-only.
Inputs: sku (required), packageName (required).
get_listingB
Read one language's store listing. Read-only.
Inputs: language (required), packageName (required), editId.
get_reviewB
Read one review by id, including the developer reply if any. Read-only.
Inputs: reviewId (required), packageName (required), translationLanguage.
get_subscriptionA
Read one subscription (base plans, prices, listings, tax settings). Read-only.
Inputs: productId (required), packageName (required).
get_subscription_offerB
Read one subscription offer (phases, regional prices, eligibility, state). Read-only.
Inputs: offerId (required), productId (required), basePlanId (required), packageName (required).
get_testersB
Read the tester Google Groups for a testing track (internal, alpha, beta or custom). Read-only.
Inputs: track (required), packageName (required), editId.
get_trackB
Read one track and its releases (status, versionCodes, userFraction, notes). Read-only.
Inputs: track (required), packageName (required), editId.
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.
Inputs: packageName (required), track, dryRun, editId, +2 more.
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.
Inputs: packageName (required).
list_apksB
List the APKs uploaded for an app (versionCode, binary hashes). Read-only.
Inputs: packageName (required), editId.
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.
Inputs: pageSize.
list_bundlesB
List the app bundles uploaded for an app (versionCode, sha1, sha256). Read-only.
Inputs: packageName (required), editId.
list_imagesB
List the images of one type for one language (id, sha256, preview url). Omit imageType to list every type. Read-only.
Inputs: language (required), packageName (required), editId, imageType.
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.
Inputs: packageName (required).
list_languagesA
List the languages an app's store listing is localized into, plus the default language. Read-only.
Inputs: packageName (required), editId.
list_listingsA
List the store listing (title, shortDescription, fullDescription, video) for every language of an app. Read-only.
Inputs: packageName (required), editId.
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.
Inputs: packageName (required).
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.
Inputs: packageName (required), token, allPages, maxResults, +1 more.
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.
Inputs: productId (required), packageName (required), basePlanId.
list_subscriptionsA
List subscriptions with their base plans (billing period, state, regional prices) and listings. Read-only.
Inputs: packageName (required).
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.
Inputs: packageName (required), editId.
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' }].
Inputs: productId (required), basePlanId (required), packageName (required), regionalPriceMigrations (required), +2 more.
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.
Inputs: toTrack (required), fromTrack (required), packageName (required), dryRun, +6 more.
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.
Inputs: path (required), method (required), body, query.
replace_imagesA
Replace the whole set of images of one type for one language: delete the existing ones and upload files in the given order — atomically, in one edit, so the listing never shows a half-replaced set. Set dryRun:true to upload into a temporary edit, validate and discard.
Inputs: files (required), language (required), imageType (required), packageName (required), +3 more.
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.
Inputs: reviewId (required), replyText (required), packageName (required), dryRun.
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.
Inputs: metricSet (required), packageName (required).
reporting_list_anomaliesB
List anomalies Google detected in an app's vitals (sudden crash/ANR-rate changes). OPTIONAL Reporting API. Read-only.
Inputs: packageName (required), filter.
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). query is the request body: { timelineSpec: { aggregationPeriod: 'DAILY', startTime: { year, month, day, timeZone: { id: 'America/Los_Angeles' } }, endTime: {...} }, metrics: ['crashRate', 'distinctUsers'], dimensions: ['versionCode'], filter?, pageSize? }. Use reporting_get_metric_set first to see the freshness window. Read-only.
Inputs: query (required), metricSet (required), packageName (required).
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.
Inputs: path (required), method (required), body, query.
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.
Inputs: packageName (required), filter, orderBy, pageSize.
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.
Inputs: file (required), dryRun, editId, languages, +3 more.
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.
Inputs: packageName (required), track, dryRun, editId, +3 more.
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.
Inputs: packageName (required), userFraction (required), track, dryRun, +3 more.
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.
Inputs: packageName (required), label, includeImages.
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.
Inputs: packageName (required), dryRun, editId, contactEmail, +4 more.
update_inapp_productA
Patch an in-app product: only the fields in product change (status, listings, defaultPrice, prices, ...). Changing defaultPrice/prices is a PRICE CHANGE and is gated by PLAY_MCP_ALLOW_PRICE_CHANGES. Set dryRun:true to see the before/after diff without writing. Takes effect immediately (no edit).
Inputs: sku (required), product (required), packageName (required), dryRun, +2 more.
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.
Inputs: language (required), packageName (required), title, video, +5 more.
update_subscriptionA
Patch a subscription. updateMask lists the fields to replace (e.g. 'listings' or 'basePlans'); subscription carries their new values. Updating basePlans changes PRICES for NEW subscribers and is gated by PLAY_MCP_ALLOW_PRICE_CHANGES (existing subscribers move only via migrate_base_plan_prices). Set dryRun:true to see current vs proposed without writing.
Inputs: productId (required), updateMask (required), packageName (required), subscription (required), +3 more.
update_subscription_offerB
Patch a subscription offer. updateMask lists the fields to replace (e.g. 'phases', 'regionalConfigs', 'offerTags'); offer carries the new values.
Inputs: offer (required), offerId (required), productId (required), basePlanId (required), +4 more.
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.
Inputs: track (required), packageName (required), googleGroups (required), dryRun, +2 more.
update_trackA
Low-level: replace a track's releases array with exactly what you pass (TrackRelease objects). Prefer create_release / promote_release / the rollout tools; use this for unusual shapes. Production tracks are gated by PLAY_MCP_ALLOW_PRODUCTION_RELEASE. Set dryRun:true to validate without publishing.
Inputs: track (required), releases (required), packageName (required), dryRun, +2 more.
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.
Inputs: filePath (required), packageName (required), dryRun, editId, +1 more.
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.
Inputs: filePath (required), packageName (required), dryRun, editId, +3 more.
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.
Inputs: filePath (required), fileType (required), packageName (required), versionCode (required), +3 more.
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.
Inputs: filePath (required), language (required), imageType (required), packageName (required), +3 more.
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.
Inputs: filePath (required), packageName (required).
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.
Inputs: editId (required), packageName (required).
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
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.
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.
App Store and Google Play reviews, ratings, sentiment and developer replies as agent tools.
Related MCP Servers
- 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-
- AlicenseNot gradedqualityCmaintenanceEnables autonomous AI agents and developers to programmatically manage Google Play apps via the Play Developer API v3, including uploading Android App Bundles, managing release tracks, updating store listings, and replying to user reviews.3MIT