Skip to main content
Glama
Flutter Driver MCP

Flutter Driver MCP

by funkyfunc

πŸ§ͺ Flutter Driver MCP

An MCP server that lets AI agents see, tap, type, scroll, and assert inside live Flutter apps β€” no pre-written tests required.

Flutter Driver MCP bridges your LLM (Claude, Gemini, etc.) to a running Flutter application via Flutter's test framework + WebSocket, giving the agent full interactive control of the UI.

Now with screen recording! Record your app sessions as native video files (MP4/MOV) on iOS Simulators, macOS Desktop, and Android devices.


πŸ’‘ Why Flutter Driver MCP?

Zero-config injection. No changes to your app's source code. The harness is auto-injected at launch time β€” just point at a project path and go.

Focused, LLM-optimized toolset. 26 purpose-built tools instead of hundreds. Every tool description fits comfortably in an LLM context window without drowning the model in irrelevant options.

Suggestive errors. When a widget isn't found, the harness fuzzy-matches against the live widget tree and returns "Did you mean…?" suggestions β€” dramatically reducing agent retry loops and wasted tokens.

Token-efficient inspection. Widget tree output strips Dart generics, flattens layout boilerplate (Container, Padding, SizedBox…), and omits heavyweight coordinate data by default. The agent sees only what matters.

Full WidgetTester power. Every interaction runs through Flutter's real test framework with pumpAndSettle() after each action β€” no timing hacks, no flaky coordinate-based taps.

Related MCP server: Mobile Device MCP

✨ What Can It Do?

Category

Tools

Lifecycle

start_app Β· stop_app Β· pilot_hot_restart Β· list_devices Β· start_recording Β· stop_recording

Interaction

tap Β· enter_text Β· scroll Β· drag_and_drop Β· scroll_until_visible Β· wait_for Β· press_key

Inspection

get_widget_tree Β· get_accessibility_tree Β· explore_screen Β· get_text Β· screenshot

Assertions

assert

Navigation

navigate_to Β· go_back Β· get_current_route

Environment

simulate_background Β· set_network_status Β· intercept_network

Utilities

read_logs Β· batch_actions Β· wait_for_animation

Unified Selectors

All interaction tools accept a simple target string instead of verbose JSON:

"#my_key"                    β†’  find by Key
"text=\"Submit\""              β†’  find by text
"type=\"Checkbox\""            β†’  find by widget type
"tooltip=\"Back\""             β†’  find by tooltip
"semanticsLabel=\"Username\""  β†’  find by semantics label (hint text, accessibility label)

Suggestive Errors

When a widget isn't found, the harness scans the tree and returns "Did you mean…?" suggestions β€” dramatically reducing agent retry loops.


πŸ— Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    JSON-RPC/stdio    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   WebSocket   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  MCP Client  β”‚ ◄──────────────────► β”‚  Node.js Server  β”‚ ──connects──► β”‚  Dart Harness    β”‚
β”‚  (LLM Agent) β”‚                      β”‚  (index.ts)      β”‚               β”‚  (harness.dart)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚  inside Flutter  β”‚
                                                                         β”‚  flutter_test    β”‚
                                                                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  1. The Node.js MCP server receives tool calls from any MCP-compatible client.

  2. On start_app, it injects a Dart harness into the project's integration_test/ directory and launches flutter run --machine.

  3. The harness starts a WebSocket server inside the app. The Node server connects to it and sends JSON-RPC commands.

  4. All interactions (tap, scroll, assert, screenshot, etc.) execute inside the real Flutter test framework with full access to the widget tree.

Zero external dependencies. The harness only imports dart:* core libraries and package:flutter_test (already in every Flutter project). No changes to your pubspec.yaml, no entitlement edits, no setup steps.


πŸ“¦ Installation

You can run the server directly via npx in your MCP configuration without installing anything globally.

If you instead wish to install it globally:

npm install -g flutter-driver-mcp

Prerequisites

  • Node.js β‰₯ 18

  • Flutter SDK on your PATH

  • Any Flutter project β€” no additional dependencies or setup required


πŸš€ Usage

Claude Code

To add this server to Claude Code, run:

claude mcp add flutter-driver -- npx -y flutter-driver-mcp

Other MCP clients (Desktop, IDEs, etc.)

Add the following to your MCP client configuration:

{
  "mcpServers": {
    "flutter-driver": {
      "command": "npx",
      "args": ["-y", "flutter-driver-mcp"]
    }
  }
}

Quick start

Once your MCP client is connected, just tell the agent what you want β€” it'll figure out the tools:

"Launch my app at /path/to/my_app on macOS and test the login flow"

"Verify the account creation flow is working"

