Skip to main content
Glama
flutter-bridge-mcp

flutter-bridge-mcp

by Amodh2022

flutter-bridge-mcp

Your Android tools see a Flutter app as one opaque rectangle. This makes that rectangle readable. Point at wrong-looking text on screen and get back the file and line that built it, edit it, hot reload, then diff the pixels to prove only that changed.

It also does the Android basics — logcat, crashes, taps, screenshots — filtered and truncated server-side so a response can never blow up the model's context.

Tools

Reading logs

Tool

Purpose

list_devices

Connected devices/emulators

read_logs

Recent logcat, filtered by package / tag / level / substring

find_crashes

Whole stack traces from the crash buffer

clear_logs

Wipe buffers before reproducing a bug

capture_start / capture_read / capture_stop

Background ring-buffer capture

retrace

De-obfuscate an R8/ProGuard trace with mapping.txt

Driving the device

Tool

Purpose

screenshot

See the screen (downscaled before sending)

ui_dump

List on-screen elements with tap coordinates

tap

Tap by element text, or by coordinate

swipe

Scroll or drag, by direction or coordinates

input_text

Type into the focused field, optionally clearing or submitting

press_key

back, home, recents, enter, dpad, volume, power…

launch_app / stop_app

Cold starts, and clear_data for first-run tests

device_info

Screen size, density, Android version, foreground activity

Flutter ↔ Android bridge

Android tooling sees a Flutter app as one opaque SurfaceView; Dart tooling sees widgets but knows nothing about logcat, ANRs or native crashes. These join the two.

Tool

Purpose

flutter_connect

Attach to the running app's Dart VM Service

flutter_widget_tree

Live widget tree, each widget tagged with its source file and line

flutter_locate

The bridge — tap coordinates and the code that built the widget

flutter_diagnose

Dart exceptions correlated with the native errors around them

flutter_attach

Attach the Flutter tool so hot reload works

flutter_hot_reload

Apply Dart edits to the running app

flutter_detach

End the attach session, leaving the app running

The loop

The point of the bridge is this cycle:

screenshot          see the bug
flutter_locate      -> "Total Patients" at (410,269), built by
                       lib/presentation/dashboard/widgets/dashboard_grid.dart:76
ui_checkpoint       remember the screen
<edit that line>
flutter_hot_reload  apply it
ui_diff             confirm exactly what changed, and nothing else

ui_diff reports the percentage of pixels that changed, clusters them into regions, and returns the screen with those regions outlined — so "did my fix land, and did it disturb anything else?" is answered rather than eyeballed.

Tool

Purpose

ui_checkpoint

Remember the current screen

ui_diff

Compare now against a checkpoint, outlining what moved

Requires a debug or profile build. The VM Service URI is printed to logcat only at launch, so if it has scrolled away use flutter_connect(package=..., relaunch=True).

Prefer ui_dump and tap(text=...) over screenshots and raw coordinates: it is far cheaper, and it does not break when the layout or resolution changes. Screenshots are downscaled, so coordinates read off one are not device coordinates.

There is deliberately no arbitrary adb shell tool.

For how the join works, how the VM Service is discovered without adding a package to your app, and how this compares to the other Flutter and Android MCP servers, see design.md.

Related MCP server: adb_mcp_server

Install

It needs Python 3.10+ and adb — from Android Studio's SDK, or platform-tools.

pipx install flutter-bridge-mcp

pipx puts the server in its own virtualenv and the flutter-bridge-mcp executable on your PATH, which is what the MCP configs below expect. On Debian/Ubuntu, sudo apt install pipx && pipx ensurepath first.

A plain pip install works too, but on Debian, Ubuntu and recent Fedora it fails with error: externally-managed-environment — those distros forbid pip from writing into the system Python (PEP 668). Use pipx, or a virtualenv of your own:

python3 -m venv ~/.venvs/flutter-bridge
~/.venvs/flutter-bridge/bin/pip install flutter-bridge-mcp

Then point your MCP config at ~/.venvs/flutter-bridge/bin/flutter-bridge-mcp rather than the bare command. Do not reach for --break-system-packages; it writes into the Python your package manager owns.

