Skip to main content
Glama

Play Store MCP Bridge

An authenticated remote MCP server for the Google Play Developer API. Self-host on Railway, then connect Claude, Codex, ChatGPT, Cursor, Gemini, or another compatible MCP client to https://your-domain/mcp using Google OAuth.

The bridge preserves all 117 original Google Play tools and their input schemas, and adds 6 tools for remote files and persistent background operations. Python/FastMCP calls the API directly; the remote service does not wrap a stdio subprocess. A separate local stdio entrypoint remains available.

Contents

Related MCP server: Google Play Developer API MCP Server

1. Authentication and access model

This project uses two separate Google authentication mechanisms:

Component

Purpose

Configuration

Google OAuth Web client

Identifies people signing in to MCP and the file portal

GOOGLE_OAUTH_CLIENT_ID, GOOGLE_OAUTH_CLIENT_SECRET

Google service account

Authorizes calls to the Google Play Developer API

GOOGLE_PLAY_STORE_CREDENTIALS and Play Console permissions

Bridge allowlist

Restricts which Google identities may use the bridge

BRIDGE_ALLOWED_EMAILS or BRIDGE_ALLOWED_SUBJECTS

Signing in with Google does not grant the service account access to your apps. A valid service-account key also does not replace MCP OAuth sign-in. Configure both before connecting a client.

Allowed users share the configured Play service account and file library. Background-operation results are private to the submitting Google subject. This model suits an individual or a trusted team. For independent Play accounts, deploy separate instances unless you deliberately use the advanced per-request credential path described below.

For a normal remote connection, users need the server URL and their Google sign-in. They do not need to paste a service-account key into Claude or Codex.

2. Install from source

You need Git, Python 3.11+, and uv. The remote bridge also requires Redis, writable persistent storage, and a public HTTPS origin.

git clone https://github.com/quyendang/playstore-mcp.git
cd playstore-mcp
uv sync --frozen

There are two entrypoints:

Command

Behavior

uv run --frozen playstore-mcp-bridge

Starts the authenticated remote HTTP bridge, including the portal and job queue

uv run --frozen play-store-mcp

Starts the original local stdio server

The Dockerfile starts the remote bridge by default. Running uvx play-store-mcp installs the upstream PyPI package, which is not the same as running this bridge from source.

For local remote-bridge development, run Redis locally and use an HTTPS tunnel. Set BRIDGE_PUBLIC_URL to the tunnel origin, register both Google callbacks in section 4, and choose a writable BRIDGE_DATA_DIR. Export the required variables before running playstore-mcp-bridge; the application does not automatically load .env files.

The shell examples below use Bash/Zsh on macOS or Linux. Replace domains, paths, email addresses, and package names with your own values.

3. Set up the Google Play service account

3.1. Enable the API

  1. Open Google Cloud Console and create or select a project.

  2. Go to APIs & Services → Library.

  3. Find Google Play Android Developer API (androidpublisher.googleapis.com) and enable it in the project containing your service account.

Google no longer requires linking your Play developer account to a Cloud project through the old API access page. See Google Play API setup.

3.2. Create the service account and JSON key

  1. Open IAM & Admin → Service Accounts → Create service account.

  2. Give it a name such as playstore-mcp and finish creating it. App access is granted separately in Play Console; Cloud Project Owner is not required for that purpose.

  3. Open the account, then select Keys → Add key → Create new key → JSON → Create.

  4. Save the downloaded file outside your repository. Note its client_email for the next step.

Keep the original private_key, client_email, and token_uri values. If key creation is blocked by an organization policy, ask your administrator to review that policy. See Google's service-account key instructions.

Do not commit the key. On macOS/Linux, restrict access to the local file:

chmod 600 /absolute/path/service-account.json

3.3. Grant access in Google Play Console

  1. Open Play Console → Users and permissions → Invite new users.

  2. Enter the service account's client_email, not the personal email used to sign in to MCP.

  3. Under App permissions, select the relevant apps, grant the permissions you need, and invite the user.

Task

Play Console permission to consider

Read app information

View app information (read-only)

Publish to testing tracks

Release apps to testing tracks

Publish production releases

Release to production, exclude devices, and use Play App Signing

Edit tester lists

Manage testing tracks and edit tester lists

Edit listings and products

Manage store presence

Reply to reviews

Reply to reviews

