Discover MCPs & agents
Loading MCPs and agents…
Loading MCPs and agents…
A Model Context Protocol (MCP) server that enables AI assistants to interact with web browsers and mobile applications using WebDriverIO. Automate Chrome browsers, iOS apps, and Android apps—all through a unified interface.
From the repo.
A Model Context Protocol (MCP) server that enables AI assistants to interact with web browsers, local Electron applications, and mobile applications using WebdriverIO. Automate Chrome, Firefox, Edge, Safari, Electron, iOS, and Android through a unified interface.
Add the following configuration to your MCP client settings:
Standard config (works in most clients):
{
"mcpServers": {
"wdio-mcp": {
"command": "npx",
"args": [
"-y",
"@wdio/mcp@latest"
]
}
}
}
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS),
%APPDATA%\Claude\claude_desktop_config.json (Windows), or ~/.config/Claude/claude_desktop_config.json (Linux):
{
"mcpServers": {
"wdio-mcp": {
"command": "npx",
"args": [
"-y",
"@wdio/mcp@latest"
]
}
}
}
claude mcp add wdio-mcp -- npx -y @wdio/mcp@latest
Add to your VS Code settings.json or cline_mcp_settings.json file:
{
"mcpServers": {
"wdio-mcp": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@wdio/mcp@latest"
]
}
}
}
Go to Cursor Settings → MCP → Add new MCP Server, or create .cursor/mcp.json:
{
"mcpServers": {
"wdio-mcp": {
"command": "npx",
"args": [
"-y",
"@wdio/mcp@latest"
]
}
}
}
Use the Codex CLI:
codex mcp add wdio-mcp npx "@wdio/mcp@latest"
Or edit ~/.codex/config.toml:
[mcp_servers.wdio-mcp]
command = "npx"
args = ["@wdio/mcp@latest"]
Go to Advanced settings → Extensions → Add custom extension, or run:
goose configure
Or edit ~/.config/goose/config.yaml:
extensions:
wdio-mcp:
name: WebDriverIO MCP
cmd: npx
args: [ -y, "@wdio/mcp@latest" ]
enabled: true
type: stdio
Edit ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"wdio-mcp": {
"command": "npx",
"args": [
"-y",
"@wdio/mcp@latest"
]
}
}
}
Edit Zed settings (~/.config/zed/settings.json):
{
"context_servers": {
"wdio-mcp": {
"source": "custom",
"command": "npx",
"args": [
"-y",
"@wdio/mcp@latest"
]
}
}
}
code --add-mcp '{"name":"wdio-mcp","command":"npx","args":["-y","@wdio/mcp@latest"]}'
⚠️ Restart Required: After adding the configuration, fully restart your MCP client to apply the changes.
If you prefer to install globally:
npm install -g @wdio/mcp
Then use wdio-mcp as the command:
{
"mcpServers": {
"wdio-mcp": {
"command": "wdio-mcp"
}
}
}
📖 Need help? Follow the MCP install guide.
By default the server uses stdio (subprocess) transport. For clients that cannot launch subprocesses (e.g. llama.cpp, OpenAI Codex secure mode), enable HTTP transport:
npx @wdio/mcp --http --port 3000
| Flag | Default | Description |
|---|---|---|
--http | — | Enable HTTP transport mode |
--port | 3000 | Port to listen on |
--allowedHosts | localhost,127.0.0.1,::1 | Allowed Host header values (DNS rebinding protection) |
--allowedOrigins | (none — browser clients blocked) | Allowed Origin values for CORS. Use * to allow all. |
Then point your MCP client at http://localhost:3000/mcp.
npm install -g appiumappium driver install xcuitest (requires Xcode on macOS)appium driver install uiautomator2 (requires Android Studio)Start the Appium server before using mobile features:
appium
# Server runs at http://127.0.0.1:4723 by default
Run browser and mobile app tests on cloud real devices and browsers without any local setup. Currently supports BrowserStack, Sauce Labs, LambdaTest, TestingBot, and Digital.ai Testing.
Set your provider credentials as environment variables or in your MCP client config:
export BROWSERSTACK_USERNAME=your_username
export BROWSERSTACK_ACCESS_KEY=your_access_key
{
"mcpServers": {
"wdio-mcp": {
"command": "npx",
"args": ["-y", "@wdio/mcp@latest"],
"env": {
"BROWSERSTACK_USERNAME": "your_username",
"BROWSERSTACK_ACCESS_KEY": "your_access_key"
}
}
}
}
export SAUCE_USERNAME=your_username
export SAUCE_ACCESS_KEY=your_access_key
{
"mcpServers": {
"wdio-mcp": {
"command": "npx",
"args": ["-y", "@wdio/mcp@latest"],
"env": {
"SAUCE_USERNAME": "your_username",
"SAUCE_ACCESS_KEY": "your_access_key"
}
}
}
}
| SAUCE_USERNAME | Sauce Labs username (required) |
| SAUCE_ACCESS_KEY | Sauce Labs access key (required) |
The data center is set per-session via the region parameter in start_session (defaults to eu-central-1).
export TESTMU_USERNAME=your_username
export TESTMU_ACCESS_KEY=your_access_key
{
"mcpServers": {
"wdio-mcp": {
"command": "npx",
"args": ["-y", "@wdio/mcp@latest"],
"env": {
"TESTMU_USERNAME": "your_username",
"TESTMU_ACCESS_KEY": "your_access_key"
}
}
}
}
| TESTMU_USERNAME | LambdaTest username (required) |
| TESTMU_ACCESS_KEY | LambdaTest access key (required) |
export TESTINGBOT_KEY=your_key
export TESTINGBOT_SECRET=your_secret
{
"mcpServers": {
"wdio-mcp": {
"command": "npx",
"args": ["-y", "@wdio/mcp@latest"],
"env": {
"TESTINGBOT_KEY": "your_key",
"TESTINGBOT_SECRET": "your_secret"
}
}
}
}
| TESTINGBOT_KEY | TestingBot key (required) |
| TESTINGBOT_SECRET | TestingBot secret (required) |
export DIGITALAI_CLOUD_URL=https://your-cloud.example.com
export DIGITALAI_ACCESS_KEY=your_access_key
{
"mcpServers": {
"wdio-mcp": {
"command": "npx",
"args": ["-y", "@wdio/mcp@latest"],
"env": {
"DIGITALAI_CLOUD_URL": "https://your-cloud.example.com",
"DIGITALAI_ACCESS_KEY": "your_access_key"
}
}
}
}
| DIGITALAI_CLOUD_URL | Digital.ai cloud host, e.g. https://your-cloud.example.com (required) |
| DIGITALAI_ACCESS_KEY | Digital.ai access key (required) |
The access key is sent via the digitalai:options capability (mobile) or the flat digitalai:accessKey capability (web).
Report pass/fail status: WebdriverIO defaults to the BiDi protocol, over which Digital.ai's cloud cannot observe command failures — so reports default to "Passed". To make the cloud reflect actual pass/fail, opt in to classic WebDriver per session:
start_session({
provider: 'digitalai', platform: 'browser', browser: 'chrome', os: 'Windows 10',
capabilities: { 'wdio:enforceWebDriverClassic': true }
})
(Pure client-side assertion failures still report as "Passed" — only failures that reach the cloud as WebDriver command errors are detected.)
Mobile (Appium): configure your Digital.ai project for Appium-server execution and pick its default Appium version via the project's "Manage default Appium server version" setting — the version is chosen at the project level (and tracks the versions your cloud supports), so this MCP does not pin one. See Appium Server Test Execution.
Run a browser on a specific OS/version combination:
// BrowserStack
start_session({
provider: 'browserstack',
platform: 'browser',
browser: 'chrome', // chrome | firefox | edge | safari
browserVersion: 'latest', // default: latest
os: 'Windows', // e.g. "Windows", "OS X"
osVersion: '11', // e.g. "11", "Sequoia"
reporting: {
project: 'My Project',
build: 'v1.2.0',
session: 'Login flow'
}
})
// Sauce Labs
start_session({
provider: 'saucelabs',
platform: 'browser',
browser: 'chrome',
os: 'Windows', // combined with osVersion → platformName
osVersion: '11', // e.g. "11", "15" (numbered Mac naming)
region: 'eu-central-1', // default: eu-central-1
reporting: {
build: 'v1.2.0',
session: 'Login flow'
}
})
// LambdaTest
start_session({
provider: 'testmu',
platform: 'browser',
browser: 'chrome',
os: 'Windows', // combined with osVersion → platformName
osVersion: '11', // e.g. "11", "Sequoia" (optional)
reporting: {
project: 'My Project',
build: 'v1.2.0',
session: 'Login flow'
}
})
// TestingBot
start_session({
provider: 'testingbot',
platform: 'browser',
browser: 'chrome',
os: 'Windows', // combined with osVersion → platformName (default: Windows 11)
osVersion: '11',
reporting: {
build: 'v1.2.0',
session: 'Login flow'
}
})
// Digital.ai
start_session({
provider: 'digitalai',
platform: 'browser',
browser: 'chrome',
os: 'Windows', // combined with osVersion → digitalai:osName (optional)
osVersion: '11',
reporting: {
session: 'Login flow' // → digitalai:testName (flat capability, not nested)
}
})
Provider-specific
os/osVersionbehavior:
- BrowserStack —
osandosVersionmap to separatebstack:options.os/bstack:options.osVersionfields.- Sauce Labs / LambdaTest / TestingBot —
osandosVersionare combined into the W3CplatformNamecapability (e.g.,os: 'Windows'+osVersion: '11'→platformName: 'Windows 11'). These providers useplatformNamevalues like"Windows 11","MacOS Sequoia", or"Linux". TestingBot defaults toWindows 11whenosis omitted.- Digital.ai —
osandosVersionare combined into the flatdigitalai:osNamecapability (NOTplatformName), e.g.os: 'Windows'+osVersion: '11'→digitalai:osName: 'Windows 11'.
Test on cloud real devices. First upload your app (or use an existing app URL):
// BrowserStack: returns bs:// URL
upload_app({ provider: 'browserstack', path: '/path/to/app.apk' })
// Sauce Labs: returns storage:filename= reference
upload_app({ provider: 'saucelabs', path: '/path/to/app.apk' })
// LambdaTest: returns lt:// URL
upload_app({ provider: 'testmu', path: '/path/to/app.apk' })
// TestingBot: returns tb:// URL
upload_app({ provider: 'testingbot', path: '/path/to/app.apk' })
// Digital.ai: returns cloud:<package-or-bundle> reference
upload_app({ provider: 'digitalai', path: '/path/to/app.apk' })
// Start a session
start_session({
provider: 'browserstack',
platform: 'android',
app: 'bs://abc123...',
deviceName: 'Samsung Galaxy S23',
platformVersion: '13.0'
})
// Sauce Labs native app
start_session({
provider: 'saucelabs',
platform: 'android',
app: 'storage:filename=myapp.apk',
deviceName: 'Samsung.*',
platformVersion: '16'
})
// LambdaTest native app
start_session({
provider: 'testmu',
platform: 'android',
app: 'lt://abc123...',
deviceName: 'Pixel 7',
platformVersion: '13'
})
// TestingBot native app
start_session({
provider: 'testingbot',
platform: 'android',
app: 'tb://abc123...',
deviceName: 'Pixel 7',
platformVersion: '13'
})
// Digital.ai native app — devices are selected via a deviceQuery
start_session({
provider: 'digitalai',
platform: 'android',
app: 'cloud:com.example.app',
deviceQuery: "@os='android' and @version='14' and @name='.*Pixel.*'"
// or omit deviceQuery and pass deviceName / platformVersion to build one
})
Run a browser on a cloud mobile device — real device or emulator/simulator — without uploading an app:
// BrowserStack — Chrome on Android emulator
start_session({
provider: 'browserstack',
platform: 'android',
browser: 'chrome',
deviceName: 'Google Pixel 7',
platformVersion: '13'
})
// Sauce Labs — Safari on iOS simulator
start_session({
provider: 'saucelabs',
platform: 'ios',
browser: 'safari',
deviceName: 'iPhone 15',
platformVersion: '18',
region: 'eu-central-1'
})
// LambdaTest — Chrome on Android emulator
start_session({
provider: 'testmu',
platform: 'android',
browser: 'chrome',
deviceName: 'Pixel 7',
platformVersion: '13'
})
// TestingBot — Chrome on Android emulator
start_session({
provider: 'testingbot',
platform: 'android',
browser: 'chrome',
deviceName: 'Pixel 7',
platformVersion: '13'
})
// Digital.ai — Chrome on an Android device (real or emulator; selected via a deviceQuery)
start_session({
provider: 'digitalai',
platform: 'android',
browser: 'chrome',
deviceName: 'Pixel 7',
platformVersion: '13'
// or omit deviceName / platformVersion and pass deviceQuery directly, e.g.
// deviceQuery: "@os='android' and @emulator='true'" to force an emulator
})
Note: Mobile browser sessions do not require
app,appPath, ornoReset. The provider launches a browser directly on the selected device — real or emulator/simulator.
Use list_apps to see previously uploaded apps:
list_apps({ provider: 'browserstack' })
list_apps({ provider: 'saucelabs', sortBy: 'app_name' })
list_apps({ provider: 'testmu' })
list_apps({ provider: 'testingbot' })
list_apps({ provider: 'digitalai' })
list_apps({ provider: 'browserstack', organizationWide: true })
To test against URLs that are only accessible on your local machine or internal network, enable a local tunnel:
// Auto-start tunnel (provider manages lifecycle)
start_session({
provider: 'saucelabs',
platform: 'browser',
tunnel: true // auto-starts tunnel before session
})
// Use an already-running tunnel
start_session({
provider: 'saucelabs',
platform: 'browser',
tunnel: 'external' // uses existing tunnel
})
The tunnel parameter replaces the deprecated browserstackLocal, saucelabsLocal, and testmuLocal params. Set it to true to auto-start the tunnel (stopped automatically after the session), or 'external' to use a tunnel already running on your machine.
Note: With
tunnel: truethe provider downloads and manages the tunnel binary for you. Fortunnel: 'external'you run it yourself — thewdio://saucelabs/local-binary,wdio://testmu/local-binary, andwdio://testingbot/local-binaryresources provide download URLs and setup instructions. The TestingBot Tunnel is a single cross-platform Java JAR (requires Java 11+) rather than a per-platform binary.
All session types support reporting labels that appear in the provider dashboard:
| Field | Description |
|---|---|
reporting.project | Group sessions under a project name |
reporting.build | Tag sessions with a build/version label |
reporting.session | Name for the individual test session |
| Tool | Description |
|---|---|
upload_app | Upload a local .apk or .ipa to the provider; returns an app URL/reference |
list_apps | List apps previously uploaded to the provider's app storage |
Both tools require a provider parameter ('browserstack', 'saucelabs', 'testmu', 'testingbot', or 'digitalai').
--remote-debugging-port — ideal for testing
authenticated or pre-configured sessions| Tool | Description |
|---|---|
start_session | Start a browser, local Electron application, or mobile app session; attach: true retains the existing Chrome CDP connection mode |
attach_session | Attach to an existing remote WebDriver/Appium session by ID without creating a new session |
launch_chrome | Launch a new Chrome instance with remote debugging enabled (for use with start_session({ attach: true })) |
close_session | Close or detach from the current session (supports detach: true to disconnect without terminating) |
emulate_device | Emulate a mobile/tablet device preset (viewport, DPR, UA, touch); requires BiDi session |
open_web_extension | Install a web extension through WebDriver BiDi and open one of its extension pages so normal page tools can drive its UI |
| Tool | Description |
|---|---|
navigate | Navigate to a URL |
get_elements | Get visible, interactable elements on the page. Supports inViewportOnly (default: true) to filter viewport elements, and includeContainers (default: false) to include layout containers on mobile |
get_accessibility_tree | Get the page accessibility tree with roles, names, and selectors. Supports filtering by role and pagination. Browser-only. |
get_screenshot | Take a screenshot of the current page or screen (base64-encoded, auto-resized to max 2000px / 1MB) |
get_tabs | List all open browser tabs with handle, title, URL, and active status. Browser-only. |
scroll | Scroll in a direction (up/down) by specified pixels. Browser-only. |
execute_script | Execute arbitrary JavaScript in the browser, or Appium mobile commands on devices |
execute_electron_script | Execute privileged JavaScript in the Electron main process (Electron sessions only) |
trigger_electron_deeplink | Trigger an Electron deeplink whose scheme was explicitly configured at session start |
mock | Configure a session-scoped mock: an Electron main-process API function or a browser network request |
get_mock_calls | Inspect call arguments (Electron) or intercepted request records (browser) for a session-scoped mock |
manage_mock | Clear, reset, or restore a session-scoped mock |
switch_tab | Switch to a different browser tab by handle or 0-based index. Browser-only. |
switch_frame | Switch into an iframe by CSS/XPath selector, or back to the top-level frame if no selector is given. Browser-only. |
| Tool | Description |
|---|---|
click_element | Click an element |
set_value | Type text into input fields |
| Tool | Description |
|---|---|
get_cookies | Get all cookies for the current session, or a single cookie by name |
set_cookie | Set a cookie with name, value, and optional attributes |
delete_cookies | Delete all cookies or a specific cookie |
| Tool | Description |
|---|---|
tap_element | Tap an element by selector or coordinates |
swipe | Swipe in a direction (up/down/left/right) |
drag_and_drop | Drag from one location to another |
| Tool | Description |
|---|---|
get_contexts | List available automation contexts (NATIVE_APP, WEBVIEW_*) and the currently active one |
switch_context | Switch between native and webview contexts |
| Tool | Description |
|---|---|
get_app_state | Get the current lifecycle state of a mobile app (not installed / not running / background / foreground) |
rotate_device | Rotate to portrait or landscape |
hide_keyboard | Hide on-screen keyboard |
set_geolocation | Set device GPS location |
| Tool | Description |
|---|---|
query_docs | Search the official WebdriverIO documentation and return the most relevant excerpts with page, section and source URL. query is required (2-3 distinctive keywords — a sentence dilutes the ranking); limit defaults to 5 (max 20); fullPage (default: false) returns the full matched pages instead of excerpts. Session-independent |
| Resource | Description |
|---|---|
wdio://sessions | Index of all recorded sessions |
wdio://session/current/steps | Step log for the active session |
wdio://session/current/code | Generated runnable WebdriverIO JS for the active session |
wdio://session/{id}/steps | Step log for any past session by ID |
wdio://session/{id}/code | Generated JS for any past session by ID |
wdio://session/current/elements | Interactable elements (viewport-only by default) |
wdio://session/current/accessibility | Accessibility tree |
wdio://session/current/screenshot | Screenshot (base64) |
wdio://session/current/cookies | Browser cookies |
wdio://session/current/tabs | Open browser tabs |
wdio://session/current/contexts | Native/webview contexts (mobile) |
wdio://session/current/context | Currently active context (mobile) |
wdio://session/current/app-state/{bundleId} | Mobile app lifecycle state for a given bundle ID |
wdio://session/current/geolocation | Device geolocation |
wdio://session/current/capabilities | Resolved WebDriver capabilities for the active session |
wdio://session/current/logs | Crash/console logs for the current session. Auto-detects session type — browser: console logs + JS exceptions; Android: logcat; iOS: crashlog + syslog |
wdio://browserstack/local-binary | BrowserStack Local binary download URL and start command |
wdio://saucelabs/local-binary | Sauce Connect binary download URL and start command |
wdio://testmu/local-binary | TestMu Tunnel binary download URL and start command |
wdio://testingbot/local-binary | TestingBot Tunnel JAR download URL and start command (Java 11+) |
wdio://docs/index | Every page of the official WebdriverIO documentation as tab-separated title, source path and slug |
wdio://docs/page/{slug} | Full markdown of one documentation page, truncated at 40000 characters. Slugs come from wdio://docs/index ("~" in place of "/") |
Example 1: Testing Demo Android App (Book Scanning)
Test the Demo Android app at C:\Users\demo-liveApiGbRegionNonMinifiedRelease-3018788.apk on emulator-5554:
1. Start the app with auto-grant permissions
2. Get visible elements on the onboarding screen
3. Tap "Skip" to bypass onboarding
4. Verify main screen loads
5. Take a screenshot
Example 2: Testing World of Books E-commerce Site
You are a Testing expert, and want to assess the basic workflows of worldofbooks.com:
- Open World of Books (accept all cookies)
- Get visible elements to see navigation structure
- Search for a fiction book
- Choose one and validate if there are NEW and used book options
- Report your findings at the end
Basic web testing prompt:
You are a Testing expert, and want to assess the basic workflows of a web application:
- Open World of Books (accept all cookies)
- Search for a fiction book
- Choose one and validate if there are NEW and used book options
- Report your findings at the end
Browser configuration options:
// Default settings (headed mode, 1280x1080)
start_session({platform: 'browser'})
// Firefox
start_session({platform: 'browser', browser: 'firefox'})
// Edge
start_session({platform: 'browser', browser: 'edge'})
// Safari (headed only; requires macOS)
start_session({platform: 'browser', browser: 'safari'})
// Headless mode
start_session({platform: 'browser', headless: true})
// Custom dimensions
start_session({platform: 'browser', windowWidth: 1920, windowHeight: 1080})
// Pass custom capabilities (e.g. Chrome extensions, profile, prefs)
start_session({
platform: 'browser',
headless: false,
capabilities: {
'goog:chromeOptions': {
args: ['--user-data-dir=/tmp/wdio-mcp-profile', '--load-extension=/path/to/unpacked-extension']
}
}
})
Mocking is available through mock, get_mock_calls, and manage_mock, for both Electron main-process API functions and browser network requests.
mockType accepts 'electron' or 'browser'. Browser mocks are the default in WebDriver sessions when mockType is omitted. Electron sessions require an explicit selection because they can target both kinds of mocks. iOS/Android Appium sessions do not support mocking.
mock({
mockType: 'electron', apiName: 'dialog', funcName: 'showOpenDialog',
behavior: 'mockResolvedValue', value: { canceled: false, filePaths: ['/tmp/example.txt'] }
})
// Interact with the renderer to open the application's file picker, then inspect its calls.
get_mock_calls({ mockType: 'electron', apiName: 'dialog', funcName: 'showOpenDialog' })
manage_mock({ mockType: 'electron', apiName: 'dialog', funcName: 'showOpenDialog', action: 'restore' })
Electron behavior defaults to mockReturnValue; mockResolvedValue and mockRejectedValue support async APIs. Each has a Once variant for queued responses. Repeated configuration preserves the existing mock and call history. Values must be JSON; omit value for undefined. clear removes call history, reset also removes behavior and queued responses, and restore reinstates the original function. These tools support individual API functions; class mocks and arbitrary mock implementations are not exposed.
Browser mocks intercept network requests matching a url glob (for example **/api/todos) with an optional method filter. behavior is optional: omit it to passively record matching requests without altering them — the mock still continues every request. respond and respondOnce require value (the JSON response body) and accept optional statusCode and headers; abort/abortOnce fail the request, and redirect/redirectOnce send it to the URL in value. get_mock_calls returns { calls, callCount } for the intercepted requests, and manage_mock takes clear, reset, or restore.
start_session({ platform: 'browser', browser: 'chrome', capabilities: { webSocketUrl: true } })
// Observe-only: record every request to /api/todos without changing responses.
mock({ mockType: 'browser', url: '**/api/todos' })
navigate({ url: 'https://example.com/todos' })
get_mock_calls({ mockType: 'browser', url: '**/api/todos' })
// Or overwrite the response instead.
mock({ mockType: 'browser', url: '**/api/todos', method: 'GET', behavior: 'respond', value: [{ id: 1, title: 'Buy milk' }], statusCode: 200 })
// Interact with the page, then inspect the intercepted requests.
get_mock_calls({ mockType: 'browser', url: '**/api/todos', method: 'GET' })
manage_mock({ mockType: 'browser', url: '**/api/todos', method: 'GET', action: 'restore' })
Browser mocks require a BiDi-enabled session — start it with capabilities: { webSocketUrl: true }. Attached sessions default to BiDi off, so browser mocks do not work there unless the attached session negotiated BiDi; in Electron sessions browser mocks additionally require BiDi. Handles belong to the active browser session and cannot be reused after it closes or is replaced. All three tools participate in tracing and generated replay.
Electron support is local-only and uses the official @wdio/electron-service standalone lifecycle. It requires Node.js 22.12 or newer. Put service options such as appBinaryPath, appEntryPoint, and appArgs in capabilities['wdio:electronServiceOptions']; use top-level electronRootDir for the service's Electron Builder/Electron Forge discovery. When testing a binary outside the project, set browserVersion to the Electron version so the service can select a compatible Chromedriver.
start_session({
platform: 'electron',
browserVersion: '33.2.1',
capabilities: {
'wdio:electronServiceOptions': {
appBinaryPath: '/path/to/MyApp.app/Contents/MacOS/MyApp',
appArgs: ['--disable-gpu']
}
}
})
// Privileged: this code runs in the Electron main process, not the renderer.
execute_electron_script({ script: 'return electron.app.getName()' })
Existing browser DOM tools work against the Electron renderer. close_session always tears down MCP-managed Electron sessions; detach: true is intentionally unsupported. Main/renderer log capture can be enabled with captureMainProcessLogs or captureRendererLogs plus logDir. To trigger an app deeplink, explicitly configure its URI scheme when starting the Electron session. The scheme has no colon and only URLs with that exact scheme can be dispatched:
start_session({
platform: 'electron',
electronDeeplinkScheme: 'myapp',
capabilities: {
'wdio:electronServiceOptions': {
appBinaryPath: '/path/to/MyApp.app/Contents/MacOS/MyApp'
}
}
})
trigger_electron_deeplink({ url: 'myapp://open/item' })
Attach to a running Chrome instance:
// First, launch Chrome with remote debugging enabled:
//
// macOS (must quit Chrome first — open -a ignores args if Chrome is already running):
// pkill -x "Google Chrome" && sleep 1
// /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
// --remote-debugging-port=9222 \
// --user-data-dir=/tmp/chrome-debug &
//
// Linux:
// google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug &
//
// Verify it's ready: curl http://localhost:9222/json/version
start_session({attach: true})
start_session({attach: true, port: 9333})
start_session({attach: true, port: 9222, navigationUrl: 'https://app.example.com'})
Connect to an existing WebDriver endpoint:
Use provider: 'external' when another process already owns the browser or webview lifecycle and exposes a W3C
WebDriver endpoint. This is useful for Selenium Grid sessions, externally managed browser drivers, or desktop apps like Tauri apps that
embed a webview and expose WebDriver separately. The MCP server connects to the endpoint; it does not launch or stop the
target application, start tunnels, or manage framework-specific setup.
// Defaults to http://127.0.0.1:4445/ and browserName: 'chrome'
start_session({provider: 'external', platform: 'browser'})
// Custom WebDriver endpoint and capabilities
start_session({
provider: 'external',
platform: 'browser',
webdriverConfig: {
protocol: 'http',
hostname: '127.0.0.1',
port: 4445,
path: '/'
},
capabilities: {
browserName: 'tauri'
}
})
For desktop webview apps such as Tauri, first start the app and its WebDriver bridge outside of this MCP server, then
pass the endpoint and the required capabilities. For example, a Tauri WebDriver bridge may require
capabilities: {browserName: 'tauri'}.
Attach to an existing WebDriver or Appium session:
Use attach_session when the session has already been created by another process. The MCP reuses the selected provider's
endpoint and credentials, registers the appropriate browser/mobile command set locally, and does not issue a new-session
request. Attached sessions are externally managed: close_session() detaches by default, while
close_session({detach: false}) explicitly terminates the remote session.
// Existing BrowserStack App Automate session
attach_session({
provider: 'browserstack',
platform: 'ios',
sessionId: 'existing-browserstack-session-id',
capabilities: {
'appium:deviceName': 'iPhone 15',
'appium:automationName': 'XCUITest'
}
})
// Existing session on a local Appium server
attach_session({
provider: 'local',
platform: 'android',
sessionId: 'existing-appium-session-id',
appiumConfig: {
protocol: 'http',
host: '127.0.0.1',
port: 4723,
path: '/'
}
})
// Existing mobile session on a custom W3C WebDriver endpoint
attach_session({
provider: 'external',
platform: 'ios',
sessionId: 'existing-grid-session-id',
webdriverConfig: {
protocol: 'https',
hostname: 'grid.example.com',
port: 443,
path: '/wd/hub'
}
})
An existing cloud session must keep using the tunnel it was created with. attach_session never starts or stops a tunnel,
so keep the original tunnel process alive for as long as the session needs it.
Device emulation (requires BiDi session):
// Device emulation (requires BiDi session)
start_session({capabilities: {webSocketUrl: true}})
emulate_device() // list available presets
emulate_device({device: 'iPhone 15'}) // activate emulation
emulate_device({device: 'Pixel 7'}) // switch device
emulate_device({device: 'reset'}) // restore desktop defaults
Web extensions (requires BiDi session):
start_session({platform: 'browser', browser: 'chrome', capabilities: {webSocketUrl: true}})
open_web_extension({
extensionData: {type: 'path', path: '/path/to/unpacked-extension'},
path: 'options.html'
})
// Drive the extension UI with the normal page tools.
get_elements()
click_element({selector: '#save'})
// For remote/cloud sessions, send a packaged extension archive as base64.
open_web_extension({
extensionData: {type: 'base64', value: '<base64-encoded-zip>'},
path: 'options.html'
})
Testing an iOS app on simulator:
Test my iOS app located at /path/to/MyApp.app on iPhone 15 Pro simulator:
1. Start the app session
2. Tap the login button
3. Enter "testuser" in the username field
4. Take a screenshot of the home screen
5. Close the session
Preserving app state between sessions:
Test my Android app without resetting data:
1. Start app session with noReset: true and fullReset: false
2. App launches with existing login state and user data preserved
3. Run test scenarios
4. Close session (app remains installed with data intact)
Testing an iOS app on real device:
Test my iOS app on my physical iPhone:
1. Start app session with:
- platform: iOS
- appPath: /path/to/MyApp.ipa
- deviceName: My iPhone
- udid: 00008030-001234567890ABCD (your device's UDID)
- platformVersion: 17.0
2. Run your test scenario
3. Close the session
Testing an Android app:
Test my Android app /path/to/app.apk on the Pixel_6_API_34 emulator:
1. Start the app with auto-grant permissions
2. Get visible elements (use inViewportOnly: false to see all elements)
3. Swipe up to scroll
4. Tap on the "Settings" button using text matching
5. Verify the settings screen is displayed
Advanced element detection:
Test my app and debug layout issues:
1. Start the app session
2. Get visible elements with includeContainers: true to see the layout hierarchy
3. Analyze ViewGroup, FrameLayout, and ScrollView containers
4. Use inViewportOnly: false to find off-screen elements that need scrolling
Hybrid app testing (switching contexts):
Test my hybrid app:
1. Start the Android app session
2. Tap "Open Web" button in native context
3. List available contexts
4. Switch to WEBVIEW context
5. Click the login button using CSS selector
6. Switch back to NATIVE_APP context
7. Verify we're back on the home screen
⚠️ Session Management:
close_session({ detach: true }) to disconnect without terminating the session on the Appium servernoReset and fullReset parameters during session creationnoReset: true or without appPath will automatically detach on closeattach_session always detach on close unless detach: false is explicitly requested⚠️ Task Planning:
⚠️ Mobile Automation:
Web (CSS/XPath):
button.my-class, #element-id//button[@class='my-class']button=Exact text, a*=Contains textMobile (Cross-Platform):
~loginButton (works on both iOS and Android)android=new UiSelector().text("Login")-ios predicate string:label == "Login" AND visible == 1//android.widget.Button[@text="Login"]State Preservation with noReset/fullReset:
Control app state when creating new sessions using the noReset and fullReset parameters:
| noReset | fullReset | Behavior |
|---|---|---|
true | false | Preserve state: App stays installed, data preserved |
false | false | Clear app data but keep app installed (default) |
false | true | Full reset: Uninstall and reinstall app (clean slate) |
Example with state preservation:
// Preserve login state between test runs
start_session({
platform: 'android',
appPath: '/path/to/app.apk',
deviceName: 'emulator-5554',
noReset: true, // Don't reset app state
fullReset: false, // Don't uninstall
autoGrantPermissions: true,
capabilities: {
'appium:chromedriverExecutable': '/path/to/chromedriver',
'appium:autoWebview': true
}
})
// App launches with existing user data, login tokens, preferences intact
Detach from Sessions:
The close_session tool supports a detach parameter that disconnects from the session without terminating it on the
Appium server:
// Detach without killing the session
close_session({detach: true})
// Explicit session termination (closes the app and removes session)
close_session({detach: false})
Sessions created with noReset: true or without appPath will automatically detach on close.
Sessions adopted with attach_session are externally managed and also detach by default; pass detach: false only when the
MCP should deliberately terminate the existing remote session.
This is particularly useful when:
Both iOS and Android sessions now support automatic handling of system permissions and alerts:
autoGrantPermissions (default: true): Automatically grants app permissions (camera, location, etc.)autoAcceptAlerts (default: true): Automatically accepts system alerts and dialogsautoDismissAlerts (optional): Set to true to dismiss alerts instead of accepting themThis eliminates the need to manually handle permission popups during automated testing.
Every tool call is automatically recorded to a session history. You can inspect sessions and export runnable code via MCP resources — no extra tool calls needed:
wdio://sessions — lists all recorded sessions with type, timestamps, and step countwdio://session/current/steps — step log for the active sessionwdio://session/current/code — generated runnable WebdriverIO JS for the active sessionwdio://session/{sessionId}/steps — step log for any past session by IDwdio://session/{sessionId}/code — generated JS for any past session by IDThe generated script reconstructs the full session — including capabilities, navigation, clicks, and inputs — as a
standalone import { remote } from 'webdriverio' file. For cloud provider sessions it includes the full try/catch/finally
with automatic session result marking via the provider's REST API.
Passing trace: true to start_session produces a Playwright-compatible .trace zip in the .trace/ directory when
the session closes. The zip is playable at player.vibium.dev and shows a filmstrip of
screenshots alongside the action timeline.
How screenshots are timed
Appium's takeScreenshot round-trip takes 700–1300 ms on a local emulator, which is long enough for the previous
action's animations to settle. We exploit this: each screenshot is captured before the next action fires, so what
the Appium server returns is already the settled result of the prior action.
The tricky part is making the trace player show that screenshot under the right action. The player associates a
screencast-frame event with whichever action's time window contains the frame's timestamp field. If the timestamp
is set to "now" (capture time), it falls before the current action's startTime and the player labels it as the
before state of the next action — one action out of sync.
The fix: stamp each screencast-frame with lastAfterEndTime — the endTime of the action that just completed. That
places the frame inside the previous action's window, so the player shows it as the result of that action, not the
precursor to the next one.
Timeline (monotonic ms):
prev.endTime ← frame timestamp stamped here
│
│ [screenshot captured here — shows settled state after prev action]
│
curr.startTime
│
│ [action executes]
│
curr.endTime ← next frame will be stamped here
The final screenshot at session close is stamped with the last action's endTime, so it renders under that action
rather than appearing as an orphaned frame after the timeline ends.
The wdio://session/current/logs resource returns crash reports, console errors, and system logs for the current
session, auto-detecting the session type to fetch the right log buffer:
| Session Type | Log Sources | Contents |
|---|---|---|
| Browser | getLogs('browser') | Console output + uncaught JS exceptions |
| Android | getLogs('logcat') | System logs, crash dumps, fatal exceptions |
| iOS | getLogs('crashlog') + getLogs('syslog') | Crash/panic reports + system diagnostics |
Note: Reading this resource clears the log buffer (per the WebDriver spec). Subsequent reads return only entries accumulated since the last read. Browser logs require Chromium (Chrome/Edge) — Firefox and Safari do not support the
getLogscommand.
The response is JSON with sessionType, logTypes (available log types), and entries — each entry includes level,
message, timestamp (Unix ms), and timestampISO.
Browser automation not working?
Mobile automation not working?
appiumadb devices (Android) or Xcode Devices (iOS)Found issues or have suggestions? Please share your feedback!
Replace {MCP_ENDPOINT_URL} with this MCP’s endpoint URL (from its repo or docs above). No API key — you connect directly.
Tool
OS
Config file: ~/.cursor/mcp.json
{
"mcpServers": {
"mcp-server": {
"url": "{MCP_ENDPOINT_URL}"
}
}
}Paste into mcpServers in the config file. Restart Cursor after saving.
If this MCP is also published on mcpchannel.ai, you can subscribe from Browse and use the gateway config there instead.