"Take a screenshot and verify the home screen loaded"

The agent handles start_app, explore_screen, tap, assert, screenshot, etc. automatically. No need to spell out tool calls.


πŸ”§ Tool Reference

Lifecycle

Tool

Description

start_app

Injects the harness, launches the app via flutter run, and connects over WebSocket. Surfaces actual build errors (compiler messages, Xcode failures) instead of generic timeouts.

stop_app

Gracefully sends app.stop to the Flutter daemon, kills processes, and cleans up. Auto-stops any active recording.

pilot_hot_restart

Sends a full restart command to the running app (preserves session).

list_devices

Lists available Flutter devices (simulators, emulators, physical, desktop). No running app required.

start_recording

Starts recording the screen of the running app's device. Supports iOS Simulators (MP4), macOS Desktop (MOV), and Android (MP4, max 180s). Auto-stops after 5 minutes or on stop_app.

stop_recording

Stops the current recording and finalizes the video file. Returns the file path, format, duration, and size. Called automatically by stop_app if a recording is active.

Interaction

Tool

Description

tap

Taps, long-presses, or double-taps a widget. Set gesture: "long_press" or gesture: "double" to change behavior. Defaults to a normal tap. Automatically scrolls it into view first.

enter_text

Enters text into a TextField. Supports finding fields by hint text via semanticsLabel="Hint". Set clearFirst: true to explicitly clear the field before typing. Optionally sends a TextInputAction (e.g. done, search).

scroll

Scrolls or swipes a widget. Use dx/dy for pixel-precise scrolling, or direction (up/down/left/right) + optional distance for named swipe gestures.

drag_and_drop

Drags from a source widget to a destination widget or custom pixel offset.

scroll_until_visible

Scrolls a scrollable container until a target widget appears.

wait_for

Polls until a widget appears (with timeout). Set gone: true to wait for disappearance instead (e.g. loading spinners).

press_key

Simulates a keyboard key press (enter, tab, escape, backspace, arrow keys, etc.).

Inspection

Tool

Description

get_widget_tree

Returns the full widget tree as JSON. Use summaryOnly: true to filter layout noise.

get_accessibility_tree

Returns the Semantics tree β€” compact, labels-focused, ideal for LLMs. Pass includeRect: true if coordinates are needed.

explore_screen

Maps all interactive elements on the current screen using the native Semantics tree. Each element includes a suggestedTarget β€” a copy-pasteable selector string guaranteed to work with interaction tools.

get_text

Returns the raw text string from a widget (supports Text, EditableText, and RichText descendants).

screenshot

Captures a PNG screenshot. Without a target, captures the full app (defaults to type: "app"). With a target, captures that specific widget.

Assertions

Tool

Description

assert

Runs an assertion on a widget. Use check to specify the type: exists, not_exists, text_equals (with expected), text_contains (substring match with expected), count (with expected integer), state (with stateKey + expected), visible, or enabled (with expected).

Navigation & Environment

Tool

Description

navigate_to

Pushes a named route via Navigator.pushNamed. Does NOT work with GoRouter or other custom routers β€” use tap() to navigate via on-screen elements instead.

go_back

Pops the current route or dismisses modal overlays (bottom sheets, dialogs). Falls back to Escape key for overlays that aren't Navigator routes.

get_current_route

Returns the name of the currently active route β€” lets the agent know where it is.

simulate_background

Sends the app to background and brings it back after a duration.

set_network_status

Toggles WiFi on/off (macOS/iOS Simulator only).

intercept_network

Registers a mock HTTP response for a URL pattern. Pass null to clear.

Utilities

Tool

Description

read_logs

Returns the last N lines from the app's stdout/stderr.

batch_actions

Executes multiple actions in a single tool call (e.g. fill a form: 5Γ— enter_text + tap). Runs sequentially with pumpAndSettle between each.

wait_for_animation

Waits for all animations to finish before proceeding.


🀝 Using with the Official Dart/Flutter MCP Server

Flutter Driver MCP is complementary to the official Dart/Flutter MCP server. While there is some overlap in app interaction (like widget tree inspection), they serve distinct roles in a developer's workflow:

Feature

Official Dart MCP

Flutter Driver MCP

Primary Focus

IDE productivity, code analysis & linting

Live AI-Driven E2E Testing

Connectivity

Dart Tooling Daemon (DTD)

WebSocket to Injected Harness

App Control

Hot reload/restart, workspace symbols

Mocking, backgrounding, network interception

AI Discovery

Widget Tree (standard)

Semantics-first (explore_screen)

Assertions

Manual tree inspection by agent

On-device assertions (assert with check parameter)

Key Tools

