diff --git a/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-cli-runtime-validation/SKILL.md b/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-cli-runtime-validation/SKILL.md new file mode 100644 index 0000000..7f5c7f3 --- /dev/null +++ b/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-cli-runtime-validation/SKILL.md @@ -0,0 +1,97 @@ +--- +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. +--- +> Calls in this skill run via the `flutter-mcp-toolkit` CLI binary: +> flutter-mcp-toolkit exec --name --args '' +> Output is JSON on stdout. Errors come as `{"error":{"code":..., "message":..., "recovery":...}}`. +> Throughout this skill, calls are written as `tap_widget(selector: "...")` — translate to the CLI form. +> If the binary isn't on PATH, see `flutter-mcp-toolkit-setup`. + +# 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//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//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: `desktop_window` uses macOS ScreenCaptureKit then Chrome CDP (`Page.captureScreenshot`); Linux/Windows use CDP when remote debugging is reachable. Pass `--web-browser-debugging-port` if discovery fails. With platform views detected, `validate-runtime` does not fall back to `flutter_layer` after a successful `desktop_window` capture. Check `captureHints.weakSignalsDetected` for `Texture`-only apps. +- Executor recovery retries host capture once (`desktopCaptureRetried` in screenshot payloads). When `captureHints.platformViewsDetected` is true, validate-runtime does not fall back to `flutter_layer`. Otherwise it may retry once with `flutter_layer` after a failed host capture. +- 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.capturePlatformViewsDetected` and `captureFocusAttempted` for platform-view routing. +- Use `data.summary.captureFallbackUsed` to see whether a `flutter_layer` retry ran (skipped when platform views were detected). +- 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":,"y":}'` + +## 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. diff --git a/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-control/SKILL.md b/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-control/SKILL.md new file mode 100644 index 0000000..b909d7f --- /dev/null +++ b/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-control/SKILL.md @@ -0,0 +1,239 @@ +--- +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. +--- +> Calls in this skill run via the `flutter-mcp-toolkit` CLI binary: +> flutter-mcp-toolkit exec --name --args '' +> Output is JSON on stdout. Errors come as `{"error":{"code":..., "message":..., "recovery":...}}`. +> Throughout this skill, calls are written as `tap_widget(selector: "...")` — translate to the CLI form. +> If the binary isn't on PATH, see `flutter-mcp-toolkit-setup`. + +## 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: ) +``` + +### 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: ) +→ 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: ) +``` + +### 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 +``` diff --git a/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-custom-tools/SKILL.md b/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-custom-tools/SKILL.md new file mode 100644 index 0000000..9ae5b41 --- /dev/null +++ b/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-custom-tools/SKILL.md @@ -0,0 +1,139 @@ +--- +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. +--- +> Calls in this skill run via the `flutter-mcp-toolkit` CLI binary: +> flutter-mcp-toolkit exec --name --args '' +> Output is JSON on stdout. Errors come as `{"error":{"code":..., "message":..., "recovery":...}}`. +> Throughout this skill, calls are written as `tap_widget(selector: "...")` — translate to the CLI form. +> If the binary isn't on PATH, see `flutter-mcp-toolkit-setup`. + +# 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 Function(ServiceExtensionRequestMap request)` where **`ServiceExtensionRequestMap` is `Map`**. + +- 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/_surfaces.dart`** exporting **`registerXSurfaces()`** that returns **`Set`** 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`**. +- 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”. diff --git a/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-debug/SKILL.md b/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-debug/SKILL.md new file mode 100644 index 0000000..0922a84 --- /dev/null +++ b/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-debug/SKILL.md @@ -0,0 +1,386 @@ +--- +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. +--- +> Calls in this skill run via the `flutter-mcp-toolkit` CLI binary: +> flutter-mcp-toolkit exec --name --args '' +> Output is JSON on stdout. Errors come as `{"error":{"code":..., "message":..., "recovery":...}}`. +> Throughout this skill, calls are written as `tap_widget(selector: "...")` — translate to the CLI form. +> If the binary isn't on PATH, see `flutter-mcp-toolkit-setup`. + +## 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//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://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//ws"}) +evaluate_dart_expression(expression: "x.toString()", connection: {targetId: "ws://127.0.0.1:8182//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//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: ""}`. + +### `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. Check `captureHints` on `get_view_details` or screenshot payloads. Strong signals (`platformViewsDetected`): `UiKitView`, `AppKitView`, `AndroidView`, `HtmlElementView` — use `desktop_window` or `auto` (macOS app, iOS Simulator, or Chrome/web on any host with CDP). Web truth capture: macOS ScreenCaptureKit first, then Chrome CDP (`captureBackend: cdp`); override port with `--web-browser-debugging-port`. Weak signals (`weakSignalsDetected`): `Texture` only — prefer `desktop_window` on macOS host but `auto` does not upgrade. WGPU/custom engines without platform views: set `MCPToolkitBinding.captureHintsContributor` in the app. Image-only `get_screenshots` tools also return routing JSON in `meta` and a leading text block. Showcase: `make showcase-stop` then `make showcase` (macOS `AppKitView`) or `flutter run -d chrome` (web `HtmlElementView` + CDP capture). +3. Run `focus_window` (MCP: `fmt_focus_window`) then retry `get_screenshots` with `mode: desktop_window`. +4. For **`validate-runtime`**, executor recovery retries focus+capture once (`desktopCaptureRetried`). When platform views are detected, validate-runtime does not fall back to `flutter_layer`. Read `capturePlatformViewsDetected`, `captureFocusAttempted`, and `captureFallbackUsed` in `data.summary`. + +### `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 ` + +### `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 --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`. 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. diff --git a/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-guide/SKILL.md b/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-guide/SKILL.md new file mode 100644 index 0000000..2e49481 --- /dev/null +++ b/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-guide/SKILL.md @@ -0,0 +1,79 @@ +--- +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. +--- +> Calls in this skill run via the `flutter-mcp-toolkit` CLI binary: +> flutter-mcp-toolkit exec --name --args '' +> Output is JSON on stdout. Errors come as `{"error":{"code":..., "message":..., "recovery":...}}`. +> Throughout this skill, calls are written as `tap_widget(selector: "...")` — translate to the CLI form. +> If the binary isn't on PATH, see `flutter-mcp-toolkit-setup`. + +## 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` | +| Harness Script lint/run/Maestro (`*.hs.yaml`, app registry) | Install **flutter_harness** — skill `flutter-mcp-semantic-test` in that repo | +| HS capture bundles / promo video | **flutter_harness** + **flutter_mcp_video** (not bundled in toolkit `init`) | + +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`. diff --git a/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-inspect/SKILL.md b/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-inspect/SKILL.md new file mode 100644 index 0000000..f4aa90d --- /dev/null +++ b/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-inspect/SKILL.md @@ -0,0 +1,228 @@ +--- +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. +--- +> Calls in this skill run via the `flutter-mcp-toolkit` CLI binary: +> flutter-mcp-toolkit exec --name --args '' +> Output is JSON on stdout. Errors come as `{"error":{"code":..., "message":..., "recovery":...}}`. +> Throughout this skill, calls are written as `tap_widget(selector: "...")` — translate to the CLI form. +> If the binary isn't on PATH, see `flutter-mcp-toolkit-setup`. + +## 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//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//ws"}) +``` + +Returns: `{"connected": true, "targetId": "ws://127.0.0.1:8181//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. diff --git a/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-repo-maintainer/SKILL.md b/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-repo-maintainer/SKILL.md new file mode 100644 index 0000000..943a775 --- /dev/null +++ b/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-repo-maintainer/SKILL.md @@ -0,0 +1,127 @@ +--- +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. +--- +> Calls in this skill run via the `flutter-mcp-toolkit` CLI binary: +> flutter-mcp-toolkit exec --name --args '' +> Output is JSON on stdout. Errors come as `{"error":{"code":..., "message":..., "recovery":...}}`. +> Throughout this skill, calls are written as `tap_widget(selector: "...")` — translate to the CLI form. +> If the binary isn't on PATH, see `flutter-mcp-toolkit-setup`. + +# 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 | +| [packages/core/lib/src/runtime_version.dart](https://github.com/Arenukvern/mcp_flutter/blob/main/packages/core/lib/src/runtime_version.dart) | `kFlutterMcpVersion` (`x-release-please-version`) | +| [packages/server_capability_core/lib/src/fmt_capability.dart](https://github.com/Arenukvern/mcp_flutter/blob/main/packages/server_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`). + +**Harness / video (separate repos):** [flutter_harness](https://github.com/Arenukvern/flutter_harness), [flutter_mcp_video](https://github.com/Arenukvern/flutter_mcp_video) — not maintained in this plugin tree. Three-repo layout: [flutter_harness/docs/RELATED_REPOS.md](https://github.com/Arenukvern/flutter_harness/blob/main/docs/RELATED_REPOS.md). + +## 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 `` 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//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//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//` may symlink to `plugin/skills//`. + +### HyperFrames promo (flutter_mcp_video repo) + +Video skill and projects live in **[flutter_mcp_video](https://github.com/Arenukvern/flutter_mcp_video)** (`skills/hyperframes-video/`, `projects/video-projects//`). Not bundled in toolkit `make sync-skills`. + +When shipping a promo there: edit the video repo; run `bash tool/check_doc_paths.sh` in that repo before merge. Toolkit repo only hosts shared brand assets under `plugin/assets/` (symlink targets for v7-weaver). + +## 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 diff --git a/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-setup/SKILL.md b/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-setup/SKILL.md new file mode 100644 index 0000000..576ce4f --- /dev/null +++ b/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp-toolkit-setup/SKILL.md @@ -0,0 +1,222 @@ +--- +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. +--- +> Calls in this skill run via the `flutter-mcp-toolkit` CLI binary: +> flutter-mcp-toolkit exec --name --args '' +> Output is JSON on stdout. Errors come as `{"error":{"code":..., "message":..., "recovery":...}}`. +> Throughout this skill, calls are written as `tap_widget(selector: "...")` — translate to the CLI form. +> If the binary isn't on PATH, see `flutter-mcp-toolkit-setup`. + +## 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 ` (test a specific URI), global `--vm-service-uri ` (same as `--target` when omitted on `doctor`), `--timeout-ms ` (default: 2500). + +```bash +# Global URI works for doctor (same as validate-runtime) +flutter-mcp-toolkit --vm-service-uri 'ws://127.0.0.1:8181//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//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//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//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//ws` | +| `init ` | 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 `, `--dart-vm-host `, `--vm-service-uri `, `--log-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 ` + +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`. diff --git a/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp/SKILL.md b/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp/SKILL.md new file mode 100644 index 0000000..5fb96ed --- /dev/null +++ b/example/.claude/skills/flutter-mcp-toolkit/flutter-mcp/SKILL.md @@ -0,0 +1,91 @@ +--- +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. +--- +> Calls in this skill run via the `flutter-mcp-toolkit` CLI binary: +> flutter-mcp-toolkit exec --name --args '' +> Output is JSON on stdout. Errors come as `{"error":{"code":..., "message":..., "recovery":...}}`. +> Throughout this skill, calls are written as `tap_widget(selector: "...")` — translate to the CLI form. +> If the binary isn't on PATH, see `flutter-mcp-toolkit-setup`. + +# 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_`); 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`. diff --git a/example/.claude/skills/grill-me/SKILL.md b/example/.claude/skills/grill-me/SKILL.md new file mode 100644 index 0000000..bd04394 --- /dev/null +++ b/example/.claude/skills/grill-me/SKILL.md @@ -0,0 +1,10 @@ +--- +name: grill-me +description: Interview the user relentlessly about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions "grill me". +--- + +Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer. + +Ask the questions one at a time. + +If a question can be answered by exploring the codebase, explore the codebase instead. diff --git a/example/.gitignore b/example/.gitignore index 24476c5..b917480 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related @@ -21,6 +23,9 @@ migrate_working_dir/ # is commented out by default. #.vscode/ +# Flutter MCP cache +.flutter_mcp/ + # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id diff --git a/example/CLAUDE.md b/example/CLAUDE.md new file mode 100644 index 0000000..6c1c4ff --- /dev/null +++ b/example/CLAUDE.md @@ -0,0 +1,57 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Structure + +This is the **example app** for the `flutter_curve` package (located at `../`). It's an interactive demo that visualizes physics-based animation curves (Spring, Bounce, Gravity, Cubic easing) with real-time parameter controls. + +The parent package (`../`) contains the actual curve implementations — this directory is only the demo app. + +## Commands + +```bash +# Install dependencies +flutter pub get + +# Run the app +flutter run + +# Analyze / lint +flutter analyze + +# Run tests +flutter test + +# Build for web +flutter build web --base-href // + +# Full web deploy to GitHub Pages (see Makefile) +make deploy OUTPUT= TOKEN= +``` + +## Architecture + +**State management:** Riverpod (`ProviderScope` wraps the app in `main.dart`). Currently only one provider exists: `springIsAdvancedModeProvider` (StateProvider) in `spring_option.dart`. + +**MCP toolkit:** `main.dart` initializes `MCPToolkitBinding` (via the `mcp_toolkit` package) inside a `runZonedGuarded` wrapper. Zone errors are forwarded to `MCPToolkitBinding.instance.handleZoneError()` for capture and reporting. + +**Platform routing:** After MCP toolkit setup, `main.dart` checks `kIsWeb` to route between `AppPage` (mobile) and `WebPage` (web). Both render `CurvePanel` as their core content. + +**CurvePanel** (`widgets/curve_panel.dart`) is the central orchestrator — it holds the selected `CurveType`, animation duration, and composes: +- `CurveIllustration` — CustomPaint line chart of the curve +- `BallAnimation` — animated circle moving along the curve +- `EffectAnimation` — 4 effect demos (opacity, rotate, translate, scale) +- `CurveOption` — parameter sliders (one subclass per curve type) +- `CodePreview` — generated Dart code for the current config + +**CurveOption** and **CodePreview** are abstract base classes with a factory constructor that returns the appropriate subclass based on `CurveType`. Each curve type has its own `*_option.dart` and `*_code_preview.dart`. + +**Supported curve types** (CurveType enum): `spring`, `bounce`, `forceWithGravity`, `gravity`, `easeIn`, `easeOut`, `easeInOut`. + +## Key Relationships + +- `curve_option/curve_option.dart` — abstract base + `CurveOptionItem` (reusable labeled slider widget) +- `code_preview/code_preview.dart` — abstract base; subclasses generate code strings for each curve +- `style.dart` — `SizeConstant` holds shared layout dimensions +- The `flutter_curve` package is referenced via `path: ../` in `pubspec.yaml` diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 118ee1d..9feca59 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -44,6 +44,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.example" // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. minSdkVersion flutter.minSdkVersion targetSdkVersion flutter.targetSdkVersion diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index 592ceee..ec97fc6 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index 592ceee..c4855bf 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Podfile b/example/ios/Podfile new file mode 100644 index 0000000..e549ee2 --- /dev/null +++ b/example/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/example/lib/main.dart b/example/lib/main.dart index 8daa261..81d023d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,12 +1,24 @@ +import 'dart:async'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mcp_toolkit/mcp_toolkit.dart'; import 'src/curve_app/app/app_page.dart'; import 'src/curve_app/web/web_page.dart'; void main() { - runApp(const ProviderScope(child: MyApp())); + runZonedGuarded( + () { + WidgetsFlutterBinding.ensureInitialized(); + MCPToolkitBinding.instance + ..initialize() + ..initializeFlutterToolkit(); + runApp(const ProviderScope(child: MyApp())); + }, + (error, stack) => MCPToolkitBinding.instance.handleZoneError(error, stack), + ); } class MyApp extends StatelessWidget { diff --git a/example/lib/src/curve_app/widgets/curve_panel.dart b/example/lib/src/curve_app/widgets/curve_panel.dart index 1bd7368..89143d8 100644 --- a/example/lib/src/curve_app/widgets/curve_panel.dart +++ b/example/lib/src/curve_app/widgets/curve_panel.dart @@ -116,12 +116,6 @@ class _CurvePanelState extends State { width: width, ); break; - default: - curveOptionWidget = BounceOption( - onChanged: _onCurveChanged, - width: width, - ); - break; } return kIsWeb diff --git a/example/linux/flutter/generated_plugin_registrant.cc b/example/linux/flutter/generated_plugin_registrant.cc index e71a16d..f6f23bf 100644 --- a/example/linux/flutter/generated_plugin_registrant.cc +++ b/example/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/example/linux/flutter/generated_plugins.cmake b/example/linux/flutter/generated_plugins.cmake index 2e1de87..f16b4c3 100644 --- a/example/linux/flutter/generated_plugins.cmake +++ b/example/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/example/macos/Flutter/Flutter-Debug.xcconfig b/example/macos/Flutter/Flutter-Debug.xcconfig index c2efd0b..4b81f9b 100644 --- a/example/macos/Flutter/Flutter-Debug.xcconfig +++ b/example/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Flutter/Flutter-Release.xcconfig b/example/macos/Flutter/Flutter-Release.xcconfig index c2efd0b..5caa9d1 100644 --- a/example/macos/Flutter/Flutter-Release.xcconfig +++ b/example/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..8236f57 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,8 @@ import FlutterMacOS import Foundation +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/example/macos/Podfile b/example/macos/Podfile new file mode 100644 index 0000000..ff5ddb3 --- /dev/null +++ b/example/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock new file mode 100644 index 0000000..beea7f5 --- /dev/null +++ b/example/macos/Podfile.lock @@ -0,0 +1,22 @@ +PODS: + - FlutterMacOS (1.0.0) + - url_launcher_macos (0.0.1): + - FlutterMacOS + +DEPENDENCIES: + - FlutterMacOS (from `Flutter/ephemeral`) + - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + +EXTERNAL SOURCES: + FlutterMacOS: + :path: Flutter/ephemeral + url_launcher_macos: + :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + +SPEC CHECKSUMS: + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + url_launcher_macos: c83b920a14ed6c0ec4d6069c3ec3e19222607405 + +PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 + +COCOAPODS: 1.16.2 diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 27e0f50..e3ec45d 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 53444F5394B20D90B2B5C3D4 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78B582CF6BBC70B9E63B936B /* Pods_RunnerTests.framework */; }; + E39F46DD26B83190F87518DD /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E8800A69160B3C038B12B9E /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -64,7 +66,7 @@ 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -76,8 +78,16 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 3D00C97B4400EB7E0A2A18C4 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 3E8800A69160B3C038B12B9E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 48E0DCE5F48CF809F73CECB2 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 78B582CF6BBC70B9E63B936B /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 8877C7351B2B81AD93DC67D7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + C6F65745A44F31F2DE8753D3 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + DCF2ECBF491F4E25DF5145B1 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + F5C4AB57BB0DDC34851E8714 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -85,6 +95,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 53444F5394B20D90B2B5C3D4 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -92,6 +103,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E39F46DD26B83190F87518DD /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -125,6 +137,7 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + 35DBACBC3D2E322BA14A9C91 /* Pods */, ); sourceTree = ""; }; @@ -172,9 +185,25 @@ path = Runner; sourceTree = ""; }; + 35DBACBC3D2E322BA14A9C91 /* Pods */ = { + isa = PBXGroup; + children = ( + 8877C7351B2B81AD93DC67D7 /* Pods-Runner.debug.xcconfig */, + F5C4AB57BB0DDC34851E8714 /* Pods-Runner.release.xcconfig */, + DCF2ECBF491F4E25DF5145B1 /* Pods-Runner.profile.xcconfig */, + 3D00C97B4400EB7E0A2A18C4 /* Pods-RunnerTests.debug.xcconfig */, + C6F65745A44F31F2DE8753D3 /* Pods-RunnerTests.release.xcconfig */, + 48E0DCE5F48CF809F73CECB2 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + 3E8800A69160B3C038B12B9E /* Pods_Runner.framework */, + 78B582CF6BBC70B9E63B936B /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -186,6 +215,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + 9CB1E5E8F3F3312F0F559D6E /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -204,11 +234,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + C36DC009E505E4F8B59E912E /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + C5A6F39736D11E8F2B17E4B6 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -227,7 +259,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C80D4294CF70F00263BE5 = { @@ -328,6 +360,67 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 9CB1E5E8F3F3312F0F559D6E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + C36DC009E505E4F8B59E912E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + C5A6F39736D11E8F2B17E4B6 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -379,6 +472,7 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 3D00C97B4400EB7E0A2A18C4 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -393,6 +487,7 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = C6F65745A44F31F2DE8753D3 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -407,6 +502,7 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 48E0DCE5F48CF809F73CECB2 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -457,7 +553,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -536,7 +632,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -583,7 +679,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 397f3d3..15368ec 100644 --- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + + diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift index d53ef64..b3c1761 100644 --- a/example/macos/Runner/AppDelegate.swift +++ b/example/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/example/pubspec.lock b/example/pubspec.lock index fee253f..d552f49 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,42 +5,42 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.13.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" cupertino_icons: dependency: "direct main" description: @@ -49,14 +49,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.6" + dart_mcp: + dependency: transitive + description: + name: dart_mcp + sha256: "2b34cbd60e3b64e7f770a453b84c5f569f096f6044ea343ddffc99f6b366de45" + url: "https://pub.dev" + source: hosted + version: "0.3.3" + equatable: + dependency: transitive + description: + name: equatable + sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" + url: "https://pub.dev" + source: hosted + version: "2.0.8" fake_async: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" flutter: dependency: "direct main" description: flutter @@ -68,7 +84,7 @@ packages: path: ".." relative: true source: path - version: "0.0.2" + version: "1.0.0" flutter_lints: dependency: "direct dev" description: @@ -95,6 +111,14 @@ packages: description: flutter source: sdk version: "0.0.0" + from_json_to_json: + dependency: transitive + description: + name: from_json_to_json + sha256: "792db26a52983c62da63b473629a4a65022da83dc69252162c5dccdd27b271ac" + url: "https://pub.dev" + source: hosted + version: "0.3.0" gap: dependency: "direct main" description: @@ -103,30 +127,46 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + is_dart_empty_or_not: + dependency: transitive + description: + name: is_dart_empty_or_not + sha256: "31fef508da3e89ab8cd4634d7149f72a0a2e54c841c96b72f393dfc882fdde81" + url: "https://pub.dev" + source: hosted + version: "0.2.3" + json_rpc_2: + dependency: transitive + description: + name: json_rpc_2 + sha256: "82dfd37d3b2e5030ae4729e1d7f5538cbc45eb1c73d618b9272931facac3bec1" + url: "https://pub.dev" + source: hosted + version: "4.1.0" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -139,34 +179,42 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + mcp_toolkit: + dependency: "direct main" + description: + name: mcp_toolkit + sha256: d5cb1502ddf39dcfe6fad1f393f68c172e200d88b3cddd5a6d05655bd984d111 url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.3.0" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.18.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" plugin_platform_interface: dependency: transitive description: @@ -187,23 +235,23 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" state_notifier: dependency: transitive description: @@ -216,34 +264,58 @@ packages: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + url: "https://pub.dev" + source: hosted + version: "0.7.11" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "1.4.0" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 + url: "https://pub.dev" + source: hosted + version: "2.3.1" url_launcher: dependency: "direct main" description: @@ -312,18 +384,18 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "15.0.0" web: dependency: transitive description: @@ -333,5 +405,5 @@ packages: source: hosted version: "0.5.1" sdks: - dart: ">=3.3.0 <4.0.0" + dart: ">=3.10.0-0 <4.0.0" flutter: ">=3.19.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 2f03158..a9eda03 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -40,6 +40,7 @@ dependencies: gap: ^3.0.1 url_launcher: ^6.2.5 flutter_riverpod: ^2.5.1 + mcp_toolkit: ^0.3.0 dev_dependencies: flutter_test: diff --git a/example/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc index 8b6d468..4f78848 100644 --- a/example/windows/flutter/generated_plugin_registrant.cc +++ b/example/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/example/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake index b93c4c3..88b22e5 100644 --- a/example/windows/flutter/generated_plugins.cmake +++ b/example/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST