swipium 2.0.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/README.md +32 -20
- package/THREAT_MODEL.md +109 -21
- package/dist/automationGen/run.js.map +1 -1
- package/dist/cli/init.js +101 -11
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/verify.js +2 -2
- package/dist/cli/verify.js.map +1 -1
- package/dist/consent/consent.js +365 -17
- package/dist/consent/consent.js.map +1 -1
- package/dist/context/projectRoot.js +9 -4
- package/dist/context/projectRoot.js.map +1 -1
- package/dist/context/protocolEra.js +15 -0
- package/dist/context/protocolEra.js.map +1 -0
- package/dist/drivers/DirectDriver.js +5 -2
- package/dist/drivers/DirectDriver.js.map +1 -1
- package/dist/featureTesting/executionBootstrap.js +85 -77
- package/dist/featureTesting/executionBootstrap.js.map +1 -1
- package/dist/flows/run.js +9 -5
- package/dist/flows/run.js.map +1 -1
- package/dist/lib/abortScope.js +36 -3
- package/dist/lib/abortScope.js.map +1 -1
- package/dist/lib/android.js +15 -6
- package/dist/lib/android.js.map +1 -1
- package/dist/lib/codexEnv.js +112 -0
- package/dist/lib/codexEnv.js.map +1 -0
- package/dist/lib/logger.js +18 -0
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/result.js +188 -10
- package/dist/lib/result.js.map +1 -1
- package/dist/lib/schemaHash.js +20 -31
- package/dist/lib/schemaHash.js.map +1 -1
- package/dist/lib/simctl.js +102 -3
- package/dist/lib/simctl.js.map +1 -1
- package/dist/lib/toolSchema.js +143 -0
- package/dist/lib/toolSchema.js.map +1 -0
- package/dist/lib/wda.js +5 -2
- package/dist/lib/wda.js.map +1 -1
- package/dist/mobileAudit/runner.js +12 -4
- package/dist/mobileAudit/runner.js.map +1 -1
- package/dist/oracle/failures.js +2 -2
- package/dist/oracle/failures.js.map +1 -1
- package/dist/orchestration/testThis/execute.js +21 -4
- package/dist/orchestration/testThis/execute.js.map +1 -1
- package/dist/orchestration/testThis/pipeline.js +2 -0
- package/dist/orchestration/testThis/pipeline.js.map +1 -1
- package/dist/orchestration/testThis/plan.js.map +1 -1
- package/dist/server.js +564 -139
- package/dist/server.js.map +1 -1
- package/dist/services/prepareAndroid.js +3 -2
- package/dist/services/prepareAndroid.js.map +1 -1
- package/dist/services/prepareIos.js +31 -4
- package/dist/services/prepareIos.js.map +1 -1
- package/dist/services/smoke.js +38 -4
- package/dist/services/smoke.js.map +1 -1
- package/dist/session/processRegistry.js +12 -1
- package/dist/session/processRegistry.js.map +1 -1
- package/dist/snapshot/parse.js +64 -9
- package/dist/snapshot/parse.js.map +1 -1
- package/dist/snapshot/present.js +14 -4
- package/dist/snapshot/present.js.map +1 -1
- package/dist/snapshot/settle.js +14 -3
- package/dist/snapshot/settle.js.map +1 -1
- package/dist/tools/act.js +694 -670
- package/dist/tools/act.js.map +1 -1
- package/dist/tools/agent.js +15 -16
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/appControl.js +4 -4
- package/dist/tools/appControl.js.map +1 -1
- package/dist/tools/appMap.js +11 -13
- package/dist/tools/appMap.js.map +1 -1
- package/dist/tools/build.js +4 -6
- package/dist/tools/build.js.map +1 -1
- package/dist/tools/bundletool.js +3 -3
- package/dist/tools/bundletool.js.map +1 -1
- package/dist/tools/clearOverlay.js +2 -3
- package/dist/tools/clearOverlay.js.map +1 -1
- package/dist/tools/device.js +4 -3
- package/dist/tools/device.js.map +1 -1
- package/dist/tools/doctor.js +23 -11
- package/dist/tools/doctor.js.map +1 -1
- package/dist/tools/explore.js +15 -21
- package/dist/tools/explore.js.map +1 -1
- package/dist/tools/featureTesting.js +22 -6
- package/dist/tools/featureTesting.js.map +1 -1
- package/dist/tools/firstRun.js +4 -5
- package/dist/tools/firstRun.js.map +1 -1
- package/dist/tools/flow.js +13 -17
- package/dist/tools/flow.js.map +1 -1
- package/dist/tools/flowRepair.js +3 -5
- package/dist/tools/flowRepair.js.map +1 -1
- package/dist/tools/generate.js +10 -11
- package/dist/tools/generate.js.map +1 -1
- package/dist/tools/getArtifact.js +114 -10
- package/dist/tools/getArtifact.js.map +1 -1
- package/dist/tools/health.js +2 -1
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/ios.js +35 -13
- package/dist/tools/ios.js.map +1 -1
- package/dist/tools/issues.js +8 -8
- package/dist/tools/issues.js.map +1 -1
- package/dist/tools/jobs.js +26 -10
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/metro.js +4 -5
- package/dist/tools/metro.js.map +1 -1
- package/dist/tools/mobileAudit.js +11 -10
- package/dist/tools/mobileAudit.js.map +1 -1
- package/dist/tools/note.js +2 -3
- package/dist/tools/note.js.map +1 -1
- package/dist/tools/prepareIosTarget.js +102 -31
- package/dist/tools/prepareIosTarget.js.map +1 -1
- package/dist/tools/prepareTarget.js +3 -5
- package/dist/tools/prepareTarget.js.map +1 -1
- package/dist/tools/report.js +4 -5
- package/dist/tools/report.js.map +1 -1
- package/dist/tools/resolveArtifact.js +2 -3
- package/dist/tools/resolveArtifact.js.map +1 -1
- package/dist/tools/resolveTarget.js +3 -6
- package/dist/tools/resolveTarget.js.map +1 -1
- package/dist/tools/screenRecord.js +2 -3
- package/dist/tools/screenRecord.js.map +1 -1
- package/dist/tools/screenshot.js +2 -2
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/smoke.js +4 -2
- package/dist/tools/smoke.js.map +1 -1
- package/dist/tools/snapshot.js +6 -7
- package/dist/tools/snapshot.js.map +1 -1
- package/dist/tools/startSession.js +12 -16
- package/dist/tools/startSession.js.map +1 -1
- package/dist/tools/suite.js +2 -4
- package/dist/tools/suite.js.map +1 -1
- package/dist/tools/testSuite.js +13 -19
- package/dist/tools/testSuite.js.map +1 -1
- package/dist/tools/testThis.js +28 -13
- package/dist/tools/testThis.js.map +1 -1
- package/dist/tools/visual.js +7 -9
- package/dist/tools/visual.js.map +1 -1
- package/dist/tools/wait.js +130 -21
- package/dist/tools/wait.js.map +1 -1
- package/dist/tools/wda.js +199 -60
- package/dist/tools/wda.js.map +1 -1
- package/dist/version.js +1 -1
- package/docs/README.md +4 -4
- package/docs/ci-reports.md +39 -7
- package/docs/concepts.md +37 -25
- package/docs/flows.md +1 -1
- package/docs/mcp-server.md +281 -76
- package/docs/physical-devices.md +4 -4
- package/docs/tools.md +59 -30
- package/package.json +4 -3
package/docs/mcp-server.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
Swipium is a local stdio MCP server. Your MCP client starts it as a child process and talks
|
|
4
4
|
JSON-RPC over its stdin and stdout. Nothing listens on the network.
|
|
5
5
|
|
|
6
|
+
This page covers client setup, the protocol, and how the server behaves. Sessions, consent and the
|
|
7
|
+
project root are explained in [concepts.md](concepts.md); every tool is in [tools.md](tools.md);
|
|
8
|
+
environment variables are in the
|
|
9
|
+
[README](../README.md#configuration--environment-variables).
|
|
10
|
+
|
|
11
|
+
**Contents**: [Requirements](#requirements) · [Server command](#server-command) ·
|
|
12
|
+
[Client setup](#client-setup) · [Protocol versions](#protocol-versions) ·
|
|
13
|
+
[What the server exposes](#what-the-server-exposes) · [Server behavior](#server-behavior) ·
|
|
14
|
+
[Verification](#verification) · [Debugging](#debugging) · [Troubleshooting](#troubleshooting)
|
|
15
|
+
|
|
6
16
|
## Requirements
|
|
7
17
|
|
|
8
18
|
- Node.js 20 or newer.
|
|
@@ -15,6 +25,9 @@ JSON-RPC over its stdin and stdout. Nothing listens on the network.
|
|
|
15
25
|
the structured UI tree, taps, typing and swipes. Visual-only checks work through `simctl` without
|
|
16
26
|
it: `qa_visual` baselines and diffs, OCR `find_text`, and template `find_image`.
|
|
17
27
|
|
|
28
|
+
Swipium only drives Android Emulators and iOS Simulators, never a physical device. See
|
|
29
|
+
[physical-devices.md](physical-devices.md).
|
|
30
|
+
|
|
18
31
|
### How Android tools are found
|
|
19
32
|
|
|
20
33
|
Swipium looks for an Android SDK in `$ANDROID_HOME`, then `$ANDROID_SDK_ROOT`, then the default
|
|
@@ -47,15 +60,6 @@ instead. From a source checkout, run `npm run build` and use
|
|
|
47
60
|
| `swipium init`, `verify`, `scan`, `suite`, `report`, `gc` | CLI subcommands. See `swipium --help`. |
|
|
48
61
|
| `swipium <unknown word>` | Prints usage and exits with status 2 instead of starting a server. |
|
|
49
62
|
|
|
50
|
-
## Project root
|
|
51
|
-
|
|
52
|
-
Every tool call works in one app repository. Swipium takes it from the `projectRoot` argument, then
|
|
53
|
-
the client's MCP roots, then `SWIPIUM_PROJECT_ROOT`, then `CLAUDE_PROJECT_DIR`, then the server's
|
|
54
|
-
working directory when that looks like an app. The full rules are in
|
|
55
|
-
[concepts.md](concepts.md#project-root). For client setup, the practical rule is: if your client
|
|
56
|
-
neither sends MCP roots nor lets you set a `cwd` (Claude Desktop, Windsurf), set
|
|
57
|
-
`SWIPIUM_PROJECT_ROOT` in the server `env`.
|
|
58
|
-
|
|
59
63
|
## Client setup
|
|
60
64
|
|
|
61
65
|
`swipium init <client>` prints the exact registration and changes nothing. Add `--apply` to
|
|
@@ -65,7 +69,7 @@ perform it. After a successful apply it runs `swipium verify`. Options: `--scope
|
|
|
65
69
|
| Client | `swipium init` does | Where it lands |
|
|
66
70
|
| --- | --- | --- |
|
|
67
71
|
| Claude Code | Runs `claude mcp add swipium [--scope …] -- <command>` | `local` / `user`: `~/.claude.json`; `project`: `.mcp.json` |
|
|
68
|
-
| Codex | Appends a `[mcp_servers.swipium]` block with `cwd
|
|
72
|
+
| Codex | Appends a `[mcp_servers.swipium]` block with `cwd`, timeouts and `env_vars` (see [Codex](#codex)) | `~/.codex/config.toml` (`$CODEX_HOME/config.toml` when set) |
|
|
69
73
|
| Gemini CLI | Runs `gemini mcp add --scope project\|user swipium …`; prints a manual block if that fails | `.gemini/settings.json` (project, the default), `~/.gemini/settings.json` (`--scope user`) |
|
|
70
74
|
| Cursor | Merges a `swipium` entry under `mcpServers` | `.cursor/mcp.json` (for all projects, add the same entry to `~/.cursor/mcp.json` yourself) |
|
|
71
75
|
| VS Code | Merges a `swipium` entry under `servers`; prints a `code --add-mcp …` line for the user profile | `.vscode/mcp.json` |
|
|
@@ -77,24 +81,42 @@ Which command gets written:
|
|
|
77
81
|
- **Team-shared files** get the portable `npx -y swipium`. That covers Claude `--scope project`,
|
|
78
82
|
Gemini project scope, `.cursor/mcp.json` and `.vscode/mcp.json`.
|
|
79
83
|
- **Machine-local registrations** get this machine's `node` and the absolute path of the installed
|
|
80
|
-
`dist/index.js`. That covers Claude local and user scope, Gemini user scope, and Codex. The
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
`dist/index.js`. That covers Claude local and user scope, Gemini user scope, and Codex. The node
|
|
85
|
+
path is one that survives upgrades: a Homebrew `node` is written as its `opt` path (for example
|
|
86
|
+
`/opt/homebrew/opt/node@20/bin/node`), not the versioned `Cellar` path that `brew upgrade`
|
|
87
|
+
removes; otherwise `init` prefers the `node` on your `PATH` that resolves to the running binary.
|
|
88
|
+
If Swipium itself runs from the npx cache, that path would disappear, so these get
|
|
89
|
+
`npx -y swipium` too.
|
|
83
90
|
|
|
84
91
|
For Cursor and VS Code, `init` refuses to edit a file that isn't plain JSON (for example JSONC with
|
|
85
92
|
comments), prints the entry to add by hand, and exits with status 2. An existing `swipium` entry
|
|
86
93
|
is left unchanged.
|
|
87
94
|
|
|
95
|
+
### Project root
|
|
96
|
+
|
|
97
|
+
Every tool call works in one app repository. The full resolution order is in
|
|
98
|
+
[concepts.md](concepts.md#project-root). For setup, the practical rule: if your client neither
|
|
99
|
+
sends MCP roots nor lets you set a `cwd` (Claude Desktop, Windsurf), set `SWIPIUM_PROJECT_ROOT` in
|
|
100
|
+
the server `env`. Clients on MCP 2026-07-28 never send roots (see
|
|
101
|
+
[Protocol versions](#protocol-versions)); Claude Code covers that by setting `CLAUDE_PROJECT_DIR`.
|
|
102
|
+
|
|
88
103
|
### Manual configuration
|
|
89
104
|
|
|
90
|
-
Claude Code
|
|
105
|
+
#### Claude Code
|
|
91
106
|
|
|
92
107
|
```bash
|
|
93
108
|
claude mcp add swipium --scope project -- npx -y swipium
|
|
94
109
|
```
|
|
95
110
|
|
|
96
|
-
|
|
97
|
-
|
|
111
|
+
Claude Code sets `CLAUDE_PROJECT_DIR` for the servers it launches, so Swipium picks up the open
|
|
112
|
+
project without extra config. Claude Code 2.1.285 and later (verified on 2.1.289) connect on MCP 2026-07-28, so consent
|
|
113
|
+
prompts arrive as an `InputRequiredResult` (see [Protocol versions](#protocol-versions)); the user
|
|
114
|
+
sees the same one-checkbox prompt either way.
|
|
115
|
+
|
|
116
|
+
#### Codex
|
|
117
|
+
|
|
118
|
+
Add this to `~/.codex/config.toml` (or `$CODEX_HOME/config.toml`). Codex's defaults of 10 s to
|
|
119
|
+
start and 60 s per tool call are too short for a first `npx` run and for simulator boots:
|
|
98
120
|
|
|
99
121
|
```toml
|
|
100
122
|
[mcp_servers.swipium]
|
|
@@ -103,13 +125,51 @@ args = ["-y", "swipium"]
|
|
|
103
125
|
cwd = "/absolute/path/to/your/mobile-app"
|
|
104
126
|
startup_timeout_sec = 30
|
|
105
127
|
tool_timeout_sec = 600
|
|
128
|
+
env_vars = ["SWIPIUM_TEST_EMAIL", "SWIPIUM_TEST_USERNAME", "SWIPIUM_TEST_PASSWORD", "SWIPIUM_TEST_OTP", "SWIPIUM_TEST_PIN", "SWIPIUM_TEST_TOKEN", "SWIPIUM_TEST_DEEP_LINK", "SWIPIUM_VERIFICATION_CODE", "SWIPIUM_PROJECT_ROOT", "SWIPIUM_REQUIRE_ELICITATION", "SWIPIUM_LOG_LEVEL", "SWIPIUM_OCR_CMD", "SWIPIUM_VISUAL_MASK_CMD", "SWIPIUM_RETENTION_DAYS", "SWIPIUM_RETENTION_KEEP", "ANDROID_HOME", "ANDROID_SDK_ROOT", "ANDROID_SDK_HOME", "ANDROID_USER_HOME", "ANDROID_AVD_HOME", "ANDROID_EMULATOR_HOME", "JAVA_HOME", "JAVA_TOOL_OPTIONS", "GRADLE_USER_HOME", "GRADLE_OPTS", "BUNDLETOOL_JAR", "APPIUM_HOME", "DEVELOPER_DIR", "DEVELOPMENT_TEAM", "XCODE_DEVELOPMENT_TEAM", "WDA_PROJECT_PATH", "WEBDRIVERAGENT_PROJECT", "HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "CI", "GITHUB_SHA", "GITHUB_REF_NAME", "GITHUB_SERVER_URL", "GITHUB_REPOSITORY", "GITHUB_RUN_ID", "CI_COMMIT_SHA", "CI_COMMIT_REF_NAME", "CI_PIPELINE_URL", "BITBUCKET_COMMIT", "BITBUCKET_BRANCH"]
|
|
129
|
+
# Optional: a smaller tool list (off by default). `swipium init codex` prints the core list here
|
|
130
|
+
# as a comment; it includes every tool the server instructions and qa_status point to.
|
|
131
|
+
# enabled_tools = ["qa_test_this", "qa_status", ...]
|
|
106
132
|
```
|
|
107
133
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
134
|
+
**Codex does not pass your shell environment to MCP servers.** A stdio server only gets a fixed
|
|
135
|
+
whitelist (`HOME`, `LANG`, `LC_ALL`, `LOGNAME`, `PATH`, `SHELL`, `TERM`, `TMPDIR`, `USER`,
|
|
136
|
+
`__CF_USER_TEXT_ENCODING` and the CA certificate variables), plus the names in `env_vars` and the
|
|
137
|
+
`env = { NAME = "value" }` table of `[mcp_servers.swipium]` (see the
|
|
138
|
+
[Codex config reference](https://developers.openai.com/codex/config-reference)). So
|
|
139
|
+
`SWIPIUM_TEST_*`, other `SWIPIUM_*` settings, `ANDROID_HOME` and `JAVA_HOME` exported in your shell
|
|
140
|
+
never reach Swipium unless they are listed.
|
|
141
|
+
|
|
142
|
+
- `env_vars` forwards a name from the environment Codex was started in (unset names are skipped);
|
|
143
|
+
`env` sets a literal value. Add any custom `SWIPIUM_*` flow variables and
|
|
144
|
+
`ORG_GRADLE_PROJECT_*` signing variables you use.
|
|
145
|
+
- The default list leaves out the names that grant approval (`SWIPIUM_CONSENT_PREAPPROVE`,
|
|
146
|
+
`SWIPIUM_CONSENT_PREAPPROVE_RUN_CODE`, `SWIPIUM_ALLOW_REMOTE_WDA`). Forwarding them would let an
|
|
147
|
+
inherited shell export, or a per-directory env tool such as direnv in a cloned repo, pre-approve
|
|
148
|
+
actions. If you want them, set them literally in `env = { ... }`.
|
|
149
|
+
- `env_vars` is config-only: `codex mcp add --env NAME=value` sets literal values but can't forward
|
|
150
|
+
names, so after `codex mcp add`, add the `env_vars` line and the timeouts to `config.toml` by hand.
|
|
151
|
+
|
|
152
|
+
What `swipium init codex --apply` does: it refuses if `--cwd` doesn't exist, and otherwise appends
|
|
153
|
+
the block above (with this machine's `node`, the commented `enabled_tools` line, and a few comment
|
|
154
|
+
lines). If `config.toml` already has a `[mcp_servers.swipium]` (or `[mcp_servers."swipium"]`)
|
|
155
|
+
table, it leaves it alone: when that table has no `env_vars`, it prints just the line to add;
|
|
156
|
+
otherwise it prints the expected block so you can compare.
|
|
157
|
+
|
|
158
|
+
`qa_doctor` adds two rows when the client is Codex (or you pass `qa_doctor { client: "codex" }`):
|
|
159
|
+
|
|
160
|
+
- `codex-env` lists the Swipium variables the server can see and prints the `env_vars` line. It
|
|
161
|
+
warns when no Android SDK is found (no `ANDROID_HOME`/`ANDROID_SDK_ROOT`, no SDK at the default
|
|
162
|
+
location, no `adb` on `PATH`) or `java -version` fails without `JAVA_HOME`: if you installed the
|
|
163
|
+
SDK or JDK in a custom location, forward `ANDROID_HOME` / `JAVA_HOME`; if not, install it first.
|
|
164
|
+
- `codex-tool-timeout` is a reminder only: the server can't read `tool_timeout_sec`, so keep it at
|
|
165
|
+
600 or more.
|
|
166
|
+
|
|
167
|
+
Codex 0.146 connects on MCP 2025-06-18. Known caveat: in the Codex Desktop app, custom stdio MCP
|
|
168
|
+
tools can show up in `/mcp` without being available in threads
|
|
169
|
+
([openai/codex#19425](https://github.com/openai/codex/issues/19425)). If that happens, use the
|
|
170
|
+
Codex CLI.
|
|
171
|
+
|
|
172
|
+
#### Gemini CLI
|
|
113
173
|
|
|
114
174
|
```bash
|
|
115
175
|
gemini mcp add --scope project swipium npx -- -y swipium
|
|
@@ -118,9 +178,11 @@ gemini mcp add --scope project swipium npx -- -y swipium
|
|
|
118
178
|
The `--` keeps Gemini from reading `-y` as its own flag. `init gemini` also suggests
|
|
119
179
|
`"timeout": 600000` in the settings entry.
|
|
120
180
|
|
|
121
|
-
Cursor
|
|
122
|
-
|
|
123
|
-
`swipium init
|
|
181
|
+
#### Cursor and VS Code
|
|
182
|
+
|
|
183
|
+
This is the entry `swipium init cursor` writes to `.cursor/mcp.json`. For VS Code
|
|
184
|
+
(`.vscode/mcp.json`, written by `swipium init vscode`), use the same entry under a top-level
|
|
185
|
+
`"servers"` key instead of `"mcpServers"`:
|
|
124
186
|
|
|
125
187
|
```json
|
|
126
188
|
{
|
|
@@ -135,13 +197,13 @@ Cursor (`.cursor/mcp.json`). For VS Code (`.vscode/mcp.json`), use the same entr
|
|
|
135
197
|
}
|
|
136
198
|
```
|
|
137
199
|
|
|
138
|
-
Why
|
|
139
|
-
|
|
140
|
-
variable
|
|
141
|
-
|
|
142
|
-
|
|
200
|
+
Why set `SWIPIUM_PROJECT_ROOT` when these editors can send MCP roots: roots come first in the
|
|
201
|
+
resolution order, so when the editor sends them, Swipium uses them and ignores the variable. The
|
|
202
|
+
variable is a fallback for an editor version or window that sends no roots. The editor replaces
|
|
203
|
+
`${workspaceFolder}` with the open folder; if it's left unexpanded, the value isn't an absolute
|
|
204
|
+
path and Swipium skips it.
|
|
143
205
|
|
|
144
|
-
Claude Desktop and Windsurf
|
|
206
|
+
#### Claude Desktop and Windsurf
|
|
145
207
|
|
|
146
208
|
```json
|
|
147
209
|
{
|
|
@@ -155,20 +217,63 @@ Claude Desktop and Windsurf:
|
|
|
155
217
|
}
|
|
156
218
|
```
|
|
157
219
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
220
|
+
### Headless runs
|
|
221
|
+
|
|
222
|
+
`codex exec` and `claude -p` advertise elicitation but answer every consent prompt automatically
|
|
223
|
+
(decline or cancel), so boots, installs and builds never run there. To allow specific actions, an
|
|
224
|
+
operator lists them in `SWIPIUM_CONSENT_PREAPPROVE` in the server's environment. The rules (exact
|
|
225
|
+
names only, the `SWIPIUM_CONSENT_PREAPPROVE_RUN_CODE=1` tier for actions that run code, what can
|
|
226
|
+
never be pre-approved) are in [concepts.md](concepts.md#consent). Where to set it:
|
|
227
|
+
|
|
228
|
+
- **Codex**: literally in the `env` table, since `env_vars` never forwards it:
|
|
229
|
+
`env = { SWIPIUM_CONSENT_PREAPPROVE = "prepare_plan,install_app" }`.
|
|
230
|
+
- **`claude -p`**: in the `env` of the server entry you pass with `--mcp-config`. The
|
|
231
|
+
[CI recipe](ci-reports.md#pre-approving-consents-in-ci) shows a complete config.
|
|
232
|
+
|
|
233
|
+
## Protocol versions
|
|
234
|
+
|
|
235
|
+
Swipium is a dual-era stdio server. The client's first message picks the era for the whole
|
|
236
|
+
connection:
|
|
237
|
+
|
|
238
|
+
| Client opens with | Protocol | Served as |
|
|
239
|
+
| --- | --- | --- |
|
|
240
|
+
| `initialize` (`protocolVersion` 2025-06-18 or 2025-11-25), e.g. Codex | 2025-era | Handshake, session-scoped capabilities, `elicitation/create` and `roots/list` requests to the client. |
|
|
241
|
+
| `server/discover`, or any request with the `io.modelcontextprotocol/*` `_meta` envelope, e.g. Claude Code | 2026-07-28 | No handshake: the version and client capabilities come with every request. |
|
|
242
|
+
|
|
243
|
+
What differs on a 2026-07-28 connection:
|
|
244
|
+
|
|
245
|
+
- `server/discover` returns the supported versions (`2026-07-28`), capabilities, the server
|
|
246
|
+
instructions and `serverInfo`. Every result carries `resultType`.
|
|
247
|
+
- `tools/list` is sorted by tool name (2025-era connections keep the registration order; the schema
|
|
248
|
+
hash doesn't depend on order). List results carry cache hints: `tools/list`, `prompts/list`,
|
|
249
|
+
`resources/templates/list` and `server/discover` are `ttlMs: 3600000`, `cacheScope: "public"`
|
|
250
|
+
(fixed for the life of the process); `resources/list` and `resources/read` are `ttlMs: 0`,
|
|
251
|
+
`cacheScope: "private"` (they change during a run and name local paths).
|
|
252
|
+
- Consent prompts travel inside an `InputRequiredResult`, and the answer comes back on the client's
|
|
253
|
+
retry of the tool call (see [Consent](#consent-prompts) below).
|
|
254
|
+
- There are no server-to-client requests, so MCP roots are never asked for. Pass `projectRoot`, or
|
|
255
|
+
rely on `SWIPIUM_PROJECT_ROOT` / `CLAUDE_PROJECT_DIR`.
|
|
256
|
+
- `ping` and `logging/setLevel` don't exist in 2026-07-28. Swipium logs to stderr either way.
|
|
257
|
+
|
|
258
|
+
The same in both eras: cancellation (`notifications/cancelled`), the `INVALID_ARGUMENT`,
|
|
259
|
+
`STALE_CLIENT` and `CONSENT_*` envelopes, and the `-32602` error for a missing resource.
|
|
260
|
+
|
|
261
|
+
Swipium is built on the MCP TypeScript SDK v2 (`@modelcontextprotocol/server` 2.3.1). For
|
|
262
|
+
2025-era clients the move from the 1.x SDK is invisible: tool results are byte-identical and the
|
|
263
|
+
schema hash is unchanged. The one `tools/list` difference is that `qa_act`'s `for.selector` schema
|
|
264
|
+
is inlined instead of a `$ref`.
|
|
161
265
|
|
|
162
266
|
## What the server exposes
|
|
163
267
|
|
|
164
|
-
- **Tools**: listed in [tools.md](tools.md). Run `swipium verify` to see the exact list and
|
|
165
|
-
your installed version serves. Every tool carries MCP annotations: read-only tools set
|
|
268
|
+
- **Tools**: listed in [tools.md](tools.md). Run `swipium verify` to see the exact list, count and
|
|
269
|
+
schema hash your installed version serves. Every tool carries MCP annotations: read-only tools set
|
|
166
270
|
`readOnlyHint: true` and `openWorldHint: false`, and the others also set `destructiveHint` and
|
|
167
271
|
`idempotentHint`.
|
|
168
|
-
- **Server instructions**: sent on `initialize
|
|
169
|
-
(`qa_test_this { mode: "execute" }`), the polling loop
|
|
170
|
-
`needs_input`, blockers and consent, and the
|
|
171
|
-
`sessionId` returns the same orientation plus the tool
|
|
272
|
+
- **Server instructions**: sent on `initialize` (2025) or `server/discover` (2026-07-28). They give
|
|
273
|
+
the first call (`qa_test_this { mode: "execute" }`), the polling loop
|
|
274
|
+
(`qa_job_status … waitMs: 45000`), how to relay `needs_input`, blockers and consent, and the
|
|
275
|
+
project-root order. `qa_status` without a `sessionId` returns the same orientation plus the tool
|
|
276
|
+
groups.
|
|
172
277
|
- **Prompts** (5): `swipium_setup_check`, `swipium_guardrail_validation`, `swipium_full_smoke`,
|
|
173
278
|
`swipium_bug_repro`, `swipium_convert_run_to_flow`.
|
|
174
279
|
- **Resources**:
|
|
@@ -177,42 +282,123 @@ retention) are listed in the
|
|
|
177
282
|
- `swipium://project/{projectId}/app-map`: the full app map.
|
|
178
283
|
- `swipium://project/{projectId}/app-map/{kind}/{id}`: one feature, screen or test-suite section.
|
|
179
284
|
|
|
180
|
-
`resources/list` shows only the current client's
|
|
181
|
-
sessions in this server process
|
|
182
|
-
entries per template, with the cap stated on the
|
|
183
|
-
read by URI. Clients without resource support use
|
|
285
|
+
`resources/list` shows only the current client's projects: the client's MCP roots (2025-era
|
|
286
|
+
connections only) plus the roots of sessions used in this server process. It never lists
|
|
287
|
+
sensitive-mode sessions, and it's capped at 100 entries per template, with the cap stated on the
|
|
288
|
+
last entry. Anything not listed can still be read by URI. Clients without resource support use
|
|
289
|
+
`qa_get_artifact` and `qa_app_map_read`.
|
|
184
290
|
|
|
185
291
|
## Server behavior
|
|
186
292
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
293
|
+
### What the model sees
|
|
294
|
+
|
|
295
|
+
Every tool result has a text block and `structuredContent`. Claude Code and Codex give the model
|
|
296
|
+
the `structuredContent` JSON, not the text, for successful results (Codex does it for every result
|
|
297
|
+
that has `structuredContent`; Claude Code shows the text only for errors). So successful results
|
|
298
|
+
carry:
|
|
299
|
+
|
|
300
|
+
- `summary`: the summary's first line (its headline). Results whose later lines say something the
|
|
301
|
+
payload doesn't (for example `qa_job_status` with the job's result text) keep the whole summary.
|
|
302
|
+
- `next`: next-step guidance ("Next: ...", "Call qa_report ..."), a list where each entry starts
|
|
303
|
+
with the tool to call. It's left out when the payload already has `nextBestAction`, `nextAction`,
|
|
304
|
+
`nextRecommendedAction` or `nextSteps`. Budget stops always carry `next: ["qa_report ..."]`.
|
|
305
|
+
|
|
306
|
+
Errors carry `what` and `nextSteps`. The text block stays as a plain-text copy for clients that
|
|
307
|
+
read it.
|
|
308
|
+
|
|
309
|
+
`responseMode` (`compact`, `normal`, `verbose`, set per session) mostly changes the text block, so
|
|
310
|
+
on Claude Code and Codex it makes little difference, with one exception: element lists. Outside
|
|
311
|
+
`verbose`, the `elements` of `qa_snapshot` and `qa_act` are compact one-line strings such as
|
|
312
|
+
`@e3 [button] "Log in" #login_btn [40,200][1040,245]`, about 40% smaller than one JSON object per
|
|
313
|
+
element; `verbose` returns the objects. Details: [Response modes](tools.md#response-modes) and
|
|
314
|
+
[Element lines](tools.md#element-lines).
|
|
315
|
+
|
|
316
|
+
### Every call returns within about 50 s
|
|
317
|
+
|
|
318
|
+
Client tool timeouts are short (Codex defaults to 60 s), so calls that wait are capped below that,
|
|
319
|
+
and long work runs as a background job:
|
|
320
|
+
|
|
321
|
+
- `qa_job_status` long-polls at most 50 s (`waitMs`, recommended 45000). Larger values are clamped,
|
|
322
|
+
not rejected.
|
|
323
|
+
- `qa_wait`, `qa_act` and `qa_test_this { waitForCompletion: true }` clamp `timeoutMs` to 50000
|
|
324
|
+
with a note (`qa_wait` and `qa_test_this` default to 45000).
|
|
325
|
+
- `qa_wda build` runs as a job. `qa_wda start` waits at most 45 s, then returns
|
|
326
|
+
`status: "starting"`; poll `qa_wait { for: "wda_ready" }` until WebDriverAgent is up.
|
|
327
|
+
- `qa_ios { action: "boot" }` waits at most 40 s for an iOS Simulator. A slower cold boot returns
|
|
328
|
+
`status: "booting"` with the simulator already bound; poll `qa_wait { for: "simulator_booted" }`.
|
|
329
|
+
`qa_prepare_ios_target` waits at most 30 s, then hands the rest (boot, install, launch) to a job
|
|
330
|
+
and returns `status: "booting"` with its `jobId`.
|
|
331
|
+
- Builds, test runs, exploration, Android boot and install steps, and the device preparation of
|
|
332
|
+
`qa_test_feature` are jobs too (see [Jobs](concepts.md#jobs)).
|
|
333
|
+
|
|
334
|
+
A few synchronous steps can still take longer: a large app install (`qa_ios install`, or
|
|
335
|
+
`qa_prepare_ios_target` on a simulator that is already booted), `qa_flow_run` and `qa_smoke` with
|
|
336
|
+
long saved flows, `qa_mobile_audit`, and `qa_generate { target: "appium" }` when it bootstraps a
|
|
337
|
+
device. Keep the client's tool timeout generous (Codex `tool_timeout_sec = 600`).
|
|
338
|
+
|
|
339
|
+
### Cancellation
|
|
340
|
+
|
|
341
|
+
When the client cancels a call (`notifications/cancelled`), the call's own work stops: waits,
|
|
342
|
+
long-polls, UI settling, boot waits and WebDriverAgent startup all end early, and the result is
|
|
343
|
+
`CANCELLED`. Cancelling a call never cancels a background job (use `qa_job_cancel`). The rules,
|
|
344
|
+
including what is and isn't rolled back, are in [Cancellation](concepts.md#cancellation).
|
|
345
|
+
|
|
346
|
+
### Resources and artifacts
|
|
347
|
+
|
|
348
|
+
Evidence is stored under `~/.swipium/runs/` and returned as `swipium://` URIs.
|
|
349
|
+
|
|
350
|
+
- `qa_get_artifact` returns metadata (URI, MIME type, size, local path) for images, recordings and
|
|
351
|
+
other non-text artifacts by default. Pass `mode: "inline"` only when you need the bytes.
|
|
352
|
+
- `resources/read` and `qa_get_artifact` share the same size caps. Text over 1 MB returns the first
|
|
353
|
+
1 MB (the last 1 MB for logs, including `*.log` files such as Metro and WDA logs) with a
|
|
354
|
+
`[swipium: truncated ...]` marker naming the local file to read for the rest. Binaries over 8 MB
|
|
355
|
+
aren't inlined; you get a note with the size and local path.
|
|
356
|
+
- A URI that matches a Swipium template but names nothing that exists (unknown artifact, a project
|
|
357
|
+
without an app map, an unknown section) fails with JSON-RPC error `-32602` and the URI in
|
|
358
|
+
`error.data.uri`. Swipium 2.0 sent a generic `-32603` (internal error); the spec asks clients to
|
|
359
|
+
accept the older `-32002` too.
|
|
360
|
+
|
|
361
|
+
### Invalid arguments and stale clients
|
|
362
|
+
|
|
363
|
+
- An argument a tool doesn't declare, a missing required argument or a wrong type returns
|
|
364
|
+
`INVALID_ARGUMENT` with the accepted parameter list, and nothing runs. Swipium never silently
|
|
365
|
+
drops an argument. Validation failures carry a per-field `what` (for example `uri: Required`) and
|
|
366
|
+
`invalidArguments`.
|
|
367
|
+
- An unknown tool name returns an `isError` result ("Tool ... not found").
|
|
368
|
+
- Errors never echo caller input at full size: argument names and paths are cut at 100 characters
|
|
369
|
+
(20 listed at most), `what` keeps its first and last part around a marker, every string is
|
|
370
|
+
capped, and an error still over 64 KB drops its extra fields (`extraDropped` names them).
|
|
371
|
+
- A call to a tool removed in 2.0, or a legacy call shape, returns `STALE_CLIENT` with the
|
|
372
|
+
replacement call and a hint to restart the client. `qa_doctor` accepts `expectedVersion`,
|
|
373
|
+
`expectedToolCount` and `expectedSchemaHash` and reports a mismatch. See
|
|
374
|
+
[tools.md](tools.md#unknown-arguments-and-stale-clients).
|
|
375
|
+
|
|
376
|
+
### Consent prompts
|
|
377
|
+
|
|
378
|
+
Privileged actions (builds, boots, installs, data wipes and similar) need consent. When the client
|
|
379
|
+
supports MCP form elicitation, Swipium asks the user directly and the model never sees a
|
|
380
|
+
`consentId`:
|
|
381
|
+
|
|
382
|
+
- 2025-era: an `elicitation/create` request while the tool call waits.
|
|
383
|
+
- 2026-07-28: an `InputRequiredResult` with the same form and a single-use `requestState`, sent
|
|
384
|
+
only when the request's `_meta` client capabilities declare form elicitation. A `requestState`
|
|
385
|
+
that is forged, reused or presented on another tool call fails with JSON-RPC `-32602`
|
|
386
|
+
(`Invalid or expired requestState`) and nothing runs.
|
|
387
|
+
|
|
388
|
+
Otherwise the tool returns a `requiresConsent` envelope for the agent to relay. Operator
|
|
389
|
+
pre-approval is checked first in both eras. The full model (mechanisms, outcomes, rules) is in
|
|
390
|
+
[concepts.md](concepts.md#consent); the security reasoning is in
|
|
391
|
+
[THREAT_MODEL.md](../THREAT_MODEL.md).
|
|
392
|
+
|
|
393
|
+
### Startup and shutdown
|
|
394
|
+
|
|
395
|
+
At startup the version and tool count are logged to stderr, and processes left behind by a crashed
|
|
396
|
+
earlier server are reaped in the background.
|
|
397
|
+
|
|
398
|
+
The server shuts down when the client closes stdin (even in the middle of a long call), on
|
|
399
|
+
`SIGINT` or `SIGTERM`, or when the transport closes. It then cancels running jobs, restores network
|
|
400
|
+
state it changed, and stops screen recorders and Metro. Managed WebDriverAgent keeps running so the
|
|
401
|
+
next server can reuse it; `qa_wda stop` stops it.
|
|
216
402
|
|
|
217
403
|
## Verification
|
|
218
404
|
|
|
@@ -221,13 +407,30 @@ swipium verify
|
|
|
221
407
|
```
|
|
222
408
|
|
|
223
409
|
This starts a Swipium server over stdio, checks that every tool and prompt this version declares is
|
|
224
|
-
listed, prints their names, count and schema hash, and calls `qa_doctor`. It exits with status 1 if
|
|
225
|
-
or `qa_doctor` errors. It starts the copy of Swipium you ran it with, not the
|
|
226
|
-
configured with.
|
|
410
|
+
listed, prints their names, count and schema hash, and calls `qa_doctor`. It exits with status 1 if
|
|
411
|
+
a tool is missing or `qa_doctor` errors. It starts the copy of Swipium you ran it with, not the
|
|
412
|
+
command your client is configured with.
|
|
227
413
|
|
|
228
414
|
Inside the client, call `qa_doctor`. It checks both platforms by default on macOS (ready if either
|
|
229
415
|
one is) and Android elsewhere. Pass `platform: "android" | "ios" | "both"` to be explicit.
|
|
230
416
|
|
|
417
|
+
## Debugging
|
|
418
|
+
|
|
419
|
+
Swipium logs JSON lines to stderr only (stdout carries the MCP stream). Most clients show server
|
|
420
|
+
stderr in their MCP logs. Set `SWIPIUM_LOG_LEVEL` in the server `env` to change how much it writes
|
|
421
|
+
(an unknown value falls back to `info`):
|
|
422
|
+
|
|
423
|
+
| Value | Writes |
|
|
424
|
+
| --- | --- |
|
|
425
|
+
| `error` | Errors only. |
|
|
426
|
+
| `warn` | Errors and warnings, including every operator pre-approved consent. |
|
|
427
|
+
| `info` (default) | Startup, shutdown, and notable events. |
|
|
428
|
+
| `debug` | Everything above, plus one `tool call` line per call and the protocol era of each connection. |
|
|
429
|
+
|
|
430
|
+
A `tool call` line carries `tool`, `sessionId` (when the call has one), `durationMs`, `isError`,
|
|
431
|
+
`failureCode` (on errors) and `cancelled`. Argument values are never logged, since they can hold
|
|
432
|
+
secrets.
|
|
433
|
+
|
|
231
434
|
## Troubleshooting
|
|
232
435
|
|
|
233
436
|
| Symptom | Fix |
|
|
@@ -235,9 +438,11 @@ one is) and Android elsewhere. Pass `platform: "android" | "ios" | "both"` to be
|
|
|
235
438
|
| The client lists fewer or different tools than `swipium verify`, or calls return `STALE_CLIENT` | The client is still running a server it started before the upgrade. Restart the client, or reload its MCP server list. |
|
|
236
439
|
| `PROJECT_ROOT_UNRESOLVED` | Pass an absolute `projectRoot`, or set `SWIPIUM_PROJECT_ROOT` in the server `env` (needed on Claude Desktop and Windsurf). |
|
|
237
440
|
| The server times out on first start (Codex, Gemini) | The first `npx` run downloads the package. Raise the startup timeout (Codex `startup_timeout_sec = 30`), or install globally and point the client at `swipium`. |
|
|
238
|
-
| Long tool calls time out | Raise the client's tool timeout (Codex `tool_timeout_sec = 600`, Gemini `timeout: 600000`). For builds and runs,
|
|
441
|
+
| Long tool calls time out | Raise the client's tool timeout (Codex `tool_timeout_sec = 600`, Gemini `timeout: 600000`). For builds and runs, use `qa_test_this { mode: "execute" }` and poll `qa_job_status`. |
|
|
442
|
+
| `CONSENT_DECLINED` or `CONSENT_CANCELLED` with `likelyAutomatic: true` | A headless client (`codex exec`, `claude -p`) answered the prompt. See [Headless runs](#headless-runs). |
|
|
239
443
|
| `adb` or `emulator` not found from a GUI client | Set `ANDROID_HOME` in the server `env`, or install the SDK in its default location. See [How Android tools are found](#how-android-tools-are-found). |
|
|
240
444
|
| `INVALID_ARGUMENT` listing accepted parameters | Remove the undeclared argument. If the tool list looks outdated, restart the client. |
|
|
445
|
+
| Codex: test credentials, `ANDROID_HOME` or `JAVA_HOME` ignored | Codex only passes a fixed env whitelist to MCP servers. List the names in `env_vars` under `[mcp_servers.swipium]` (see [Codex](#codex)); `qa_doctor` prints the line. |
|
|
241
446
|
| Tools missing in Codex Desktop threads | Known Codex Desktop issue ([openai/codex#19425](https://github.com/openai/codex/issues/19425)). Use the Codex CLI. |
|
|
242
447
|
| `swipium init cursor --apply` or `init vscode --apply` exits with status 2 | The existing file isn't plain JSON. Add the printed entry by hand. |
|
|
243
448
|
| `PHYSICAL_DEVICE_UNSUPPORTED` | A phone was the only device, or was requested explicitly. Start an emulator or simulator. See [physical-devices.md](physical-devices.md). |
|
package/docs/physical-devices.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Physical devices
|
|
2
2
|
|
|
3
|
-
Status: **not supported
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Status: **not supported.** Swipium runs only on Android Emulators and iOS Simulators. A physical
|
|
4
|
+
device is visible to Swipium, but Swipium never installs on it, launches on it or drives it. The
|
|
5
|
+
policy is server-side; a client can't opt in by passing a flag. This page describes the current
|
|
6
|
+
behavior and what support would need.
|
|
7
7
|
|
|
8
8
|
## The refusal rule
|
|
9
9
|
|