From a clone, to hack on it:

python3 -m venv .venv
.venv/bin/pip install -e .

The executable is then .venv/bin/flutter-bridge-mcp.

Tests

The unit suite covers log parsing, error grouping and widget-tree flattening, and needs no device:

python3 -m venv .venv
.venv/bin/pip install -e ".[test]"
.venv/bin/python -m pytest

selftest.py is the other half — it exercises the real thing against an attached device:

.venv/bin/python selftest.py --package com.example.app --project ~/StudioProjects/app

Without --package it checks the Android side only; --project adds hot reload.

Use in Claude Code (including the Claude plugin in Android Studio)

The Studio plugin reads the Claude Code CLI config, so registering it once covers both:

claude mcp add flutter-bridge flutter-bridge-mcp \
  -e ADB_PATH=/abs/path/to/Android/Sdk/platform-tools/adb

If you installed into a virtualenv rather than with pipx, use that venv's absolute path in place of the bare flutter-bridge-mcp.

Use with Gemini in Android Studio

Create mcp.json in the Studio config directory (~/.config/Google/AndroidStudio<version>/mcp.json on Linux, ~/Library/Application Support/Google/AndroidStudio<version>/ on macOS):

{
  "mcpServers": {
    "flutter-bridge": {
      "command": "/abs/path/to/flutter-bridge-mcp",
      "args": [],
      "env": { "ADB_PATH": "/abs/path/to/Android/Sdk/platform-tools/adb" }
    }
  }
}

Restart Studio, then in Gemini → Agent mode check the tools menu for flutter-bridge.

Configuration

Env var

Default

Meaning

ADB_PATH

adb on PATH

adb binary

ANDROID_SERIAL

—

Default device when several are attached

FLUTTER_BRIDGE_MAX_LINES

200

Hard cap on returned lines

FLUTTER_BRIDGE_MAX_MSG

400

Per-message truncation

FLUTTER_BRIDGE_SCAN_LINES

8000

Lines pulled from logcat before filtering

FLUTTER_BRIDGE_BUFFER

40000

Ring-buffer size for capture_start

FLUTTER_BRIDGE_TIMEOUT

30

Seconds to wait on a Dart VM Service call

FLUTTER_PATH

flutter on PATH

flutter binary, for flutter_attach

R8_JAR

—

Path to r8.jar if retrace is not on PATH

License

MIT — see LICENSE.

Available Tools

26 tools
capture_readB

Query the background capture buffer with the same filters as read_logs.

Args: stop: also end the capture after reading.

Inputs: tag, stop, limit, package, +2 more. Returns: result

capture_startA

Begin recording logcat into a background ring buffer.

Use this when the user is about to reproduce a bug: start the capture, let them reproduce it, then query with capture_read. Unlike read_logs this survives log rotation and captures everything, including verbose output.

Inputs: label, serial. Returns: result

capture_stopA

Stop the background logcat capture and release the adb process.

Inputs: no named parameters. Returns: result

clear_logsB

Clear all logcat buffers. Do this before reproducing a bug so logs stay clean.

Inputs: serial. Returns: result

device_infoB

Report screen size, density, Android version and the foreground activity.

Inputs: serial. Returns: result

find_crashesA

Extract recent crashes and ANRs as whole stack traces from the crash buffer.

Args: package: restrict to traces mentioning this package name. limit: how many of the most recent traces to return. serial: device serial when several are attached.

Inputs: limit, serial, package. Returns: result

flutter_attachA

Attach the Flutter tool to the running app so hot reload becomes possible.

Needed once per session before flutter_hot_reload: the VM Service on its own has no Dart compiler, so source edits cannot be applied without this.

Args: project_dir: the Flutter project root, the directory holding pubspec.yaml. serial: device serial when several are attached.

Inputs: project_dir (required), serial. Returns: result

flutter_connectA

Connect to a running Flutter app's Dart VM Service. Call this first.

The VM Service URI is printed to logcat only at launch, and only by debug and profile builds. If it has already scrolled out of the buffer, pass relaunch=True to restart the app and capture it.

