# 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 <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 `<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`](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
[`mcp_capability_kernel/`](https://github.com/Arenukvern/mcp_flutter/tree/main/mcp_capability_kernel)
+ [`mcp_capability_core/`](https://github.com/Arenukvern/mcp_flutter/tree/main/mcp_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.
