Swipium
An MCP server that lets coding agents QA mobile apps on Android Emulators and iOS Simulators.
Swipium gives an AI coding agent (Claude Code, Codex, Gemini CLI, Cursor, VS Code, and other MCP clients) the tools a QA engineer uses: find or build the app, boot a simulator, install and launch it, read the screen, tap and type through real user flows, and write a report backed by screenshots, logs, and UI dumps. A run can be turned into a repeatable flow, a test suite, or Appium code.
It is for mobile developers who want their agent to catch the broken login screen before a TestFlight or Play Console build. It runs locally (stdio; the server opens no network port, and the WebDriverAgent it manages for iOS takes commands on 127.0.0.1 only, though its screen stream port is reachable from the network; see Security), drives devices through adb, simctl, and WebDriverAgent rather than Appium, and asks your consent before anything with side effects. swipium.com
A session, abbreviated
You: Smoke test this app on Android with Swipium.
Agent > qa_test_this { goal: "smoke", waitForCompletion: true } (runs by default)
🔐 Consent required (low): • boot_emulator: emulator -avd Pixel_8_API_35 -no-audio -no-snapshot-save -no-boot-anim -gpu auto -no-window
• install_apk: adb -s <Pixel_8_API_35 once booted> install -r -g android/app/.../app-release.apk
You: Approve (one prompt covers boot + install)
state: "running", sessionId: "3f9c2a1b", jobId: "a41c09e2"
Agent > qa_job_status { sessionId: "3f9c2a1b", jobId: "a41c09e2", waitMs: 45000 }
status: "done", result.state: "completed"
reportHead: "RELEASE RISK: SHIP … app: PASS … coverage: COVERED … tool: PASS …"
nextRecommendedAction: qa_get_artifact { uri: "swipium://session/3f9c2a1b/report/…-summary.md" }
Agent: The app launched and passed the smoke checks with no crashes or error screens.
Three calls: start the run, wait for it, and (only when the inlined head isn't enough) read report.md. The report behind that summary, rendered with npx swipium report --latest --format markdown, includes:
**Release risk: 🟢 SHIP**
**App status:** PASS - No high-severity app/native finding observed in this run.
**Coverage status:** COVERED - Structured smoke/workflow evidence was collected.
**Tool status:** PASS - No Swipium/MCP limitations or tool errors recorded in this run.
Contents
Requirements · Quickstart · Starter prompts · How it works · Tools · Where results go · Configuration · CLI · CI · Upgrading · Troubleshooting · Security
Requirements
Node.js 22.12 or newer. On an older Node.js the server prints a warning on stderr and swipium verify fails. Swipium works with emulators and simulators only; physical devices are refused with PHYSICAL_DEVICE_UNSUPPORTED (why). Structured iOS automation needs WebDriverAgent 6.0 or newer (qa_wda {action:"setup"} builds and starts one); without it an iOS Simulator is visual-only.
MCP: a stdio server that speaks protocol revisions 2025-06-18, 2025-11-25 and 2026-07-28; the client's first message picks one. Claude Code 2.1.285 and later connect on 2026-07-28, Codex 0.146 on 2025-06-18. Any stdio MCP client should work (protocol versions).
- Android: platform-tools, the Emulator, and at least one AVD (usually via Android Studio).
adbon yourPATHis used if present, else the SDK copy from$ANDROID_HOME,$ANDROID_SDK_ROOT, or the default SDK location (~/Library/Android/sdk,~/Android/Sdk,%LOCALAPPDATA%\Android\Sdk). Theemulatorbinary that boots AVDs andaapt2come from the SDK first..aabfiles need bundletool. - iOS (macOS only): Xcode and a simulator. Taps, typing, and UI-tree snapshots need WebDriverAgent (WDA, the on-simulator automation server Appium uses); without it iOS is visual-only (iOS modes).
Quickstart
1. Register Swipium with your client. From your app repository, for Claude Code:
npx -y swipium init claude --scope project # preview; changes nothing
npx -y swipium init claude --scope project --apply # writes .mcp.json and the skill, then runs `swipium verify`
For Claude Code, init also sets SWIPIUM_MAX_WAIT_MS=110000 (one call can wait up to 110 s, so most runs finish in one call) and installs the swipium-qa agent skill under .claude/skills/ (~/.claude/skills/ for --scope user; --no-skill skips it). --trust-local (local or user scope only) also pre-approves the consent actions that run no project code, so boots and installs stop prompting while builds, Metro and WebDriverAgent still ask. A Claude Code plugin with the same server and skill is in plugin/. Swipium is also listed in the MCP Registry as io.github.GeroPalombo/swipium, so registry-aware clients can find and install it.
The same command handles codex, gemini, cursor, and vscode (swipium init <client> --apply). Claude Desktop, Windsurf, manual configs, and per-client details are in docs/mcp-server.md.
Codex: swipium init codex --apply appends a [mcp_servers.swipium] block to ~/.codex/config.toml (or $CODEX_HOME/config.toml) with longer timeouts and an env_vars line, and installs the skill under .agents/skills/. Codex doesn't pass your shell environment to MCP servers, so without that line SWIPIUM_TEST_*, ANDROID_HOME and JAVA_HOME never reach Swipium. If you already have a block, init prints the line to add. Keep tool_timeout_sec = 600 (Codex setup).
Headless runs (codex exec, claude -p, CI): these clients answer consent prompts automatically, so nothing gated (boot, install, build) runs unless you pre-approve exact actions with SWIPIUM_CONSENT_PREAPPROVE in the server env; approving in the chat doesn't help. Pre-approval isn't tied to a device, so on a machine with several emulators or simulators also pin one with SWIPIUM_DEVICE (Headless runs).
2. Restart the client and check that it lists qa_test_this, qa_doctor, and qa_report.
3. Ask the agent to test the app:
Use Swipium to smoke test this app: qa_test_this with goal "smoke" and
waitForCompletion true, poll qa_job_status while it is running, and
summarize the report.
qa_test_this runs by default (mode:"execute", since 2.3.0); pass mode:"plan" for a side-effect-free preview. goal:"smoke" is the fastest path. With no goal, qa_test_this runs the smoke check and then tries to generate a test suite. With the skill installed, "test this app with Swipium" is enough.
If a tool returns PROJECT_ROOT_UNRESOLVED, name the absolute project path in your prompt or set SWIPIUM_PROJECT_ROOT in the server config (Project root).
What you'll see
- One approval prompt listing the exact build, boot, and install commands. Clients with MCP elicitation show a real prompt; others return
requiresConsent, which the agent must relay to you. - The first build can take minutes. The run is a background job;
waitForCompletionand eachqa_job_statuspoll wait up to about 45 s by default (about 105 s with theSWIPIUM_MAX_WAIT_MS=110000thatinit claudesets). - Two states. The job
statusisrunning,done,failed, orcancelled. The run'sresult.stateiscompletedorneeds_input(jobdone), orblockedorunsafe(jobfailed).needs_inputmeans one question for you, such as login credentials. - A report with separate verdicts for the app, coverage, and Swipium itself. A completed run inlines a short head (verdicts, top findings with evidence URIs) and saves
report.mdfor the agent; a blocked run names the fix. Read it yourself withnpx swipium report --latest --format markdown.
Starter prompts
Clients that support MCP prompts can use the built-in ones instead: swipium_setup_check, swipium_guardrail_validation, swipium_full_smoke, swipium_bug_repro, swipium_test_feature, swipium_generate_suite, and swipium_convert_run_to_flow.
How it works
qa_test_this {waitForCompletion:true} > consent > job
│
qa_job_status {waitMs} <─── still running ───┘
├─ completed > reportHead inline; more in report.md (qa_get_artifact)
├─ blocked / unsafe > the fix in nextRecommendedAction
└─ needs_input > ask you one question > qa_continue_from_blocker (resumes the run)
qa_test_this finds or builds the app, boots a simulator, installs and launches the app, runs a smoke check (plus exploration or suite generation, per goal), and always writes a report. Failures carry a failureCode that qa_explain_blocker explains. For hands-on work the agent opens a session (qa_start_session), reads the screen with qa_snapshot, acts on element refs such as @e3 (stable while the element stays on screen), and batches 3 or more known steps in one qa_flow_run {flowYaml}.
Concepts, explained in docs/concepts.md:
- Project root: the repository under test, from the
projectRootargument, MCP roots,SWIPIUM_PROJECT_ROOT,CLAUDE_PROJECT_DIR, or the server's working directory. - Consent: builds, boots, installs, data wipes, network changes, and repo-supplied commands need your single-use approval.
- Sessions and jobs: a session holds one run's device, app, and evidence; long work is a job you poll.
- Secrets and redaction: credentials are redacted from text output; screenshots are not.
- iOS modes: visual-only through
simctl, or full interaction with WDA. - The app map is Swipium's durable memory of your app's screens; a flow is a replayable YAML script (docs/flows.md); a POM suite is a generated page-object test suite. See the glossary.
Tools
Grouped by capability (qa_status without arguments returns the same groups). Every tool's parameters and behavior are in docs/tools.md; swipium verify lists what your installed version exposes.
Every tool carries MCP annotations: read-only tools declare readOnlyHint:true and openWorldHint:false (so clients can auto-approve them); the rest also declare destructiveHint and idempotentHint.
Where results go
<your repo>/.swipium/: app map, flows, test suite, issue ledger, visual baselines, generated files, the WebDriverAgent build cache (cache/wda-derived-data, about 200 MB per project;ios.wda.derivedDataPathmoves it), and your configuration (config.json,fixtures.json,policy.json).swipium scanmerges its results intoconfig.jsonand keeps every key you set (WDA team, OCR and mask commands,appId); it never regenerates the file. AnappIdcounts as yours when it has noappIdSource, orappIdSource:"config.json", or when it differs fromscanAppId(the id the last scan detected, which scan stores next to it); then it is kept even with a scanappIdSourceleft in place, andappIdSourcebecomesconfig.json. One an earlier scan wrote and you didn't change is refreshed. A config from a scan older than 2.3.0 has noscanAppId: there a hand-editedappIdwith a scanappIdSourceis still replaced, and scan printsreplaced appId <old> in config.json (written by an earlier scan from <source>; ...), so removeappIdSourceto keep yours. The first time Swipium writes the app map, issue ledger, or WDA build cache in a Git repository, it adds.swipium/to.gitignore. Never pruned automatically.~/.swipium/runs/: per-session state and evidence (screenshots, logs, UI dumps, reports), returned asswipium://URIs. When a server process first loads its session registry, it prunes in the background (for at most 2 s per start) session directories older thanSWIPIUM_RETENTION_DAYS, keeping registered sessions and the newestSWIPIUM_RETENTION_KEEPper project whose root still exists.swipium gccleans up on demand. Session files are 0600 in 0700 directories, and so are~/.swipium'sregistry.json,projects.jsonandprocesses.json.
Configuration & environment variables
Set these in the MCP server's env block (or your shell, for CLI commands). This is the complete list of variables Swipium reads.
Codex forwards only the names listed in env_vars (plus a fixed whitelist such as HOME and PATH); swipium init codex writes that line for you. It deliberately leaves out CLAUDE_PROJECT_DIR, SWIPIUM_DISABLE_DEVICE_DISCOVERY and the approval grants (SWIPIUM_CONSENT_PREAPPROVE, SWIPIUM_CONSENT_PREAPPROVE_RUN_CODE, SWIPIUM_ALLOW_REMOTE_WDA): set those literally in env = { ... } so an inherited shell export or a direnv file in a cloned repo can't grant approvals. Add your own custom SWIPIUM_* flow variables (and SWIPIUM_DEVICE, if you pin a device) to the list. Details: docs/mcp-server.md.
An invalid SWIPIUM_MAX_WAIT_MS, SWIPIUM_TOOLS, or SWIPIUM_LOG_LEVEL falls back to its default with a warn line on stderr at startup.
Generated flows, suites, and code never contain credential values; they reference SWIPIUM_TEST_*, SWIPIUM_SECRET_N, or SWIPIUM_GEN_<FIELD>, which you set when replaying. Generated Appium projects read their own variables (SWIPIUM_PLATFORM, APPIUM_HOST, ANDROID_*, IOS_*, …), documented in their README.
CLI reference
With no subcommand, swipium runs the stdio MCP server (what clients launch). swipium --help prints this list.
CI
swipium report renders a finished run as JUnit, SARIF, a GitHub job summary, Markdown, or JSON; --fail-on-gate fails the job when the .swipium/policy.json release gate blocks. The CLI does not drive devices: CI still needs an agent (such as claude -p) calling the MCP tools, including qa_flow_run to replay saved flows. Nobody answers consent prompts in CI, so either install the app in the workflow before the agent step (safest) or pre-approve the exact actions the agent needs. Recipe: docs/ci-reports.md.
Upgrading
Restart your MCP client after every upgrade; a client still running the old server gets STALE_CLIENT errors.
- From 2.2.x to 2.3.0: Node.js 22.12 or newer;
qa_test_thisandqa_test_featurerun by default (mode:"plan"previews); element lines drop empty names and mark non-clickable elements with a trailing-;qa_reportreturns a lean view (format:"json"for the full report); a run that tested nothing (including one that launched the app but exercised no workflow, or only has aqa_notepass with no device evidence) is never SHIP and fails--fail-on-gate; budget stops areok:false(BUDGET_EXHAUSTED) and the default budget is 30 min / 60 actions / 40 screenshots;qa_act type(and flowinputTextwithinto) fails withTEXT_INPUT_MISMATCHwhen the field doesn't read back what was typed, coordinate targets included; an app that renders nothing, or stays on its loading screen, at launch isblockedwithBLANK_SCREEN; shared folders such as~/Library,~/Downloads,/tmpor/Applicationsare refused asprojectRoot; managed WebDriverAgents run on a per-simulator port (8101 to 8900) instead of 8100; an invalid.swipium/policy.jsonmakesswipium reportexit 2; Swipium-booted emulators quick-boot from their snapshot with-gpu auto. See the 2.3.0 upgrade checklist. - From 2.0.x to 2.2.0: element lists in
structuredContentare now@eNstrings (passresponseMode:"verbose"for objects), Codex needs theenv_varsline,qa_wda buildreturns a job, and waits end within about 50 s. See the 2.2.0 upgrade checklist. - From 1.5.x: 2.0 removed and renamed tools, reads only
SWIPIUM_*variables in flows and fixtures, and asks consent for every install. Work through the 2.0.0 upgrade checklist and the migration table first, then the 2.2.0 one.
Troubleshooting
Every error has a failureCode, nextSteps, and retrySafe. qa_explain_blocker explains any code, qa_doctor checks the toolchain, and the full catalog is in docs/tools.md.
If tool calls time out in the client, raise its tool timeout (Codex tool_timeout_sec = 600): waits and job polls end within about 50 s, but a few synchronous steps, such as a large qa_ios install, long saved flows in qa_smoke, or qa_mobile_audit, can take longer.
Security
Swipium is a local stdio process and the server opens no network port. A WebDriverAgent it manages for iOS takes commands on 127.0.0.1 only, but its MJPEG screen stream (port = WDA port + 1000, for example 9101) listens on every network interface, because WebDriverAgent can't restrict it; use a firewall on untrusted networks. Actions with side effects need your consent; each approval is single-use and bound to its session, and only the server's own environment can pre-approve actions (SWIPIUM_CONSENT_PREAPPROVE), never a repository file or the model. Known secret values are redacted from snapshots, artifacts, and reports, and generated output is checked for leaked secrets. A cloned repository's .swipium/ is treated as untrusted: repo-supplied commands are shown verbatim in the consent prompt, flows read only SWIPIUM_* variables, and Swipium never runs git. Screenshots are never redacted, so use qa_start_session { sensitive: true } to refuse all screen captures when that matters. Details: Threat Model. Report vulnerabilities privately per the Security Policy.
Contributing and license
See CONTRIBUTING.md, SUPPORT.md, the docs index, and the CHANGELOG. MIT licensed; see LICENSE.