Args: package: application id, needed for relaunch. relaunch: force-stop and restart the app to capture a fresh URI. serial: device serial when several are attached.

Inputs: serial, package, relaunch. Returns: result

flutter_detachA

End the attach session, leaving the app running on the device.

Inputs: no named parameters. Returns: result

flutter_diagnoseA

Correlate Dart-side errors with the native Android log around them.

A Flutter failure usually leaves two unrelated-looking traces: a Dart exception and, for anything crossing a platform channel, a Java/Kotlin one in logcat. This pulls both and puts them next to each other in time order.

Args: package: app to restrict native logs to, e.g. com.example.app. limit: how many Dart errors to report. serial: device serial when several are attached.

Inputs: limit, serial, package. Returns: result

flutter_hot_reloadA

Apply Dart source edits to the running app.

Requires flutter_attach first. Use full_restart after changing initState, global state or main(), which a plain reload cannot pick up.

Args: full_restart: hot restart instead of hot reload. package, serial: as elsewhere.

Inputs: serial, package, full_restart. Returns: result

flutter_locateA

Find something on screen and report both how to tap it and where it lives in code.

This is the bridge: tap coordinates come from Android's semantics tree, while the widget type and source location come from the Dart VM Service. Use it to go from "this looks wrong on screen" to the exact line that built it.

Args: text: visible text, semantics label or widget type to look for. package, serial: as elsewhere.

Inputs: text (required), serial, package. Returns: result

flutter_widget_treeA

Show the live widget tree with the source location of each widget.

Only widgets from your own project are shown by default — the framework's own wrappers are noise. Each line ends with the file and line that built it.

Args: contains: case-insensitive filter on widget type or description. max_depth: 0 for no limit, otherwise prune deeper than this. limit: max widgets returned. include_framework: also show widgets from Flutter and third-party packages. package, serial: as elsewhere.

Inputs: limit, serial, package, contains, +2 more. Returns: result

input_textA

Type into the focused field. Tap the field first so it has focus.

Args: text: the text to type. ASCII only — adb cannot type emoji or most non-Latin text. submit: press Enter afterwards. clear: delete the field's existing contents first. serial: device serial when several are attached.

Inputs: text (required), clear, serial, submit. Returns: result

launch_appA

Launch an app, optionally from a clean state.

Args: package: application id, e.g. com.example.app. activity: fully qualified activity to start instead of the launcher entry point. clear_data: wipe the app's data first, for a true first-run test. serial: device serial when several are attached.

Inputs: package (required), serial, activity, clear_data. Returns: result

list_devicesA

List connected Android devices and emulators with their state and model.

Inputs: no named parameters. Returns: result

press_keyB

Press a hardware or navigation key.

Args: key: back, home, recents, enter, tab, delete, escape, search, menu, power, wake, sleep, volume_up, volume_down, camera, or dpad_up/down/left/right/center. A raw KEYCODE_* name or a numeric keycode also works. serial: device serial when several are attached.

Inputs: key (required), serial. Returns: result

read_logsA

Read recent logcat output, filtered.

Args: package: only show logs from this app's process (e.g. com.example.app). tag: regex matched against the log tag. min_level: one of V, D, I, W, E, F. Defaults to I. contains: case-insensitive substring that must appear in tag or message. limit: max lines returned (hard-capped by FLUTTER_BRIDGE_MAX_LINES). buffer: main, system, crash, events, radio, or all. serial: device serial when several are attached.

Inputs: tag, limit, buffer, serial, +3 more. Returns: result

retraceA

De-obfuscate an R8/ProGuard stack trace using a mapping.txt file.

Args: stacktrace: the obfuscated trace, copied from find_crashes output. mapping_path: path to mapping.txt, usually under app/build/outputs/mapping//mapping.txt

Inputs: stacktrace (required), mapping_path (required). Returns: result

screenshotA

Capture what is currently on screen. Use this to see the app before acting on it.

The image is downscaled before sending, so coordinates read off it are NOT the device's own. Use ui_dump, or tap(text=...), to get real tap targets.