dart_fix, analyze_files, run_tests, pub

tap, explore_screen, assert, intercept_network

Use both together: the official server for deep code analysis, package management, and standard IDE features; use Flutter Driver MCP when you need the agent to live-test the app UI, simulate complex environment states, and verify behavior with high-level assertions.

Agent Instructions (Copy & Paste)

If you're using both servers in the same project, drop the following into your project's AGENTS.md (or equivalent instruction file) so your AI agent knows when to reach for which:

## MCP Server Usage Guide

This project has two MCP servers. Use the right one for the job:

### Official Dart/Flutter MCP Server
Use for **code-level** work β€” things you'd do in an IDE:
- Adding/removing packages (`pub add`, `pub remove`)
- Resolving symbols, finding definitions (`resolve_workspace_symbol`, `hover`)
- Running unit/widget tests (`run_tests`)
- Static analysis and formatting (`analyze_files`, `dart_fix`, `dart_format`)
- Hot reload/restart via DTD (`hot_reload`, `hot_restart`)
- Reading package source code (`read_package_uris`)

### Flutter Driver MCP Server
Use for **live UI** work β€” things a real user would do:
- Launching the app on a device (`list_devices`, `start_app`)
- Tapping, typing, scrolling, swiping, dragging (`tap` with `gesture`, `enter_text`, `scroll` with `direction`, `drag_and_drop`, `press_key`)
- Checking what's on screen (`explore_screen`, `get_widget_tree`, `screenshot`, `get_text`)
- Asserting UI state (`assert` with `check`: `exists`, `not_exists`, `text_equals`, `text_contains`, `count`, `state`, `visible`, `enabled`)
- Mocking network responses (`intercept_network`)
- Navigating (`navigate_to`, `go_back`, `get_current_route`)
- Simulating environment (`simulate_background`, `set_network_status`)
- Executing scoped waits (`wait_for` with `gone` flag)

### Key Rules
- **Hot restart**: Use `pilot_hot_restart` if the app was started via Driver's `start_app`.
  Use the official `hot_restart` if working through DTD. Never mix them.
- **Optimal workflow**: Use the Official server to edit code β†’ `pilot_hot_restart` to refresh β†’
  Driver's `explore_screen` or `assert_exists` to verify the change rendered correctly.
- After any Driver interaction (`tap`, `enter_text`, etc.), the harness automatically
  calls `pumpAndSettle()`. You don't need manual waits unless testing async network latency.

πŸ§ͺ Running Tests

The project includes comprehensive verification scripts:

# Build (includes Dart syntax verification)
npm run build

# Run the full integration test suite
npm run verify-integration

The integration tests boot a real Flutter app (test_app/), exercise all major tools over JSON-RPC, and verify correct behavior.


πŸ“„ License

MIT

Available Tools

28 tools
assertA

Runs an assertion check on a widget. Use 'check' to specify the type: exists, not_exists, text_equals, text_contains, count, state, visible, or enabled.

Inputs: check (required), target, expected, stateKey, +1 more.

batch_actionsA

Execute multiple actions in a single call. Each action is run sequentially with pumpAndSettle between them. Stops on first error. Supported tools: tap, enter_text, scroll, assert, wait_for, press_key, screenshot, get_text, explore_screen.

Inputs: actions (required).

drag_and_dropC

Drags from a source widget to a target widget (or to an offset).

Inputs: from (required), dx, dy, to, +1 more.

enter_textA

Enters text into a widget found by the target string. By default, replaces existing text. Set clearFirst to explicitly clear and re-focus the field before typing.

Inputs: text (required), action, target, clearFirst, +1 more.

explore_screenC

Maps out interactive elements on the screen.

Inputs: filter, within.

get_accessibility_treeC

Returns the accessibility (semantics) tree.

Inputs: includeRect.

get_current_routeA

Returns the name of the currently active route on the Navigator stack.

Inputs: no named parameters.

get_textB

Returns the text content of a widget identified by the target string.

Inputs: target, timeout_ms.

get_widget_treeC

Returns a JSON representation of the widget tree.

Inputs: summaryOnly.

go_backA

Pops the current route off the Navigator stack (like pressing the back button).

Inputs: no named parameters.

intercept_networkC

Mocks a network response. Pass null for both to clear.

Inputs: urlPattern, responseBody.

list_devicesA

Lists available Flutter devices. Does NOT require a running app.

Inputs: no named parameters.

pilot_hot_restartC

Performs a hot restart of the currently running app session started by this server.

Inputs: no named parameters.

press_keyC

Simulates a keyboard key press (e.g. enter, tab, escape, backspace, arrow keys).

Inputs: key (required).

read_logsB

