An MCP server that lets AI agents see and interact with your running Flutter app — tap buttons, type text, scroll, take screenshots, hot reload, and more — all through the VM Service protocol.
Prerequisites: Flutter SDK (includes Dart)
git clone https://github.com/user/fdtools.git
cd fdtools
dart pub get
dart compile exe bin/server.dart -o bin/fdtools_serverAdd to your editor's MCP config:
VS Code (.vscode/mcp.json):
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"flutter": {
"command": "/absolute/path/to/fdtools/bin/fdtools_server"
}
}
}Restart your editor / MCP client and the tools are ready.
| Tool | Arguments | Description |
|---|---|---|
attach |
vmServiceUrl (required) |
Connect via VM Service URL from flutter run output |
launch |
workingDirectory (required), platform (required), timeout |
Run flutter run --debug and auto-connect |
disconnect |
— | Close connection, kill managed flutter run if any |
status |
— | {"connected": true/false} |
| Tool | Arguments | Description |
|---|---|---|
widget_tree |
— | Full widget tree as JSON (types, labels, keys, bounds, children) |
inspect |
nodeId or selector, optional property |
Deep properties by nodeId or selector; filter to one value with property |
| Tool | Arguments | Description |
|---|---|---|
tap |
selector (required), duration |
Tap (or long-press via duration) widget by selector |
type_text |
selector (required), text (required) |
Focus field and enter text |
scroll |
selector (required), direction, amount |
Scroll a scrollable widget |
drag |
selector (required), targetSelector or dx/dy |
Drag-and-drop gesture |
screenshot |
— | Capture screen as base64 PNG |
navigate |
route, action, arguments |
Push a named route (action: "push") or pop the current route (action: "pop") |
| Tool | Arguments | Description |
|---|---|---|
wait_for |
selector (required), condition, timeout |
Wait for widget condition (visible, exists, gone) |
wait_until_settlement |
timeout |
Wait for animations/frame callbacks to complete |
| Tool | Arguments | Description |
|---|---|---|
hot_reload |
— | Apply code changes, preserve state |
hot_restart |
— | Full restart, reset app state |
| Tool | Arguments | Description |
|---|---|---|
toggle_dark_mode |
enable (bool) |
Switch between light and dark mode |
| Tool | Arguments | Description |
|---|---|---|
inspect_screen |
— | Full immersive inspection: screenshot + OCR + widget tree → unified element list with types, text, bounds, and click coordinates |
read_screen |
— | Screenshot + OCR — extract all visible text with bounding boxes and click coords |
find_text |
query (required) |
Search for text via OCR — returns matches with clickable center coordinates |
click_text |
query (required) |
Compound: screenshot → OCR → find text → tap at coordinates |
| Tool | Arguments | Description |
|---|---|---|
diagnose |
— | Run comprehensive diagnostics on the running Flutter app — widget tree health, app lifecycle, screen metrics, blank/stuck screen detection |
Target widgets with any of these formats (tried in order):
| Format | Example | Best for |
|---|---|---|
semantics:Label |
semantics:Submit |
Stable across rebuilds, accessibility-aligned |
key:value |
key:submit_btn |
Widgets with explicit Key |
text:Content |
text:Submit |
Visible text content |
index:Type:N |
index:ElevatedButton:0 |
Last resort — fragile, use with caution |
Ambiguous matches return an explicit error — the server never silently picks one.
Flutter App (debug mode)
└── VM Service WebSocket (ws://127.0.0.1:PORT/TOKEN=/ws)
└── fdtools (Dart native binary)
└── stdio JSON-RPC 2.0
└── AI Agent / MCP Client
| Capability | Mechanism |
|---|---|
| Read widget tree | ext.flutter.inspector.* VM Service extensions |
| Interact (tap, scroll) | evaluate() → WidgetsBinding.handlePointerEvent() |
| Type text | evaluate() → TextEditingController.text = ... |
| Screenshots | _flutter.screenshot extension → PNG |
| OCR / Vision | Rust native binary (ocrs engine) → text + coordinates |
| Hot reload | reloadSources() VM Service API |
| Connect | vmServiceConnectUri() WebSocket handshake |
- Only localhost (
127.0.0.1,localhost,::1) VM Service URLs are accepted - Auth tokens in URLs are masked in all log output
- The managed
flutter runprocess is killed on disconnect
# Run unit tests
dart test
# Build vision OCR binary (requires Rust toolchain)
cd vision && cargo build --release && cd ..
# Run integration tests (requires running test fixture app)
export FLUTTER_VM_SERVICE_URL=ws://127.0.0.1:<port>/ws
dart test --tags integration
# Static analysis
dart analyze lib/ bin/ --fatal-infos
# Format code
dart format .
# Build native binary
dart compile exe bin/server.dart -o bin/fdtools_serverSee doc/ARCHITECTURE.md for internals, doc/DESIGN_DECISIONS.md for v1 design rationale.
MIT — see LICENSE for details.
{ "servers": { "flutter": { "command": "/absolute/path/to/fdtools/bin/fdtools_server", "args": [] } } }