Billing, orders, and subscriptions

View financial data, orders, and cancellation survey responses; Manage orders and subscriptions

Grant permissions for the intended tasks and app scope. See the Play Console permission reference; Google's API setup guide lists the two Billing permissions in the final row.

A downloaded JSON key alone is insufficient. Cloud IAM roles do not replace Play Console app permissions.

3.4. Configure the key on the server

On Railway, create a sealed variable named GOOGLE_PLAY_STORE_CREDENTIALS and paste the complete JSON file contents. Do not wrap the JSON in shell quotes in the dashboard.

For a local process:

export GOOGLE_PLAY_STORE_CREDENTIALS="$(cat /absolute/path/service-account.json)"

Alternatively, set GOOGLE_APPLICATION_CREDENTIALS to an absolute JSON file path on the server. In Docker, that path must exist inside the container. Prefer one method; inline GOOGLE_PLAY_STORE_CREDENTIALS takes precedence over the file path.

4. Set up Google OAuth for MCP sign-in

4.1. Choose your public origin

For example:

https://playstore-mcp.example.com

Set BRIDGE_PUBLIC_URL to this HTTPS origin, without /mcp, another path, a query, or a fragment. When changing domains, update this value and both Google redirect URIs together.

In Google Cloud, open Google Auth Platform; older interfaces may call this OAuth consent screen. Configure the app name, support email, contact information, and appropriate audience. If the app is in Testing, add the people who will sign in as test users. See Google OAuth app branding.

The bridge allowlist is a separate check: those users must also appear in BRIDGE_ALLOWED_EMAILS or match an allowed Google subject. Adding a bridge user does not automatically add a Google test user. Review Google's publishing and verification requirements before relying on a Testing configuration for long-term use.

4.3. Create a Web application OAuth client

  1. Open Google Auth Platform → Clients → Create client.

  2. Choose Web application.

  3. Add these exact Authorized redirect URIs, using your own domain:

https://playstore-mcp.example.com/auth/callback
https://playstore-mcp.example.com/portal/callback
  1. Create the client and save its ID and secret in your hosting secrets:

GOOGLE_OAUTH_CLIENT_ID=YOUR_WEB_CLIENT_ID.apps.googleusercontent.com
GOOGLE_OAUTH_CLIENT_SECRET=YOUR_WEB_CLIENT_SECRET

/auth/callback handles MCP authorization; /portal/callback handles browser sign-in to the file portal. Do not use /mcp as the Google redirect URI. See Google Web OAuth and OAuth client management.

The bridge requests openid email profile to identify users. Google Play API calls use the separate service account. Keep the Google OAuth client secret on the server.

5. Deploy on Railway

5.1. Create the service and storage

  1. Create a Railway service from quyendang/playstore-mcp, or your own fork. The repository includes a Dockerfile and railway.json.

  2. Add Redis in the same project/environment. Keep persistence enabled so OAuth registrations and refresh tokens survive restarts.

  3. Attach a Volume to the app at /data. Use one app replica. The entrypoint initializes volume ownership, then drops privileges to UID 10001.

  4. Generate a Railway domain or attach a custom domain. Use it for BRIDGE_PUBLIC_URL and both Google callbacks.

An initial deployment may be unhealthy until all required settings are present. Finish configuring the variables, then redeploy. See Railway volumes.

5.2. Set variables on the app service

Variable

Value

BRIDGE_PUBLIC_URL

https://playstore-mcp.example.com, without /mcp

GOOGLE_OAUTH_CLIENT_ID

Google OAuth Web application client ID

GOOGLE_OAUTH_CLIENT_SECRET

Google OAuth Web client secret

BRIDGE_SIGNING_KEY

A stable random secret of at least 32 characters

BRIDGE_ALLOWED_EMAILS

Comma-separated Google email addresses allowed to use the bridge

BRIDGE_ADMIN_EMAILS

Admin emails; each must also be in BRIDGE_ALLOWED_EMAILS

REDIS_URL

${{Redis.REDIS_URL}}; replace Redis with your actual service name

GOOGLE_PLAY_STORE_CREDENTIALS

Complete service-account JSON in a sealed variable

BRIDGE_DATA_DIR

/data

PLAY_STORE_MCP_READ_ONLY

Start with 1; use 0 when you intend to enable Google Play writes

Generate a signing key locally:

