Skip to main content
Glama
README.md
# Play Store MCP — Python/FastMCP on Railway

Google Play MCP server with **117 original tools + 6 cloud tools**, owner-password sign-in and durable JSON storage on a Railway Volume. Google account login, Redis and SQLite are not used by the server.

Google Play API access still uses your Google service-account credentials. The owner password only authorizes access to this MCP server.

## Deploy on Railway

1. Create a service from this repository; Railway uses `Dockerfile` and `railway.json`.
2. Attach a **Volume at `/data`**. Run **one replica and one application process**. Do not run multiple Uvicorn workers.
3. Generate a public HTTPS domain.
4. Configure these variables:

| Variable | Value |
| --- | --- |
| `MCP_PUBLIC_URL` | `https://YOUR-DOMAIN`, without `/mcp` |
| `MCP_ADMIN_PASSWORD` | A private random password of at least 24 bytes |
| `MCP_DATA_DIR` | `/data` |
| `GOOGLE_PLAY_STORE_CREDENTIALS` | Complete service-account JSON, in a sealed variable |
| `PLAY_STORE_MCP_READ_ONLY` | `1` for initial read-only verification; `0` to allow Play writes |

Generate a password locally with `openssl rand -hex 32`. Do not put the actual value in source control.

`MCP_SIGNING_KEY` is optional: the server generates a stable private key at `/data/.signing-key` when it is absent. Back up the key with the Volume. Changing it invalidates portal/download tokens and makes previously encrypted job records unreadable. `MCP_BEARER_TOKEN` optionally supplies a separate automation token of at least 32 characters.

No Google OAuth Web client, consent screen, redirect URI, email allowlist, Redis service or SQL database is required. Railway supplies `PORT` automatically.

## Configure Google Play API access

Enable the Android Publisher API in the Google Cloud project, create a service account and grant it the necessary app-level permissions in Play Console. Save the downloaded service-account JSON in `GOOGLE_PLAY_STORE_CREDENTIALS`. Use the least privilege that covers your workflow.

Every connection authorized with the owner password can access the configured Play account and shared file library. This server uses a single-owner model.

## Connect a remote MCP client

Endpoint: **`https://YOUR-DOMAIN/mcp`**. Choose OAuth in the client. On this server's consent page, enter `MCP_ADMIN_PASSWORD` and authorize the requested scopes. The server issues its own access/refresh tokens; it never redirects you to Google sign-in.

The protocol uses Authorization Code + PKCE S256, hashed opaque credentials, one-use codes, refresh rotation and grant revocation. Discovery is available at `/.well-known/oauth-authorization-server` and `/.well-known/oauth-protected-resource`.

Scopes are `mcp:read`, `mcp:write`, `mcp:admin` and `offline_access`. `mcp:read` is required; store mutations additionally require `mcp:write`; updating server credentials requires `mcp:admin`. Global read-only mode overrides write scope. The owner page displays the client and requested permissions.

An API/CLI client can instead send `Authorization: Bearer YOUR_MCP_BEARER_TOKEN` when that optional token is configured. Never use the owner password or service-account JSON as a Bearer token.

| Endpoint | Purpose |
| --- | --- |
| `/mcp` | All 123 tools over Streamable HTTP |
| `/sse`, `/messages/` | Legacy HTTP+SSE |
| `/mcp-code` | Optional code mode (`search`, `get_schema`, `execute`) |
| `/portal` | Owner-password file portal |
| `/api/assets` | Authenticated uploads and file metadata |
| `/files/ID` | Authenticated or expiring signed file downloads |
| `/credentials` | Owner-admin update to the shared Play credentials, in memory |
| `/health`, `/ready` | Process and readiness probes |

## Upload files and run jobs

Open `/portal`, sign in with the owner password and upload an AAB/APK/image. Copy the returned `asset://ID` and use it in original tool arguments such as `file_path`, `apk_path`, `bundle_path` or `image_path`. Chat attachments do not automatically reach the server. Uploads are chunked and resumable; the portal reports SHA-256 after completion.

For large/long operations, call:

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

Pass this object to `start_operation`, then use `get_operation` or `list_operations` to retrieve the original result. Repeating the same owner/key/arguments returns the same job. Different arguments with the same key are rejected.

Jobs persist `queued`, `running`, `completed`, `failed` or `unknown` status. After restart, queued work resumes and running work becomes `unknown`. Failed writes also become `unknown` conservatively. Inspect Google Play before resubmitting an uncertain operation. The queue never automatically replays a possibly committed write. Job payloads/results remain encrypted, including dates and original result fields.

The six cloud tools remain `start_operation`, `get_operation`, `list_operations`, `list_assets`, `get_asset` and `delete_asset`. The original 117 names, input schemas and read-only guards are preserved.

## Storage, backups and migration

The Volume contains `state.json`, `.signing-key` (when auto-generated), lock files and `assets/`. JSON transactions use a thread lock, a file lock, private temporary files, `fsync` and atomic replacement. Corrupt state fails closed instead of silently resetting data. Back up the whole Volume privately, with its signing key.

Only one application process may own background work. Local locks do not coordinate independently deployed replicas. Keep assets and jobs off the repository and never expose Volume contents through a static web server.

### Migrate an existing Redis/SQLite deployment

Stop the old service and take a private Volume backup before changing its deployment. Keep the **original `BRIDGE_SIGNING_KEY` as `MCP_SIGNING_KEY`** so existing encrypted job results stay readable. Remove Google Web OAuth and Redis variables after migration; the service-account JSON is still required.

Run the standalone migration in a trusted environment with the Volume mounted:

```bash
uv run python scripts/migrate_json.py --data-dir /data --adopt-owner
```

Inside the new Railway image, the migration is also available as `python /app/migrate_json.py --data-dir /data --adopt-owner --uid 10001` when run as root. The script reads the old SQLite database without modifying it, verifies encrypted payloads, adopts assets/jobs into the single owner and writes `state.json`. It refuses overwrite or colliding owner/idempotency keys. The runtime refuses to silently start empty when it detects an unmigrated SQLite database. Keep the old database only as a private migration backup.

Reconnect existing MCP clients and sign in with the owner password. Old Google-backed grants and portal cookies do not carry over. Merely changing `MCP_ADMIN_PASSWORD` does not revoke existing server grants; use the `/revoke` endpoint for a grant and rotate the optional Bearer token separately when needed.

## Limits and local development

See `.env.example` for optional file, chunk, storage and queue limits. `BRIDGE_CODE_MODE=0` disables the remote code-mode endpoint. `PLAY_STORE_MCP_READ_ONLY=1` blocks all 73 Google Play mutation tools while preserving file management.

```bash
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 scripts/
uv run --frozen ruff format --check src/ tests/ docker_entrypoint.py scripts/
uv run --frozen mypy src/
uv build
```

Live Google tests are opt-in. The test suite exercises mocked API calls, owner OAuth, files, schema parity, durable JSON state and write guards. A passing mock test does not verify a real service account's Play permissions.

The original stdio command remains `CODE_MODE=0 uv run --frozen play-store-mcp --read-only`. It does not require the remote owner's password or cloud storage. See `UPSTREAM_README.md` for upstream/local usage.

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