Skip to content
Debugging
Skill

/flutter-mcp-toolkit-control

Drive a running Flutter app — tap, scroll, type, fill forms, hot-reload, navigate. Use when you need to interact with the UI.

BOOST
From plugin
mcp-flutter
38922 skills1 agent
Install
$ npx -y skills add Arenukvern/mcp_flutter --skill flutter-mcp-toolkit-control --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/flutter-mcp-toolkit-control

Context preview

The summary Claude sees to decide when to auto-load this skill.

Drive a running Flutter app — tap, scroll, type, fill forms, hot-reload, navigate. Use when you need to interact with the UI.

SKILL.md

flutter-mcp-toolkit-control.SKILL.md
name: flutter-mcp-toolkit-control
description: Drive a running Flutter app — tap, scroll, type, fill forms, hot-reload, navigate. Use when you need to interact with the UI.

<!-- @FMT_MODE_PRELUDE -->

When to use

Use this skill when you need to drive a running Flutter app as a user would:

  • Tap buttons, icons, list items, or any interactive widget.
  • Type text into fields, submit forms, clear inputs.
  • Scroll or swipe to reveal off-screen content.
  • Navigate between routes programmatically (push, pop, popUntil).
  • Dismiss dialogs and bottom sheets.
  • Give a widget keyboard focus, then press keys (Enter, Escape, Tab, arrows, ASCII chars).
  • Hot-reload or hot-restart after editing Dart source files.
  • Combine reload + screenshot + semantics in one round-trip for fast iteration.

Selectors

Most interaction tools target a widget by **ref** — a short string like `"s_0"` returned by `semantic_snapshot`. For visible widgets, call `semantic_snapshot`, scan the returned nodes, find the right ref, then pass it. Narrow the node set with `identifierPrefix` or `subtreeOf` (a ref or an identifier), and cut every node down to the keys you read with `fields`; refs are those of the full tree either way. For off-screen targets with stable semantics text or identifier, use `reveal_search`; it performs a bounded snapshot → match → scroll loop and returns a fresh `ref`/`snapshotId`.

Snapshot node keys to scan:

| Want to find | Scan field | Example value | |---|---|---| | By semantics identifier | `identifier` | `"nav.tasks"` | | By visible label / text | `label` | `"Login"` | | By value or hint | `value` / `hint` | `"user@example.com"` | | By semantic role | `type` | `"button"` | | By what it accepts | `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.

Recipes

Tap a widget by text

semantic_snapshot()
→ find node where label == "Submit" → ref "s_3"
tap_widget(ref: "s_3", snapshotId: <id>)

Fill a login form

semantic_snapshot() → email ref "s_1", password ref "s_2"
fill_form(fields: [{ref:"s_1", text:"user@example.com"}, {ref:"s_2", text:"secret"}], snapshotId: <id>)
→ one round-trip; stops on first failure

Scroll to find an item

scroll(direction: "down", distance: 300)
semantic_snapshot() → item now visible → ref "s_5"
tap_widget(ref: "s_5")

Reveal an off-screen field by identifier

reveal_search(query: "greeting_input_field", matchBy: "identifier", direction: "down", maxAttempts: 4)
→ returns ref "s_14", snapshotId 2, match, attempts trace
enter_text(ref: "s_14", snapshotId: 2, text: "hello")

Wait for a widget to appear

wait_for(predicate: {kind: "text", text: "Welcome"}, timeoutMs: 8000)
→ returns fresh snapshot when text appears
tap_widget(ref: <ref from wait_for snapshot>)

Navigate to a route

navigate(action: "push", route: "/settings", arguments: {tab: "account"})
semantic_snapshot() → fresh refs in the new screen

Hot reload after a code change

hot_reload_and_capture()
→ screenshot + semantic snapshot + errors in one call

Press the back hardware button

`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")

Tool reference

tap_widget

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

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

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": "hello@example.com"}}

Returns: `{"via": "editable_state", "verified": true, "appliedText": "..."}`. A write the field kept nothing of fails with `text_not_applied`, `appliedText` (empty) and `restoredText` (the value put back). Failures: `stale_snapshot`, `ref_not_found`, `text_not_applied`

reveal_search

Find a semantic target that may be off-screen. `query` • string • required. `matchBy` • string • optional (`text|identifier|label|value|hint`, default `text`). `direction` • string • optional (`up|down|left|right`, default `down`). `maxAttempts` • integer • optional • max 10. `distance` • number • optional. `connection` • object • optional.

{"name": "reveal_search", "arguments": {"query": "greeting_input_field", "matchBy": "identifier", "direction": "down", "maxAttempts": 4}}

Returns: `{"ref": "s_14", "snapshotId": 2, "match": {...}, "attempts": [...]}` — Failures: `missing_query`, `target_not_found`, `scroll_blocked`, `target_not_actionable`. `identifier` matches whole and case-sensitively; the other modes are case-insensitive substring tests. A miss by `identifier` carries `identifiersSeen` (how many the searched screens published) and `nearIdentifiers` — the closest of them, so `panel.tab` not found comes back with `panel.tab.overview` / `panel.tab.jobs` to pass instead.

fill_form

Batch text entry: fills multiple fields in one call. Stops on first failure. `snapshotId` validated on first field only. `fi

Read more
Ships withmcp-flutter

Inspect and drive a running Flutter app from your AI assistant. 📖 Docs: docs.page/arenukvern/mcp_flutter 🤝 Contribute: guide · contributors · code of conduct · security flutter-mcp-toolkit is a Dart MCP server + Flutter package for AI agents (Codex, Zed,

Get the whole plugin

Other skills on mcp-flutter.