# 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 MCP `tools/call` name. That's it for 90% of clients. - The CLI catalog (`flutter-mcp-toolkit exec --name `) is **unchanged** for intrinsic tools (`tap_widget`, `get_vm`, …): bare catalog names still map to `fmt_*` MCP tools. Dynamic-registry commands use their full `fmt_*` 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](https://github.com/Arenukvern/mcp_flutter/blob/main/mcp_server_dart/README.md) (Cline / Cursor / Claude sections). > **`flutter-inspector`** above means only the **legacy `mcpServers` object key**. The Claude Code subagent definition in this repo is **`flutter-mcp-toolkit-runtime`** (`plugin/agents/flutter-mcp-toolkit-runtime.md`), not `flutter-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`](https://github.com/Arenukvern/mcp_flutter/blob/main/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://…`** (exact `app.debugPort.wsUri` from `flutter run --machine`), **or** global **`--vm-service-uri`** when `--target` is omitted. If both differ, **`--target` wins** (warning on stderr). - If host **`desktop_window`** screenshot fails, the CLI **retries once** with **`flutter_layer`**. Inspect **`data.summary.captureFallbackUsed`** in the JSON result. Details: [mcp_server_dart README — validate-runtime](https://github.com/Arenukvern/mcp_flutter/blob/main/mcp_server_dart/README.md). ## What changed ### 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 `_` 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`](https://github.com/Arenukvern/mcp_flutter/blob/main/tool/contracts/expected_tool_surface.txt) and pinned by [`mcp_server_dart/test/tool_surface_snapshot_test.dart`](https://github.com/Arenukvern/mcp_flutter/blob/main/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_failed` - `live_edit_proposal_not_found` - `live_edit_apply_failed` - `live_edit_validation_failed` - `live_edit_disabled` If your client surfaced these to users, drop the corresponding branches. Re-integration is tracked in [`todo/selection_state_machine.md`](https://github.com/Arenukvern/mcp_flutter/blob/main/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 `connection` override object, the same arg names, the same defaults. - **Error envelope.** Still `{code, message, details, descriptor, recovery}`. `error.descriptor` is still the machine-readable shape. - **Resource URIs.** `visual://localhost/app/errors/{count}`, `visual://localhost/view/details`, `visual://localhost/view/screenshots` are 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-runtime` CLI** — still the one-command smoke test; current CLI also accepts global **`--vm-service-uri`** when `--target` is omitted and **retries `flutter_layer`** after a failed host `desktop_window` capture ([above](#validate-runtime-and-vm-targeting-cli)). - **Dynamic-registry host tools.** These use the same `fmt_*` names on MCP and in `exec --name`: `fmt_list_client_tools_and_resources`, `fmt_client_tool`, `fmt_client_resource`. - **VM service extension names.** `ext.mcp.toolkit.app_errors` and friends are still emitted by `mcp_toolkit` exactly as before. ## Smallest diff for a v2 client ```diff 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: ```ts // 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`](https://github.com/Arenukvern/mcp_flutter/blob/main/tool/contracts/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`](https://github.com/Arenukvern/mcp_flutter/blob/main/ARCHITECTURE.md) "MCP Server Layer (Dart-based)" and the kernel sources at [`packages/server_capability_kernel/`](https://github.com/Arenukvern/mcp_flutter/tree/main/packages/server_capability_kernel) - [`packages/server_capability_core/`](https://github.com/Arenukvern/mcp_flutter/tree/main/packages/server_capability_core) for the full picture. ## Troubleshooting ### `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](#binaries-and-mcpservers-registry-keys)). 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`](https://github.com/Arenukvern/mcp_flutter/blob/main/tool/contracts/expected_tool_surface.txt). The repo's own snapshot test enforces this exact contract.