When to use
Use this skill for read-only state inspection of a running Flutter app: what is shown on screen, recent errors, available debug targets, VM metadata, and widget tree details. Do not use for driving interaction — that is the interact skill. Start with discover_debug_apps when no connection target is established. Start with semantic_snapshot when you need to know which widgets are on screen.
Recipes
Fast inspect cycle (prefer batch)
flutter-mcp-toolkit batch --steps '[
{"name":"semantic_snapshot"},
{"name":"get_app_errors","args":{"count":5}},
{"name":"get_screenshots","args":{"mode":"flutter_layer","compress":true}}
]'
Use mode: flutter_layer on get_screenshots on macOS to avoid Screen
Recording permission failures. capture_ui_snapshot uses the separate
screenshotMode field.
Snapshot the visible UI
- Call
semantic_snapshot(). - Read
interactionSurface:flutter_widgets(tap-by-ref works),hybrid(sparse semantics),game_canvas(useevaluate_dart_expression+ screenshots). - Each interactive node has a stable
ref(s_0,s_1, …) and the response includes asnapshot_id. - Pass refs to interaction tools; pass the
snapshot_idvalue assnapshotIdto detect staleness.
After a code edit
Prefer hot_reload_and_capture over separate reload + snapshot + screenshot calls.
Find an error by message
- Call
get_app_errors(count: 10). - Inspect the
errorsarray — each entry has message, stack trace, and timestamp. - Match on message text to find the source.
List debug-mode apps
- Call
discover_debug_apps(). - Read the
targetId(canonical WebSocket URI) for each active target. - Pass the chosen URI as
connection.targetIdon subsequent tool calls.
Get widget at coordinates
- Call
inspect_widget_at_point(x: 200, y: 400). - The response identifies the deepest widget and render node at those global logical pixel coordinates.
Save a screenshot to a file
- Call
get_screenshots(). - If
meta.fileUrlsis non-empty, screenshots are on disk at those paths. Otherwise the response contains base64ImageContentblocks — extract and write manually. - To force file output, configure an images output directory on the server before calling.
Tool reference
discover_debug_apps
List all active Flutter debug targets with canonical WebSocket URIs.
connection(object, optional) — accepted by schema, ignored by executor; discovery is always local.
discover_debug_apps()
Returns: {"targets": [{"targetId": "ws://127.0.0.1:8181/<token>/ws", "host": "...", "port": 8181}]}
vm_service_unavailable— no debug-mode Flutter process found.tool_not_found— binary predates v3.0.0; runmake build.
get_app_errors
Retrieve the most recent application errors from the Dart VM.
count(integer, optional, default: 4) — number of errors to return.connection(object, optional) — connection override.
get_app_errors(count: 5)
Returns: {"message": "2 errors found", "errors": [{"message": "...", "stack": "..."}]}
vm_service_unavailable— app not reachable.connection_selection_required— multiple targets; supplyconnection.targetId.
get_screenshots
Capture screenshots of all views.
compress(boolean, optional, default: true) — compress PNG output.mode(string, optional, default:auto) —auto,flutter_layer, ordesktop_window.permissionPolicy(string, optional, default:check_only) —check_only,auto_request_once, orrequest_always.connection(object, optional) — connection override.
get_screenshots(mode: "flutter_layer", compress: false)
Returns: ImageContent blocks (base64 PNG) when no output dir configured, or TextContent URL refs + meta.fileUrls when file output is enabled.
permission_denied— retry withpermissionPolicy: "auto_request_once".vm_service_unavailable— app not reachable.
get_view_details
Get dimensions, device pixel ratio, and display ID for all views.
connection(object, optional) — connection override.
get_view_details()
Returns: {"views": [{"id": 0, "width": 1280, "height": 800, "devicePixelRatio": 2.0}]}
vm_service_unavailable— app not running.connection_selection_required— multiple targets; supplyconnection.targetId.
get_vm
Return Dart VM metadata: version, isolates list, pid, and architecture.
connection(object, optional) — connection override.
get_vm()
Returns: {"type": "VM", "name": "vm", "version": "3.x.x", "isolates": [...]}
vm_service_unavailable— app not reachable.connection_selection_required— multiple targets active.
get_extension_rpcs
List all registered VM service extension RPCs in the running app.
isolateId(string, optional) — schema-declared but not read by executor; checks all isolates when omitted.isRawResponse(boolean, optional) — schema-declared but not read by executor.connection(object, optional) — connection override.
get_extension_rpcs()
Returns: {"extensionRPCs": ["ext.flutter.inspector.getRootWidget", "ext.mcp.toolkit.semantic_snapshot"]}
vm_service_unavailable— app not running.connection_selection_required— multiple targets.
semantic_snapshot
Return a compact accessibility tree of interactive widgets with stable ref strings and a snapshot_id. A node is listed when it reads (label, value), acts (button, text field, tap, scroll…) or carries a Semantics(identifier:).
identifierPrefix(string, optional) — keep only nodes whose identifier starts with it ("nav."for one rail).subtreeOf(string, optional) — keep one node and its descendants; a ref from the latest snapshot or an identifier, the ref tried first.fields(array of strings, optional) — node keys to return;refis always kept. Names:ref id type identifier label value hint enabled focused checked toggled selected bounds actions children visibleInViewport centerInViewport center.connection(object, optional) — connection override.
The tree is always walked whole, so a ref read off a filtered snapshot is the same ref the full snapshot would give and works with every interaction tool. A filtered reply adds totalNodeCount and echoes filter; children lists kept refs only.
semantic_snapshot()
semantic_snapshot(identifierPrefix: "nav.", fields: ["identifier", "selected"])
semantic_snapshot(subtreeOf: "panel.tabs")
Returns: {"snapshot_id": 3, "nodeCount": 1, "viewport": {...}, "nodes": [{"ref": "s_0", "label": "Increment", "actions": ["tap"], "bounds": {...}, "visibleInViewport": true, "centerInViewport": true, "center": {...}}]} — the viewport is stated once in the envelope, not on each node.
subtree_root_not_found—subtreeOfis neither a ref of the latest snapshot nor an identifier in the tree; no snapshot was taken, refs andsnapshot_idare unchanged.unknown_field— a name infieldsis not a node key;acceptedFieldslists them. Over MCP and the CLI the name is refused at the boundary instead — an invalidfieldsargument naming the accepted keys, without spending a call on the app.vm_service_unavailable— app not running orMCPToolkitBinding.initialize()not called.connection_selection_required— multiple targets; supplyconnection.targetId.
inspect_widget_at_point
Identify the deepest widget and render node at a global logical coordinate.
x(integer, required) — global logical X coordinate.y(integer, required) — global logical Y coordinate.viewId(integer, optional) — FlutterView ID for multi-view apps.connection(object, optional) — connection override.
inspect_widget_at_point(x: 200, y: 400)
Returns: {"widget": {"type": "ElevatedButton", "rect": {"left": 180, "top": 380, "right": 280, "bottom": 420}}}
vm_service_unavailable— app not reachable.invalid_argument— coordinates out of view bounds.
capture_ui_snapshot
Capture screenshots, view details, and app errors in one bundled response.
errorsCount(integer, optional, default: 4) — errors to include.compress(boolean, optional, default: true) — compress screenshots.includeViewDetails(boolean, optional, default: true) — include view data.includeErrors(boolean, optional, default: true) — include app errors.screenshotMode(string, optional, default:auto) —auto,flutter_layer, ordesktop_window.permissionPolicy(string, optional, default:check_only) —check_only,auto_request_once, orrequest_always.connection(object, optional) — connection override.
capture_ui_snapshot(errorsCount: 2, includeViewDetails: false)
Returns: a TextContent JSON block with screenshots, viewDetails, and errors keys. When screenshots.images contains inline base64, each payload is lifted into a sibling image block and the JSON reports screenshots.imagesDeliveredAs: "image_blocks"; the emptied images list is then the normal shape, not a failed capture. Captures delivered through screenshots.fileUrls stay in the JSON and produce no image blocks.
vm_service_unavailable— app not running.permission_denied— retry withpermissionPolicy: "auto_request_once".
connect_debug_app
Explicitly select and connect to a Flutter debug VM target. Use when multiple apps are running or to pin a specific target for the session.
connection(object, optional) — passconnection.targetIdwith a WebSocket URI fromdiscover_debug_apps.
connect_debug_app(connection: {targetId: "ws://127.0.0.1:8181/<token>/ws"})
Returns: {"connected": true, "targetId": "ws://127.0.0.1:8181/<token>/ws", "isolates": [...]}
target_not_found— URI doesn't match a running app; re-rundiscover_debug_appsfor the exact URI.connection_failed— VM refused connection; verify the app is still running in debug mode.