Reads the last N lines from the app's stdout/stderr.

Inputs: lines.

screenshotB

Captures a screenshot. Without a target, captures the full app. With a target, captures a specific widget.

Inputs: type, target, save_path, timeout_ms.

scrollB

Scrolls or swipes a widget. Use dx/dy for pixel-precise scrolling, or direction/distance for named swipe gestures.

Inputs: dx, dy, target, distance, +2 more.

scroll_until_visibleB

Scrolls a scrollable widget until a target widget is visible.

Inputs: dx, dy, target, timeout_ms, +1 more.

set_network_statusC

Simulates network connectivity changes.

Inputs: wifi (required).

simulate_backgroundB

Simulates the app going into the background and coming back to the foreground.

Inputs: duration_ms.

start_appC

Injects the harness and starts the Flutter app in test mode.

Inputs: project_path (required), device_id.

start_recordingA

Starts recording the screen of the running app's device. Recording begins capturing immediately with no warmup delay β€” proceed with interactions right away. Supports iOS Simulators (MP4), macOS Desktop (MOV), and Android devices/emulators (MP4, max 180s). Only one recording at a time. Auto-stops if stop_app is called or after 5 minutes.

Inputs: save_path.

stop_appB

Stops the currently running Flutter app and cleans up.

Inputs: no named parameters.

stop_recordingA

Stops the current screen recording and finalizes the video file. Returns the file path, format, duration, and file size. Called automatically by stop_app if a recording is active.

Inputs: no named parameters.

tapB

Taps, long-presses, or double-taps a widget. Defaults to a normal tap. Use gesture to change.

Inputs: target, gesture, timeout_ms.

wait_forB

Waits for a widget to appear or disappear. Set gone=true to wait for disappearance (e.g. loading spinners).

Inputs: gone, target, timeout, timeout_ms.

wait_for_animationA

Pumps frames for a specified duration without waiting for all animations to settle. Useful for hero transitions, page animations, or custom animation controllers where pumpAndSettle would time out.

Inputs: duration_ms.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.4/5.0

    Scored across 28 tools

    Disambiguation5/5

    Each tool targets a distinct operation: lifecycle (start/stop/restart), device management, gestures (tap/scroll/drag), navigation, inspection, and network simulation. The gesture tools are well differentiated (tap vs enter_text vs scroll vs drag_and_drop), and even similar ones like scroll vs scroll_until_visible have clearly distinct purposes. No meaningful overlap or ambiguity exists.

    Naming Consistency4/5

    Tools follow a consistent verb_noun pattern (start_app, stop_app, get_text, wait_for, read_logs). Most names are descriptive and consistent, with minor deviations like 'assert' and 'intercept_network' mixing a bare verb style, and 'explore_screen' vs 'get_widget_tree' showing slight inconsistency in prefix style (explore vs get). Overall quite consistent.

    Tool Count4/5

    At 28 tools, this is on the heavy side but justifiable given the breadth of a Flutter driver's surface: lifecycle, gestures, navigation, inspection, network, recording, and batching. Each tool earns its place and the count is appropriate for a comprehensive UI automation server, though slightly above the typical ideal range.

    Completeness5/5

    The surface is exceptionally complete for a Flutter driver: full app lifecycle (start/stop/restart), device listing, gesture primitives, navigation, text input, widget assertions, screenshots, widget/accessibility trees, network mocking, background simulation, log reading, recording, and batch operations. The batch_actions tool even fills the gap for multi-step efficiency without round trips. No obvious dead ends or missing operations.

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      D
      maintenance
      MCP server that gives AI coding assistants the ability to see and interact with mobile devices. 49 tools for Android/iOS β€” AI-powered visual analysis (Claude + Gemini), smart tap/type by description, Flutter widget tree inspection, video recording, and test script generation. 4-tier element search with <1ms local matching. Free tier included, zero setup via npx.
      49
      65 npm
      4
      Business Source 1.1
    • A
      license
      Not graded
      quality
      C
      maintenance
      An MCP server that lets AI agents control iOS and Android devices (tap, scroll, type, take screenshots, read UI trees, and run code). Works with multiple devices at the same time.
      94 npm
      54
      MIT
    • F
      license
      A
      quality
      D
      maintenance
      An MCP server that gives AI agents the power to record, replay, and mock mobile app interactions β€” combining Maestro UI automation with Proxyman network capture to generate complete, self-contained test scripts.
      33
      1
      -
    • A
      license
      A
      quality
      D
      maintenance
      An MCP server that wraps Flutter CLI tools into structured, agent-friendly tools for AI coding agents, with features like two-phase test results, output capping, and managed app lifecycle.
      15
      17 npm
      4
      MIT