Skip to main content
Glama
Release Health MCP Server

Release Health MCP Server

by drezzzik
README.md
# Release Health MCP Server

[![CI](https://github.com/drezzzik/release-health-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/drezzzik/release-health-mcp/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

An [MCP](https://modelcontextprotocol.io) server that lets any AI agent (Claude Code, Claude Desktop, Gemini CLI, …) answer one question:

> **Is this release healthy enough to expand the rollout?**

It reads crash rate, ANR rate and top crashes per version from the Google Play Developer Reporting API and the staged-rollout state from the Play Developer API. It then recommends **expand**, **hold** or **halt** against your own thresholds.

The current version is **read-only**: it never changes a rollout. Approval-gated rollout changes are planned for milestone 3 (see [docs/ROADMAP.md](docs/ROADMAP.md)).

```mermaid
flowchart LR
  A[AI agent<br/>Claude Code / Gemini CLI] -- MCP stdio --> S[release-health-mcp]
  S -- read-only service account --> R[Play Developer Reporting API<br/>crash & ANR rates, error issues]
  S -- read-only service account --> P[Play Developer API<br/>tracks & staged rollouts]
  S --> T[(thresholds.yaml)]
```

## Tools

| Tool | Returns |
|---|---|
| `list_releases(app?, track="production")` | Releases on a track: version codes, status (`inProgress`, `halted`, `completed`), `user_fraction` |
| `get_release_health(app?, version_code?)` | User-perceived crash and ANR rates, compared with the previous version that has data |
| `top_crashes(app?, version_code?, limit=5)` | Largest crash/ANR issues by affected users, each with a sample stack trace |
| `new_issues(app?, version_code?)` | Issues first seen in this version (likely regressions) |
| `recommend_rollout(app?, version_code?)` | `expand` / `hold` / `halt`, the reasons, and a suggested next rollout fraction |

`app` is an alias from `config.yaml` or a package name. If you omit `version_code`, the server uses the release currently rolling out on production. All tools return structured JSON and are annotated `readOnlyHint`.

## Quick start (demo data, no Google account needed)

```bash
# Python 3.12+ and uv required
uv sync
cp config.example.yaml config.yaml
cp thresholds.example.yaml thresholds.yaml
uv run pytest
```

### Connect to Claude Code
This repo ships a project-scoped `.mcp.json`, so starting `claude` in the repo picks up the server (approve it on first use). To register it globally instead:

```bash
claude mcp add release-health -e RELEASE_HEALTH_CONFIG=$PWD/config.yaml -- uv --directory $PWD run release-health-mcp
```

Then ask: *"Is the current rollout of `regressed` healthy enough to expand?"*

### Connect to Gemini CLI
Add this to `~/.gemini/settings.json`:

```json
{
  "mcpServers": {
    "release-health": {
      "command": "uv",
      "args": ["--directory", "/path/to/release-health-mcp", "run", "release-health-mcp"],
      "env": { "RELEASE_HEALTH_CONFIG": "/path/to/release-health-mcp/config.yaml" }
    }
  }
}
```

### Inspect by hand
`uv run mcp dev src/release_health_mcp/server.py` opens the MCP Inspector (requires Node.js).

## Using real Google Play data

1. In Google Cloud, enable the **Google Play Developer Reporting API** and the **Google Play Android Developer API**, then create a service account and a JSON key.
2. In Play Console → *Users and permissions*, invite the service account for the apps you want with exactly two permissions: **View app information (read-only)** (tracks and rollouts) and **View app quality information (read-only)** (Android vitals: crash/ANR rates and issues). Nothing under Releases. Don't reuse a CI/fastlane key that has release rights: least privilege matters, because milestone 3 adds a *separate* account for rollout changes.
3. In `config.yaml`:
   ```yaml
   backend: play
   apps:
     ada: com.alturos.ada
     leo: app.alturos.leo
   default_app: ada
   read_key_file: ~/.config/release-health/play-read-only.json
   ```
4. Sanity check: compare `get_release_health` with *Android vitals → Overview* in Play Console for the same version and window.

Environment overrides: `RELEASE_HEALTH_CONFIG`, `RELEASE_HEALTH_BACKEND`, `RELEASE_HEALTH_READ_KEY`, `RELEASE_HEALTH_THRESHOLDS`.

### How the numbers are computed
- **Rates** come from `crashRateMetricSet` / `anrRateMetricSet`, using daily rows for the `versionCode` dimension over the last `health_window_days` days of available data. Each rate is a user-weighted average. `user_days` is the summed daily distinct users and serves as the sample size.
- **Previous version** is the highest lower `versionCode` that has data in the same window.
- **New issues** are issues whose `firstAppVersion` (within `issues_window_days`) equals the version.

## Recommendation rules

The thresholds are in `thresholds.yaml` (defaults plus per-package overrides):

| Outcome | When |
|---|---|
| **halt** | User-perceived crash rate > `max_user_perceived_crash_rate` (default 1.09%) or ANR rate > `max_user_perceived_anr_rate` (default 0.47%), which are Google Play's bad-behavior thresholds |
| **hold** | Sample < `min_user_days`, no previous version to compare with, rate up more than `max_relative_increase` vs the previous version, or a new issue affecting more than `max_new_issue_users` users |
| **expand** | Otherwise. Suggests the next value in `rollout_steps`, capped at +`max_step_pp` (20 pp) |

The decision logic lives in `src/release_health_mcp/recommend.py`. It's a pure function with table-driven tests.

## Development

```bash
uv run pytest                        # unit, recorded-API and in-process MCP tests
uv run ruff check . && uv run ruff format --check .
uv run mypy src tests scripts        # strict
```

- `tests/recorded/` holds Play API responses that `PlayBackend` is tested against with `HttpMockSequence`, so no network access is needed.
- `scripts/record_responses.py --package <pkg> --key <json> --out tests/recorded/<name> --scrub` captures real responses for new tests (read-only calls only).

```
src/release_health_mcp/
  server.py        MCP tools (MCPServer, mcp SDK v2)
  service.py       composes backend + recommendation
  recommend.py     pure decision logic
  backends/        fixture (demo JSON) | play (Google APIs)
  config.py, thresholds.py, models.py
```

## Roadmap
See [docs/ROADMAP.md](docs/ROADMAP.md): approval-gated `propose_rollout_change` (human approval, hard limits, audit log, dry-run by default), then the showcase, iOS, remote HTTP and tracing.

## License
MIT, see [LICENSE](LICENSE).

TDQS

A3.5/5.0

Scored across 5 tools

Disambiguation4/5

The tools mostly target distinct questions: listing releases, health metrics, top crashes, new regressions, and rollout advice. The only mild overlap is between top_crashes and new_issues, since both surface crash/ANR issues, but their ranking and regression framing help distinguish them.

Naming Consistency3/5

Three tools follow a clear verb_noun pattern: recommend_rollout, list_releases, and get_release_health. top_crashes and new_issues do not use the same imperative-verb convention, making the set readable but not fully consistent.

Tool Count5/5

Five tools is well-scoped for a release-health advisory server. Each tool covers a meaningful part of the workflow without excessive surface area.

Completeness4/5

The set covers release listing, health metrics, top issues, new regressions, and rollout recommendation, which is strong coverage for an advisory release-health domain. Minor gaps may exist around deeper release/track detail or historical trends, but core workflows are supported.

Maintenance

ActivityMaintained
ResponsivenessOutdated data