From flutter-mcp-toolkit
Drives running Flutter apps: tap/scroll/type widgets via semantic snapshots, fill forms, hot-reload code, navigate routes. Use for UI interactions and iteration.
How this skill is triggered — by the user, by Claude, or both
Slash command
/flutter-mcp-toolkit:flutter-mcp-toolkit-controlThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
<!-- @FMT_MODE_PRELUDE -->
Use this skill when you need to drive a running Flutter app as a user would:
Every interaction tool targets a widget by ref — a short string like "s_0" returned by semantic_snapshot. There is no by-text or by-type selector syntax on the tool itself. The workflow is: call semantic_snapshot, scan the returned nodes, find the right ref, then pass it.
Snapshot node fields to filter on:
| Want to find | Scan field | Example value |
|---|---|---|
| By visible label / text | label | "Login" |
| By value or hint | value / hint | "[email protected]" |
| By tooltip | tooltip | "Close" |
| By widget key | key | "[<'submitBtn'>]" |
| By semantic role / type | flags or actions | ["tap"] |
Example — find the "Login" button ref:
semantic_snapshot()
→ nodes: [{ref:"s_0", label:"Login", actions:["tap"]}, ...]
tap_widget(ref: "s_0")
Pass snapshotId (from the snapshot response) to any interaction call. If the tree has changed, the call returns stale_snapshot with both IDs so you know to re-snapshot. Refs are only valid against the most recent snapshot.
semantic_snapshot()
→ find node where label == "Submit" → ref "s_3"
tap_widget(ref: "s_3", snapshotId: <id>)
semantic_snapshot() → email ref "s_1", password ref "s_2"
fill_form(fields: [{ref:"s_1", text:"[email protected]"}, {ref:"s_2", text:"secret"}], snapshotId: <id>)
→ one round-trip; stops on first failure
scroll(direction: "down", distance: 300)
semantic_snapshot() → item now visible → ref "s_5"
tap_widget(ref: "s_5")
wait_for(predicate: {kind: "text", text: "Welcome"}, timeoutMs: 8000)
→ returns fresh snapshot when text appears
tap_widget(ref: <ref from wait_for snapshot>)
navigate(action: "push", route: "/settings", arguments: {tab: "account"})
semantic_snapshot() → fresh refs in the new screen
hot_reload_and_capture()
→ screenshot + semantic snapshot + errors in one call
press_key has no Back key. Use navigate(action: "pop") for Navigator pop; handle_dialog(action: "dismiss") for dialogs; press_key(key: "Escape") on desktop.
navigate(action: "pop")
Tap a widget by ref. ref • string • required. snapshotId • integer • optional. connection • object • optional.
{"name": "tap_widget", "arguments": {"ref": "s_3", "snapshotId": 7}}
Returns: {"via": "semantic_action", "ref": "s_3"} — Failures: stale_snapshot, ref_not_found
Long-press a widget by ref. ref • string • required. snapshotId • integer • optional. connection • object • optional.
{"name": "long_press", "arguments": {"ref": "s_2"}}
Returns: {"via": "semantic_action"} — Failures: stale_snapshot, ref_not_found
Enter text into a text field; taps to focus before typing. ref • string • required. text • string • required. snapshotId • integer • optional. connection • object • optional.
{"name": "enter_text", "arguments": {"ref": "s_1", "text": "[email protected]"}}
Returns: {"via": "editable_state"} — Failures: stale_snapshot, ref_not_found
Batch text entry: fills multiple fields in one call. Stops on first failure. snapshotId validated on first field only. fields • array of {ref, text} • required. snapshotId • integer • optional. connection • object • optional.
{"name": "fill_form", "arguments": {"fields": [{"ref":"s_1","text":"user"},{"ref":"s_2","text":"pass"}], "snapshotId": 5}}
Returns: {"filled": 2} — Failures: stale_snapshot, ref_not_found
Scroll to reveal content. "down" reveals content below (finger swipes up). direction • string • required (up|down|left|right). ref • string • optional (falls back to screen center). distance • number • optional • default 300. snapshotId • integer • optional. connection • object • optional.
{"name": "scroll", "arguments": {"direction": "down", "ref": "s_0", "distance": 500}}
Returns: {"via": "semantic_action"} — Failures: ref_not_found, stale_snapshot
High-velocity fling. Same direction model as scroll. Always Tier 2 pointer events. direction • string • required. ref • string • optional. distance • number • optional • default 300. snapshotId • integer • optional. connection • object • optional.
{"name": "swipe", "arguments": {"direction": "left", "ref": "s_4"}}
Returns: {"via": "pointer_events"} — Failures: ref_not_found, web_gesture_not_supported
Drag from one widget to another. Always Tier 2. fromRef • string • required. toRef • string • required. snapshotId • integer • optional. connection • object • optional.
{"name": "drag", "arguments": {"fromRef": "s_2", "toRef": "s_7"}}
Returns: {"via": "pointer_events"} — Failures: ref_not_found, web_gesture_not_supported
Synthesize a mouse hover. Desktop/web only — no hover concept on mobile. ref • string • required. snapshotId • integer • optional. connection • object • optional.
{"name": "hover", "arguments": {"ref": "s_5"}}
Returns: {"via": "pointer_events"} — Failures: ref_not_found, platform error on mobile
Synthesize key press (down+up). Accepted: Enter Escape Tab Backspace Delete Space ArrowUp ArrowDown ArrowLeft ArrowRight plus single ASCII (a-z 0-9). key • string • required. ctrl/shift/alt/meta • boolean • optional • default false. connection • object • optional.
{"name": "press_key", "arguments": {"key": "Enter"}}
Returns: {"key": "Enter"} — Failures: unsupported_key, no_focus
Wait for a UI predicate; returns fresh semantic snapshot. Predicates: {kind:"text",text} | {kind:"noText",text} | {kind:"time",ms} | {kind:"stable",stableWindowMs}. predicate • object • required. timeoutMs • integer • optional • default 5000 • max 30000. connection • object • optional.
{"name": "wait_for", "arguments": {"predicate": {"kind": "text", "text": "Dashboard"}, "timeoutMs": 8000}}
Returns: fresh semantic snapshot — Failures: timeout, invalid_predicate
Drive the registered Navigator. Requires MCPToolkitBinding.instance.navigatorKey = key in the app. action • string • required (push|pop|popUntil). route • string • required for push/popUntil. arguments • object • optional (for push). connection • object • optional.
{"name": "navigate", "arguments": {"action": "push", "route": "/profile", "arguments": {"userId": "42"}}}
Returns: {"action": "push", "route": "/profile"} — Failures: navigator_not_configured, route_not_found
Dismiss the topmost popup/dialog route. Only action: "dismiss" supported. Requires navigatorKey = key on MCPToolkitBinding.instance in the app. action • string • required (must be "dismiss"). connection • object • optional.
{"name": "handle_dialog", "arguments": {"action": "dismiss"}}
Returns: {"dismissed": true} — Failures: navigator_not_configured, no_dialog
Hot reload the app. Preserves state. force • boolean • optional • default false (reload even without source changes). connection • object • optional.
{"name": "hot_reload_flutter", "arguments": {}}
Returns: "Hot reload completed" + report JSON — Failures: vm_not_connected, compilation_error
Full restart. App state not preserved. No required params. connection • object • optional.
{"name": "hot_restart_flutter", "arguments": {}}
Returns: {"report": {"type": "Success", "success": true}} — Failures: vm_not_connected
Hot reload then capture screenshot + semantics + errors in one call. compress • boolean • default true. includeSemantics • boolean • default true. includeErrors • boolean • default true. errorsCount • integer • default 4. connection • object • optional.
{"name": "hot_reload_and_capture", "arguments": {"includeErrors": true}}
Returns: screenshot (base64) + semantic snapshot + errors — Failures: vm_not_connected, compilation_error
wait_for before tap_widget after navigationAfter navigate(action: "push") the new route's widgets are not in the tree yet. Use wait_for with a text predicate to confirm the destination has rendered, then snapshot and act.
navigate(action: "push", route: "/checkout")
wait_for(predicate: {kind: "text", text: "Order Summary"}, timeoutMs: 5000)
semantic_snapshot() → tap target widgets
fill_form over multiple enter_text callsEach enter_text is a separate VM round-trip. fill_form sends all field/text pairs in one call; snapshotId is checked once (on the first field). For any form with 2+ fields, always prefer fill_form.
# Avoid: 2 round-trips
enter_text(ref: "s_1", text: "Alice")
enter_text(ref: "s_2", text: "secret")
# Prefer: 1 round-trip
fill_form(fields: [{ref: "s_1", text: "Alice"}, {ref: "s_2", text: "secret"}])
hot_reload_*, wait for the new tree before continuingHot reload completes asynchronously. Use wait_for(predicate: {kind:"stable", stableWindowMs:300}) to confirm the tree has settled before calling semantic_snapshot. Or use hot_reload_and_capture which returns a post-reload snapshot directly.
hot_reload_flutter()
wait_for(predicate: {kind: "stable", stableWindowMs: 300})
semantic_snapshot() → interact with reloaded widgets
# Or in one call (preferred):
hot_reload_and_capture() → screenshot + semantics + errors already post-reload
npx claudepluginhub arenukvern/mcp_flutter --plugin flutter-mcp-toolkitDiagnoses issues in running Flutter apps by reading logs, evaluating Dart expressions, and interpreting error envelopes. Use when crashes or unexpected behavior occur.
Provides expert guidance on Flutter 3.27+ and Dart 3.3+ for building/optimizing mobile/web/desktop apps, covering Riverpod/BLoC state, GoRouter navigation, Drift storage, Patrol testing, on-device AI.
Guides expert Flutter 3.x and Dart 3.x development for multi-platform apps, including advanced widgets, state management with Riverpod/Bloc/GetX, performance optimization, and architecture patterns.