# AppClaw

> AI-powered mobile automation agent — describe what you want in plain English, AppClaw reads the screen, reasons, and acts. LLM-agnostic, open-source, zero telemetry.

- **Type:** MCP server
- **Install:** `agentstack add mcp-appiumtestdistribution-appclaw`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [AppiumTestDistribution](https://agentstack.voostack.com/s/appiumtestdistribution)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [AppiumTestDistribution](https://github.com/AppiumTestDistribution)
- **Source:** https://github.com/AppiumTestDistribution/AppClaw
- **Website:** https://appclaw.in/

## Install

```sh
agentstack add mcp-appiumtestdistribution-appclaw
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

AppClaw

AI-powered mobile automation agent for Android and iOS. Tell it what to do in plain English — it figures out what to tap, type, and swipe.

```
You: "Send a WhatsApp message to Mom
      saying good morning"

AppClaw:
  Step 1: Open WhatsApp
  Step 2: Search for Mom
  Step 3: Open chat with Mom
  Step 4: Type "good morning"
  Step 5: Tap Send
  Step 6: Done

  ✅ Goal completed in 6 steps.
```

## Prerequisites

1. **Node.js** 18+
2. **Device connected** — USB, emulator, or simulator
3. **LLM API key** from any supported provider (Anthropic, OpenAI, Google, Groq, or local Ollama)

## Installation

### From npm

```bash
npm install -g appclaw
```

Create a `.env` file in your working directory:

```bash
cp .env.example .env
```

### Local development

```bash
git clone https://github.com/AppiumTestDistribution/appclaw.git
cd appclaw
npm install
cp .env.example .env
```

Edit `.env` based on your preferred mode:

Vision mode (recommended)

Screenshot-first mode using Stark (df-vision + Gemini) for element location. Requires a Gemini API key.

```env
LLM_PROVIDER=gemini
LLM_API_KEY=your-gemini-api-key
LLM_MODEL=gemini-3.1-flash-lite
AGENT_MODE=vision
```

DOM mode

Uses XML page source to find elements by accessibility ID, xpath, etc. No vision needed — works with any LLM provider.

```env
LLM_PROVIDER=gemini            # or anthropic, openai, groq, ollama
LLM_API_KEY=your-api-key
AGENT_MODE=dom
```

## Usage

### Platform & device selection

AppClaw supports both Android and iOS (simulators + real devices). On macOS, you'll get an interactive prompt to choose. For CI or to skip prompts, use flags:

```bash
# Android (default — no flags needed)
appclaw "Open Settings"

# iOS Simulator (auto-selects the booted simulator)
appclaw --platform ios --device-type simulator "Open Settings"

# iOS Simulator — pick by name
appclaw --platform ios --device-type simulator --device "iPhone 17 Pro" "Open Settings"

# iOS Real Device — pick by UDID
appclaw --platform ios --device-type real --udid 00008120-XXXX "Open Settings"

# Env vars work too (great for .env or CI)
PLATFORM=ios DEVICE_TYPE=simulator appclaw "Open Settings"
```

> **Tip:** If only one simulator is booted, it's auto-selected — no `--udid` needed.

### Agent mode (LLM-driven)

```bash
# Interactive mode (prompts for platform + goal)
appclaw

# Pass goal directly
appclaw "Open Settings"
appclaw "Search for cats on YouTube"
appclaw "Turn on WiFi"
appclaw "Send hello on WhatsApp to Mom"

# Or with npx (no global install)
npx appclaw "Open Settings"
```

When running from a local clone, use `npm start` instead:

```bash
npm start
npm start "Open Settings"
```

**Export a replayable test** — pass `--export` and the agent's trajectory is written as a runnable vitest spec when the goal completes:

```bash
# Default path: $EXPORT_DIR/.test.ts (defaults to .appclaw/exports/)
appclaw --export "Open YouTube and search for Appium 3.0"

# Bare filename → EXPORT_DIR/
appclaw --export youtube.test.ts "Open YouTube and search for Appium 3.0"

# Path with directory hint → used verbatim
appclaw --export tests/e2e/youtube.test.ts "Open YouTube"

# Override the directory for one run
appclaw --export-dir tests/recorded --export youtube.test.ts "Open YouTube"

# Or set it persistently in .env
echo 'EXPORT_DIR=tests/recorded' >> .env
```

The export drops the wrong-direction branch when verification rejected a `done`, preserves the auto-launched app as the first step, and translates internal agent tools back to natural language so the generated test reads like English. See the [SDK section](#sdk-typescript--javascript) below for what the test file looks like.

### YAML flows (no LLM needed)

Run declarative automation steps from a YAML file — fast, repeatable, zero LLM cost:

```bash
appclaw --flow examples/flows/google-search.yaml
```

Flows support both structured and natural language syntax:

**Structured:**

```yaml
appId: com.android.settings
name: Turn on WiFi
---
- launchApp
- wait: 2
- tap: 'Connections'
- tap: 'Wi-Fi'
- done: 'Wi-Fi turned on'
```

**Natural language:**

```yaml
name: YouTube search
---
- open YouTube app
- click on search icon
- type "Appium 3.0" in the search bar
- perform search
- scroll down until "TestMu AI" is visible
- verify video from TestMu AI is visible
- done
```

Supported natural language patterns include: `open `, `click/tap `, `type "text"`, `scroll up/down`, `swipe left/right`, `scroll down until "X" is visible`, `wait N seconds`, `go back`, `press home`, `verify/assert  is visible`, `press enter`, and `done`. Questions like `"whats on the screen?"` or `"how many items are there?"` are answered via vision without executing any action.

### Parallel & suite runs

Run the same flow on N devices simultaneously, or distribute a suite of flows across N workers:

**Same flow, N devices** — add `parallel: N` to the flow's metadata:

```yaml
name: youtube_parallel
platform: android
parallel: 2
---
- open YouTube app
- search for "Appium 3.0"
- assert "TestMu AI" is visible
- done
```

```bash
appclaw --flow youtube.yaml   # spins up 2 devices, runs flow on both concurrently
```

**Suite: different flows, N workers** — a suite YAML lists flows and a worker count:

```yaml
name: youtube_suite
platform: android
parallel: 2
flows:
  - flows/login.yaml
  - flows/search.yaml
  - flows/playback.yaml
```

```bash
appclaw --flow youtube-suite.yaml   # 2 devices pull from queue until all 3 flows finish
```

The VS Code extension shows a **live multi-device grid** — each device card updates in real time with a per-device step log, progress bar, and pass/fail result. Failed flows can be re-run with **Re-run Failed** from the summary notification.

### Playground (interactive REPL)

Build YAML flows interactively on a real device — type commands and watch them execute live:

```bash
appclaw --playground

# iOS simulator
appclaw --playground --platform ios --device-type simulator

# Specific device
appclaw --playground --platform ios --device-type simulator --device "iPhone 17 Pro"
```

Features:

- Type natural-language commands that execute immediately on the device
- Steps accumulate as you go
- Export to a YAML flow file or a runnable SDK test (vitest spec) — format picked from the file extension
- Slash commands: `/help`, `/steps`, `/export`, `/clear`, `/device`, `/disconnect`

**Export formats** — `/export` dispatches by extension:

```
> /export my-flow.yaml              # YAML flow (default behaviour)
> /export tests/youtube.test.ts     # SDK vitest spec
> /export youtube.test.ts           # bare filename → EXPORT_DIR/youtube.test.ts
```

Bare filenames for SDK tests land in `EXPORT_DIR` (default `.appclaw/exports`); paths with a directory hint are used verbatim. YAML files stay in the current directory regardless.

### SDK (TypeScript / JavaScript)

Drive a device programmatically from a vitest / jest / mocha test. The SDK exposes the same natural-language layer as the playground, so steps you build interactively can be lifted into a test file as-is.

```ts
import { AppClaw, AppClawAssertionError } from 'appclaw';
import { describe, it } from 'vitest';
import 'dotenv/config';

describe('YouTube smoke', () => {
  it('searches and verifies a result', async () => {
    const app = new AppClaw({
      provider: 'gemini',
      apiKey: process.env.LLM_API_KEY,
      platform: 'android',
      agentMode: 'vision',
      video: true, // record screen, embed in report
      mcpDebug: false, // silence appium-mcp stderr noise
    });

    await app.run('open YouTube app');
    await app.run('tap search icon');
    await app.run('type "Appium 3.0"');
    await app.run('tap first result');

    // Throws AppClawAssertionError on failure — vitest marks the test red.
    // The error message includes the original claim, the LLM's reason (in
    // vision mode), and a snapshot of visible texts (in DOM mode).
    await app.verify('the video by TestMu AI is visible');

    await app.teardown(); // closes the appium session, writes the report
  }, 120_000);
});
```

**Three execution surfaces:**

| Method                           | Use when                                                                                                                                                  |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `app.run(instruction, options?)` | One natural-language step. Returns `{ success, action, message }` — does NOT throw on failure. `options` overrides wait/scroll config for this call only. |
| `app.verify(claim)`              | Assertion. Throws `AppClawAssertionError` so the test framework records a failure.                                                                        |
| `app.runFlow(path)`              | Execute a YAML flow file end-to-end.                                                                                                                      |
| `app.runGoal(goal, opts?)`       | Hand the goal to the LLM agent (same as CLI agent mode). Supports `exportPath` (see below).                                                               |

**Recording goal runs as replayable tests** — pass `exportPath` to `runGoal()` and the agent's trajectory is written as a runnable vitest spec when it finishes:

```ts
await app.runGoal('open YouTube, search for Appium 3.0, play first result', {
  exportPath: 'tests/e2e/youtube.test.ts',
});
```

The exported file replays via `app.run(...)` calls — no LLM cost, no agent loop. The exporter:

- Prepends the synthetic `launch_app` step the preprocessor handled (so the launch isn't missing from the replay)
- Drops the entire branch before a rejected `done` decision — the recovery path the agent took after a verification failure is the only one preserved
- Translates internal agent tools (`find_and_click`, `find_and_type`, etc.) back into natural language so the test reads like English

Read the four caveats embedded in the generated file header before running it in CI — replays don't inherit the agent's safety net.

**Per-command overrides** — `app.run()` takes an optional second argument that wins over the instance defaults for that one step. Useful for a slow screen that needs a longer wait, or a tight list that needs a shorter scroll:

```ts
// Wait up to 20s for this specific (slow-loading) screen
await app.run('click on Dashboard', { waitTimeout: 20000 });

// Scroll a short distance, up to 5 times, to find an item
await app.run('scroll down until Karma is visible', { scrollMode: 'short', scrollTimes: 5 });

// A single full-screen swipe
await app.run('swipe up', { scrollMode: 'full' });
```

| `RunOptions` field | Purpose                                                                                 |
| ------------------ | --------------------------------------------------------------------------------------- |
| `waitTimeout`      | Implicit-wait timeout (ms) for this command's target element. Overrides instance value. |
| `waitInterval`     | Poll cadence (ms) for this command's implicit wait.                                     |
| `scrollMode`       | Scroll/swipe distance: `short` (~30%) / `medium` (~60%) / `full` (~90%) of the screen.  |
| `scrollTimes`      | Repeat count (plain swipe) or max scroll attempts (`scroll … until …`).                 |

`scrollMode` / `scrollTimes` can also be set on the constructor as instance-wide defaults.

**Constructor options** (all optional — env vars fall through):

| Option         | Default       | Purpose                                                                                                                                             |
| -------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `provider`     | `gemini`      | LLM provider (`anthropic` / `openai` / `gemini` / `groq` / `ollama`)                                                                                |
| `apiKey`       | env           | API key for the chosen provider                                                                                                                     |
| `model`        | (auto)        | Model ID override                                                                                                                                   |
| `platform`     | `android`     | Target platform                                                                                                                                     |
| `deviceUdid`   | (auto)        | Pin to a specific device — required when running tests in parallel                                                                                  |
| `agentMode`    | `dom`         | `dom` or `vision` — vision-mode `verify()` failures include the LLM's reason                                                                        |
| `waitTimeout`  | `10000`       | Implicit wait (ms) — each action polls until its target is on screen before acting, so you don't need `wait …` steps between calls. `0` = fail-fast |
| `waitInterval` | `300`         | Poll cadence (ms) for `waitTimeout`                                                                                                                 |
| `scrollMode`   | (engine ~60%) | Default scroll/swipe distance: `short` / `medium` / `full`. Override per call via `run()`                                                           |
| `scrollTimes`  | (parsed)      | Default scroll/swipe repeat / max-scroll count. Override per call via `run()`                                                                       |
| `video`        | `false`       | Record screen and embed in the auto-generated report                                                                                                |
| `report`       | `true`        | Write an HTML report to `.appclaw/runs/` on teardown                                                                                                |
| `mcpDebug`     | env / `false` | Stream `[appium-mcp]` subprocess logs. Overrides `MCP_DEBUG=1`                                                                                      |
| `silent`       | `false`       | Suppress the per-step `✓ #N tap "label"` log lines. Default off — SDK consumers see device activity to match the playground UX.                     |

**TypeScript types** — the package ships full type declarations (`package.json` → `types: dist/sdk/index.d.ts`), so editors give autocomplete on every option and `tsc` rejects typos before a test runs. The public types are importable by name:

```ts
import { AppClaw, AppClawStepError, AppClawAssertionError } from 'appclaw';
import type { AppClawOptions, RunOptions, ScrollDistance, RunResult, FlowResult } from 'appclaw';
```

Definitions:

```ts
/** How far each scroll/swipe travels, as a fraction of the screen. */
type ScrollDistance = 'short' | 'medium' | 'full'; // ~30% / ~60% / ~90%

/** Constructor config — every field optional; unset falls back to env / defaults. */
interface AppClawOptions {
  provider?: 'anthropic' | 'openai' | 'gemini' | 'groq' | 'ollama';
  apiKey?: string;
  model?: string;
  platform?: 'android' | 'ios';
  deviceUdid?: string;
  agentMode?: 'dom' | 'vision';
  maxSteps?: number;
  stepDelay?: number;
  waitTimeout?: number; // implicit-wait timeout (ms), default 10000
  waitInterval?: number; // poll cadence (ms), default 300
  scrollMode?: ScrollDistance;
  scrollTimes?: number;
  silent?: boolean;
  failOnError?: boolean;
  report?: boolean;
  reportName?: string;
  video?: boolean;
  mcpTransport?: 'stdio' | 'sse';
  mcpHost?: string;
  mcpPort?: number;
  mcpDebug?: boolean;
}

/** Per-command overrides — the optional 2nd arg to `app.run()`. */
interface RunOptions {
  wai

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [AppiumTestDistribution](https://github.com/AppiumTestDistribution)
- **Source:** [AppiumTestDistribution/AppClaw](https://github.com/AppiumTestDistribution/AppClaw)
- **License:** Apache-2.0
- **Homepage:** https://appclaw.in/

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** yes
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-appiumtestdistribution-appclaw
- Seller: https://agentstack.voostack.com/s/appiumtestdistribution
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
