Skip to main content
ClaudeWave

Shared Android Instrumented Runner: Enables junit5 tests to be run by multiple orgs / repos etc. on a shared set of android devices

MCP ServersRegistry oficial1 estrellas0 forks● GoApache-2.0Actualizado today
ClaudeWave Trust Score
87/100
✓ Trusted
Passed
  • ✓Open-source license (Apache-2.0)
  • ✓Actively maintained (<30d)
  • ✓Clear description
  • ✓Topics declared
  • ✓Documented (README)
Flags
  • !Install pipes a remote script into a shell (curl | sh)
Last scanned: 10/16/2026
Install in Claude Code / Claude Desktop
Method: Manual · sair
Claude Code CLI
git clone https://github.com/compscidr/sair
claude_desktop_config.json (Claude Desktop)
{
  "mcpServers": {
    "sair": {
      "command": "sair",
      "env": {
        "SAIR_API_KEY": "<sair_api_key>"
      }
    }
  }
}
1. Run the command above in your terminal (Claude Code), or paste the JSON config into claude_desktop_config.json (Claude Desktop).
2. Replace any <placeholder> values with your API keys or paths.
3. Restart Claude. The MCP server and its tools appear automatically.
💡 Install the binary first: go install github.com/compscidr/sair@latest (make sure it ends up on your PATH).
Detected environment variables
SAIR_API_KEY
Casos de uso

Resumen de MCP Servers

# SAIR — Shared Android Instrumented Runner

SAIR lets CI pipelines safely share physical Android devices. It provides
device locking, ADB protocol translation, and per-runner isolation so multiple
jobs never collide on the same device.

## Why

Without SAIR, every CI job that can see a phone uses it at the same time:

```
CI job 1 ──┐
           ├──▶ adb ──▶ Phone     ✗ both jobs install / uninstall the same APK
CI job 2 ──┘                      ✗ one job's reboot or settings change breaks
                                    the other's tests mid-run
                                  ✗ connectedCheck runs on every attached
                                    phone, including ones another job is using
```

With SAIR, a job must hold a lock before it sees a device, and waiting jobs
queue:

```
CI job 1 ──┐                 ┌──▶ Phone A (locked by job 1)
           ├──▶ Proxy ───────┤
CI job 2 ──┘      ▲          └──▶ Phone B (locked by job 2)
                  │ locks
CI job 3 ···  Orchestrator: job 3 waits in the queue until
                            a phone is released
```

## GitHub Action

