Migrating from v2.x to v3.0
v3.0 is the capability-kernel cut. The most visible change for users is that every MCP tool now surfaces under a capability prefix. This guide covers what changed, what stayed, and the smallest possible diff to get a v2 client running again.
TL;DR
- Add
fmt_to the front of every MCPtools/callname. That's it for 90% of clients. - The CLI catalog (
flutter-mcp-toolkit exec --name <name>) is unchanged for intrinsic tools (tap_widget,get_vm, …): bare catalog names still map tofmt_*MCP tools. Dynamic-registry commands use their fullfmt_*spelling in both CLI and MCP (fmt_list_client_tools_and_resources,fmt_client_tool,fmt_client_resource). - Resource URIs (
visual://localhost/...) are unchanged.
If you see tool_not_found, prepend fmt_ to the tool name.
Config quick reference: use MCP server binary flutter-mcp-toolkit-server, registry key flutter-mcp-toolkit under mcpServers (legacy flutter-inspector still works). Copy-paste examples: mcp_server_dart README (Cline / Cursor / Claude sections).
flutter-inspectorabove means only the legacymcpServersobject key. The Claude Code subagent definition in this repo isflutter-mcp-toolkit-runtime(plugin/agents/flutter-mcp-toolkit-runtime.md), notflutter-inspector.
Binaries and mcpServers registry keys
| Surface | v2.x (legacy) | v3.0 (current) |
|---|---|---|
| CLI binary | flutter_mcp_cli | flutter-mcp-toolkit |
| MCP server binary | flutter_inspector_mcp | flutter-mcp-toolkit-server |
JSON key in mcpServers | often flutter-inspector | flutter-mcp-toolkit (canonical); flutter-inspector still accepted |
After make build, binaries live under mcp_server_dart/build/. The canonical plugin mcp.json shows recommended args (--resources, --images, --dynamics) and ${FLUTTER_MCP_BIN:-flutter-mcp-toolkit-server} for the server command.
validate-runtime and VM targeting (CLI)
The smoke test is flutter-mcp-toolkit validate-runtime:
- Pass
--target ws://…(exactapp.debugPort.wsUrifromflutter run --machine), or global--vm-service-uriwhen--targetis omitted. If both differ,--targetwins (warning on stderr). - If host
desktop_windowscreenshot fails, the CLI retries once withflutter_layer. Inspectdata.summary.captureFallbackUsedin the JSON result.
MCP tool names are prefixed
The server now composes its tool surface from Capability instances loaded
into a host registry. Each capability registers its tools under a bare name
and the kernel applies the <capabilityId>_ prefix before publishing to
tools/list. Today there is one capability — fmt — so every tool is
prefixed fmt_.
| v2.x | v3.0 |
|---|---|
tap_widget | fmt_tap_widget |
enter_text | fmt_enter_text |
scroll | fmt_scroll |
long_press | fmt_long_press |
swipe | fmt_swipe |
drag | fmt_drag |
hover | fmt_hover |
press_key | fmt_press_key |
semantic_snapshot | fmt_semantic_snapshot |
wait_for | fmt_wait_for |
fill_form | fmt_fill_form |
navigate | fmt_navigate |
handle_dialog | fmt_handle_dialog |
connect_debug_app | fmt_connect_debug_app |
discover_debug_apps | fmt_discover_debug_apps |
get_vm | fmt_get_vm |
get_extension_rpcs | fmt_get_extension_rpcs |
hot_reload_flutter | fmt_hot_reload_flutter |
hot_restart_flutter | fmt_hot_restart_flutter |
hot_reload_and_capture | fmt_hot_reload_and_capture |
evaluate_dart_expression | fmt_evaluate_dart_expression |
get_recent_logs | fmt_get_recent_logs |
get_view_details | fmt_get_view_details |
get_app_errors | fmt_get_app_errors |
get_screenshots | fmt_get_screenshots |
capture_ui_snapshot | fmt_capture_ui_snapshot |
inspect_widget_at_point | fmt_inspect_widget_at_point |
debug_dump_layer_tree (--dumps) | fmt_debug_dump_layer_tree |
debug_dump_semantics_tree (--dumps) | fmt_debug_dump_semantics_tree |
debug_dump_render_tree (--dumps) | fmt_debug_dump_render_tree |
debug_dump_focus_tree (--dumps) | fmt_debug_dump_focus_tree |
listClientToolsAndResources | fmt_list_client_tools_and_resources |
runClientTool | fmt_client_tool |
runClientResource | fmt_client_resource |
The locked v3.0 surface is checked in at
tool/contracts/expected_tool_surface.txt
and pinned by
mcp_server_dart/test/tool_surface_snapshot_test.dart.
Any change to the surface fails CI, so what you see there is what
tools/list will return.
Live-edit was removed
The flutter_live_edit/ packages and the live-edit tool surface were
excised from v3.0. Five error codes that targeted live-edit are no longer
emitted:
live_edit_backend_failedlive_edit_proposal_not_foundlive_edit_apply_failedlive_edit_validation_failedlive_edit_disabled
If your client surfaced these to users, drop the corresponding branches.
Re-integration is tracked in
todo/selection_state_machine.md
for a future minor release.
What did NOT change
- Tool schemas. Every migrated tool kept its v2 input-schema shape
byte-for-byte. The same
connectionoverride object, the same arg names, the same defaults. - Error envelope. Still
{code, message, details, descriptor, recovery}.error.descriptoris still the machine-readable shape. - Resource URIs.
visual://localhost/app/errors/{count},visual://localhost/view/details,visual://localhost/view/screenshotsare unchanged. Resources are not subject to the capability prefix. - CLI commands.
flutter-mcp-toolkit exec --name tap_widget --args ...works exactly as in v2. The CLI uses the catalog vocabulary directly, not the MCP wire surface. validate-runtimeCLI — still the one-command smoke test; current CLI also accepts global--vm-service-uriwhen--targetis omitted and retriesflutter_layerafter a failed hostdesktop_windowcapture (above).- Dynamic-registry host tools. These use the same
fmt_*names on MCP and inexec --name:fmt_list_client_tools_and_resources,fmt_client_tool,fmt_client_resource. - VM service extension names.
ext.mcp.toolkit.app_errorsand friends are still emitted bymcp_toolkitexactly as before.
Smallest diff for a v2 client
const tap = await mcp.callTool({
- name: 'tap_widget',
+ name: 'fmt_tap_widget',
arguments: { ref, snapshotId, connection: { uri } },
});
If you wrap MCP tool calls in a helper, the cleanest fix is to prepend the prefix at the helper boundary so callers stay v2-style:
// helper.ts
const FMT_TOOLS = new Set([
'tap_widget', 'enter_text', 'scroll', /* ... */
]);
function mcpName(bare: string) {
return FMT_TOOLS.has(bare) ? `fmt_${bare}` : bare;
}
await mcp.callTool({ name: mcpName('tap_widget'), arguments: {...} });
(Source the list from
expected_tool_surface.txt
to stay in sync.)
Why this changed
v2 had every tool registered through a flat hand-written mixin. Adding a new vertical of functionality required touching the server's mixin, the shared command catalog, the schema helper, and the dispatch path — and there was no way to plug in third-party capabilities without editing the core repo.
v3 inverts that: a Capability is a thing that registers its tools/
resources through a CapabilityContext it gets handed at load time. The
host applies the prefix and brokers dart_mcp dispatch. New verticals
land as new capabilities; the prefix keeps their surfaces from colliding
with fmt_.
See
ARCHITECTURE.md
"MCP Server Layer (Dart-based)" and the kernel sources at
mcp_capability_kernel/
mcp_capability_core/for the full picture.
tool_not_found for a v2-named tool
You're still calling the unprefixed name. Add fmt_. v3.0 has no opt-out
fallback for the legacy surface — the --use-capability-kernel flag was
removed when the legacy path was deleted in T9.
Plugin / agent guides recommend v2 names
Update to the v3 names. The bundled plugin/skills/ tree tracks v3; if you have a fork or a vendored copy, refresh it.
MCP config still uses flutter-inspector or flutter_inspector_mcp
Rename the mcpServers entry to flutter-mcp-toolkit and set command to …/build/flutter-mcp-toolkit-server (see migration guide — Binaries). Keeping flutter-inspector as the key is still valid but legacy.
CI tests now break on snapshot diffs
If your CI ran tools/list against a pinned set of tool names, update the
fixture to match
expected_tool_surface.txt.
The repo's own snapshot test enforces this exact contract.