python3 -c 'import secrets; print(secrets.token_urlsafe(48))'

Keep it stable and back it up securely. Changing it invalidates sessions/tokens and prevents reading existing job records encrypted with the old key.

For example, set BRIDGE_ALLOWED_EMAILS to you@example.com,teammate@example.com. These are human Google identities, not the service account's client_email. Every allowed user receives access to the shared Play account and file library.

5.3. Deploy and check the endpoints

Apply the settings and redeploy. The bridge listens on 0.0.0.0, using Railway's PORT, then MCP_PORT, then port 8000. The health check in railway.json targets /ready.

curl --fail https://playstore-mcp.example.com/health
curl --fail https://playstore-mcp.example.com/ready
curl -i https://playstore-mcp.example.com/mcp
  • /health checks the process and returns service: playstore-mcp-bridge and original_tools: 117.

  • /ready checks Redis, SQLite, and whether a Play client has initialized; success returns {"status":"ready"}. It does not verify permissions on every app.

  • An unauthenticated /mcp request should require OAuth, normally with HTTP 401. Use an MCP client to complete the connection.

Open /portal and sign in with an allowed email to verify the portal callback. The bridge already protects its remote tools with OAuth; a manually configured static Bearer token is not required for normal client setup.

6. Connect MCP clients after deployment

Replace the example domain with your deployed domain. Use https://playstore-mcp.example.com/mcp, sign in to Google, and approve the bridge connection. Your email must pass both Google's app access rules and the bridge allowlist.

Claude Code

claude mcp add --scope user --transport http playstore \
  https://playstore-mcp.example.com/mcp
claude mcp list

Open Claude Code, run /mcp, select the server, and complete authentication in your browser. CLI versions that support it can also run claude mcp login playstore. No custom credential header is needed. See Claude Code MCP.

Codex CLI / Codex app

codex mcp add playstore --url https://playstore-mcp.example.com/mcp
codex mcp login playstore
codex mcp list

Alternatively, add this to ~/.codex/config.toml, then run the login command:

[mcp_servers.playstore]
url = "https://playstore-mcp.example.com/mcp"

Complete Google sign-in and approve the bridge connection. Start a new chat after configuration changes; use /mcp in the CLI to inspect the connection. In the desktop app, configure the remote MCP server and choose Authenticate when prompted. See OpenAI's MCP documentation.

Claude Desktop

If your version offers custom remote connectors, add the /mcp URL in its connector settings and complete OAuth. Menu names and availability depend on your app version and account.

For a stdio configuration, mcp-remote can connect to the remote OAuth server. Install Node.js LTS with npx, then merge this entry into your existing configuration:

{
  "mcpServers": {
    "playstore": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://playstore-mcp.example.com/mcp"]
    }
  }
}

The claude_desktop_config.json locations are documented in the MCP Desktop guide:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Quit and reopen Claude, then complete browser sign-in. If the app cannot find npx, use its absolute executable path.

Cursor

Add this entry to ~/.cursor/mcp.json or your project's MCP configuration:

{
  "mcpServers": {
    "playstore": {
      "url": "https://playstore-mcp.example.com/mcp"
    }
  }
}

Enable the server in MCP settings, complete OAuth when prompted, and make its tools available to the Agent. See Cursor MCP.

Gemini CLI

Merge this into mcpServers in ~/.gemini/settings.json:

{
  "mcpServers": {
    "playstore": {
      "httpUrl": "https://playstore-mcp.example.com/mcp"
    }
  }
}

Gemini CLI uses httpUrl for Streamable HTTP. Restart the CLI, inspect /mcp, and complete the server's OAuth flow. See Gemini CLI MCP.

ChatGPT and Gemini web

  • ChatGPT: add a custom MCP server/app/plugin with the /mcp URL, choose OAuth, and complete Google sign-in. Use a connection with the appropriate tool capabilities for write actions. Labels and availability depend on your account; see OpenAI custom MCP.

  • Gemini web: under Connected Apps → Custom apps, enter the same URL and authorize it. Server compatibility cannot enable an account feature that is unavailable to you. See Google's custom-app instructions.

The Google OAuth client ID and secret belong on the server. Do not paste them into the client examples above. Client-to-bridge callbacks are handled by MCP OAuth; the two Google redirect URIs in section 4 remain the bridge callbacks.

7. Verify your setup and troubleshoot

