Google Play Console MCP
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| GOOGLE_PLAY_MCP_USE_ADC | No | Set to '1' to use Application Default Credentials (ADC) via `gcloud auth application-default login` instead of a service account or keychain. Off unless set. | 0 |
| GOOGLE_PLAY_MCP_READ_ONLY | No | Set to '1' to refuse every write operation, for exploratory sessions. | 0 |
| GOOGLE_PLAY_STORE_CREDENTIALS | No | Path to a service account JSON key file or inline JSON content. Alternative to GOOGLE_APPLICATION_CREDENTIALS. | |
| GOOGLE_APPLICATION_CREDENTIALS | No | Path to a Google service account JSON key file. Takes precedence over the keychain. Alternative to GOOGLE_PLAY_STORE_CREDENTIALS. | |
| GOOGLE_PLAY_MCP_REPORTS_BUCKET | No | Cloud Storage bucket URI for bulk reports (e.g. pubsite_prod_rev_0123456789). Required for revenue and acquisition tools. |
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 publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
| logging | {} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| deploy_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. |
| deploy_app_multilangC | Deploy an APK or AAB file with multi-language release notes. |
| promote_releaseA | Promote a release from one track to another. Two-step: preview first, then confirm with the preview's token. |
| get_releasesA | Get release status for all tracks of an app. |
| halt_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. |
| update_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. |
| get_app_detailsB | Get app details including title, description, and developer info. |
| get_reviewsC | Get recent reviews for an app. |
| reply_to_reviewC | Reply to a user review. |
| get_reviewA | Get a single user review by its ID. |
| list_subscriptionsA | List all subscription products for an app. |
| get_subscription_statusC | Get the status of a subscription purchase. |
| list_voided_purchasesB | List voided purchases (refunds, chargebacks). |
| get_product_purchaseB | Get the status of an in-app product purchase. |
| acknowledge_product_purchaseA | Acknowledge an in-app product purchase. Purchases not acknowledged within 3 days are automatically refunded. |
| consume_product_purchaseA | Consume an in-app product purchase (for consumable products). Marks the product as consumed so the user can purchase it again. |
| refund_orderC | Refund an order, optionally revoking the user's entitlement. |
| cancel_subscription_purchaseC | Cancel a subscription purchase. |
| defer_subscription_purchaseB | Defer a subscription purchase's next renewal. |
| revoke_subscription_purchaseC | Revoke (refund) a subscription purchase. |
| get_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. |
| list_in_app_productsB | List all in-app products for an app. |
| get_in_app_productB | Get details of a specific in-app product. |
| create_in_app_productA | Create a new in-app product in the catalog. Disabled in read-only mode. |
| update_in_app_productB | Update (replace) an existing in-app product. Disabled in read-only mode. |
| patch_in_app_productB | Partially update an existing in-app product. Disabled in read-only mode. |
| delete_in_app_productB | Delete an in-app product from the catalog. Disabled in read-only mode. |
| batch_get_in_app_productsC | Get details for multiple in-app products at once. |
| batch_delete_in_app_productsA | Delete multiple in-app products in a single operation. Disabled in read-only mode. |
| get_one_time_productC | Get details of a specific one-time product. |
| list_one_time_productsA | List all one-time products for an app. |
| batch_get_one_time_productsA | Get details for multiple one-time products at once. |
| patch_one_time_productA | Create or update a one-time product (patch is create-or-update). Disabled in read-only mode. |
| delete_one_time_productA | Delete a one-time product from the catalog. Disabled in read-only mode. |
| batch_update_one_time_productsA | Update multiple one-time products in a single operation. Disabled in read-only mode. |
| batch_delete_one_time_productsA | Delete multiple one-time products in a single operation. Disabled in read-only mode. |
| batch_delete_purchase_optionsA | Delete multiple purchase options from a one-time product in a single operation. Disabled in read-only mode. |
| batch_update_purchase_option_statesB | Activate or deactivate multiple purchase options in a single operation. Disabled in read-only mode. |
| list_purchase_option_offersC | List all offers for a one-time product purchase option. |
| batch_get_purchase_option_offersA | Get details for multiple one-time product offers at once. |
| activate_purchase_option_offerB | Activate a one-time product offer, making it available to eligible buyers. Disabled in read-only mode. |
| deactivate_purchase_option_offerB | Deactivate a one-time product offer, making it unavailable to new buyers. Disabled in read-only mode. |
| cancel_purchase_option_offerA | Cancel a one-time product offer (e.g. a pre-order offer). Disabled in read-only mode. |
| batch_update_purchase_option_offersB | Create or update multiple one-time product offers in a single operation. Disabled in read-only mode. |
| batch_update_purchase_option_offer_statesA | Activate, deactivate or cancel multiple one-time product offers in a single operation. Disabled in read-only mode. |
| batch_delete_purchase_option_offersB | Delete multiple one-time product offers in a single operation. Disabled in read-only mode. |
| get_subscriptionA | Get details of a specific subscription product. |
| create_subscriptionA | Create a new subscription product in the catalog. Disabled in read-only mode. |
| patch_subscriptionB | Partially update an existing subscription product. Disabled in read-only mode. |
| delete_subscriptionB | Delete a subscription product from the catalog. Disabled in read-only mode. |
| batch_get_subscriptionsC | Get details for multiple subscription products at once. |
| batch_update_subscriptionsC | Update multiple subscription products in a single operation. Disabled in read-only mode. |
| activate_base_planA | Activate a subscription base plan, making it available to new subscribers. Disabled in read-only mode. |
| deactivate_base_planA | Deactivate a subscription base plan, making it unavailable to new subscribers. Existing subscribers keep their subscription. Disabled in read-only mode. |
| delete_base_planA | Delete a subscription base plan. Only inactive base plans with no active subscribers can be deleted. Disabled in read-only mode. |
| migrate_base_plan_pricesB | Migrate subscribers to the current base plan prices. Disabled in read-only mode. |
| batch_migrate_base_plan_pricesB | Migrate prices for multiple base plans in a single operation. Disabled in read-only mode. |
| batch_update_base_plan_statesA | Activate or deactivate multiple base plans in a single operation. Disabled in read-only mode. |
| get_subscription_offerC | Get details of a specific subscription offer. |
| list_subscription_offersC | List all offers for a subscription base plan. |
| create_subscription_offerC | Create a new subscription offer. Disabled in read-only mode. |
| patch_subscription_offerB | Partially update an existing subscription offer. Disabled in read-only mode. |
| activate_subscription_offerA | Activate a subscription offer, making it available to eligible subscribers. Disabled in read-only mode. |
| deactivate_subscription_offerA | Deactivate a subscription offer, making it unavailable to new subscribers. Disabled in read-only mode. |
| delete_subscription_offerA | Delete a subscription offer. Only inactive offers with no active subscribers can be deleted. Disabled in read-only mode. |
| batch_get_subscription_offersB | Get details for multiple subscription offers in a single operation. |
| batch_update_subscription_offersC | Update multiple subscription offers in a single operation. Disabled in read-only mode. |
| batch_update_subscription_offer_statesA | Activate or deactivate multiple subscription offers in a single operation. Disabled in read-only mode. |
| get_listingC | Get store listing for a specific language. |
| update_listingC | Update store listing for a specific language. |
| list_all_listingsB | List all store listings for all languages. |
| get_testersC | Get testers for a specific testing track. |
| update_testersC | Update testers for a specific testing track. |
| get_orderC | Get detailed order/transaction information. |
| batch_get_ordersB | Get detailed information for multiple orders at once. |
| get_external_transactionC | Get an external (alternative billing) transaction. |
| create_external_transactionB | Create an external (alternative billing) transaction. |
| refund_external_transactionB | Refund an external (alternative billing) transaction. |
| get_device_tier_configC | Get a device tier config. |
| list_device_tier_configsA | List all device tier configs for an app. |
| create_device_tier_configB | Create a new device tier config. Disabled in read-only mode. |
| list_usersA | List users with access to a developer account. |
| create_userA | Grant a user access to a developer account. Disabled in read-only mode. |
| update_userC | Update a user's account access. Disabled in read-only mode. |
| delete_userC | Remove a user's access to a developer account. Disabled in read-only mode. |
| create_grantB | Grant a user app-level access. Disabled in read-only mode. |
| update_grantC | Update a user's app-level access. Disabled in read-only mode. |
| delete_grantC | Remove a user's app-level access. Disabled in read-only mode. |
| set_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. |
| list_app_recoveriesB | List all app recovery actions for an app version. |
| create_app_recoveryB | Create a draft app recovery action. Disabled in read-only mode. |
| deploy_app_recoveryC | Deploy an app recovery action to users. Disabled in read-only mode. |
| cancel_app_recoveryB | Cancel an app recovery action. Disabled in read-only mode. |
| add_app_recovery_targetingA | Add targeting to an app recovery action. Disabled in read-only mode. |
| list_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_apkB | Download a single generated APK to a local file. |
| get_system_apk_variantB | Get a previously created system APK variant. |
| list_system_apk_variantsB | List previously created system APK variants for an app bundle version. |
| create_system_apk_variantA | Create a system APK variant from an uploaded app bundle. Disabled in read-only mode. |
| download_system_apk_variantA | Download a previously created system APK variant to a local file. |
| get_expansion_fileB | Get APK expansion file information. Expansion files are used for large apps (especially games) that exceed the 100MB APK size limit. |
| list_apksB | List the APKs currently uploaded for an app. |
| list_bundlesA | List the Android App Bundles currently uploaded for an app. |
| upload_apkA | Upload an APK to a new edit and commit it. Disabled in read-only mode. |
| upload_bundleA | Upload an Android App Bundle (.aab) to a new edit and commit it. Disabled in read-only mode. |
| upload_deobfuscation_fileA | Upload a deobfuscation (ProGuard mapping or native symbols) file. Disabled in read-only mode. |
| upload_expansion_fileA | Upload an APK expansion file (OBB) to a new edit and commit it. Disabled in read-only mode. |
| list_imagesA | List the store-listing images for a language and image type. |
| upload_imageA | Upload a store-listing image (PNG or JPEG) to a new edit and commit it. Disabled in read-only mode. |
| delete_imageA | Delete a single store-listing image by ID and commit the edit. Disabled in read-only mode. |
| delete_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. |
| validate_package_nameA | Validate package name format before using it in other operations. |
| validate_trackA | Validate track name before using it in deployment operations. |
| validate_listing_textA | Validate store listing text lengths before updating. |
| batch_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. |
| upload_internal_app_sharing_apkA | Upload an APK to internal app sharing. Disabled in read-only mode. |
| upload_internal_app_sharing_bundleB | Upload an app bundle (.aab) to internal app sharing. Disabled in read-only mode. |
| list_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. |
| list_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 |
| get_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. |
| get_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. |
| get_metric_set_metadataA | Get metadata for one vitals metric set, including its freshness. |
| query_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. |
| list_vitals_anomaliesB | List statistical anomalies Play detected across the vitals metric sets. |
| search_error_issuesA | Search grouped error issues — clusters of similar crashes or ANRs. |
| search_error_reportsA | Search raw error reports — individual crashes and ANRs with stack traces. |
| validate_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. |
| get_country_availabilityB | Read which countries a track is distributed to. |
| update_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. |
| create_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. |
| update_trackB | Update a track's release configuration. |
| delete_listingC | Delete one locale's store listing. |
| delete_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. |
| add_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. |
| patch_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. |
| update_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. |
| acknowledge_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. |
| archive_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. |
| convert_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. |
| delete_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. |
| check_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 |
| list_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 |
| list_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. |
| get_installs_reportA | Daily and active-device installs, uninstalls and upgrades for one month. |
| get_ratings_reportB | Daily and total average rating for one month. |
| get_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. |
| get_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. |
| get_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. |
| get_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. |
| get_subscriptions_reportA | New, cancelled and active subscriptions by country for one month. |
| get_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. |
| search_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. |
| get_store_listingA | Read any app's public store listing — including a competitor's. |
| browse_store_chartA | Browse a store chart: top free, top paid or top grossing. |
| get_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. |
| scrape_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. |
| list_store_enumsA | The collection, category, sort and age values the store tools accept. Returns: Valid values per argument |
| check_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. |
| preflight_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". |
| list_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 |
| preflight_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. |
| preflight_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. |
| create_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. |
| describe_editA | Read back everything currently staged in an open edit. Use before committing. Sections that cannot be read are listed under
|
| validate_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. |
| commit_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. |
| delete_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. |
| android_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. |
| list_locale_coverageB | Which locales have a store listing, and which targets are still missing. |
| clone_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. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
| publish_screenshots | Walk the generate -> preflight -> upload path for store screenshots. Google Play Console MCP does not render screenshots; it validates and uploads them. This prompt names the handoff so an agent does not have to infer it, including the alpha-channel rule that makes an otherwise clean render fail at upload. |
| declare_data_safety | Walk the export -> preflight -> declare path for the Data Safety form. The step everyone skips is the first one. This declaration has no read API, so the export is not just the input — it is the only rollback that will ever exist, and it has to be taken before anything is written. |
| release_health_check | Decide whether a release that just shipped is healthy enough to widen. |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
| store_image_specs_resource | Play's rules for every store-listing image type. |
| report_families_resource | The Cloud Storage bulk-report families, including discontinued ones. |
| vitals_metric_sets_resource | The Play Developer Reporting metric sets and their resource names. |
| config_resource | What this server has configured, without revealing any of it. Reports whether each plane can work, not the values — a resource is read into the model's context, so a bucket id or key path does not belong here. |
TDQS
Scored across 170 tools
Most tools map to a unique resource+action, and the descriptions work hard to separate near-neighbors like get_reviews vs scrape_reviews, get_product_purchase vs get_product_purchase_v2, and update_expansion_file vs patch_expansion_file. A few clusters—deploy_app/deploy_app_multilang/batch_deploy and the expansion-file operations—still require careful reading, so it is not perfectly unambiguous.
The overwhelming majority are lowercase snake_case verb_noun names with predictable get/list/create/update/delete/batch/upload/download prefixes. Minor exceptions like android_publisher_invoke and deploy_app_multilang keep it from a perfect 5, but the overall pattern is highly learnable.
170 tools is far beyond typical MCP norms and will heavily burden an agent's context and tool-selection space. The scope genuinely covers much of the Play Console, so it is not an extreme mismatch, but the count is still too high for comfortable use.
The set covers the full lifecycle for releases, monetization, subscriptions, orders, store listings, reviews, reporting, vitals, and user access. The android_publisher_invoke escape hatch also covers unwrapped endpoints, and the preflight/validation tools close operational dead ends.