MCP Server
Experimental
tapflow's AI Automation axis — the MCP server and the flow runner — is experimental. The manual QA dashboard is the mature, production path; this axis is additive and still maturing. Expect rough edges, especially in selector matching and in timing right after an app launches.
@tapflowio/mcp-server exposes tapflow as a Model Context Protocol (MCP) server. Claude Code, Codex, and any other MCP-compatible LLM agent can control iOS simulators and Android emulators as native tools — no scripting, no hardcoded selectors.
These three guides fit together: connect a coding agent here, learn the flow YAML format in the Flow Reference, then combine them in MCP in CI/CD, where an agent authors a flow once and CI replays it deterministically.
When to use this
Repeatable automated testing is where this shines. One-off manual checks are still faster done by hand.
- CI/CD regression tests — After each build, an agent boots a simulator, installs the build, walks through key flows, captures screenshots, and reports regressions. No human intervention needed. → MCP in CI/CD
- Multi-device matrix — Run the same flow on iPhone SE (iOS 16), iPhone 15 Pro (iOS 17), and an Android emulator in sequence without manually switching devices.
- Natural language QA scripts — Non-developers (QA, PM) describe test scenarios in plain text; the agent executes them. No coordinate mapping or brittle selectors.
How it connects
LLM Agent (Claude Code, etc.)
↓ MCP protocol (stdio)
@tapflowio/mcp-server
↓ WebSocket + REST
tapflow relay
↓ WebSocket
Mac Agent (iOS · Android)The MCP server is a local process that bridges the LLM agent to your self-hosted relay. App data never leaves your network.
Prerequisites
- A running tapflow relay.
- A personal access token (PAT) created in the dashboard. Go to Settings → Tokens → New token and choose the API Type. The Tokens page is shown to Admins, Developers and QA.
Installation
npm install -g @tapflowio/mcp-serverSetup
Claude Code
Register tapflow with the claude mcp add command:
claude mcp add tapflow --scope project \
--env TAPFLOW_RELAY_URL=ws://localhost:4000 \
--env TAPFLOW_TOKEN=tflw_pat_your_token_here \
-- tapflow-mcp--scope project saves the config to .mcp.json so the whole team shares it. Use --scope local (the default) if you only want it for yourself.
If the relay is on a remote server, change the URL:
claude mcp add tapflow --scope project \
--env TAPFLOW_RELAY_URL=wss://your-relay.example.com \
--env TAPFLOW_TOKEN=tflw_pat_your_token_here \
-- tapflow-mcpOther MCP clients (Cursor, VS Code, Codex)
Any MCP-compatible client can use tapflow. Add the following to your MCP config JSON:
{
"mcpServers": {
"tapflow": {
"command": "tapflow-mcp",
"env": {
"TAPFLOW_RELAY_URL": "ws://localhost:4000",
"TAPFLOW_TOKEN": "tflw_pat_your_token_here"
}
}
}
}Environment variables
| Variable | Description | Default |
|---|---|---|
TAPFLOW_RELAY_URL | Relay WebSocket URL | ws://localhost:4000 |
TAPFLOW_TOKEN | An API-type PAT (view, builds:write), created in Settings → Tokens. Leave it out to read the token file | (none) |
TAPFLOW_TOKEN_FILE | Path of the token file | ~/.config/tapflow/credentials.json |
Keep the token in a file
Without TAPFLOW_TOKEN, the MCP server reads its token from a file. tapflow token set is the simplest way to store one, and it checks the token with the relay first; on a computer without the tapflow CLI, write the file by hand in the shape below. When the relay refuses the token, the server reads the file again and retries once, so replacing a token needs no re-registration and no restart.
- The file is
~/.config/tapflow/credentials.json, ortapflow/credentials.jsonunderXDG_CONFIG_HOMEwhen that is set, or the path inTAPFLOW_TOKEN_FILE. It is in the home directory of the computer that runs the MCP server, not of the Mac the relay runs on. - Each relay has its own token, keyed by the relay address's origin; write
ws://ashttp://andwss://ashttps://. - The file holds a token, so keep it at mode
600, readable only by you. The MCP server warns when other users can read it (not checked on Windows).
{
"version": 1,
"relays": {
"http://localhost:4000": { "token": "tflw_pat_your_token_here" }
}
}TAPFLOW_TOKEN, when set, wins over the file and is never replaced by it. To move a registered TAPFLOW_TOKEN into the file, remove it from the MCP registration.
When a token is revoked, the relay closes the connection right away. Write the new token to the file and the next tool call reconnects with it. The relay releases the sessions the old connection held, so call connect_device again.
Connected straight to the relay's port on the Mac it runs on, the MCP server starts and drives devices without a token. screenshot, query_ui_tree, list_builds, list_runs and get_run still need one. When the relay refuses a token, the error says which relay refused it, where the token came from, and how to fix it.
Available tools
| Tool | Description |
|---|---|
list_builds | List the apps and builds on the relay (where the buildId for install_app and launch_app comes from) |
list_devices | List connected simulators and emulators |
connect_device | Join a session (required before controlling a device). Also returns watchUrl, a dashboard link for watching the device |
disconnect_device | End a session |
boot_device | Boot a simulator or emulator |
shutdown_device | Power the device down (frees resources; forces a cold boot next time) |
screenshot | Capture the current screen (PNG, or JPEG on iOS when requested) |
query_ui_tree | Read the on-screen UI as a structured accessibility tree (role, label, identifier, frame) |
tap | Tap at a coordinate |
swipe | Swipe between two coordinates |
type_text | Type text into the focused field |
press_key | Press a keyboard key |
press_button | Press a hardware button (home, lock, etc.) |
install_app | Install an app |
launch_app | Launch an installed app |
run_flow | Replay a YAML flow deterministically (no further LLM calls) |
list_runs | List the run records tapflow flow run left on the relay (filter by status, build, flow name, CI or local) |
get_run | Read one run record: each failed flow's steps (a passed flow's step count), failure message and file path, and screenshots of up to four failed flows |
Typical workflow
An LLM agent typically calls tools in this order:
list_devices → get available devices and sessionIds
connect_device → join a session, hand the person the watch link
boot_device → wait for the device to be ready (skip if already booted)
install_app → install the build
launch_app → launch the app
screenshot → capture screen → LLM analyzes
tap / swipe / ... → interact
screenshot → verify result → repeat
disconnect_device → end the sessionDevice already booted
If list_devices returns "status": "booted" for a device, you can skip boot_device.
For running this in a CI pipeline, see MCP in CI/CD.
Watch the device a coding agent drives
connect_device returns watchUrl in its result. Open that link in a browser to watch, live, the device the coding agent is driving. The tool description tells the coding agent to hand the link to the person who asked, so when you ask for something like "test the login screen", the coding agent gives you the link as it starts testing.
The page is reached only through the link. It is for the person who asked the coding agent to test, to watch their own work, so the dashboard has no list of these sessions. A teammate who opens the QA session's device list sees the device as in use, with Coding agent is driving it as the reason.
- Watching is read-only. Nothing you click reaches the device, and there is no sound.
- Any teammate signed in to the dashboard can watch. Whatever the coding agent types on the device shows on screen too, so do not test with values the team should not see.
- Up to 4 people can watch one session at a time.
- Agent activity beside the device shows what the coding agent does, in the order it happens, and a blue cursor stays on the device where it last tapped, gliding to the next spot with a brief trail. When a flow runs through
run_flowortapflow flow run, each step is a row with the actions taken in it underneath; steps start open and can be collapsed. Typing shows as a count of characters, and the URL opened or the character key pressed is not named. Nothing is stored, so the log starts when you open the page. When the session ends, the last picture and the log stay until you close the page. - If the coding agent's connection drops briefly, the page keeps the last picture and waits for it to come back. The watch ends when the coding agent finishes with the session (
disconnect_device) or another teammate starts using the device.
How the link address is chosen
If the relay has an address teammates can open (a tunnel or relay.url), the link uses it, and you can share it with teammates as it is. Otherwise, including when relay.url is a localhost address, the MCP server builds the link from the TAPFLOW_RELAY_URL it connected with. For example, connected with ws://localhost:4000, the link starts with http://localhost:4000/automation/sessions/ and opens on the computer running the MCP server. For an address to send to teammates, see External access.
Fix a failed run
When tapflow flow run fails in CI, a coding agent can read the run and repair the flow. Ask it something like "fix last night's failed CI run", and it uses the tools in this order:
list_runs → find the failed run (status: failed)
get_run → which step failed and why, the screen at that moment, and the flow file's path
(edit) → change the YAML at the recorded path
run_flow → replay the fixed flow on a device to check it- A recorded flow file path is as the runner was given it, relative to the directory it ran in (the CI job's working directory, for a CI run). The coding agent finds that file in its own checkout.
- Run records are only read. The coding agent replays the flow itself with
run_flow; nothing re-runs the CI job. get_runreturns up to 4 screenshots of failed flows with the record. A passed flow's last screen and the videos are not included; open the run's page at the result'surlto see them.- A failure whose
failureKindisenvironmentwas the device, the agent or the relay, not the app or the flow. There is nothing to fix in the flow.
The result's url is built from the TAPFLOW_RELAY_URL the MCP server connected with. Unlike the watch link, it does not use the relay's public address, so a server that connected by a name inside a container network (such as ws://relay:4000) gets a link a browser cannot open.
For when runs are recorded and what the dashboard shows, see Flow Runs.