After the client reports a successful connection:

  1. Ask: “Use Play Store MCP to validate the package name com.yourcompany.yourapp.” validate_package_name verifies tool access, without calling Google Play.

  2. Ask: “Use get_releases to read releases for com.yourcompany.yourapp. Do not change anything.” Replace this with a real app accessible to your service account.

  3. Open /portal and test file upload if you need remote deployment tools.

/mcp exposes 117 original tools plus 6 bridge tools. It does not collapse them into three meta-tools. Connect to /mcp-code separately if you want code mode.

Symptom

Check

BRIDGE_PUBLIC_URL must be an HTTPS origin

Use https://domain, without /mcp, another path, or a query

redirect_uri_mismatch

Match scheme, hostname, and both Google callbacks exactly; verify the Web client ID

Google denies access while the app is in Testing

Check the OAuth audience and Google test-user list

Account is not on the allowlist

Add the verified human Google email to BRIDGE_ALLOWED_EMAILS

Admin email must also be allowed

Include every admin email in BRIDGE_ALLOWED_EMAILS

/health succeeds but /ready returns 503

Check Redis, the SQLite volume, and Play credential initialization in deployment logs

/ready succeeds but Play API returns 403

Verify service-account client_email, app scope, and task permissions in Play Console

App not found / Google returns 404

Use the actual application ID and grant access to that app

/mcp returns 401 before sign-in

Complete OAuth through the MCP client; a service-account JSON key is not a bridge Bearer token

Origin-related HTTP 403

Check the Origin sent by the client/proxy against BRIDGE_PUBLIC_URL

Invalid host header

Match the public domain/Host to the hostname in BRIDGE_PUBLIC_URL

Laptop file path is not found

Upload through /portal and use asset://ID; chat attachments are not transferred automatically

Google Play writes are blocked

Set PLAY_STORE_MCP_READ_ONLY=0, redeploy, and verify Play Console permissions

Tokens/job records fail after a deployment

Check for signing-key changes, Redis persistence, and the /data volume

An operation has status unknown

Check Google Play before submitting a new operation

Exclude service-account JSON, OAuth secrets, signing keys, and token-bearing download links when sharing logs.

8. Upload and download files

Files attached to a conversation are not automatically uploaded to this server. Open https://your-domain/portal, sign in, upload an APK/AAB/image, and copy its asset://ID reference.

The portal uploads in chunks. To resume, enter the upload ID and select the same original file. Compare SHA-256 when verifying a resumed artifact.

The original file_path, apk_path, bundle_path, and image_path parameters accept asset://ID. Generated/system APK downloads retain destination_path as the output filename and add asset_id and a temporary download_url. Use get_asset to obtain a fresh link after expiry. Responses stream bytes and support HTTP range downloads; transfer size and storage quotas are enforced.

Remote file paths are confined to the asset directory. The local stdio entrypoint retains its original file behavior.

For CI/API uploads, authenticate with a bridge OAuth Bearer token:

  1. POST /api/assets with {"name":"app.aab","size":12345}.

  2. PUT /api/assets/ID/chunks?offset=0 with raw chunk bytes, within the configured chunk limit. Continue at the offset returned by the server.

  3. POST /api/assets/ID/complete with {} or {"sha256":"expected-hash"}.

  4. Use the returned asset reference in an original tool.

Do not put a service-account key in a URL. Browser mutations using a session cookie require a same-origin request. Download links carry temporary authorization and should be kept private.

9. Run background operations

start_operation(tool_name, arguments, idempotency_key) queues any original tool for durable execution using the shared Play account configured on the server. Use it for large artifacts, long deployments, downloads, or batches so a client request timeout does not cancel the operation.

Example arguments for start_operation:

{
  "tool_name": "deploy_app",
  "arguments": {
    "package_name": "com.example.app",
    "track": "internal",
    "file_path": "asset://YOUR_ASSET_ID"
  },
  "idempotency_key": "com.example.app-internal-build-42"
}

Poll get_operation(operation_id), or call list_operations() after reconnecting. The original tool result appears under result. The same owner/key/arguments return the same job; reusing a key with different arguments is rejected. Queue payloads and results are encrypted on the Volume.

Statuses are queued, running, completed, failed, and unknown. A restart during execution or an unsuccessful write result is conservatively marked unknown: check Google Play before creating a new job. In-flight writes are not automatically replayed; queued jobs resume after restart. Google edits for the same package are serialized across direct calls and jobs.

