# Dusk MCP Overview
`fluttersdk_dusk` does not ship its own MCP server. It plugs into the substrate MCP server
hosted by [`fluttersdk_artisan`](https://fluttersdk.com/artisan/mcp/overview) by exporting an
`ArtisanServiceProvider` (`DuskArtisanProvider`) that contributes 31 MCP tool descriptors.
When the consumer registers the provider in `bin/artisan.dart` (or via the auto-discovered
`lib/app/_plugins.g.dart` barrel), the substrate MCP server picks up the dusk tools at
`initialize` time and surfaces them alongside its own 10 substrate tools, so the AI client
sees a single unified catalog.
- [Substrate MCP server, dusk tool descriptors](#substrate-and-descriptors)
- [The 31 dusk tools](#tool-catalog)
- [Dispatch surfaces: `ext.dusk.*` vs. `artisan:dusk:*`](#dispatch-surfaces)
- [Lifecycle: state file, lazy reconnect, snap-act loop](#lifecycle)
- [Related](#related)
---
## Substrate MCP server, dusk tool descriptors
The substrate MCP server lives inside `fluttersdk_artisan`. The binary `dart run
fluttersdk_artisan:mcp` speaks stdio JSON-RPC, reads `~/.artisan/state.json` to find the
running Flutter app's VM Service URI, and collects every registered provider's
`mcpTools()` list at boot. `DuskArtisanProvider.mcpTools()` returns 31 `McpToolDescriptor`
instances; the substrate server registers each as a regular MCP tool and dispatches calls
through the descriptor's declared `extensionMethod`. No additional server process is
launched for dusk; one MCP endpoint, one server, plugin-extensible.
This means every `.mcp.json` snippet that wires the substrate MCP server already gives the
AI client access to the dusk tools. There is no separate `fluttersdk_dusk:mcp` binary to
add, no second `cwd` to configure. See [setup.md](setup.md) for the per-client install
matrix.
---
## The 31 dusk tools
`DuskArtisanProvider.mcpTools()` returns the following 31 descriptors. The list is sorted
alphabetically; each link jumps to the per-tool entry in [tool-reference.md](tool-reference.md).
| Tool | Purpose |
|---|---|
| [`dusk_blur`](tool-reference.md#dusk_blur) | Clear keyboard focus from whatever currently holds it. |
| [`dusk_clear`](tool-reference.md#dusk_clear) | Empty the `TextEditingController` of a resolved text field. |
| [`dusk_close_app`](tool-reference.md#dusk_close_app) | Request a graceful shutdown via `SystemNavigator.pop()`. |
| [`dusk_console`](tool-reference.md#dusk_console) | Read recent log entries from the telescope store. |
| [`dusk_dblclick`](tool-reference.md#dusk_dblclick) | Double-click a widget by ref. |
| [`dusk_device_profile`](tool-reference.md#dusk_device_profile) | Emulate a named device profile via CDP. |
| [`dusk_dismiss_modals`](tool-reference.md#dusk_dismiss_modals) | Pop every modal route above the first persistent route. |
| [`dusk_drag`](tool-reference.md#dusk_drag) | Drag from one widget to another by ref. |
| [`dusk_evaluate`](tool-reference.md#dusk_evaluate) | Evaluate a Dart expression in the running isolate. |
| [`dusk_exceptions`](tool-reference.md#dusk_exceptions) | Read recent exceptions from the telescope store. |
| [`dusk_find`](tool-reference.md#dusk_find) | Mint a re-resolvable `q` handle by text / label / key. |
| [`dusk_focus`](tool-reference.md#dusk_focus) | Request keyboard focus on a widget by ref. |
| [`dusk_get_routes`](tool-reference.md#dusk_get_routes) | List route paths declared by the running router. |
| [`dusk_hot_reload_and_snap`](tool-reference.md#dusk_hot_reload_and_snap) | Hot reload, snap, screenshot, exceptions in one round-trip. |
| [`dusk_hover`](tool-reference.md#dusk_hover) | Hover a mouse cursor over a widget by ref. |
| [`dusk_navigate`](tool-reference.md#dusk_navigate) | Navigate to a route path. |
| [`dusk_navigate_back`](tool-reference.md#dusk_navigate_back) | Pop the top route off the navigator stack. |
| [`dusk_observe`](tool-reference.md#dusk_observe) | Structured candidate list of interactive widgets (Stagehand pattern). |
| [`dusk_press_key`](tool-reference.md#dusk_press_key) | Press a hardware key with optional modifiers. |
| [`dusk_resize_viewport`](tool-reference.md#dusk_resize_viewport) | Resize the web viewport via CDP. |
| [`dusk_right_click`](tool-reference.md#dusk_right_click) | Fire a right (secondary mouse) click. |
| [`dusk_screenshot`](tool-reference.md#dusk_screenshot) | Capture a screenshot of the running app. |
| [`dusk_scroll`](tool-reference.md#dusk_scroll) | Scroll a Scrollable widget by ref. |
| [`dusk_select_option`](tool-reference.md#dusk_select_option) | Select an option in a DropdownButton. |
| [`dusk_set_checkbox`](tool-reference.md#dusk_set_checkbox) | Read + conditionally toggle a Checkbox / Switch. |
| [`dusk_snap`](tool-reference.md#dusk_snap) | Capture a YAML Semantics snapshot with `e` refs. |
| [`dusk_tap`](tool-reference.md#dusk_tap) | Tap a widget by ref. |
| [`dusk_triple_click`](tool-reference.md#dusk_triple_click) | Fire three primary clicks (~100ms apart). |
| [`dusk_type`](tool-reference.md#dusk_type) | Type text into a TextField by ref. |
| [`dusk_wait_for`](tool-reference.md#dusk_wait_for) | Wait until a UI condition is satisfied. |
| [`dusk_wait_for_network_idle`](tool-reference.md#dusk_wait_for_network_idle) | Wait for zero in-flight HTTP requests. |
Tool names are frozen: the `dusk_` shape is part of the alpha-2 cross-repo contract.
Renames break agent prompts and pinned consumer scripts.
---
## Dispatch surfaces: `ext.dusk.*` vs. `artisan:dusk:*`
The substrate MCP server inspects each descriptor's `extensionMethod` field to choose a
dispatch path. Dusk uses two:
- **`ext.dusk.*` (28 tools).** The default path. The MCP server calls
`VmServiceClient.callServiceExtension(method, args)` against the running Flutter app's VM
Service. The handler runs inside the app isolate and returns a `ServiceExtensionResponse`.
This is the standard pattern; every action / inspection tool uses it.
- **`artisan:dusk:*` (3 tools).** Dispatched in-process by the MCP server via the artisan
registry, executing the matching CLI command (`dusk:hot_reload_and_snap`,
`dusk:resize`, `dusk:device`). These three tools cannot run inside the app isolate:
`dusk_hot_reload_and_snap` triggers `vm.reloadSources` against the very isolate that would
be servicing the call (deadlock), and `dusk_resize_viewport` /
`dusk_device_profile` drive Chrome DevTools Protocol on the host's Chromium subprocess.
The substrate routes them through the CLI command instead so the orchestration runs
outside the target isolate.
Both paths return MCP `CallToolResult` content; the agent sees no difference at the JSON-RPC
layer. The split exists purely so the same tool catalog can mix in-isolate VM Service calls
and out-of-isolate CLI drivers without a second binary.
---
## Lifecycle: state file, lazy reconnect, snap-act loop
Dusk inherits the substrate's lifecycle. The MCP server stays online even when no Flutter
app is running: `~/.artisan/state.json` may be absent at `initialize` time, and the server
still registers every tool descriptor. The first `tools/call` against a `dusk_*` tool
triggers a lazy reconnect: the MCP server reads `state.json`, opens a WebSocket to the VM
Service URI, and dispatches the call. Subsequent calls reuse the cached connection.
The canonical agent loop:
```
dusk_snap -> capture Semantics tree, read refs
dusk_find / read -> identify the target widget
dusk_tap / type / -> drive an action against the ref
scroll / drag
dusk_wait_for / -> bridge async UI transitions
wait_for_network_idle
dusk_snap -> observe the new state, loop
```
The `e` refs from `dusk_snap` are frozen at snap time; re-snap after any navigation,
modal open/close, or significant rebuild. `q` refs from `dusk_find` re-resolve on every
action call so they survive rebuilds as long as the predicates still match.
---
## Related
- [setup.md](setup.md): per-client install matrix (Claude Code, Cursor, Windsurf, VS Code,
etc.) plus the reconnect ritual after editing `.mcp.json` or `.artisan/mcp.json`.
- [tool-reference.md](tool-reference.md): per-tool input schema, return shape, and example
JSON-RPC payload for every dusk tool.
- [Substrate MCP overview](https://fluttersdk.com/artisan/mcp/overview): the underlying
artisan MCP server that hosts the dusk tools.