This repo is also a GitHub Action that puts `sair-acquire`, `sair-release` and
`sair-wifi` on `PATH`. The tools need an orchestrator account: sign up at
[sair.run](https://sair.run) to get an API key, store it as the repository
secret `SAIR_API_KEY`, and set the repository variable `SAIR_PROXY_URL` to
your bench's proxy (see [Run](#run)). Then:

```yaml
steps:
  - uses: actions/checkout@v4
  - uses: compscidr/sair@v0.1.18
  - run: sair-acquire --count 1 > /dev/null
    env:
      SAIR_PROXY_URL: ${{ vars.SAIR_PROXY_URL }}
      SAIR_API_KEY: ${{ secrets.SAIR_API_KEY }}
  - run: ./gradlew connectedCheck
  - if: always()
    run: sair-release --status ${{ job.status }}
    env:
      SAIR_API_KEY: ${{ secrets.SAIR_API_KEY }}
```

The [full workflow example](#example-github-actions-workflow) explains each
step.

## AI agents (MCP)

`sair-mcp` is an MCP server (stdio) for coding agents. It can run an app's
instrumented tests on a real phone in one call, or lease a phone and install
APKs, run shell commands, take screenshots and read logcat on it. It talks to
the orchestrator's agent API with an API key from [sair.run](https://sair.run).
The agent's machine needs no proxy or adb.

Install it with `go install github.com/compscidr/sair/cmd/sair-mcp@latest`,
take `sair-mcp` from a release tarball, or use `sair-mcp.mcpb` from a release
(an [MCP Bundle](https://github.com/modelcontextprotocol/mcpb) for clients
such as Claude Desktop). Then add it to your agent, e.g. Claude Code:

```bash
claude mcp add sair -e SAIR_API_KEY=sair_... -- sair-mcp
```

or any client's JSON config:

```json
{ "mcpServers": { "sair": { "command": "sair-mcp", "env": { "SAIR_API_KEY": "sair_..." } } } }
```

| Tool | Does |
|------|------|
| `run_tests` | Uploads `app_apk` and `test_apk` (paths on the agent's machine), waits for the run, returns per-test results, instrument output and a logcat tail |
| `list_devices` | Lists the phones the key may use: serial, model, `sdk` (API level), shared, in use |
| `lease_device` | Takes a phone (`serial`, `wait_s` optional) and returns the lease id |
| `install_apk` | Installs an APK on the leased phone, returns its package |
| `shell` | Runs a command, returns output and exit code |
| `screenshot` | Returns a PNG of the screen |
| `logcat` | Returns the most recent `lines` (default 500) |
| `release_device` | Ends the lease: uninstalls what it installed and frees the phone |

The server heartbeats the leases it takes, so an agent that thinks for a while
between calls keeps its phone. A lease still ends at the plan's maximum
(free 15 min, solo 1 h, team 2 h, enterprise 4 h). On a phone shared by another
user, shell commands are limited to a safe set (see the orchestrator README).
`SAIR_API_URL` points it at another orchestrator (default
`https://api.sair.run`).

It is listed in the [official MCP registry](https://registry.modelcontextprotocol.io)
as `io.github.compscidr/sair`. Every release publishes it: the release
workflow builds `sair-mcp.mcpb`, fills `server.json`'s version, download URL
and SHA-256 (the values in the repo are placeholders), and publishes it with
`mcp-publisher`, logged in through GitHub OIDC. Check it with
`curl "https://registry.modelcontextprotocol.io/v0/servers?search=io.github.compscidr/sair"`.

## Quick Start

### Install

```bash
curl -fsSL https://raw.githubusercontent.com/compscidr/sair/main/install.sh | bash
```

This detects your OS and architecture, downloads the latest release, and
installs all binaries to `~/.local/bin`.

### Prerequisites

- `adb` installed on the machine with Android devices
- One or more Android devices connected via USB with USB debugging enabled
  (don't sign them in to a personal account: see
  [Securing a bench](#securing-a-bench))

### Run

```bash
# 1. Start a real ADB server on a non-default port (5038), so it doesn't
#    conflict with the proxy which will own the standard port (5037).
adb -P 5038 start-server

# 2. Set your API key (shared by proxy and device-source)
export SAIR_API_KEY=your-api-key

# 3. Start the proxy and device source
sair-proxy &
sair-device-source
```

The proxy connects to the hosted orchestrator at `orchestrator.sair.run` by
default (with TLS). Override with `ORCHESTRATOR_ADDR` for local development.

### Use

Acquire a device lock (blocks until one is available), run your tests, then
release:

```bash
ACQUIRE_OUTPUT=$(sair-acquire)
eval "$ACQUIRE_OUTPUT"
./gradlew connectedCheck
sair-release
```

After eval, stock `adb` automatically talks to the proxy through the scoped
port and only sees the locked devices.

When the bench sets `WIFI_SSID` on the device source, Wi-Fi is managed for
you:

- **On acquire** each phone joins the bench Wi-Fi and the acquire returns only
  once the phone is *online* (below); it fails otherwise. `--no-wifi` hands it
  over *offline* instead, verified the same way rather than trusting the
  previous release.
- **On release**, and when a lock expires or is cancelled, each phone is reset:
  every suggestion added through `adb shell` and every saved network is
  forgotten and the radio is toggled, and the reset waits until the phone is
  offline with the radio back on. The next job never inherits the last one's
  network.
- **Also on release**, every third-party package the phone did not have when
  the device source first saw it is uninstalled (test APKs and `.test`
  packages included, with their app data and signed-in accounts), and the
  browser and mail apps are force-stopped; see `BASELINE_PACKAGES`,
  `RESET_FORCE_STOP` and `RESET_CLEAR_PACKAGES`. What was removed is written to
  the proxy log and the lock's log on the orchestrator, naming any Wi-Fi
  suggestion that went with a package. This runs before the Wi-Fi reset, so a
  suggestion a test app leaked for the bench network is gone before the reset
  waits for the phone to be offline.
- **On acquire** the same cleanup runs first (reverse forwards, packages),
  before the Wi-Fi check. A job that never released, because it had no
  `if: always()` release step, its runner died, or the proxy was down when
  the lock expired, is cleaned up by the next job, whose lock log then shows
  `sair: uninstalled … (left by an earlier job)`.
- **Awake and unlocked**, on acquire and again after the release reset: the
  screen is woken and the keyguard dismissed, then verified (`dumpsys power`
  says `Awake`, `dumpsys window policy` says the keyguard is not showing),
  with up to three attempts since a one-shot dismiss can race on some phones.
  A phone whose keyguard stays up fails the acquire with `screen locked`; that
  means a PIN, pattern or password is set, which sair cannot dismiss, so set
  the phone's screen lock to Swipe or None. The reason is also reported as
  the device's `warning` (proxy `/status`, device report) until a wake
  succeeds. A phone with `stay_on_while_plugged_in` off is logged as a
  warning, since it will sleep and re-lock between steps. Android 4.x phones
  (no `wm`) are verified through their own `mShowingLockscreen` field; a dump
  in a format sair does not know is logged once and treated as unlocked.
- **Rebooted on release** once its uptime is over `REBOOT_UPTIME_HOURS`
  (24 by default), while the phone is unacquired: the reset waits for the
  boot and the wake above before the next acquire of that phone can proceed,
  and the phone is not offered while it is down. The lock log gets
  `sair: rebooted after release (uptime …)` and the proxy's `/status` lists
  each phone's `last_reboot`. This runs before the Wi-Fi reset, so the
  offline wait is not spent twice. A phone with a PIN, pattern or password
  is never rebooted this way, since it would come back on "unlock to start"
  and need a human; the device source logs that with the fix. The wake and
  the reboot run whether or not the bench manages Wi-Fi.

A bench that was already running jobs has phones carrying the last job's
packages, so "first seen" would record those into the baseline. A test package
and its target, where `pm list instrumentation` names both and both are
installed, are never baselined, which covers CI leftovers without touching
apps that merely ship an instrumentation of their own; for anything else, or to
be certain, set
`BASELINE_PACKAGES` explicitly after one look at `pm list packages -3` on each
phone (set it empty to keep no third-party package at all). The device source
logs the baseline it recorded, by name, so an unwanted entry is visible in the
first log after the upgrade.
- **Mid-job**, `sair-wifi off` / `sair-wifi on` drop and restore the network
  without the job knowing the credentials, and return only once the phone is
  offline or online. Each phone is switched on its own, so two phones can ask
  for opposite states at the same time.
- **From the phone**, a test can toggle its own Wi-Fi between two assertions
  with the lock's device toke
adbandroidconnectedtestfirebase-test-labinstrumentedjunitjunit4junit5tests

Lo que la gente pregunta sobre sair

¿Qué es compscidr/sair?

+

compscidr/sair es mcp servers para el ecosistema de Claude AI. Shared Android Instrumented Runner: Enables junit5 tests to be run by multiple orgs / repos etc. on a shared set of android devices Tiene 1 estrellas en GitHub y su última actualización registrada es del 2026-10-16.

¿Cómo se instala sair?

+

Puedes instalar sair clonando el repositorio (https://github.com/compscidr/sair) o siguiendo las instrucciones del README en GitHub. ClaudeWave también te ofrece bloques de instalación rápida en esta misma página.

¿Es seguro usar compscidr/sair?

+

Nuestro agente de seguridad ha analizado compscidr/sair y le ha asignado un Trust Score de 87/100 (tier: Trusted). Revisa el desglose completo de comprobaciones superadas y flags en esta página.

¿Quién mantiene compscidr/sair?

+

compscidr/sair es mantenido por compscidr. La última actividad registrada en GitHub es del 2026-10-16, con 2 issues abiertos.

¿Hay alternativas a sair?

+

Sí. En ClaudeWave puedes explorar mcp servers similares en /categories/mcp, ordenados por popularidad o actividad reciente.

Despliega sair en tu cloud

Lleva este repo a producción en minutos. Cada plataforma genera su propio entorno con variables de entorno editables.

¿Mantienes este repo? Añade un badge a tu README

Pega el badge en tu README de GitHub para mostrar que está auditado por ClaudeWave. Cada badge enlaza de vuelta a esta página y muestra el Trust Score actual.

Featured on ClaudeWave: compscidr/sair
[![Featured on ClaudeWave](https://claudewave.com/api/badge/compscidr-sair)](https://claudewave.com/repo/compscidr-sair)
<a href="https://claudewave.com/repo/compscidr-sair"><img src="https://claudewave.com/api/badge/compscidr-sair" alt="Featured on ClaudeWave: compscidr/sair" width="320" height="64" /></a>

Más MCP Servers

Alternativas a sair