Use jobs where duplicate write submissions matter. Direct tools retain their original semantics. Jobs reject per-request credential headers so a caller's service-account key is not persisted in the queue.

10. Endpoints and configuration

Endpoint

Purpose

/mcp

OAuth-protected Streamable HTTP with all 123 tools

/sse, /messages/

Legacy HTTP+SSE compatibility with the same tools

/mcp-code

Optional search, get_schema, execute code-mode surface

/portal

Google-authenticated shared file management

/api/assets

Authenticated file management/upload API

/credentials

Admin-only, in-memory update to shared Play credentials

/health, /ready

Process and readiness probes

All transport aliases share one OAuth resource: the public origin. OAuth registrations and tokens are encrypted in Redis. The allowlist is checked on authenticated requests.

Write permissions and code mode

  • PLAY_STORE_MCP_READ_ONLY=1 blocks all 73 Google Play write tools. Local file uploads, downloads, and deletion remain available.

  • To enable writes, grant the appropriate Play Console permissions, set the variable to 0, and redeploy.

  • /mcp-code can execute multiple tools under one client approval. Prefer /mcp when you want the client to present individual tools.

  • BRIDGE_CODE_MODE=0 disables /mcp-code. The upstream CODE_MODE variable controls the separate stdio surface.

Optional limits

See .env.example for the complete example configuration.

Variable

Default / purpose

BRIDGE_ALLOWED_SUBJECTS

Optional comma-separated Google subject IDs allowed to connect

BRIDGE_MAX_FILE_BYTES

8 GiB per file

BRIDGE_MAX_CHUNK_BYTES

8 MiB per chunk

BRIDGE_MAX_STORAGE_BYTES

32 GiB reserved file storage; adjust to your actual Volume capacity

BRIDGE_MAX_PENDING_JOBS

100

BRIDGE_DOWNLOAD_TTL

900 seconds

PLAY_STORE_MCP_LOG_LEVEL

INFO

Per-request credentials and key rotation

Direct tools still support X-Google-Credentials / X-Google-Credentials-Base64 after bridge OAuth authorization. This advanced path is not needed for normal client connections. Base64 contains the complete private key; send it only to a trusted server over HTTPS. Background operations always use the shared credentials and reject these headers.

POST /credentials accepts the original JSON/Base64 payload formats, but requires an identity in BRIDGE_ADMIN_EMAILS. It updates shared credentials in memory only; update the Railway secret to persist a change across restarts. The upstream PLAY_STORE_MCP_ADMIN_TOKEN does not replace bridge OAuth admin access.

Local stdio compatibility

After exporting your Google Play credentials:

CODE_MODE=0 uv run --frozen play-store-mcp --read-only

A stdio client starts this command itself; Redis and bridge OAuth are not required. Use uv --directory /absolute/path/playstore-mcp run --frozen play-store-mcp --read-only when the client's working directory differs. See UPSTREAM_README.md for upstream/local behavior.

11. Development and attribution

Original tool group

Count

Publishing and batch deploy

8

Listings and images

7

Reviews and testers

5

Products, options, subscription catalogs/plans/offers

47

Purchases, orders, and external transactions

16

Users/grants, data safety, device tiers, app recovery

16

APK/AAB, expansion/deobfuscation, generated/system APK, internal sharing

15

Validation

3

Original total

117

The six bridge tools are start_operation, get_operation, list_operations, list_assets, get_asset, and delete_asset. The test suite checks the tool manifest against the original implementation, including input schemas and write guards.

uv sync --frozen --extra dev
uv run --frozen pytest -q -o addopts='' --ignore=tests/test_integration.py --ignore=tests/test_live_api.py
uv run --frozen ruff check src/ tests/ docker_entrypoint.py
uv run --frozen ruff format --check src/ tests/ docker_entrypoint.py
uv run --frozen mypy src/
uv build

Live Google tests are opt-in. Bridge tests use mocked Google calls and in-memory Redis. This README describes the authenticated remote bridge; docs/ and UPSTREAM_README.md retain upstream/local documentation.

Based on lusky3/play-store-mcp, source snapshot aebcd31, under the original MIT License. The original Google API implementation and tests are retained. See also FastMCP OAuth.

Related MCP Connectors

Related MCP Servers