// AUTOGENERATED by tool/build_skill_assets.dart. Do not edit.
// Run `make sync-skills` from the repo root to regenerate.
// ignore_for_file: leading_newlines_in_multiline_strings, unnecessary_raw_strings

class SkillAsset {
  const SkillAsset({
    required this.id,
    required this.frontmatter,
    required this.body,
    required this.relativePath,
  });
  final String id;
  final String frontmatter;
  final String body;
  final String relativePath;
}

class SkillAssets {
  static const List<SkillAsset> skills = [
    SkillAsset(
      id: 'flutter-mcp-toolkit-guide',
      frontmatter: r'''name: flutter-mcp-toolkit-guide
description: Entry point for inspecting or driving a running Flutter app from your AI assistant — routes to the right task skill (inspect / control / debug / custom app surfaces) and runs preflight.''',
      body: r'''
<!-- @FMT_MODE_PRELUDE -->

## When to use

Use this skill when the user wants to inspect or drive a running Flutter app
from this conversation. Examples:
- "Tap the login button in my app"
- "Why is the home screen blank?"
- "Take a screenshot and tell me what's broken"
- "Expose my cart / flags / internal state to the agent via MCP"

If the user is asking about Flutter concepts unrelated to a running app
(architecture questions, package selection), this skill does not apply.

## Step 1: Preflight

Always run `flutter-mcp-toolkit doctor --json` first. Parse the output:

- `status: "ok"` — proceed to Step 2.
- `status: "error"` and `error.code: "binary_not_found"` — load
  `flutter-mcp-toolkit-setup` and follow its install instructions.
- `status: "error"` and `error.code: "vm_not_connected"` — load
  `flutter-mcp-toolkit-setup` and follow its troubleshooting section.
- Any other error — load `flutter-mcp-toolkit-debug` and read the error
  envelope playbook.

## Step 2: Pick the right skill for the user's intent

| User intent | Load skill |
|---|---|
| Read state ("what's on screen?", "show me errors", "screenshot") | `flutter-mcp-toolkit-inspect` |
| Drive UI ("tap X", "type into Y", "scroll to Z", "hot reload") | `flutter-mcp-toolkit-control` |
| Diagnose ("why is X failing?", "show recent logs", "evaluate expression") | `flutter-mcp-toolkit-debug` |
| Register app-specific MCP tools/resources (`MCPCallEntry`, `bootstrapFlutter` `additionalEntries`) | `flutter-mcp-toolkit-custom-tools` |

If the task spans more than one (e.g. "tap the button and show me what
changed"), load `inspect` AND `control`. Skills are additive.

## Step 3: Execute

Each task skill has the tool list, parameter shapes, and example calls. Follow
the prelude at the top of the skill — it tells you whether you're calling MCP
tools or shelling out to the CLI.

## Tool taxonomy reference

The core toolkit tools fall into these categories. The full list with
parameter shapes lives in the task skills.

- **Inspection (read-only):** `discover_debug_apps`, `get_app_errors`,
  `get_screenshots`, `get_view_details`, `get_vm`, `get_extension_rpcs`,
  `semantic_snapshot`, `inspect_widget_at_point`, `capture_ui_snapshot`,
  `connect_debug_app`. → `flutter-mcp-toolkit-inspect`.
- **Interaction (mutating):** `tap_widget`, `long_press`, `enter_text`,
  `fill_form`, `scroll`, `swipe`, `drag`, `hover`, `press_key`, `wait_for`,
  `navigate`, `handle_dialog`, `hot_reload_flutter`, `hot_restart_flutter`,
  `hot_reload_and_capture`. → `flutter-mcp-toolkit-control`.
- **Debug:** `get_recent_logs`, `evaluate_dart_expression`. →
  `flutter-mcp-toolkit-debug`.
- **Dynamic registry (app-defined):** after registration in the Flutter app,
  list with `list_client_tools_and_resources`, then `client_tool` /
  `client_resource` — wire names as **`fmt_*`** when calling MCP. →
  `flutter-mcp-toolkit-custom-tools`.

## When in doubt

If `doctor` is green but a tool call fails, read the returned `error.code`
and `error.recovery` fields. The full code → recovery table is in
`flutter-mcp-toolkit-debug`.
''',
      relativePath: 'skills/flutter-mcp-toolkit-guide/SKILL.md',
    ),
    SkillAsset(
      id: 'flutter-mcp-toolkit-setup',
      frontmatter: r'''name: flutter-mcp-toolkit-setup
description: Verify the flutter-mcp-toolkit install, run doctor preflight, troubleshoot connection issues. Use when the toolkit isn't responding or first-time setup.''',
      body: r'''
<!-- @FMT_MODE_PRELUDE -->

## When to use

Use this skill when:

- First-time install: `flutter-mcp-toolkit` is not yet on PATH.
- `doctor --json` returns any check with `"status": "fail"`.
- MCP server fails to connect or tools return `vm_not_connected` / `connect_failed`.
- Visual capture or toolkit-bridge commands are returning unexpected errors.

---

## Verify install

```bash
flutter-mcp-toolkit --version
```

Expected output: version string (e.g. `flutter-mcp-toolkit 3.0.0`).

If you get `command not found`, the binary is not on PATH:

```bash
# Binary is built to mcp_server_dart/build/ inside the repo
export PATH="$PATH:/path/to/mcp_flutter/mcp_server_dart/build"
# Or rebuild from source
cd /path/to/mcp_flutter && make build
```

Then verify with `flutter-mcp-toolkit --version`.

---

## Run doctor

Always run doctor before any VM-dependent command:

```bash
flutter-mcp-toolkit doctor --json
```

Flags: `--target <ws_uri>` (test a specific URI), global `--vm-service-uri <ws_uri>` (same as `--target` when omitted on `doctor`), `--timeout-ms <n>` (default: 2500).

```bash
# Global URI works for doctor (same as validate-runtime)
flutter-mcp-toolkit --vm-service-uri 'ws://127.0.0.1:8181/<token>/ws' doctor --json
```

Sample green output:

```json
{
  "summary": { "criticalFailures": 0 },
  "checks": [
    { "id": "vm_target_reachable", "status": "pass", "critical": true },
    { "id": "mcp_toolkit_extensions", "status": "pass", "critical": true },
    { "id": "dynamic_registry_available", "status": "pass", "critical": false }
  ]
}
```

**Triage:** `criticalFailures > 0` means VM/setup is blocked — not that every tool is broken. `dynamic_registry_available: pass` with `vm_target_reachable: fail` usually means a stale URI after hot restart; run `discover_debug_apps` and pass the new `targetId`.

Read `error.descriptor` (not top-level) for retry policy and exit codes. Each check includes `fix_command` — run it directly.

---

## Recover by error code

### `binary_not_found`

Binary missing or not on PATH. Rebuild and add to PATH:

```bash
cd /path/to/mcp_flutter && make build
export PATH="$PATH:/path/to/mcp_flutter/mcp_server_dart/build"
```

### `vm_not_connected`

Flutter app not running, stale token after restart, or URI not resolved:

```bash
flutter-mcp-toolkit exec --name discover_debug_apps --args '{}'
flutter-mcp-toolkit exec --name status --args '{}'
flutter-mcp-toolkit doctor --json --target ws://127.0.0.1:8181/<new-token>/ws
```

After a successful auto re-attach, `meta.recovery.reattachedTo` shows the new endpoint.

### `connect_failed`

Wrong port, app not started, or stale token. Pass explicit URI from `app.debugPort.wsUri`:

```bash
flutter-mcp-toolkit exec --name get_vm --args '{"connection":{"uri":"ws://127.0.0.1:8181/<token>/ws"}}'
```

### `connection_selection_required`

Multiple debug targets detected. List with `discover_debug_apps`, then pass the chosen URI from `details.availableTargets` explicitly to `get_vm`.

### `hot_reload_failed`

Dart compilation error or VM disconnected. Check errors, fix, then retry:

```bash
flutter-mcp-toolkit exec --name get_app_errors --args '{}'
```

### `visual_capture_unsupported`

macOS screen recording permission not granted or unsupported platform:

```bash
flutter-mcp-toolkit permissions request --kind visual_capture
```

---

## Connection issues (deeper troubleshooting)

**Port conflicts**: VM service defaults to 8181. Override if another process holds it:

```bash
flutter run --debug --host-vmservice-port=8182 -d macos
flutter-mcp-toolkit --dart-vm-port 8182 doctor --json
```

Use `flutter run --machine` and copy `app.debugPort.wsUri` when you need the exact websocket URI (recommended for `validate-runtime` and `exec`).

**Flutter app not in debug mode**: Release/profile builds don't expose the VM service. Always use `flutter run --debug`.

**`mcp_toolkit` not initialized**: Doctor's `mcp_toolkit_extensions` check will fail. Add before `runApp` — use `flutter-mcp-toolkit codegen-init` to generate the boilerplate (see CLI surface below). After adding, hot restart (not hot reload — binding init requires a full restart).

**Multiple apps / wrong target**: Pass `--target` with the exact websocket URI:

```bash
flutter-mcp-toolkit doctor --json --target ws://127.0.0.1:8181/<token>/ws
```

---

## CLI surface

The binary is `flutter-mcp-toolkit` (built to `mcp_server_dart/build/`).

| Subcommand                  | Purpose                                                 | Minimal example                                                                |
| --------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `exec`                      | Run a single named command against the VM               | `flutter-mcp-toolkit exec --name get_vm --args '{}'`                           |
| `batch`                     | Run multiple commands in one call                       | `flutter-mcp-toolkit batch --steps '[{"name":"get_vm"},{"name":"status"}]'`    |
| `schema`                    | Print the JSON schema for a named command               | `flutter-mcp-toolkit schema --name hot_reload_flutter`                         |
| `capabilities`              | List all registered capabilities                        | `flutter-mcp-toolkit capabilities`                                             |
| `serve`                     | Start the MCP server (stdio transport)                  | `flutter-mcp-toolkit serve`                                                    |
| `snapshot create`           | Capture and save a named snapshot                       | `flutter-mcp-toolkit snapshot create --name baseline --args '{}'`              |
| `snapshot diff`             | Diff two snapshots                                      | `flutter-mcp-toolkit snapshot diff --from baseline --to current`               |
| `bundle create`             | Package a snapshot into a publishable bundle            | `flutter-mcp-toolkit bundle create --from-snapshot baseline --output ./out`    |
| `doctor`                    | Run preflight checks (VM + toolkit + registry)          | `flutter-mcp-toolkit doctor --json`                                            |
| `permissions status`        | Check a permission (e.g. visual_capture)                | `flutter-mcp-toolkit permissions status --kind visual_capture`                 |
| `permissions request`       | Request a permission                                    | `flutter-mcp-toolkit permissions request --kind visual_capture`                |
| `permissions open-settings` | Open OS settings for a permission                       | `flutter-mcp-toolkit permissions open-settings --kind visual_capture`          |
| `validate-runtime`          | End-to-end VM + toolkit + capture smoke test            | `flutter-mcp-toolkit validate-runtime --target ws://127.0.0.1:8181/<token>/ws` |
| `init <agent>`              | Install skills + MCP server config for an AI agent      | `flutter-mcp-toolkit init claude-code`                                         |
| `codegen-init`              | Add toolkit dependency and emit `main.dart` boilerplate | `flutter-mcp-toolkit codegen-init`                                             |

Global flags (before the subcommand): `--dart-vm-port <n>`, `--dart-vm-host <host>`, `--vm-service-uri <ws_uri>`, `--log-level <level>`, `--dumps`, `-h/--help`.

**VM targeting:** Global `--vm-service-uri` applies to `doctor` and `validate-runtime` when subcommand `--target` is omitted. If both are set and differ, `--target` wins (stderr warning).

**`validate-runtime` screenshots:** the first capture uses `auto` (often `desktop_window` on macOS). If that step fails with a retryable `get_screenshots_failed`, the CLI retries once with `flutter_layer`. On success, `data.summary.captureFallbackUsed` is `true` in the JSON envelope.

---

### `init <agent>`

Install the flutter-mcp-toolkit skills + MCP server config for an AI agent.

Targets: `claude-code` | `cursor` | `codex` | `cline` | `agents-skills` | `all`.

```bash
flutter-mcp-toolkit init claude-code        # install for Claude Code (project-scoped)
flutter-mcp-toolkit init cursor --scope user # install user-globally for Cursor
flutter-mcp-toolkit init all --mode cli      # install for every detected agent in CLI mode
```

Mode auto-detects (MCP if registered, else CLI). Override with `--mode mcp|cli|auto`.

**Alternative (skills only, open ecosystem):** `npx skills add Arenukvern/mcp_flutter -a cursor -y` installs the same `SKILL.md` bundles via [skills.sh](https://skills.sh); it does not write `mcp.json` — run `init` afterward or configure `mcpServers` manually. See [AI agent overview](https://github.com/Arenukvern/mcp_flutter/blob/main/docs/ai_agents/overview.mdx).

---

### `codegen-init`

From a Flutter project root, add `flutter_mcp_toolkit` as a dependency and emit
the boilerplate snippet for `lib/main.dart`.

```bash
cd my-flutter-app
flutter-mcp-toolkit codegen-init             # runs `flutter pub add` + prints snippet
flutter-mcp-toolkit codegen-init --no-pub-add  # snippet only, skip pub add
```

---

## Reinstall / upgrade

The install script is idempotent — re-running it replaces the binary in place:

```bash
curl -fsSL https://raw.githubusercontent.com/Arenukvern/mcp_flutter/main/install.sh | bash
```

After reinstall, verify with `flutter-mcp-toolkit --version`.
''',
      relativePath: 'skills/flutter-mcp-toolkit-setup/SKILL.md',
    ),
    SkillAsset(
      id: 'flutter-mcp-toolkit-inspect',
      frontmatter: r'''name: flutter-mcp-toolkit-inspect
description: Read state from a running Flutter app — semantic snapshot, view details, errors, screenshots, VM info. Use when you need to understand what the app is showing.''',
      body: r'''
<!-- @FMT_MODE_PRELUDE -->

## When to use

Use this skill for read-only state inspection of a running Flutter app: what is shown on screen, recent errors, available debug targets, VM metadata, and widget tree details. Do not use for driving interaction — that is the interact skill. Start with `discover_debug_apps` when no connection target is established. Start with `semantic_snapshot` when you need to know which widgets are on screen.

## Recipes

### Fast inspect cycle (prefer `batch`)

```bash
flutter-mcp-toolkit batch --steps '[
  {"name":"semantic_snapshot"},
  {"name":"get_app_errors","args":{"count":5}},
  {"name":"get_screenshots","args":{"screenshotMode":"flutter_layer","compress":true}}
]'
```

Use `screenshotMode: flutter_layer` on macOS to avoid Screen Recording permission failures.

### Snapshot the visible UI

1. Call `semantic_snapshot()`.
2. Read `interactionSurface`: `flutter_widgets` (tap-by-ref works), `hybrid` (sparse semantics), `game_canvas` (use `evaluate_dart_expression` + screenshots).
3. Each interactive node has a stable `ref` (`s_0`, `s_1`, …) and the response includes a `snapshot_id`.
4. Pass refs to interaction tools; pass `snapshot_id` to detect staleness.

### After a code edit

Prefer `hot_reload_and_capture` over separate reload + snapshot + screenshot calls.

### Find an error by message

1. Call `get_app_errors(count: 10)`.
2. Inspect the `errors` array — each entry has message, stack trace, and timestamp.
3. Match on message text to find the source.

### List debug-mode apps

1. Call `discover_debug_apps()`.
2. Read the `targetId` (canonical WebSocket URI) for each active target.
3. Pass the chosen URI as `connection.targetId` on subsequent tool calls.

### Get widget at coordinates

1. Call `inspect_widget_at_point(x: 200, y: 400)`.
2. The response identifies the deepest widget and render node at those global logical pixel coordinates.

### Save a screenshot to a file

1. Call `get_screenshots()`.
2. If `meta.fileUrls` is non-empty, screenshots are on disk at those paths. Otherwise the response contains base64 `ImageContent` blocks — extract and write manually.
3. To force file output, configure an images output directory on the server before calling.

## Tool reference

### discover_debug_apps

List all active Flutter debug targets with canonical WebSocket URIs.

- `connection` (object, optional) — accepted by schema, ignored by executor; discovery is always local.

```
discover_debug_apps()
```

Returns: `{"targets": [{"targetId": "ws://127.0.0.1:8181/<token>/ws", "host": "...", "port": 8181}]}`

- `vm_service_unavailable` — no debug-mode Flutter process found.
- `tool_not_found` — binary predates v3.0.0; run `make build`.

### get_app_errors

Retrieve the most recent application errors from the Dart VM.

- `count` (integer, optional, default: 4) — number of errors to return.
- `connection` (object, optional) — connection override.

```
get_app_errors(count: 5)
```

Returns: `{"message": "2 errors found", "errors": [{"message": "...", "stack": "..."}]}`

- `vm_service_unavailable` — app not reachable.
- `connection_selection_required` — multiple targets; supply `connection.targetId`.

### get_screenshots

Capture screenshots of all views.

- `compress` (boolean, optional, default: true) — compress PNG output.
- `mode` (string, optional, default: `auto`) — `auto`, `flutter_layer`, or `desktop_window`.
- `permissionPolicy` (string, optional, default: `check_only`) — `check_only`, `auto_request_once`, or `request_always`.
- `connection` (object, optional) — connection override.

```
get_screenshots(mode: "flutter_layer", compress: false)
```

Returns: `ImageContent` blocks (base64 PNG) when no output dir configured, or `TextContent` URL refs + `meta.fileUrls` when file output is enabled.

- `permission_denied` — retry with `permissionPolicy: "auto_request_once"`.
- `vm_service_unavailable` — app not reachable.

### get_view_details

Get dimensions, device pixel ratio, and display ID for all views.

- `connection` (object, optional) — connection override.

```
get_view_details()
```

Returns: `{"views": [{"id": 0, "width": 1280, "height": 800, "devicePixelRatio": 2.0}]}`

- `vm_service_unavailable` — app not running.
- `connection_selection_required` — multiple targets; supply `connection.targetId`.

### get_vm

Return Dart VM metadata: version, isolates list, pid, and architecture.

- `connection` (object, optional) — connection override.

```
get_vm()
```

Returns: `{"type": "VM", "name": "vm", "version": "3.x.x", "isolates": [...]}`

- `vm_service_unavailable` — app not reachable.
- `connection_selection_required` — multiple targets active.

### get_extension_rpcs

List all registered VM service extension RPCs in the running app.

- `isolateId` (string, optional) — schema-declared but not read by executor; checks all isolates when omitted.
- `isRawResponse` (boolean, optional) — schema-declared but not read by executor.
- `connection` (object, optional) — connection override.

```
get_extension_rpcs()
```

Returns: `{"extensionRPCs": ["ext.flutter.inspector.getRootWidget", "ext.mcp.toolkit.semantic_snapshot"]}`

- `vm_service_unavailable` — app not running.
- `connection_selection_required` — multiple targets.

### semantic_snapshot

Return a compact accessibility tree of interactive widgets with stable `ref` strings and a `snapshot_id`.

- `connection` (object, optional) — connection override.

```
semantic_snapshot()
```

Returns: `{"snapshot_id": 3, "nodes": [{"ref": "s_0", "label": "Increment", "actions": ["tap"]}]}`

- `vm_service_unavailable` — app not running or `MCPToolkitBinding.initialize()` not called.
- `connection_selection_required` — multiple targets; supply `connection.targetId`.

### inspect_widget_at_point

Identify the deepest widget and render node at a global logical coordinate.

- `x` (integer, required) — global logical X coordinate.
- `y` (integer, required) — global logical Y coordinate.
- `viewId` (integer, optional) — FlutterView ID for multi-view apps.
- `connection` (object, optional) — connection override.

```
inspect_widget_at_point(x: 200, y: 400)
```

Returns: `{"widget": {"type": "ElevatedButton", "rect": {"left": 180, "top": 380, "right": 280, "bottom": 420}}}`

- `vm_service_unavailable` — app not reachable.
- `invalid_argument` — coordinates out of view bounds.

### capture_ui_snapshot

Capture screenshots, view details, and app errors in one bundled response.

- `errorsCount` (integer, optional, default: 4) — errors to include.
- `compress` (boolean, optional, default: true) — compress screenshots.
- `includeViewDetails` (boolean, optional, default: true) — include view data.
- `includeErrors` (boolean, optional, default: true) — include app errors.
- `screenshotMode` (string, optional, default: `auto`) — `auto`, `flutter_layer`, or `desktop_window`.
- `permissionPolicy` (string, optional, default: `check_only`) — `check_only`, `auto_request_once`, or `request_always`.
- `connection` (object, optional) — connection override.

```
capture_ui_snapshot(errorsCount: 2, includeViewDetails: false)
```

Returns: single `TextContent` JSON block with `screenshots`, `viewDetails`, and `errors` keys.

- `vm_service_unavailable` — app not running.
- `permission_denied` — retry with `permissionPolicy: "auto_request_once"`.

### connect_debug_app

Explicitly select and connect to a Flutter debug VM target. Use when multiple apps are running or to pin a specific target for the session.

- `connection` (object, optional) — pass `connection.targetId` with a WebSocket URI from `discover_debug_apps`.

```
connect_debug_app(connection: {targetId: "ws://127.0.0.1:8181/<token>/ws"})
```

Returns: `{"connected": true, "targetId": "ws://127.0.0.1:8181/<token>/ws", "isolates": [...]}`

- `target_not_found` — URI doesn't match a running app; re-run `discover_debug_apps` for the exact URI.
- `connection_failed` — VM refused connection; verify the app is still running in debug mode.
''',
      relativePath: 'skills/flutter-mcp-toolkit-inspect/SKILL.md',
    ),
    SkillAsset(
      id: 'flutter-mcp-toolkit-control',
      frontmatter: r'''name: flutter-mcp-toolkit-control
description: Drive a running Flutter app — tap, scroll, type, fill forms, hot-reload, navigate. Use when you need to interact with the UI.''',
      body: r'''
<!-- @FMT_MODE_PRELUDE -->

## When to use

Use this skill when you need to drive a running Flutter app as a user would:
- Tap buttons, icons, list items, or any interactive widget.
- Type text into fields, submit forms, clear inputs.
- Scroll or swipe to reveal off-screen content.
- Navigate between routes programmatically (push, pop, popUntil).
- Dismiss dialogs and bottom sheets.
- Press keyboard keys (Enter, Escape, Tab, arrows, ASCII chars).
- Hot-reload or hot-restart after editing Dart source files.
- Combine reload + screenshot + semantics in one round-trip for fast iteration.

## Selectors

Every interaction tool targets a widget by **ref** — a short string like `"s_0"` returned by `semantic_snapshot`. There is no by-text or by-type selector syntax on the tool itself. The workflow is: call `semantic_snapshot`, scan the returned nodes, find the right ref, then pass it.

Snapshot node fields to filter on:

| Want to find | Scan field | Example value |
|---|---|---|
| By visible label / text | `label` | `"Login"` |
| By value or hint | `value` / `hint` | `"user@example.com"` |
| By tooltip | `tooltip` | `"Close"` |
| By widget key | `key` | `"[<'submitBtn'>]"` |
| By semantic role / type | `flags` or `actions` | `["tap"]` |

Example — find the "Login" button ref:
```
semantic_snapshot()
→ nodes: [{ref:"s_0", label:"Login", actions:["tap"]}, ...]
tap_widget(ref: "s_0")
```

Pass `snapshotId` (from the snapshot response) to any interaction call. If the tree has changed, the call returns `stale_snapshot` with both IDs so you know to re-snapshot. Refs are only valid against the most recent snapshot.

## Recipes

### Tap a widget by text
```
semantic_snapshot()
→ find node where label == "Submit" → ref "s_3"
tap_widget(ref: "s_3", snapshotId: <id>)
```

### Fill a login form
```
semantic_snapshot() → email ref "s_1", password ref "s_2"
fill_form(fields: [{ref:"s_1", text:"user@example.com"}, {ref:"s_2", text:"secret"}], snapshotId: <id>)
→ one round-trip; stops on first failure
```

### Scroll to find an item
```
scroll(direction: "down", distance: 300)
semantic_snapshot() → item now visible → ref "s_5"
tap_widget(ref: "s_5")
```

### Wait for a widget to appear
```
wait_for(predicate: {kind: "text", text: "Welcome"}, timeoutMs: 8000)
→ returns fresh snapshot when text appears
tap_widget(ref: <ref from wait_for snapshot>)
```

### Navigate to a route
```
navigate(action: "push", route: "/settings", arguments: {tab: "account"})
semantic_snapshot() → fresh refs in the new screen
```

### Hot reload after a code change
```
hot_reload_and_capture()
→ screenshot + semantic snapshot + errors in one call
```

### Press the back hardware button

`press_key` has no `Back` key. Use `navigate(action: "pop")` for Navigator pop; `handle_dialog(action: "dismiss")` for dialogs; `press_key(key: "Escape")` on desktop.

```
navigate(action: "pop")
```

## Tool reference

### tap_widget
Tap a widget by ref. `ref` • string • required. `snapshotId` • integer • optional. `connection` • object • optional.
```json
{"name": "tap_widget", "arguments": {"ref": "s_3", "snapshotId": 7}}
```
Returns: `{"via": "semantic_action", "ref": "s_3"}` — Failures: `stale_snapshot`, `ref_not_found`

### long_press
Long-press a widget by ref. `ref` • string • required. `snapshotId` • integer • optional. `connection` • object • optional.
```json
{"name": "long_press", "arguments": {"ref": "s_2"}}
```
Returns: `{"via": "semantic_action"}` — Failures: `stale_snapshot`, `ref_not_found`

### enter_text
Enter text into a text field; taps to focus before typing. `ref` • string • required. `text` • string • required. `snapshotId` • integer • optional. `connection` • object • optional.
```json
{"name": "enter_text", "arguments": {"ref": "s_1", "text": "hello@example.com"}}
```
Returns: `{"via": "editable_state"}` — Failures: `stale_snapshot`, `ref_not_found`

### fill_form
Batch text entry: fills multiple fields in one call. Stops on first failure. `snapshotId` validated on first field only. `fields` • array of `{ref, text}` • required. `snapshotId` • integer • optional. `connection` • object • optional.
```json
{"name": "fill_form", "arguments": {"fields": [{"ref":"s_1","text":"user"},{"ref":"s_2","text":"pass"}], "snapshotId": 5}}
```
Returns: `{"filled": 2}` — Failures: `stale_snapshot`, `ref_not_found`

### scroll
Scroll to reveal content. `"down"` reveals content below (finger swipes up). `direction` • string • required (`up|down|left|right`). `ref` • string • optional (falls back to screen center). `distance` • number • optional • default 300. `snapshotId` • integer • optional. `connection` • object • optional.
```json
{"name": "scroll", "arguments": {"direction": "down", "ref": "s_0", "distance": 500}}
```
Returns: `{"via": "semantic_action"}` — Failures: `ref_not_found`, `stale_snapshot`

### swipe
High-velocity fling. Same direction model as `scroll`. Always Tier 2 pointer events. `direction` • string • required. `ref` • string • optional. `distance` • number • optional • default 300. `snapshotId` • integer • optional. `connection` • object • optional.
```json
{"name": "swipe", "arguments": {"direction": "left", "ref": "s_4"}}
```
Returns: `{"via": "pointer_events"}` — Failures: `ref_not_found`, `web_gesture_not_supported`

### drag
Drag from one widget to another. Always Tier 2. `fromRef` • string • required. `toRef` • string • required. `snapshotId` • integer • optional. `connection` • object • optional.
```json
{"name": "drag", "arguments": {"fromRef": "s_2", "toRef": "s_7"}}
```
Returns: `{"via": "pointer_events"}` — Failures: `ref_not_found`, `web_gesture_not_supported`

### hover
Synthesize a mouse hover. Desktop/web only — no hover concept on mobile. `ref` • string • required. `snapshotId` • integer • optional. `connection` • object • optional.
```json
{"name": "hover", "arguments": {"ref": "s_5"}}
```
Returns: `{"via": "pointer_events"}` — Failures: `ref_not_found`, platform error on mobile

### press_key
Synthesize key press (down+up). Accepted: `Enter Escape Tab Backspace Delete Space ArrowUp ArrowDown ArrowLeft ArrowRight` plus single ASCII (`a-z` `0-9`). `key` • string • required. `ctrl/shift/alt/meta` • boolean • optional • default false. `connection` • object • optional.
```json
{"name": "press_key", "arguments": {"key": "Enter"}}
```
Returns: `{"key": "Enter"}` — Failures: `unsupported_key`, `no_focus`

### wait_for
Wait for a UI predicate; returns fresh semantic snapshot. Predicates: `{kind:"text",text}` | `{kind:"noText",text}` | `{kind:"time",ms}` | `{kind:"stable",stableWindowMs}`. `predicate` • object • required. `timeoutMs` • integer • optional • default 5000 • max 30000. `connection` • object • optional.
```json
{"name": "wait_for", "arguments": {"predicate": {"kind": "text", "text": "Dashboard"}, "timeoutMs": 8000}}
```
Returns: fresh semantic snapshot — Failures: `timeout`, `invalid_predicate`

### navigate
Drive the registered Navigator. Requires `MCPToolkitBinding.instance.navigatorKey = key` in the app. `action` • string • required (`push|pop|popUntil`). `route` • string • required for push/popUntil. `arguments` • object • optional (for push). `connection` • object • optional.
```json
{"name": "navigate", "arguments": {"action": "push", "route": "/profile", "arguments": {"userId": "42"}}}
```
Returns: `{"action": "push", "route": "/profile"}` — Failures: `navigator_not_configured`, `route_not_found`

### handle_dialog
Dismiss the topmost popup/dialog route. Only `action: "dismiss"` supported. Requires `navigatorKey = key` on `MCPToolkitBinding.instance` in the app. `action` • string • required (must be `"dismiss"`). `connection` • object • optional.
```json
{"name": "handle_dialog", "arguments": {"action": "dismiss"}}
```
Returns: `{"dismissed": true}` — Failures: `navigator_not_configured`, `no_dialog`

### hot_reload_flutter
Hot reload the app. Preserves state. `force` • boolean • optional • default false (reload even without source changes). `connection` • object • optional.
```json
{"name": "hot_reload_flutter", "arguments": {}}
```
Returns: `"Hot reload completed"` + report JSON — Failures: `vm_not_connected`, `compilation_error`

### hot_restart_flutter
Full restart. App state not preserved. No required params. `connection` • object • optional.
```json
{"name": "hot_restart_flutter", "arguments": {}}
```
Returns: `{"report": {"type": "Success", "success": true}}` — Failures: `vm_not_connected`

### hot_reload_and_capture
Hot reload then capture screenshot + semantics + errors in one call. `compress` • boolean • default true. `includeSemantics` • boolean • default true. `includeErrors` • boolean • default true. `errorsCount` • integer • default 4. `connection` • object • optional.
```json
{"name": "hot_reload_and_capture", "arguments": {"includeErrors": true}}
```
Returns: screenshot (base64) + semantic snapshot + errors — Failures: `vm_not_connected`, `compilation_error`

## Patterns

### Always `wait_for` before `tap_widget` after navigation

After `navigate(action: "push")` the new route's widgets are not in the tree yet. Use `wait_for` with a text predicate to confirm the destination has rendered, then snapshot and act.

```
navigate(action: "push", route: "/checkout")
wait_for(predicate: {kind: "text", text: "Order Summary"}, timeoutMs: 5000)
semantic_snapshot() → tap target widgets
```

### Prefer `fill_form` over multiple `enter_text` calls

Each `enter_text` is a separate VM round-trip. `fill_form` sends all field/text pairs in one call; `snapshotId` is checked once (on the first field). For any form with 2+ fields, always prefer `fill_form`.

```
# Avoid: 2 round-trips
enter_text(ref: "s_1", text: "Alice")
enter_text(ref: "s_2", text: "secret")

# Prefer: 1 round-trip
fill_form(fields: [{ref: "s_1", text: "Alice"}, {ref: "s_2", text: "secret"}])
```

### After `hot_reload_*`, wait for the new tree before continuing

Hot reload completes asynchronously. Use `wait_for(predicate: {kind:"stable", stableWindowMs:300})` to confirm the tree has settled before calling `semantic_snapshot`. Or use `hot_reload_and_capture` which returns a post-reload snapshot directly.

```
hot_reload_flutter()
wait_for(predicate: {kind: "stable", stableWindowMs: 300})
semantic_snapshot() → interact with reloaded widgets

# Or in one call (preferred):
hot_reload_and_capture() → screenshot + semantics + errors already post-reload
```
''',
      relativePath: 'skills/flutter-mcp-toolkit-control/SKILL.md',
    ),
    SkillAsset(
      id: 'flutter-mcp-toolkit-debug',
      frontmatter: r'''name: flutter-mcp-toolkit-debug
description: Diagnose problems in a running Flutter app — read logs, evaluate Dart expressions, interpret error envelopes. Use when something broke.''',
      body: r'''
<!-- @FMT_MODE_PRELUDE -->

## When to use

Use this skill when something broke and you need to understand why:
- A tool call returned an error envelope (`ok: false`).
- The app behaves unexpectedly and you need runtime log output.
- You need to inspect live app state without changing it (read `AgentState.instance.value`).
- A prior `control` action (tap, hot_reload, navigate) completed without error but the result is wrong.

Do NOT use this skill for:
- Reading what is currently on screen — use `flutter-mcp-toolkit-inspect`.
- The toolkit itself failing to connect — load `flutter-mcp-toolkit-setup`.

## Triage flow

1. **Error envelope returned?** Read `error.code` first, then `error.descriptor.retryable`. Look the code up in the Error envelope playbook below.
2. **Retryable error?** Run `flutter-mcp-toolkit doctor --json`. If doctor fails, load `flutter-mcp-toolkit-setup`.
3. **Need log output?** Call `get_recent_logs` with `count: 100` and a level filter. Look for stack traces or assertion messages near the timestamp of the failure.
4. **Need live state?** Call `evaluate_dart_expression` with a targeted expression (e.g. `MyBloc.instance.state.toString()`). Do this after logs, not instead of them.
5. **Chaining with inspect?** Order: `semantic_snapshot` → `evaluate_dart_expression` → `get_recent_logs`. Snapshot gives you the current widget tree before expression evaluation mutates nothing; logs give trailing context.
6. **Multiple targets?** If `connection_selection_required`, call `discover_debug_apps`, pick the `targetId`, then pass `connection: {targetId: "..."}` to every subsequent call.

## Tool reference

### get_recent_logs

Retrieve recent `print()` and `debugPrint()` output from the running app's main isolate.

- `count` • integer • optional, default: 50 — number of log lines to return.
- `connection` • object • optional — connection override; required when multiple debug apps are running.

```
get_recent_logs(count: 100)
get_recent_logs(count: 50, connection: {targetId: "ws://127.0.0.1:8181/<token>/ws"})
```

Returns: `{"logs": ["[INFO] page loaded", "[ERROR] assertion failed: ..."], "count": 50}`

Read-only; no code executed. Returns only lines buffered since last app start or hot restart. On failure see `getRecentLogsFailed` in the playbook.

### evaluate_dart_expression

Evaluate a Dart expression in the running app's main isolate and return its string representation.

- `expression` • string • **required** — Dart expression (e.g. `"MyClass.instance.counter"`).
- `connection` • object • optional — connection override.

```
evaluate_dart_expression(expression: "Navigator.of(context).canPop()")
evaluate_dart_expression(expression: "AgentState.instance.value.toString()")
```

Returns: `{"result": "42"}` — always a string-serialized value. Executes arbitrary code in the live isolate — avoid side-effecting expressions. Debug mode only. On failure see `evaluateExpressionFailed` in the playbook.

## Connect / multi-app flows

When `discover_debug_apps` returns more than one entry (or any call returns `connection_selection_required`):

1. Call `discover_debug_apps()` — read `targets[*].targetId` for each running app.
2. Identify the target by port or hostname.
3. Call `connect_debug_app(connection: {targetId: "ws://127.0.0.1:<port>/<token>/ws"})` to pin the session.
4. Pass the same `connection` object to every subsequent tool call for the session.

Connection override pattern — pass `connection` on any call:
```
get_recent_logs(count: 50, connection: {targetId: "ws://127.0.0.1:8182/<token>/ws"})
evaluate_dart_expression(expression: "x.toString()", connection: {targetId: "ws://127.0.0.1:8182/<token>/ws"})
```

If the target changes (app restarted, port shifted), re-run `discover_debug_apps` to get the new `targetId`. Stale URIs return `connect_failed`.

## Error envelope playbook

Every failure returns `{code, message, details, descriptor, recovery}`. Always read `error.descriptor` (not the top-level envelope) for `retryable` and `exitCode`. Run `error.recovery.fix_command` directly when provided.

### `unexpectedExecutorError` (`unexpected_executor_error`)
**Means:** unhandled exception in the command executor.
**Causes:** bug in the server; unexpected nil; unrecoverable VM state.
**Recovery:**
1. `flutter-mcp-toolkit doctor --json`
2. If doctor passes, retry once; if it recurs, file a bug with `error.details`.

### `connectFailed` (`connect_failed`)
**Means:** connection to the VM Service WebSocket failed.
**Causes:** wrong port, stale token, app not running.
**Recovery:**
1. `flutter-mcp-toolkit exec --name get_vm --args '{"connection":{"uri":"ws://127.0.0.1:8181/<token>/ws"}}'`
2. Get the exact URI from `app.debugPort.wsUri` in Flutter output.

### `vmNotConnected` (`vm_not_connected`)
**Means:** a VM-dependent command was called before a connection was established.
**Recovery:**
1. `flutter-mcp-toolkit exec --name status --args '{}'`
2. Then `flutter-mcp-toolkit doctor --json`.

### `connectionSelectionRequired` (`connection_selection_required`)
**Means:** multiple debug targets exist; an explicit target is required.
**Causes:** more than one Flutter app running in debug mode simultaneously.
**Recovery:**
1. `flutter-mcp-toolkit exec --name discover_debug_apps --args '{}'`
2. Pick the correct `targetId` from `details.availableTargets`.
3. Retry the original call with `connection: {targetId: "<chosen_id>"}`.

### `discoverDebugAppsFailed` (`discover_debug_apps_failed`)
**Means:** discovery scan of local VM Service ports failed.
**Recovery:** `flutter-mcp-toolkit doctor --json`

### `getVmFailed` (`get_vm_failed`)
**Means:** `get_vm` RPC to the VM Service failed.
**Recovery:** `flutter-mcp-toolkit doctor --json`

### `getExtensionRpcsFailed` (`get_extension_rpcs_failed`)
**Means:** listing registered extension RPCs failed.
**Recovery:** `flutter-mcp-toolkit doctor --json`

### `hotReloadFailed` (`hot_reload_failed`)
**Means:** hot reload was rejected by the Dart compiler or VM.
**Causes:** compile error in changed files; isolate in bad state.
**Recovery:**
1. `flutter-mcp-toolkit exec --name get_app_errors --args '{}'`
2. Fix the compile error, then retry.

### `hotRestartFailed` (`hot_restart_failed`)
**Means:** full hot restart failed.
**Recovery:** `flutter-mcp-toolkit doctor --json` — if VM is unreachable, restart the app manually.

### `getActivePortsFailed` (`get_active_ports_failed`)
**Means:** scan for active debug ports failed.
**Recovery:** `flutter-mcp-toolkit doctor --json`

### `getAppErrorsFailed` (`get_app_errors_failed`)
**Means:** retrieving app errors from the toolkit bridge failed.
**Recovery:** `flutter-mcp-toolkit doctor --json`

### `getScreenshotsFailed` (`get_screenshots_failed`)
**Means:** screenshot capture failed (wrong mode, host window not available, Simulator window race, etc.).
**Recovery:**
1. `flutter-mcp-toolkit doctor --json` — check `visual_capture_permission_denied` separately.
2. For **MCP / `exec`**, retry `get_screenshots` or `capture_ui_snapshot` with `screenshotMode: flutter_layer` when `desktop_window` or host capture is flaky (macOS unfocused window, iOS Simulator).
3. For **`validate-runtime`**, a `flutter_layer` retry runs automatically after a failed host `desktop_window` attempt; read `data.summary.captureFallbackUsed` in the result envelope.

### `visualCapturePermissionDenied` (`visual_capture_permission_denied`)
**Means:** macOS Screen Recording permission is not granted.
**Recovery:**
1. `flutter-mcp-toolkit permissions request --kind visual_capture`
2. Or open System Settings → Privacy & Security → Screen Recording.

### `visualCaptureUnsupported` (`visual_capture_unsupported`)
**Means:** visual capture is not supported on this platform or capture mode. Not retryable.
**Recovery:** `flutter-mcp-toolkit permissions status && flutter-mcp-toolkit doctor --json`

### `getViewDetailsFailed` (`get_view_details_failed`)
**Means:** retrieving FlutterView dimensions failed.
**Recovery:** `flutter-mcp-toolkit doctor --json`

### `debugDumpFailed` (`debug_dump_failed`)
**Means:** a VM debug-dump RPC (render tree, semantics, layers) failed.
**Recovery:** `flutter-mcp-toolkit doctor --json`

### `dynamicRegistryDisabled` (`dynamic_registry_disabled`)
**Means:** a dynamic tool/resource call was made but the dynamic registry is disabled. Not retryable.
**Recovery:** `flutter-mcp-toolkit --dynamics exec --name status --args '{}'` — pass `--dynamics` flag to enable.

### `dynamicRegistryListFailed` (`dynamic_registry_list_failed`)
**Means:** listing dynamic tools/resources from the registry failed.
**Recovery:** `flutter-mcp-toolkit doctor --json`

### `missingToolName` (`missing_tool_name`)
**Means:** a dynamic tool call was made without providing a tool name. Not retryable.
**Recovery:** include `tool_name` parameter in the call; `flutter-mcp-toolkit schema --name fmt_client_tool`.

### `dynamicToolFailed` (`dynamic_tool_failed`)
**Means:** invocation of a dynamic tool failed.
**Recovery:** `flutter-mcp-toolkit doctor --json`

### `missingResourceUri` (`missing_resource_uri`)
**Means:** a dynamic resource read was called without a URI. Not retryable.
**Recovery:** include `uri` parameter; `flutter-mcp-toolkit schema --name fmt_client_resource`.

### `dynamicResourceFailed` (`dynamic_resource_failed`)
**Means:** reading a dynamic resource failed.
**Recovery:** `flutter-mcp-toolkit doctor --json`

### `sessionManagerNotConfigured` (`session_manager_not_configured`)
**Means:** a session command was called but no session manager is wired. Not retryable.
**Recovery:** `flutter-mcp-toolkit doctor --json` — server config issue; reload `flutter-mcp-toolkit-setup`.

### `sessionNotFound` (`session_not_found`)
**Means:** the requested session ID does not exist. Not retryable.
**Recovery:** list active sessions; start a new session before referencing it.

### `invalidCommand` (`invalid_command`)
**Means:** command name or argument schema is invalid. Not retryable.
**Recovery:** `flutter-mcp-toolkit schema --name <command_name>`

### `stateStoreReadFailed` (`state_store_read_failed`)
**Means:** reading from persistent state store failed.
**Recovery:** `flutter-mcp-toolkit doctor --json` — check filesystem permissions on state directory.

### `stateStoreWriteFailed` (`state_store_write_failed`)
**Means:** writing to persistent state store failed.
**Recovery:** `flutter-mcp-toolkit doctor --json` — check disk space and permissions.

### `stateLockTimeout` (`state_lock_timeout`)
**Means:** acquiring the state lock timed out (concurrent agent contention).
**Causes:** another agent or CLI call holds the lock; deadlock.
**Recovery:** wait and retry; if recurring, kill other agent processes holding the lock.

### `stateLockConflict` (`state_lock_conflict`)
**Means:** a conflicting state lock was detected.
**Causes:** parallel agents writing simultaneously.
**Recovery:** serialise calls; retry after the conflicting operation completes.

### `diagnoseFailed` (`diagnose_failed`)
**Means:** the composite `diagnose` command failed.
**Recovery:** `flutter-mcp-toolkit doctor --json`

### `explainErrorsFailed` (`explain_errors_failed`)
**Means:** the error-explanation command failed.
**Recovery:** `flutter-mcp-toolkit doctor --json`

### `unsupportedSummaryProvider` (`unsupported_summary_provider`)
**Means:** an unrecognised summary provider was requested. Not retryable.
**Recovery:** `flutter-mcp-toolkit schema --name diagnose` — check allowed `provider` values.

### `snapshotNotFound` (`snapshot_not_found`)
**Means:** the referenced snapshot ID does not exist. Not retryable.
**Recovery:** `flutter-mcp-toolkit snapshot create --name <snapshot_id> --args '{}'`

### `snapshotInvalid` (`snapshot_invalid`)
**Means:** snapshot payload is malformed or fails validation. Not retryable.
**Recovery:** recreate the snapshot; do not reuse corrupted files.

### `staleSnapshot` (`stale_snapshot`)
**Means:** the provided `snapshotId` no longer matches the current app state.
**Causes:** a hot reload or interaction changed the widget tree after the snapshot was taken.
**Recovery:**
1. `evaluate_dart_expression(expression: "true")` — verify app is reachable.
2. `semantic_snapshot()` — obtain a fresh snapshot ID, then retry the original call.

### `bundleBuildFailed` (`bundle_build_failed`)
**Means:** bundle creation or publish failed.
**Recovery:** `flutter-mcp-toolkit doctor --json` — check build artefacts and output path.

### `writeBlocked` (`write_blocked`)
**Means:** a write was blocked because `--no-overwrite` is set and the target already exists. Not retryable.
**Recovery:** retry without `--no-overwrite`, or choose a different `--output`/`--name`.

### `doctorCriticalFailed` (`doctor_critical_failed`)
**Means:** one or more critical doctor checks failed.
**Recovery:** `flutter-mcp-toolkit doctor --json` — read `checks[*]` where `status: "fail"` and `critical: true`; load `flutter-mcp-toolkit-setup`.

### `interactionFailed` (`interaction_failed`)
**Means:** a tap/scroll/swipe/drag/long_press/enter_text call failed.
**Causes:** stale `ref`; widget not visible or not interactive; toolkit bridge not initialized.
**Recovery:**
1. `semantic_snapshot()` — get fresh refs.
2. Retry with the new ref.

### `semanticSnapshotFailed` (`semantic_snapshot_failed`)
**Means:** `semantic_snapshot` execution failed.
**Recovery:** `flutter-mcp-toolkit doctor --json` — verify `MCPToolkitBinding.initialize()` is called.

### `evaluateExpressionFailed` (`evaluate_expression_failed`)
**Means:** `evaluate_dart_expression` execution failed.
**Causes:** expression syntax error; exception thrown at runtime; isolate not reachable.
**Recovery:** simplify the expression; check syntax; `flutter-mcp-toolkit doctor --json`.

### `getRecentLogsFailed` (`get_recent_logs_failed`)
**Means:** `get_recent_logs` retrieval failed.
**Recovery:** `flutter-mcp-toolkit doctor --json` — verify toolkit is initialized.

### `waitTimeout` (`wait_timeout`)
**Means:** `wait_for` predicate did not match before `timeoutMs` elapsed.
**Causes:** predicate condition never becomes true; app state does not change; `timeoutMs` too short.
**Recovery:**
1. `semantic_snapshot()` — verify the expected widget state.
2. Increase `timeoutMs` or adjust the predicate.

### `waitForFailed` (`wait_for_failed`)
**Means:** `wait_for` execution failed (malformed predicate or toolkit error).
**Recovery:** `flutter-mcp-toolkit schema --name wait_for`

### `pressKeyFailed` (`press_key_failed`)
**Means:** `press_key` execution failed.
**Recovery:** `flutter-mcp-toolkit schema --name press_key`

### `handleDialogFailed` (`handle_dialog_failed`)
**Means:** `handle_dialog` (dismiss/accept dialog) execution failed.
**Causes:** no dialog present; dialog already dismissed.
**Recovery:** `semantic_snapshot()` — verify a dialog is visible before calling.

### `navigateFailed` (`navigate_failed`)
**Means:** `navigate` push/pop/popUntil failed.
**Recovery:** `flutter-mcp-toolkit schema --name navigate`

### `navigatorNotRegistered` (`navigator_not_registered`)
**Means:** `navigate` was called but the app did not register a `GlobalKey<NavigatorState>`. Not retryable.
**Causes:** `MCPToolkitBinding.instance.navigatorKey` was never set in the host app.
**Recovery:** assign `MCPToolkitBinding.instance.navigatorKey = navigatorKey` in the app's `main.dart` and hot restart.

### `fillFormFailed` (`fill_form_failed`)
**Means:** `fill_form` orchestration failed (transport or per-field error).
**Recovery:** `flutter-mcp-toolkit schema --name fill_form`

### `hoverFailed` (`hover_failed`)
**Means:** `hover` execution failed.
**Recovery:** `flutter-mcp-toolkit doctor --json`

### `unknown` (`unknown_error`)
**Means:** fallback for any unrecognised error code.
**Recovery:** `flutter-mcp-toolkit doctor --json` — inspect `error.details` for raw cause.
''',
      relativePath: 'skills/flutter-mcp-toolkit-debug/SKILL.md',
    ),
    SkillAsset(
      id: 'flutter-mcp-toolkit-custom-tools',
      frontmatter: r'''name: flutter-mcp-toolkit-custom-tools
description: Use this skill when the agent exposes app-specific surfaces by registering custom MCP tools and resources inside the Flutter app (mcp_toolkit dynamic registry — MCPCallEntry, bootstrapFlutter additionalEntries / addEntries). Covers tool vs resource vs evaluate-expression, Map-based handlers, schema strictness, discovery via fmt_list_client_tools_and_resources, fmt_client_tool, fmt_client_resource, and lifecycle pitfalls.''',
      body: r'''
<!-- @FMT_MODE_PRELUDE -->

# Custom MCP Toolkit Tools & Resources (Dynamic Registry)

Use this when bundled MCP tools (screenshot, semantic snapshot, tap, …) are not enough and you need **app-specific** read surfaces or actions — e.g. cart totals, feature flags, curated debug snapshots of internal state. Entries are registered **in the Flutter process** and exposed to the agent through the **dynamic registry**.

## Pick the right primitive

| Need | Use |
|------|-----|
| One-off read of a simple value | **`fmt_evaluate_dart_expression`** (no app code change). |
| Stable **read-only** payload (diagnostics, JSON snapshot, “current route”) | **`MCPCallEntry.resource`** + **`fmt_client_resource`**. Prefer resources when the contract is “GET-like” and idempotent. |
| Parameterized or mutating action, or reusable named operation | **`MCPCallEntry.tool`** + **`fmt_client_tool`**. |

## Handler signature (tools and resources)

[`MCPCallHandler`](https://github.com/Arenukvern/mcp_flutter/blob/main/mcp_toolkit/lib/src/mcp_models.dart) is `FutureOr<MCPCallResult> Function(ServiceExtensionRequestMap request)` where **`ServiceExtensionRequestMap` is `Map<String, String>`**.

- Tool arguments arrive as **string values** keyed by schema property names — mirror the README pattern: `request['n']`, `request['userId']`, then parse (`int.tryParse`, `double.tryParse`, `jsonDecode` for nested blobs if the wire format sends JSON-as-string).
- Do **not** use `request.arguments` — that is not the app-side API.

## Minimal tool registration

```dart
import 'package:mcp_toolkit/mcp_toolkit.dart';

final tool = MCPCallEntry.tool(
  handler: (request) async {
    final userId = request['userId'] ?? '';
    final cart = CartRepository.instance.forUser(userId);
    return MCPCallResult(
      message: 'ok',
      parameters: {
        'total': cart.total,
        'items': cart.items.map((i) => i.toJson()).toList(),
      },
    );
  },
  definition: MCPToolDefinition(
    name: 'cart_get_snapshot',
    description: 'Return current cart total and items for a user.',
    inputSchema: {
      'type': 'object',
      'additionalProperties': false,
      'properties': {
        'userId': {'type': 'string'},
      },
      'required': ['userId'],
    },
  ),
);

await MCPToolkitBinding.instance.addEntries(entries: {tool});
```

Prefer **`MCPToolkitBinding.instance.bootstrapFlutter(additionalEntries: { ... }, runApp: ...)`** so tools/resources register in one place with zone/error setup — same entries shape as above.

Register **after** `initialize()` / **`bootstrapFlutter`** wiring, **once** at bootstrap — not inside `build`, not per-widget `initState`.

## Custom resources

Resources are for **read-only** MCP surfaces: diagnostics, config summaries, or JSON blobs the agent polls without treating them as imperative actions.

```dart
MCPCallEntry.resource(
  definition: MCPResourceDefinition(
    name: 'app_cart_digest',
    description: 'Compact cart summary for agents (read-only).',
    mimeType: 'application/json',
  ),
  handler: (request) async => MCPCallResult(
    message: 'Cart digest',
    parameters: {
      'itemCount': CartRepository.instance.visibleCount,
      'currency': CartRepository.instance.currencyCode,
    },
  ),
),
```

- **`name`** must be `snake_case` (letters, digits, underscores). [`resourceUri`](https://github.com/Arenukvern/mcp_flutter/blob/main/mcp_toolkit/lib/src/mcp_models.dart) maps it to a **`visual://localhost/...`** URI (underscore segments become path segments). Agents consume it via **`fmt_client_resource`** using that URI / listing from **`fmt_list_client_tools_and_resources`**.
- Set **`mimeType`** honestly (`application/json` vs `text/plain`) so clients know how to interpret payloads.

## Schema rules (tools)

The MCP server enforces strict JSON Schema:

- Prefer **`additionalProperties: false`** unless you intentionally accept arbitrary keys. Unknown keys **fail validation** — good for catching agent typos.
- Mark **`required`** for anything the handler reads unconditionally.
- Prefer primitives and **`enum`** over unconstrained strings.
- **`parameters`** in **`MCPCallResult`** must be JSON-serializable; non-serializable objects degrade to **`toString()`**.

## Discovery from the agent side

1. **`fmt_list_client_tools_and_resources`** — enumerate app-registered tools and resources.
2. **`fmt_client_tool`** — invoke a tool by name with JSON args (CLI: `flutter-mcp-toolkit exec --name fmt_client_tool --args '...'` per your transport).
3. **`fmt_client_resource`** — fetch a registered resource (URI from listing / `resourceUri` convention).

If something should appear but does not: confirm **`addEntries`** completed (**`await`**), then hot **restart** — reload does not always replay discovery cleanly.

## Lifecycle gotchas

- **Hot reload** + **`addEntries`** from widget code → duplicate registrations. Register once in **`main()` / bootstrap**, not in **`build`**.
- **Hot restart** clears VM state; registrations tied to **`bootstrapFlutter`** / **`main`** run again on boot — correct pattern survives restart.
- **Debug mode only** — release builds do not expose these VM service extensions.
- **Naming**: flat global namespace per app — prefix tools/resources (`cart_`, `flags_`, `nav_`) to avoid collisions with builtins or other domains.

## When the agent authors surfaces for the user’s app

1. Ensure **`mcp_toolkit`** is in **`pubspec.yaml`**.
2. Add **`lib/mcp_tools/<domain>_surfaces.dart`** exporting **`registerXSurfaces()`** that returns **`Set<MCPCallEntry>`** or performs **`addEntries`** once.
3. Wire **`registerXSurfaces()`** from **`bootstrapFlutter(..., additionalEntries: ...)`** or call **`addEntries`** immediately after **`initializeFlutterToolkit`** inside **`bootstrapFlutter`**’s chain — **never** from **`StatefulWidget` lifecycle**.
4. Tight schemas (**`additionalProperties: false`**, explicit **`required`**).
5. Hot **restart**, then **`fmt_list_client_tools_and_resources`** before first **`fmt_client_tool`** / **`fmt_client_resource`** call.

## Safety and scope

- Treat handlers as **powerful debug hooks**: avoid exposing secrets, full databases, or unchecked filesystem/network IO.
- Keep handlers thin: delegate to domain/services already used by the app (same DI/getters), **don’t** duplicate business logic in MCP-only paths unless intentional.

## Common traps

- **`request.arguments`** — wrong shape; use **`request['key']`** on **`Map<String, String>`**.
- Missing **`await`** on **`addEntries`** → race before discovery lists your surface.
- Returning **`Future`** instances inside **`parameters`** → useless serialization; **`await`** inside the handler.
- **`inputSchema`** out of sync with the handler → agents trust the schema; update both.

## Related

- Driving the live app (snapshot / tap / reload): **`flutter-mcp-toolkit-guide`** → **`flutter-mcp-toolkit-inspect`** / **`flutter-mcp-toolkit-control`**.
- Repository **`ARCHITECTURE.md`** → “Dynamic Registry Architecture”.
''',
      relativePath: 'skills/flutter-mcp-toolkit-custom-tools/SKILL.md',
    ),
    SkillAsset(
      id: 'flutter-mcp',
      frontmatter: r'''name: flutter-mcp
description: Use this skill whenever inspecting, interacting with, or live-editing a running Flutter app via the Flutter MCP toolkit server (`mcpServers` key **`flutter-mcp-toolkit`**, or legacy **`flutter-inspector`**). Covers preflight, snapshot/tap/enter/scroll loop, hot-reload validation, and error envelope parsing.''',
      body: r'''
<!-- @FMT_MODE_PRELUDE -->

# Flutter MCP

Golden path for agents driving a live Flutter app via the **`flutter-mcp-toolkit`** MCP server entry. Older configs may use the legacy **`flutter-inspector`** `mcpServers` registry id for the same binary (that id is **not** the Claude subagent **`flutter-mcp-toolkit-runtime`**).

## When to use

- User references a running Flutter app (debug mode) and wants to inspect, screenshot, interact with, or hot-reload it.
- User pastes a VM service URI (`ws://127.0.0.1:8181/.../ws`) or mentions port 8181.
- You need runtime proof (before/after screenshots) that a code edit took effect.

## Preflight (always first)

Before any VM-dependent call:

1. Call `doctor` (or run `flutter-mcp-toolkit doctor --json`) — parses env, ports, app reachability.
2. Confirm required toolkit extensions exist on the target:
   - `ext.mcp.toolkit.app_errors`
   - `ext.mcp.toolkit.view_details`
   - `ext.mcp.toolkit.view_screenshots`
   - `ext.mcp.toolkit.inspect_widget_at_point`

If missing, stop and report the instrumentation gap — do **not** guess:

- Add `mcp_toolkit` to `pubspec.yaml`.
- Ensure `MCPToolkitBinding.instance..initialize()..initializeFlutterToolkit();` runs before `runApp`.
- Hot **restart** (not reload) — reload is often insufficient for extension registration.

## Tool naming (v3.0.0+)

All MCP tools surface under the `fmt_` capability prefix
(`fmt_tap_widget`, `fmt_hot_reload_and_capture`, etc.). The prefix is
mandatory in `tools/call`. Skill-local references below use the bare name
for readability — when invoking, prepend `fmt_`. Dynamic-registry host
tools (`fmt_list_client_tools_and_resources`, `fmt_client_tool`,
`fmt_client_resource`) use the same prefix for a single consistent surface.

## Interaction loop (Playwright-style)

1. `fmt_semantic_snapshot` → returns `s_0..s_N` refs + `snapshot_id`.
2. `fmt_tap_widget` / `fmt_enter_text` / `fmt_scroll` / `fmt_swipe` / `fmt_long_press` / `fmt_drag` — act on a ref. **Always pass `snapshotId`** — you get a structured `stale_snapshot` error if the tree moved, instead of a silent wrong tap.
3. `fmt_evaluate_dart_expression` — read state directly (e.g. `AgentState.instance.counter`).
4. `fmt_hot_reload_and_capture` — after a code edit, returns reload status + screenshot + fresh snapshot + errors in one response. Prefer this over manual reload + separate capture.

## Error envelope contract

Errors are `{code, message, details, descriptor, recovery}`. Parse `error.descriptor` (not the top-level object) for the machine-readable shape. Strict schemas default to `additionalProperties: false` — unknown params reject.

Common codes and recovery:

- `connection_selection_required` — retry with `arguments.connection.targetId` or exact `arguments.connection.uri` from `app.debugPort.wsUri`.
- `target_not_found` — refresh targets, then prefer exact `arguments.connection.uri`.
- `stale_snapshot` — call `fmt_semantic_snapshot` again, then retry the action with the new `snapshot_id`.
- `tool_not_found` — confirm the prefixed name (`fmt_<tool>`); v3.0.0 dropped legacy unprefixed names.
- Empty screenshot output — verify the server was not started with `--no-images`.
- Missing view resource/tool — verify the server was not started with `--no-resources`.

## Visual QA

- Before/after screenshots are the proof artifact for any UI claim. Capture before with `fmt_capture_ui_snapshot` (or one of the `visual://localhost/...` resources), edit, `fmt_hot_reload_and_capture`, compare.
- For each reported visual issue, attach coordinate + `fmt_inspect_widget_at_point` output.
- Map defects to source via `fmt_get_app_errors` top stack frame (`file`, `line`, `column`) when available.
- Do **not** use `fmt_debug_dump_*` unless explicitly requested (server must be started with `--dumps`; high token cost).

## Permissions (macOS)

Screen Recording permission belongs to the process running `flutter-mcp-toolkit` (or the MCP server host). If visual capture is denied:

```bash
flutter-mcp-toolkit permissions status
flutter-mcp-toolkit permissions request
flutter-mcp-toolkit permissions open-settings
```

## Non-modifiable apps

If the target app cannot be instrumented (third-party binary, restricted env), report flutter-mcp as unavailable for that app. Do **not** claim screenshot/layout/error inspection success.

## Related

- For the dynamic-tools side (registering custom MCP tools from inside the Flutter app), see the `flutter-mcp-toolkit-custom-tools` skill.
- For routing across setup / inspect / control / debug skills, see `flutter-mcp-toolkit-guide`.
''',
      relativePath: 'skills/flutter-mcp/SKILL.md',
    ),
    SkillAsset(
      id: 'flutter-mcp-cli-runtime-validation',
      frontmatter: r'''name: flutter-mcp-cli-runtime-validation
description: Run Flutter MCP runtime validation from CLI in two steps (launch app, then run validate-runtime), including toolkit-extension gating, screenshot/layout capture, app error collection, optional reload verification, and retry handling for transient first-connect failures.''',
      body: r'''
<!-- @FMT_MODE_PRELUDE -->

# Flutter MCP CLI Runtime Validation

Use this skill when you need agent-style runtime validation through `flutter-mcp-toolkit` with minimal operator steps.

## Two-Step Flow

1. Launch the Flutter app in debug mode.
2. Run one CLI command:

```bash
dart run mcp_server_dart/bin/flutter_mcp_toolkit.dart --save-images --output-dir .flutter_mcp/runtime_validation validate-runtime \
  --target ws://127.0.0.1:8181/<token>/ws \
  --timeout-ms 10000 \
  --post-reload-delay-ms 500 \
  --after-reload
```

Optional skill install in the same command:

```bash
dart run mcp_server_dart/bin/flutter_mcp_toolkit.dart validate-runtime \
  --target ws://127.0.0.1:8181/<token>/ws \
  --install-skill
```

Permission behavior for this flow:

- `validate-runtime` stays read/write only for visual capture and defaults to `auto_request_once`.
- `doctor` remains read-only.
- On macOS, Screen Recording permission belongs to the host process running `flutter-mcp-toolkit`.
- On web, `flutter_layer` is the only supported truth path and no OS permission prompt is expected.
- If the first `capture_ui_snapshot` attempt uses host `desktop_window` and fails (common on macOS when the window is not foregrounded, or for iOS Simulator), `validate-runtime` automatically retries once with `flutter_layer`.
- You may pass the VM URI as global `--vm-service-uri` instead of `validate-runtime --target` when only one URI is needed.

## What `validate-runtime` Must Prove

- Doctor preflight passes critical checks.
- Required toolkit extensions exist:
  - `ext.mcp.toolkit.app_errors`
  - `ext.mcp.toolkit.view_details`
  - `ext.mcp.toolkit.view_screenshots`
  - `ext.mcp.toolkit.inspect_widget_at_point`
- Screenshot capture works.
- View details (layout metadata) are available.
- App errors are retrievable.
- If `--after-reload` is enabled, post-reload screenshot also works.

## Output Handling

- Use `data.summary` as pass/fail status for automation.
- Use `data.summary.captureFallbackUsed` to see whether the `flutter_layer` retry succeeded after a failed `desktop_window` attempt.
- Use `data.steps` for per-step evidence and retries.
- Use `data.doctor.checks` to explain setup blockers.
- Use `data.summary.screenshotFiles` for saved screenshot paths when `--save-images` is enabled.
- When `--save-images` is enabled, read screenshot file URLs from step data.
- For visual debugging reports, also run:
  - `exec --name capture_ui_snapshot --args '{"errorsCount":4,"compress":true,"includeViewDetails":true,"includeErrors":true}'`
  - `exec --name inspect_widget_at_point --args '{"x":<int>,"y":<int>}'`

## Failure Rules

- If toolkit extensions are missing, stop and report instrumentation gap with exact fix:
  - add `mcp_toolkit` to app dependencies
  - ensure `MCPToolkitBinding.instance.bootstrapFlutter(...)` or equivalent manual initialization runs before `runApp`
  - hot restart or rerun the app
- If first explicit URI connect fails, retry is automatic for retryable connection errors.
- If screenshots are blank, verify app window is visible and retry.
- If macOS visual capture is denied, use:
  - `dart run mcp_server_dart/bin/flutter_mcp_toolkit.dart permissions status`
  - `dart run mcp_server_dart/bin/flutter_mcp_toolkit.dart permissions request`
  - `dart run mcp_server_dart/bin/flutter_mcp_toolkit.dart permissions open-settings`
- If app cannot be instrumented, do not claim screenshot/layout/error inspection success.

## Visual QA + Source Mapping Rules

- Always compare before/after screenshot evidence around changes.
- For each reported visual issue, provide coordinate + `inspect_widget_at_point` output.
- Map defects to source using `get_app_errors` top stack frame (`file`, `line`, `column`) when available.
- Do not use `debug_dump_*` unless explicitly requested.

## Challenge Cases (Always Call Out Explicitly)

- No running debug app: `doctor` critical failure on `vm_target_reachable`; request app launch before continuing.
- Wrong target URI/token: treat as connection mismatch and retry with exact `app.debugPort.wsUri`.
- Toolkit added but still missing extensions: hot reload is often insufficient, require hot restart/full rerun.
- Non-modifiable app (cannot add toolkit): report inspection as unavailable instead of guessing.
''',
      relativePath: 'skills/flutter-mcp-cli-runtime-validation/SKILL.md',
    ),
    SkillAsset(
      id: 'flutter-mcp-toolkit-repo-maintainer',
      frontmatter: r'''name: flutter-mcp-toolkit-repo-maintainer
description: >-
  Maintain mcp_flutter releases, CHANGELOG, version pins, docs, and CI.
  Use when cutting a release, editing CHANGELOG.md, bumping VERSION, running
  release-please, sync-skills, check-contracts, or updating install/docs for
  npx skills and flutter-mcp-toolkit init.''',
      body: r'''
<!-- @FMT_MODE_PRELUDE -->

# flutter-mcp-toolkit repo maintainer

Golden path for **this repository** (not end-user Flutter apps). Prefer
release-please on `main`; use manual steps only when the Release PR path is blocked.

## When to use

- Cutting a release or promoting `## [Unreleased]` in CHANGELOG.md
- Adding/editing contributor docs, AI agent install docs, or plugin skills
- Verifying version sync or skill asset drift before merge
- Troubleshooting release-please, `release.yml` binaries, or `install.sh` version pins

## Version touchpoints (must match root `VERSION`)

| File | Field |
|------|--------|
| [VERSION](https://github.com/Arenukvern/mcp_flutter/blob/main/VERSION) | repo pin |
| [plugin/EXPECTED_SERVER_VERSION](https://github.com/Arenukvern/mcp_flutter/blob/main/plugin/EXPECTED_SERVER_VERSION) | installer pin |
| [mcp_shared_core/lib/src/runtime_version.dart](https://github.com/Arenukvern/mcp_flutter/blob/main/mcp_shared_core/lib/src/runtime_version.dart) | `kFlutterMcpVersion` (`x-release-please-version`) |
| [mcp_capability_core/lib/src/fmt_capability.dart](https://github.com/Arenukvern/mcp_flutter/blob/main/mcp_capability_core/lib/src/fmt_capability.dart) | `version` getter |
| [mcp_server_dart/pubspec.yaml](https://github.com/Arenukvern/mcp_flutter/blob/main/mcp_server_dart/pubspec.yaml) | `version:` |
| [mcp_toolkit/pubspec.yaml](https://github.com/Arenukvern/mcp_flutter/blob/main/mcp_toolkit/pubspec.yaml) | `version:` |
| [plugin/.cursor-plugin/plugin.json](https://github.com/Arenukvern/mcp_flutter/blob/main/plugin/.cursor-plugin/plugin.json) | `version` |
| [plugin/.codex-plugin/plugin.json](https://github.com/Arenukvern/mcp_flutter/blob/main/plugin/.codex-plugin/plugin.json) | `version` |
| [plugin/.claude-plugin/plugin.json](https://github.com/Arenukvern/mcp_flutter/blob/main/plugin/.claude-plugin/plugin.json) | `version` |
| [.claude-plugin/marketplace.json](https://github.com/Arenukvern/mcp_flutter/blob/main/.claude-plugin/marketplace.json) | `plugins[0].version` |
| [.release-please-manifest.json](https://github.com/Arenukvern/mcp_flutter/blob/main/.release-please-manifest.json) | `"."` key |
| [mcp_server_dart/lib/src/skill_assets.g.dart](https://github.com/Arenukvern/mcp_flutter/blob/main/mcp_server_dart/lib/src/skill_assets.g.dart) | **generated** — embeds `plugin/.cursor-plugin/plugin.json`, `plugin/.codex-plugin/plugin.json`, `plugin/mcp.json`, and all `plugin/skills/*/SKILL.md` |

After any version bump in `plugin/*-plugin/plugin.json` or edit under `plugin/skills/`: run `make sync-skills`, then `make check-contracts` (includes `check_version_sync.sh` and `check_skill_assets_drift.sh`).

## Changelog workflow

1. Add user-facing notes under `## [Unreleased]` in [CHANGELOG.md](https://github.com/Arenukvern/mcp_flutter/blob/main/CHANGELOG.md) (Keep a Changelog sections: Added, Changed, Fixed, Documentation).
2. Use conventional commit titles on `main` (`feat:`, `fix:`, `docs:`) so release-please can aggregate.
3. Do **not** edit the plan file in `.cursor/plans/`.

### Markdown lint (MD052)

Keep a Changelog **requires** version headings like `## [3.0.1]`. Linters treat `[3.0.1]` as an undefined **reference link** (MD052: "No link definition found").

- **Do not remove** the file-top `<!-- markdownlint-disable MD052 -->` in CHANGELOG.md (release-please must keep it when prepending sections).
- In bullet text, use **backticks** for code identifiers: `` `MCPCallEntry.resourceUri` ``, not `[MCPCallEntry.resourceUri]`.
- Before merge: `bash tool/contracts/check_changelog_markdown.sh` (also in `make check-contracts`).

## Automated release (preferred)

```mermaid
flowchart LR
  main[merge_to_main] --> rp[release-please.yml]
  rp --> pr[Release_PR]
  pr --> tag[vX_Y_Z_tag]
  tag --> rel[release.yml_binaries]
```

1. Merge PRs to `main` with conventional commits.
2. Wait for **Release PR** from [release-please.yml](https://github.com/Arenukvern/mcp_flutter/blob/main/.github/workflows/release-please.yml).
3. **skill assets:** [release_pr_sync_skills.yml](https://github.com/Arenukvern/mcp_flutter/blob/main/.github/workflows/release_pr_sync_skills.yml) auto-commits `skill_assets.g.dart` on Release PRs when drift is detected; posts a checklist comment on PR open. If **skill-assets-drift** still fails, run `make sync-skills` locally and push (release-please bumps `plugin/*-plugin/plugin.json` but not the generated bundle).
4. Review VERSION, CHANGELOG, pubspecs, plugin pins in that PR → merge.
5. release-please creates `vX.Y.Z` + GitHub release notes.
6. [release.yml](https://github.com/Arenukvern/mcp_flutter/blob/main/.github/workflows/release.yml) attaches `flutter_mcp_*` tarballs (does not overwrite release body).

Config: [release-please-config.json](https://github.com/Arenukvern/mcp_flutter/blob/main/release-please-config.json).

## Manual release (fallback)

Use when release-please is unavailable or you must ship from a branch:

1. Move `## [Unreleased]` bullets into `## [X.Y.Z]` (add date), leave empty `## [Unreleased]`.
2. Bump all version touchpoints above to `X.Y.Z`.
3. Update `.release-please-manifest.json` `"."` to `X.Y.Z`.
4. `make sync-skills` (required whenever plugin manifests or skills change — release-please bumps plugin JSON but not `skill_assets.g.dart`).
5. `make check-contracts`
6. Commit: `chore: release X.Y.Z`
7. Tag: `git tag vX.Y.Z` and push tag (triggers binary workflow).

Build artifacts locally: `make release-artifacts` or `bash tool/release/build_release_artifacts.sh --version X.Y.Z`.

## Docs map (single sources of truth)

| Topic | Canonical doc |
|-------|----------------|
| End-user agent install | [docs/ai_agents/overview.mdx](https://github.com/Arenukvern/mcp_flutter/blob/main/docs/ai_agents/overview.mdx) |
| `npx skills` + lockfile | overview § Install via `npx skills`; [.skills.json.example](https://github.com/Arenukvern/mcp_flutter/blob/main/.skills.json.example) |
| Contributor / releases | [docs/contributing/contribution_guide.mdx](https://github.com/Arenukvern/mcp_flutter/blob/main/docs/contributing/contribution_guide.mdx) |
| Plugin layout | [plugin/README.md](https://github.com/Arenukvern/mcp_flutter/blob/main/plugin/README.md) |
| Marketplace copy SSOT | [docs/ai_agents/marketplace_copy.yaml](https://github.com/Arenukvern/mcp_flutter/blob/main/docs/ai_agents/marketplace_copy.yaml) |
| Distribution / stores | [docs/ai_agents/marketplace_distribution.mdx](https://github.com/Arenukvern/mcp_flutter/blob/main/docs/ai_agents/marketplace_distribution.mdx) |
| Store submission runbook | [docs/contributing/marketplace_submission_runbook.mdx](https://github.com/Arenukvern/mcp_flutter/blob/main/docs/contributing/marketplace_submission_runbook.mdx) |
| Skill bodies | `plugin/skills/<id>/SKILL.md` → `make sync-skills` |

Avoid duplicating install tables in README — link to overview.

## Skills maintenance

- Canonical skills: `plugin/skills/` (repo root `skills/` → symlink for `npx skills`).
- New bundled skill: add `plugin/skills/<id>/SKILL.md`, append `id` to `expectedSkillIds` in [build_skill_assets.dart](https://github.com/Arenukvern/mcp_flutter/blob/main/mcp_server_dart/tool/build_skill_assets.dart), run `make sync-skills`.
- Local Cursor copy: `.cursor/skills/<id>/` may symlink to `plugin/skills/<id>/`.

## Pre-merge checklist

- [ ] `make check-contracts`
- [ ] `make sync-skills` if `plugin/skills/` or `plugin/*-plugin/plugin.json` changed
- [ ] CHANGELOG `[Unreleased]` updated for user-visible changes
- [ ] No secrets in committed configs
''',
      relativePath: 'skills/flutter-mcp-toolkit-repo-maintainer/SKILL.md',
    ),
  ];