Args: max_width: longest edge of the returned image in pixels. serial: device serial when several are attached.

Inputs: serial, max_width.

stop_appA

Force-stop an app. Pair with launch_app to test a cold start.

Args: package: application id, e.g. com.example.app. serial: device serial when several are attached.

Inputs: package (required), serial. Returns: result

swipeA

Swipe or scroll. Give a direction for a centred swipe, or explicit coordinates.

Args: direction: up, down, left or right. "up" scrolls the content up (reveals what is below), matching how a finger moves. x1, y1, x2, y2: start and end points, used when direction is omitted. duration_ms: swipe duration; raise it for a slow drag, lower it to fling. serial: device serial when several are attached.

Inputs: x1, x2, y1, y2, +3 more. Returns: result

tapA

Tap the screen, either at a coordinate or on the element matching text.

Prefer text: it survives layout and resolution differences. Exact matches win over partial ones; if several elements match, none is tapped and they are listed so you can pick a coordinate instead.

Args: x, y: device coordinates. Ignored when text is given. text: text, content description or resource id of the element to tap. serial: device serial when several are attached.

Inputs: x, y, text, serial. Returns: result

ui_checkpointA

Remember the current screen so a later ui_diff can show what changed.

Take one before editing code, then call ui_diff after hot reloading.

Args: label: name for this checkpoint. serial: device serial when several are attached.

Inputs: label, serial. Returns: result

ui_diffA

Compare the screen now against a checkpoint and highlight what moved.

Returns a summary plus the current screen with changed regions outlined, so you can confirm an edit did what you meant and did not disturb anything else.

Args: label: which checkpoint to compare against. threshold: per-channel difference (0-255) that counts as a real change. Raise it to ignore animations and anti-aliasing. max_width: longest edge of the returned image. serial: device serial when several are attached.

Inputs: label, serial, max_width, threshold.

ui_dumpA

List on-screen UI elements with their tap coordinates.

Cheaper and more precise than a screenshot when you only need to find a control.

Args: contains: case-insensitive filter on text, content description or id. clickable_only: only return elements that accept taps. limit: max elements returned. serial: device serial when several are attached.

Inputs: limit, serial, contains, clickable_only. Returns: result

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    A3.8/5.0

    Scored across 26 tools

    Disambiguation4/5

    Most tools target a distinct action and resource, but read_logs and capture_read share the same filter semantics, and ui_dump and flutter_locate both find on-screen elements. The descriptions are detailed enough to disambiguate, so the overlap is manageable.

    Naming Consistency5/5

    All tool names follow a consistent snake_case verb_noun style: list_devices, read_logs, clear_logs, launch_app, flutter_connect, ui_diff. The flutter_* and ui_* prefixes further group related operations, making the naming predictable and scannable.

    Tool Count2/5

    At 26 tools, the set exceeds the well-scoped range and feels heavy for an MCP server. The tools are organized into clear categories, but the capture_* trio, multiple UI-inspection tools, and the attach/reload/detach sequence add bulk that could have been consolidated.

    Completeness5/5

    The surface covers the full Flutter debugging loop: log capture, crash extraction, retracing, UI inspection, interaction, VM Service connection, widget tree analysis, hot reload, and visual verification via ui_diff. There are no obvious dead ends or missing core operations for the stated purpose.

    Maintenance

    ActivitySlowing
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      C
      quality
      D
      maintenance
      Enables comprehensive control of Android devices via ADB for Flutter development, UI testing, and visual QA workflows. Provides 60+ tools for device management, UI inspection, app testing, performance profiling, and debugging through natural language.
      77
      27 PyPI
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables remote control of Android devices via ADB, including screen operations, input, app management, file transfer, and advanced tools like UI testing and performance analysis.
      25
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      Enables an agent to inspect and interact with Android emulators or physical devices via ADB, capturing UI snapshots, tapping nodes, typing text, and reading app logs.
      10
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables AI assistants and IDEs to drive real Android devices and emulators for cross-app UI automation, testing, and diagnostics, including collecting Logcat output and screenshots.
      Apache 2.0