# Dusk MCP Tool Reference Per-tool input schema, return shape, and example payload for every `dusk_*` MCP tool contributed by `DuskArtisanProvider`. 31 tools total: 28 dispatch through `ext.dusk.*` VM Service extensions and 3 (`dusk_hot_reload_and_snap`, `dusk_resize_viewport`, `dusk_device_profile`) route through the `artisan:dusk:*` substrate path to a CLI command because the orchestration cannot run inside the target isolate. Sections are ordered alphabetically. Every section names the dispatch surface (`extensionMethod`) at the top so the consumer knows which path the server takes. All example payloads show the `params.arguments` object inside the `tools/call` JSON-RPC request; the substrate MCP server wraps the response as `CallToolResult` text content. ## Table of contents - [`dusk_blur`](#dusk_blur) - [`dusk_clear`](#dusk_clear) - [`dusk_close_app`](#dusk_close_app) - [`dusk_console`](#dusk_console) - [`dusk_dblclick`](#dusk_dblclick) - [`dusk_device_profile`](#dusk_device_profile) - [`dusk_dismiss_modals`](#dusk_dismiss_modals) - [`dusk_drag`](#dusk_drag) - [`dusk_evaluate`](#dusk_evaluate) - [`dusk_exceptions`](#dusk_exceptions) - [`dusk_find`](#dusk_find) - [`dusk_focus`](#dusk_focus) - [`dusk_get_routes`](#dusk_get_routes) - [`dusk_hot_reload_and_snap`](#dusk_hot_reload_and_snap) - [`dusk_hover`](#dusk_hover) - [`dusk_navigate`](#dusk_navigate) - [`dusk_navigate_back`](#dusk_navigate_back) - [`dusk_observe`](#dusk_observe) - [`dusk_press_key`](#dusk_press_key) - [`dusk_resize_viewport`](#dusk_resize_viewport) - [`dusk_right_click`](#dusk_right_click) - [`dusk_screenshot`](#dusk_screenshot) - [`dusk_scroll`](#dusk_scroll) - [`dusk_select_option`](#dusk_select_option) - [`dusk_set_checkbox`](#dusk_set_checkbox) - [`dusk_snap`](#dusk_snap) - [`dusk_tap`](#dusk_tap) - [`dusk_triple_click`](#dusk_triple_click) - [`dusk_type`](#dusk_type) - [`dusk_wait_for`](#dusk_wait_for) - [`dusk_wait_for_network_idle`](#dusk_wait_for_network_idle) --- ## dusk_blur Dispatch: `ext.dusk.blur` Clear keyboard focus from whatever currently holds it (Playwright `locator.blur()` / `document.activeElement.blur()` parity). ### Input schema | Parameter | Type | Required | Description | |---|---|---|---| | `includeSnapshot` | boolean | no | Embed the post-blur snapshot in the response. Default `false`. | ### Returns Success: `{ blurred: true, hadFocus: bool }`. `hadFocus` is `false` when no node held focus at call time (still treated as success, idempotent). Error: returned via MCP `isError: true` when the focus-tree walk fails internally. ### Example call ```json { "name": "dusk_blur", "arguments": { "includeSnapshot": false } } ``` Response: ```json { "blurred": true, "hadFocus": true } ``` --- ## dusk_clear Dispatch: `ext.dusk.clear` Empty the `TextEditingController` backing the resolved text field (Playwright `locator.clear()` parity). ### Input schema | Parameter | Type | Required | Description | |---|---|---|---| | `ref` | string | yes | Widget ref of a `TextField` / `TextFormField` / `EditableText` (e.g. `"e5"`). | | `includeSnapshot` | boolean | no | Embed the post-clear snapshot. Default `false`. | ### Returns Success: `{ ref: "e", text: "" }`. Error: `DuskActionabilityException` (when the gate fails) or `DuskStaleHandleException` (when the ref is unknown / stale) surfaced as the wire error string `"Widget ref= is not actionable: "`. ### Example call ```json { "name": "dusk_clear", "arguments": { "ref": "e5" } } ``` Response: ```json { "ref": "e5", "text": "" } ``` --- ## dusk_close_app Dispatch: `ext.dusk.close_app` Request a graceful shutdown of the running Flutter app via `SystemNavigator.pop()`. On mobile + desktop this terminates the app; on web the call is a no-op (browsers do not allow programmatic tab close). ### Input schema No parameters. ### Returns Success: an empty object `{}`. After the call the VM Service URI is gone, so the next `dusk_*` tool returns a VM-Service-unreachable error. ### Example call ```json { "name": "dusk_close_app", "arguments": {} } ``` --- ## dusk_console Dispatch: `ext.dusk.console` Read recent log entries from the running app's telescope store. Missing-telescope graceful: returns an empty list when the host has not wired telescope. ### Input schema | Parameter | Type | Required | Description | |---|---|---|---| | `limit` | integer | no | Maximum number of entries to return. Default `50`. | | `minLevel` | string | no | Minimum severity level (`INFO`, `WARNING`, `ERROR`). Omit for all levels. | ### Returns Success: `{ entries: [ { level, message, time, logger }, ... ] }`. Error: never; missing telescope is treated as the empty-entries success path. ### Example call ```json { "name": "dusk_console", "arguments": { "limit": 10, "minLevel": "ERROR" } } ``` --- ## dusk_dblclick Dispatch: `ext.dusk.dblclick` Double-click a widget by ref. Synthesizes two pointer Down+50ms+Up sequences at the widget's center with ~100ms between them (Playwright double-click model). Triggers `GestureDetector.onDoubleTap`. ### Input schema | Parameter | Type | Required | Description | |---|---|---|---| | `ref` | string | yes | Widget ref from a prior `dusk_snap`. Shape `e` or `q`. | ### Returns Success: `{ ref: "e" }`. The actionability gate runs once before the first tap; the post-action snapshot is captured after the second tap completes. Error: `"Widget ref= is not actionable: "` (gate failure) or stale-handle error when the ref is unknown. ### Example call ```json { "name": "dusk_dblclick", "arguments": { "ref": "e7" } } ``` --- ## dusk_device_profile Dispatch: `artisan:dusk:device` Emulate a named device profile (viewport + DPR + touch + user agent) via Chrome DevTools Protocol. Requires the substrate to have been started with `--cdp-port`. ### Input schema | Parameter | Type | Required | Description | |---|---|---|---| | `preset` | string | no | One of `iphone-x`, `iphone-13`, `iphone-15-pro`, `pixel-5`, `pixel-8`, `ipad-pro-12.9`, `desktop-1440`, `desktop-1920`. Omit when using `list` or `reset`. | | `list` | boolean | no | List all available presets. When `true`, `preset` + `reset` are ignored. Default `false`. | | `reset` | boolean | no | Clear all viewport overrides (metrics + touch + user agent). Default `false`. | ### Returns Success (`preset`): `{ applied: "", viewport: { width, height, dpr, mobile, touch } }`. Success (`list`): `{ presets: [ { name, width, height, dpr, mobile }, ... ] }`. Success (`reset`): `{ reset: true }`. Error: unknown preset name (the response suggests running with `list: true`); `cdpPort` not configured. ### Example call ```json { "name": "dusk_device_profile", "arguments": { "preset": "iphone-x" } } ``` --- ## dusk_dismiss_modals Dispatch: `ext.dusk.dismiss_modals` Pop every modal route (dialog, bottom sheet, popup) currently above the first persistent route. Idempotent. ### Input schema No parameters. ### Returns Success: `{ popped: }`: the number of modals that were popped. `0` when no modals were open. Error: never; safe to call speculatively. ### Example call ```json { "name": "dusk_dismiss_modals", "arguments": {} } ``` --- ## dusk_drag Dispatch: `ext.dusk.drag` Drag from one widget to another by ref tokens. Synthesizes pointer Down + 5x intermediate Move events + Up sequence from `startRef`'s center to `endRef`'s center. ### Input schema | Parameter | Type | Required | Description | |---|---|---|---| | `startRef` | string | yes | Source widget ref (`e`). | | `endRef` | string | yes | Target widget ref (`e`). | ### Returns Success: `{ startRef, endRef }`. Both refs are echoed for caller bookkeeping. Error: actionability gate failure on either ref, or stale-handle on either. ### Example call ```json { "name": "dusk_drag", "arguments": { "startRef": "e12", "endRef": "e18" } } ``` --- ## dusk_evaluate Dispatch: `ext.dusk.evaluate` Evaluate a Dart expression in the running app isolate via the Tinker bridge (`ext.tinker.evaluate`). MCP-only: `magic_tinker` owns the connected REPL surface. ### Input schema | Parameter | Type | Required | Description | |---|---|---|---| | `expression` | string | yes | Single Dart expression. No statements, no trailing semicolon. | ### Returns Success: `{ result: "" }`. Error: returns an MCP error when the Tinker plugin is not installed; never crashes the app. ### Example call ```json { "name": "dusk_evaluate", "arguments": { "expression": "Auth.user?.email" } } ``` Response: ```json { "result": "user@example.com" } ``` --- ## dusk_exceptions Dispatch: `ext.dusk.exceptions` Read recent exception entries from the telescope exception watcher. Missing-telescope graceful: returns an empty list when telescope is not wired. ### Input schema | Parameter | Type | Required | Description | |---|---|---|---| | `limit` | integer | no | Maximum number of entries to return. Default `20`. | ### Returns Success: `{ entries: [ { type, message, stackHead, time }, ... ] }`. `stackHead` is the first 3 lines of the stack trace. ### Example call ```json { "name": "dusk_exceptions", "arguments": { "limit": 5 } } ``` --- ## dusk_find Dispatch: `ext.dusk.find` Find a widget by semantic query (text / semanticsLabel / key) and return a re-resolvable `q` handle. Unlike snapshot-frozen `e` refs, `q` re-executes the tree walk on every subsequent action call, so the handle survives widget rebuilds as long as the predicates still match. ### Input schema | Parameter | Type | Required | Description | |---|---|---|---| | `text` | string | no | Exact match against accessibility label first, then `Text.data`. | | `contains` | string | no | Substring match against accessibility label first, then `Text.data` (case-sensitive). Use when the visible label is dynamic (counters, timestamps, plurals). | | `semanticsLabel` | string | no | Exact match against `SemanticsNode.label` only (no Text fallback). | | `key` | string | no | Match against a widget `Key`. For `ValueKey`, pass the inner value's `toString()`. | At least one of the four must be supplied. When multiple are passed they form an intersection. ### Returns Success on first match: `{ ref: "q", matched: true }`. No match: `{ ref: null, matched: false }`. Error: surfaced only when a follow-up action call finds zero live matches against the handle (`"stale handle"`); the agent must re-find or re-snap, never silently retry. ### Example call ```json { "name": "dusk_find", "arguments": { "text": "Submit" } } ``` --- ## dusk_focus Dispatch: `ext.dusk.focus` Request keyboard focus on the widget identified by `ref` (Playwright `locator.focus()` parity). Walks to the nearest `Focus` ancestor and calls `requestFocus()`. ### Input schema | Parameter | Type | Required | Description | |---|---|---|---| | `ref` | string | yes | Widget ref (e.g. `"e5"`). | | `includeSnapshot` | boolean | no | Embed the post-focus snapshot. Default `false`. | ### Returns Success: `{ ref: "", focused: true }`. ### Example call ```json { "name": "dusk_focus", "arguments": { "ref": "e5" } } ``` --- ## dusk_get_routes Dispatch: `ext.dusk.get_routes` List the route paths declared by the running app's `MagicRouter`. Returns an empty list when no Magic router is installed. ### Input schema No parameters. ### Returns Success: `{ routes: [ { path, name }, ... ] }`. Parameterised paths render with `:id`-style placeholders. ### Example call ```json { "name": "dusk_get_routes", "arguments": {} } ``` Response: ```json { "routes": [ { "path": "/monitors", "name": "monitors.index" }, { "path": "/monitors/:id", "name": "monitors.show" } ] } ``` --- ## dusk_hot_reload_and_snap Dispatch: `artisan:dusk:hot_reload_and_snap` Hot reload the running Flutter app, then capture a snapshot, screenshot, and recent exceptions in one round-trip. Routes through the CLI command because an in-isolate handler cannot reload itself. ### Input schema | Parameter | Type | Required | Description | |---|---|---|---| | `screenshot` | boolean | no | Capture a screenshot after the reload. Default `true`. | ### Returns Success: `{ reloaded: true, durationMs: , snapshot: "", screenshot: "", recentExceptions: [...] }`. Compile error: `{ reloaded: false, durationMs: , error: "", recentExceptions: [...] }`. `snapshot` + `screenshot` are omitted on compile error. ### Example call ```json { "name": "dusk_hot_reload_and_snap", "arguments": { "screenshot": false } } ``` --- ## dusk_hover Dispatch: `ext.dusk.hover` Hover a mouse cursor over a widget by ref. Mouse-only (no touch equivalent). Synthesizes a `PointerHoverEvent` of `PointerDeviceKind.mouse` at the widget's center. ### Input schema | Parameter | Type | Required | Description | |---|---|---|---| | `ref` | string | yes | Widget ref (`e`). | ### Returns Success: `{ ref: "" }`. No-op on touch-only devices. Error: actionability gate failure, or stale-handle. ### Example call ```json { "name": "dusk_hover", "arguments": { "ref": "e8" } } ``` --- ## dusk_navigate Dispatch: `ext.dusk.navigate` Navigate the running Flutter app to a route path. Resolves through `MagicRoute.to(...)` when Magic is installed, falling back to `Navigator.of(root).pushNamed(...)`. ### Input schema | Parameter | Type | Required | Description | |---|---|---|---| | `route` | string | yes | Route path. Must start with `/`. Example: `/monitors/123`. | ### Returns Success: `{ route: "" }`. ALWAYS re-snap after; refs from a prior snapshot are invalidated. ### Example call ```json { "name": "dusk_navigate", "arguments": { "route": "/login" } } ``` --- ## dusk_navigate_back Dispatch: `ext.dusk.navigate_back` Pop the top route off the active navigator stack. Equivalent to pressing the system Back button. No-op when the stack has only one route. ### Input schema No parameters. ### Returns Success: `{ popped: bool }`. `false` when the stack already had only one route. ### Example call ```json { "name": "dusk_navigate_back", "arguments": {} } ``` --- ## dusk_observe Dispatch: `ext.dusk.observe` Return a structured candidate list of every interactive widget on screen. Implements Stagehand's observe-once-act-many pattern (no server-side LLM). Each candidate carries a re-resolvable `q` ref. ### Input schema | Parameter | Type | Required | Description | |---|---|---|---| | `intent` | string | no | Free-form caller hint (e.g. `"login form"`). Echoed in audit logs, NOT used server-side. | | `roles` | string | no | Comma-separated role filter (`button,textbox,link,checkbox,heading,image`). Omit for every role. | | `limit` | integer | no | Maximum number of candidates. Default `50`. | | `includeEnrichers` | string | no | `"true"` (default subset), `"false"` (none), or `"full"` (every field). | ### Returns Success: `{ candidates: [ { ref, role, label, value, bounds, isEnabled, isVisible, enrichers: { ... } }, ... ] }`. The enricher subset projects `magicFormField`, `magicRoute`, `magicGateResult`, `wind.breakpoint`, `wind.states` by default. ### Example call ```json { "name": "dusk_observe", "arguments": { "intent": "login form", "roles": "textbox,button", "limit": 20 } } ``` --- ## dusk_press_key Dispatch: `ext.dusk.press_key` Press a hardware key (optionally with modifiers). Synthesizes `KeyDownEvent` + `KeyUpEvent` through `ServicesBinding.instance.keyboard.handleKeyEvent`. ### Input schema | Parameter | Type | Required | Description | |---|---|---|---| | `key` | string | yes | Logical key label (e.g. `Enter`, `Escape`, `Tab`, `ArrowDown`, `S`). | | `modifiers` | array | no | Subset of `control`, `shift`, `alt`, `meta` held during the press. | ### Returns Success: `{ key: "