  static const String cursorPluginManifest = r'''{
  "name": "flutter-mcp-toolkit",
  "description": "Flutter MCP toolkit: inspect and drive debug apps (semantic snapshot, tap, hot-reload) and register custom MCP tools and resources at runtime from your Flutter app or game via mcp_toolkit — closed agent feedback loop.",
  "version": "3.0.7",
  "author": {
    "name": "Arenukvern",
    "url": "https://github.com/Arenukvern/mcp_flutter"
  },
  "homepage": "https://github.com/Arenukvern/mcp_flutter",
  "repository": "https://github.com/Arenukvern/mcp_flutter",
  "license": "MIT",
  "keywords": [
    "flutter",
    "dart",
    "mcp",
    "mcp-toolkit",
    "vm-service",
    "inspector",
    "live-edit",
    "dynamic-tools",
    "runtime-tools",
    "game-debug"
  ]
}
''';
  static const String codexPluginManifest = r'''{
  "name": "flutter-mcp-toolkit",
  "version": "3.0.7",
  "description": "Flutter MCP toolkit: inspect and drive debug apps (semantic snapshot, tap, hot-reload) and register custom MCP tools and resources at runtime from your Flutter app or game via mcp_toolkit — closed agent feedback loop.",
  "author": {
    "name": "Arenukvern",
    "url": "https://github.com/Arenukvern/mcp_flutter"
  },
  "homepage": "https://github.com/Arenukvern/mcp_flutter",
  "repository": "https://github.com/Arenukvern/mcp_flutter",
  "license": "MIT",
  "keywords": [
    "flutter",
    "dart",
    "mcp",
    "mcp-toolkit",
    "vm-service",
    "inspector",
    "live-edit",
    "dynamic-tools",
    "runtime-tools",
    "game-debug"
  ],
  "skills": "./skills",
  "mcpServers": "./mcp.json",
  "interface": {
    "displayName": "Flutter MCP Toolkit",
    "shortDescription": "Inspect, drive, and extend Flutter debug apps via MCP — including runtime custom tools.",
    "longDescription": "flutter-mcp-toolkit is a Dart MCP server plus Flutter package (mcp_toolkit) for AI-assisted Flutter development in debug mode. Built-in: 27 fmt_* MCP tools and bundled agent skills. Dynamic registry: register app-specific tools and resources at runtime with addMcpTool / MCPCallEntry; agents discover via fmt_list_client_tools_and_resources and invoke via fmt_client_tool / fmt_client_resource. Requires debug app with mcp_toolkit and flutter-mcp-toolkit-server on PATH. Complements official Dart MCP.",
    "developerName": "Arenukvern",
    "category": "Developer Tools",
    "capabilities": [
      "Read",
      "Write"
    ],
    "websiteURL": "https://github.com/Arenukvern/mcp_flutter",
    "privacyPolicyURL": "https://github.com/Arenukvern/mcp_flutter/blob/main/LICENSE",
    "termsOfServiceURL": "https://github.com/Arenukvern/mcp_flutter/blob/main/LICENSE",
    "defaultPrompt": [
      "Use flutter-mcp-toolkit to capture a semantic snapshot of my running Flutter app and list any app errors.",
      "Register a custom MCP tool in my Flutter app for cart state, then discover it with fmt_list_client_tools_and_resources and call it."
    ],
    "brandColor": "#02569B",
    "composerIcon": "./assets/icon.png",
    "logo": "./assets/logo.png",
    "screenshots": [
      "./assets/screenshot-1.png"
    ]
  }
}
''';
  static const String mcpServerConfig = r'''{
  "mcpServers": {
    "flutter-mcp-toolkit": {
      "command": "${FLUTTER_MCP_BIN:-flutter-mcp-toolkit-server}",
      "args": [
        "--dart-vm-host=localhost",
        "--dart-vm-port=8181",
        "--resources",
        "--images",
        "--dynamics"
      ],
      "env": {}
    }
  }
}
''';
}
