MCP server with WebdriverIO for browser and mobile app automation (iOS/Android via Appium)
WebdriverIO MCP demonstrates solid definition quality with 23 well-structured tools. All tools have clear, descriptive names following verb_noun conventions (get_, set_, click_, tap_, etc.). Descriptions are generally 50-150 characters and actionable. Input schemas are explicitly defined using Zod with type constraints and descriptions. Tool annotations (readOnlyHint, idempotentHint, destructiveHint) are correctly applied. However, several tools lack output schema documentation, parameter relationships are undocumented in some cases, and error handling guidance is minimal. The server excels at parameter constraint clarity (enums for device orientation, swipe direction, etc.) but falls short on recovery patterns and missing output structures.
Waits for an element, scrolls it into view, and fires element.click(). May trigger navigation, form submission, or modals. Browser sessions only — on iOS element.click() is silently ignored; use tap_element instead. Default timeout: 3000ms.
Deletes all cookies or a single cookie by name from the current browser session. Irreversible — deleted cookies cannot be recovered.
Drags an element to another element or to relative x/y offsets. x and y are offsets from the source element, not absolute screen coordinates (unlike tap_element). Provide targetSelector OR both x and y. Mobile-only.
Emulates a mobile or tablet device in the current browser session by setting viewport, DPR, user-agent, and touch events. Requires a BiDi-enabled session (start_session with capabilities: { webSocketUrl: true }). Omit device to list available presets. Pass "reset" to restore desktop defaults. Changes persist for all subsequent tool calls until reset or session close. Browser-only.
Executes arbitrary JavaScript in the Electron main process. This is privileged code execution with access to Electron APIs; use only with trusted scripts.
Missing tool definition: switch_tab is mentioned in descriptions (get_tabs says 'Use before switch_tab') but has no explicit definition in provided source code. Cannot verify naming, schema, or description.
Output schemas not documented. Tools return results (CallToolResult with text content) but the structure of response fields (e.g., what fields does get_elements return? What is the format of accessibility tree nodes?) is not explicitly specified in tool definitions.
Error handling lacks recovery guidance. Tools return isError: true with text, but no guidance on what the LLM should do next. Example from browserstack.tool.ts: 'Missing credentials' error has no suggestion to set environment variables inline with the error response.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | C | 69 | 2026-07-28+ | v2 |
| 2026-03-09 | F | 0 | - | v1 |
Executes arbitrary JavaScript in browser page context or Appium mobile: commands. Can read/modify DOM, trigger events, terminate apps, or run Android shell commands — use only when no dedicated tool covers the action. Browser: pass JS in script, use 'return' for values, string args matching selectors auto-resolve to elements. Mobile: use 'mobile: <command>' syntax in script with args array (e.g. "mobile: pressKey", "mobile: activateApp"). Prefer click_element/set_value/get_elements for standard interactions.
Returns the page accessibility tree with roles, names, and selectors. Browser-only. Supports filtering by ARIA roles and pagination via limit/offset.
Returns the current state of a mobile app: not installed, not running, background, or foreground. Mobile-only.
Returns available automation contexts and the currently active one. Use before switch_context to discover NATIVE_APP and WEBVIEW_* targets. Mobile-only.
Returns all cookies for the current session, or a single cookie by name. Use to verify auth state, session tokens, or feature flags after login flows.
Returns interactable elements on the current page with selectors, text, and bounding boxes. Supports filtering by element type, viewport visibility, and pagination. Use when the wdio://session/current/elements resource does not return desired elements.
Lists all browser tabs with handle, title, URL, and which is active. Use before switch_tab to find the target handle or index. Browser-only.
Dismisses the on-screen keyboard on mobile. Call after text entry when the keyboard obscures elements. No-op if already hidden. Mobile-only.
List apps uploaded to BrowserStack App Automate. Reads BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY from environment.
List apps uploaded to a cloud provider (BrowserStack App Automate, Sauce Labs App Storage, TestMu Real Device Cloud, TestingBot Storage, or Digital.ai Applications). Reads provider-specific credentials from environment.
Rotates a mobile device to portrait or landscape orientation. Waits for the OS rotation animation to complete. Use to test orientation-dependent layouts. Mobile-only; no effect in browser sessions.
Sets a browser cookie on the active session. The browser must already be on the target domain — cookies cannot be set cross-domain. Use to inject session tokens or feature flags without login flows.
Overrides GPS coordinates for the session. Affects navigator.geolocation in browsers and location services on mobile. Location permissions must already be granted to the app.
Performs a full-screen swipe gesture. Direction is content movement — "up" scrolls content upward (finger moves down). For browser scrolling use scroll; for dragging a specific element use drag_and_drop. No error if content cannot scroll further. Mobile-only.
Switches between native and webview automation contexts in a hybrid mobile app. In NATIVE_APP context, use accessibility IDs; in WEBVIEW_* context, use CSS/XPath. Changes persist for all subsequent commands. Accepts context name or 1-based index. Use get_contexts to discover available targets. Mobile-only.
Taps a matched element via element.tap() or at absolute screen coordinates (x, y). No scroll-into-view or wait — element must already be visible on screen. Use instead of click_element on iOS where element.click() is ignored. Provide selector OR both x and y. Mobile-only.
Triggers a deeplink through the active Electron application. The Electron session must be started with electronDeeplinkScheme matching the URL scheme. Packaged binaries are required for deeplinks on Windows and Linux.
Upload a local .apk or .ipa to BrowserStack App Automate. Returns a bs:// URL for use in start_session.
Destructive operations lack confirmation. delete_cookies is IRREVERSIBLE but has no dry-run, confirmation step, or undo mechanism. execute_electron_script and execute_script are DESTRUCTIVE but descriptions do not mention confirmation or preview options.
Parameter dependency relationships undocumented. Examples: tap_element requires EITHER selector OR (x AND y), drag_and_drop requires EITHER targetSelector OR (x AND y), but mutual exclusivity is not stated in parameter descriptions. LLMs may pass both and cause ambiguity.
Minimal descriptions for some parameters. hide_keyboard has empty input schema ({}), rotate_device 'orientation' description is adequate but other tools like get_tabs lack parameter depth.
Platform/capability constraints not consistently highlighted. Tools like click_element, tap_element, set_geolocation have platform limitations (browser-only, mobile-only, iOS-specific) documented in descriptions, but these are narrative text, not machine-parseable constraints. No capability negotiation mechanism.
Pagination limits not enforced server-side. list_apps accepts 'limit' but no mention of maximum; get_accessibility_tree accepts limit=0 (no limit). Large unbounded results risk context window exhaustion. Rubric baseline: enforce cap at 20-50 items.