tldraw
Draw and visually collaborate with your agents on tldraw's canvas.
TLtldraw/tldraw51k105 new stars in the last 7 days
Inspect and drive Flutter debug apps/games: semantic snapshots, search, and custom client tools
Tool names on MCP are prefixed with fmt_, binaries and mcpServers keys were renamed — see Migrating from v2.x to v3.0.
This project now uses a shared core execution layer:
flutter-mcp-toolkit is the canonical command surface (connect, inspect, execute, diagnostics). fmtk is the short alias for the same CLI entrypoint.flutter-mcp-toolkit-server is a thin MCP protocol adapter that maps MCP tool/resource calls to the same core executor.tools/list names use the fmt_ capability prefix; CLI exec --name uses command catalog names or aliases outside the MCP tools/call namespace. Resource URIs are unchanged.The shared core module is available as flutter_mcp_core inside this package.
Use this sequence first on macOS:
mcp_toolkit to the app and call MCPToolkitBinding.instance.bootstrapFlutter(...).fmtk validate-runtime (or the canonical long form flutter-mcp-toolkit validate-runtime).fmt_list_client_tools_and_resources,
fmt_client_tool,
fmt_client_resource.Treat exec as expert mode. validate-runtime is the default first-pass proof command.
The CLI is now agent-first and exposes a canonical interface:
exec --name <command> --args <json>schema [--name <command>]capabilitiesserve (JSON-RPC 2.0 over stdio)snapshot create --name <id> [--args <json>]snapshot diff --from <id> --to <id>bundle create --from-snapshot <id> [--output <dir>]doctor [--json] [--target <path>] [--timeout-ms <n>]permissions status|request|open-settings [--kind visual_capture]Safe-write flags for write-producing commands:
snapshot create: --check --diff --backup --no-overwritebundle create: --check --diff --backup --no-overwriteexec targets commands in the shared CommandCatalog (CLI command names may
be bare because they appear only after --name; the CLI is not the MCP
tools/call namespace):
connect, session_start, session_exec, session_end, diagnose, watch, explain_errors, status, discover_debug_apps, get_vm, get_extension_rpcs, hot_reload_flutter, hot_restart_flutter, get_active_ports, get_app_errors, get_screenshots, focus_window, get_view_details, inspect_widget_at_point, capture_ui_snapshot, debug_dump_layer_tree, debug_dump_semantics_tree, debug_dump_render_tree, debug_dump_focus_tree, fmt_list_client_tools_and_resources, fmt_client_tool, fmt_client_resource, dynamicRegistryStats, semantic_snapshot, tap_widget, long_press, enter_text, reveal_search, scroll, swipe, drag, hot_reload_and_capture, evaluate_dart_expression, get_recent_logs.
MCP names. When invoked via MCP
tools/call, exposed tools use thefmt_capability prefix (e.g.fmt_tap_widget,fmt_hot_reload_and_capture). The dynamic-registry host trio is exposed asfmt_list_client_tools_and_resources,fmt_client_tool, andfmt_client_resourcewhere enabled.dynamicRegistryStatsremains available from the CLI catalog for low-level diagnostics and is not part of the default MCP tool surface.
Interaction tools (catalog names: semantic_snapshot → tap_widget / enter_text / reveal_search / scroll / swipe / long_press / drag) follow a Playwright-style ref model: take a snapshot, then pass ref: "s_N" (and optional snapshotId for staleness detection) into the interaction tool. reveal_search is the bounded helper for off-screen semantic targets: it snapshots, matches one narrow selector, scrolls up to maxAttempts, and returns a fresh ref/snapshotId plus trace. hot_reload_and_capture fuses reload + screenshot + fresh snapshot + errors. evaluate_dart_expression runs an ad-hoc Dart expression against the app's root library. See docs/start_here/cli_quick_recipes.mdx and docs/guides/interaction_cookbook.mdx for the full surface and golden paths.
Packaged installs include both flutter-mcp-toolkit and fmtk; examples below use dart run for source-tree development. Prefer fmtk for compact local loops and keep flutter-mcp-toolkit in onboarding, install, PATH, and MCP config docs where the searchable canonical name helps.
CLI runs the same shared command catalog/executor as MCP. Preferred debugging path:
discover_debug_apps -> capture_ui_snapshot -> inspect_widget_at_point.
get_active_ports and dynamicRegistryStats remain available in CLI for low-level diagnostics, but are intentionally not MCP-exposed by default.
# introspection
dart run bin/flutter_mcp_toolkit.dart schema
dart run bin/flutter_mcp_toolkit.dart capabilities
# one-shot execution (machine envelope)
dart run bin/flutter_mcp_toolkit.dart exec --name status --args '{}'
dart run bin/flutter_mcp_toolkit.dart exec --name get_vm --args '{}'
dart run bin/flutter_mcp_toolkit.dart exec --name get_vm --args '{"connection":{"targetId":"ws://127.0.0.1:8181/<token>/ws"}}'
dart run bin/flutter_mcp_toolkit.dart exec --name discover_debug_apps --args '{}'
dart run bin/flutter_mcp_toolkit.dart exec --name capture_ui_snapshot --args '{"connection":{"targetId":"ws://127.0.0.1:8181/<token>/ws"}}'
dart run bin/flutter_mcp_toolkit.dart exec --name inspect_widget_at_point --args '{"x":120,"y":220,"connection":{"targetId":"ws://127.0.0.1:8181/<token>/ws"}}'
# session lifecycle
dart run bin/flutter_mcp_toolkit.dart exec --name session_start --args '{"mode":"uri","uri":"ws://127.0.0.1:8181/<token>/ws"}'
dart run bin/flutter_mcp_toolkit.dart exec --name session_exec --args '{"command":"get_app_errors","arguments":{"count":4}}'
dart run bin/flutter_mcp_toolkit.dart exec --name session_end --args '{}'
# reproducible artifacts
dart run bin/flutter_mcp_toolkit.dart snapshot create --name baseline --args '{"commands":[{"name":"status","args":{}}]}' --check --diff
dart run bin/flutter_mcp_toolkit.dart snapshot diff --from baseline --to after_fix
dart run bin/flutter_mcp_toolkit.dart bundle create --from-snapshot baseline --backup
# environment preflight
dart run bin/flutter_mcp_toolkit.dart doctor --json
dart run bin/flutter_mcp_toolkit.dart permissions status
dart run bin/flutter_mcp_toolkit.dart permissions request
dart run bin/flutter_mcp_toolkit.dart permissions open-settings
dart run bin/flutter_mcp_toolkit.dart exec --name get_extension_rpcs --args '{}'
# explicit capture policy/mode
dart run bin/flutter_mcp_toolkit.dart exec --name get_screenshots --args '{"mode":"desktop_window","permissionPolicy":"auto_request_once"}'
dart run bin/flutter_mcp_toolkit.dart exec --name capture_ui_snapshot --args '{"screenshotMode":"auto","permissionPolicy":"auto_request_once"}'
# one-command runtime validation (after app launch)
dart run bin/flutter_mcp_toolkit.dart --save-images --output-dir .flutter_mcp/app validate-runtime \
--target ws://127.0.0.1:8181/<token>/ws \
--timeout-ms 10000 \
--post-reload-delay-ms 500 \
--after-reload
# same, using global VM URI (equivalent to --target when only one is passed)
dart run bin/flutter_mcp_toolkit.dart --vm-service-uri ws://127.0.0.1:8181/<token>/ws validate-runtime \
--timeout-ms 10000
# optional: install bundled skill during runtime validation
dart run bin/flutter_mcp_toolkit.dart validate-runtime \
--target ws://127.0.0.1:8181/<token>/ws \
--install-skill
CLI runtime gate for app inspection:
ext.mcp.toolkit.app_errors, ext.mcp.toolkit.view_details, ext.mcp.toolkit.view_screenshots, and ext.mcp.toolkit.inspect_widget_at_point from get_extension_rpcs.mcp_toolkit is installed, initialized, and the app is hot restarted or rerun.captureHints on get_view_details or screenshot payloads (platformViewsDetected or weakSignalsDetected). Use mode: desktop_window (or keep auto, which upgrades on macOS host when strong signals are present) instead of flutter_layer.desktopCaptureRetried in the payload). You can also call focus_window then retry get_screenshots with desktop_window.doctor --json --target <ws_uri> --timeout-ms 10000.--output-dir is set, validate-runtime mirrors its JSON envelope to <output-dir>/validate-runtime.json and screenshot files are written under <output-dir>/.mcp_screenshots/.validate-runtime tries host desktop_window first when auto selects it. Executor recovery retries focus+capture once (desktopCaptureRetried). If desktop_window still fails, validate-runtime retries once with flutter_layer (including when platform views are detected, e.g. the showcase AppKitView). Check data.summary.capturePlatformViewsDetected, captureFocusAttempted, and captureFallbackUsed. Web headful capture options: ADR 0007.doctor stays read-only. It now reports visual_capture_backend, visual_capture_permission, visual_capture_truth_mode, and app_permission_bridge.auto_request_once for exec get_screenshots, exec capture_ui_snapshot, and validate-runtime. Raw command schemas still default to check_only.desktop_window (including iOS Simulator and Chrome / web on a Mac host). Screen Recording permission belongs to the host process running flutter-mcp-toolkit, not the Flutter app. Use permissions request for the native prompt and permissions open-settings after a denial.get_view_details and view_screenshots include captureHints when native platform views are detected (AndroidView, UiKitView, AppKitView, HtmlElementView, PlatformViewLink). Texture sets weakSignalsDetected with a soft warning (no auto upgrade). Hybrid engines without platform views can use MCPToolkitBinding.captureHintsContributor in the app.get_screenshots MCP responses include routing metadata in meta and a leading JSON text block when captureHints / warnings are present.AppKitView on macOS (make showcase), HtmlElementView on web (flutter run -d chrome). Use make showcase-stop before a new macOS run.chrome / web / web-server): desktop_window uses macOS ScreenCaptureKit → Chrome CDP (Page.captureScreenshot) → flutter_layer. Global flags: --web-browser-debugging-port, --web-port. Opt-in live test: RUN_WEB_CDP_INTEGRATION=1 dart test test/web_cdp_integration_test.dart. See ADR 0007.permissions or doctor can verify bridge-backed
permission tools/resources. Use --target <ws_uri> or the global
--vm-service-uri <ws_uri> when probing those platforms.desktop_window never silently falls back. auto may fall back to flutter_layer, but responses always report requestedMode, actualMode, permissionStatus, and fallbackReason.Troubleshooting:
flutter-mcp-toolkit permissions status first. If status is still denied, open System Settings from the CLI and grant Screen Recording to the terminal or client process you are using.doctor --json shows visual_capture_truth_mode=flutter_layer on macOS, you are not getting native window pixels yet.desktop_window fails, use flutter_layer or pass --web-browser-debugging-port from Chrome’s --remote-debugging-port. For web targets, pass --flutter-device chrome on validate-runtime. validate-runtime still retries flutter_layer after a failed desktop_window even when platform views are detected.validate-runtime retries those failures before returning red, and may also retry with flutter_layer after a failed desktop_window screenshot.Failure matrix:
missing_mcp_toolkit_wiring: app did not expose the required toolkit extensions. Fix app bootstrap and hot restart.bad_target_uri_or_unreachable_vm_service: explicit target URI is wrong or the VM service is not reachable.permission_denied: host capture backend lacks permission.host_capture_backend_instability: native capture backend flaked during desktop-window capture, usually around reload.error.descriptor; do not parse legacy top-level fields.--check --diff before actual writes.write_blocked explicitly when using --no-overwrite.flutter-mcp-toolkit doctor --json as preflight before VM-dependent execution.One-shot commands return one JSON envelope with stable fields:
{
"ok": true,
"data": {},
"error": null,
"meta": {
"schemaVersion": "core-envelope/v1",
"command": "status",
"timestamp": "2026-03-03T00:00:00.000Z",
"durationMs": 2
}
}
Failures use one strict envelope in error:
code, message, details, descriptor (category, retryable, exitCode, httpLikeStatus), recovery.
The full contract table is documented in docs/ai_agents/troubleshooting.mdx.
serve runs JSON-RPC 2.0 over stdio. Key methods:
initialize, capabilities/get, schema/get, command/execute, watch/start, watch/stop, snapshot/create, snapshot/diff, bundle/create, session/start, session/endwatch/event, session/changedWatch notifications are NDJSON JSON-RPC notifications with monotonic seq per watch.
Targeting contracts:
command/execute: optional params.args.connectionwatch/start: optional params.args.connection (applied once before watch loop starts)snapshot/create: per-step optional args.commands[i].args.connection.flutter_mcp/.flutter_mcp/state.json.flutter_mcp/state.lock.flutter_mcp/snapshots/.flutter_mcp/bundles/State operations use a lock with stale-lock TTL recovery to support concurrent agents safely.
The server keeps startup non-blocking and defers target lock until a VM-dependent call is executed.
Resolution policy for VM-dependent commands/resources:
connection_selection_required when multiple targets exist and no explicit target is provided.Selection-required errors are returned as structured JSON so agents can retry immediately:
{
"code": "connection_selection_required",
"message": "Multiple debug targets detected. Retry with URI connection.targetId.",
"descriptor": {
"category": "validation",
"retryable": true,
"exitCode": 64,
"httpLikeStatus": 409
},
"details": {
"reason": "multiple_targets",
"availableTargets": [
{
"targetId": "ws://127.0.0.1:8181/<token>/ws",
"host": "localhost",
"port": 8181,
"endpoint": "ws://127.0.0.1:8181/<token>/ws",
"isSticky": false,
"isCurrent": false
}
],
"suggestedAction": "retry_with_connection_target",
"example": {
"connection": { "targetId": "ws://127.0.0.1:8181/<token>/ws" }
},
"howToRetry": {
"connection": { "targetId": "ws://127.0.0.1:8181/<token>/ws" }
}
},
"recovery": {
"summary": "Select an explicit VM target and retry the command.",
"fix_command": "flutter-mcp-toolkit exec --name discover_debug_apps --args '{}'"
}
}
CLI one-shot and daemon calls use the same handshake semantics. For ambiguous multi-target sessions, retry with an explicit selector:
{
"connection": { "targetId": "ws://127.0.0.1:8181/<token>/ws" }
}
All VM-dependent MCP tools now accept optional arguments.connection:
{
"connection": {
"targetId": "ws://127.0.0.1:8181/<token>/ws",
"mode": "auto",
"host": "localhost",
"port": 8181,
"uri": "ws://127.0.0.1:8181/<token>/ws",
"forceReconnect": false
}
}
Notes:
targetId is the preferred selector and must be full VM websocket URI.host:port target IDs are rejected; use URI target IDs or connection.uri.connection.uri with exact Flutter machine app.debugPort.wsUri.targetId lookup misses but URI is a full tokenized VM path (/<token>/ws), server attempts direct connect fallback.exec --args and daemon command/execute / watch/start accept the same optional nested connection object.connect_debug_app accepts the same connection shape.fmt_list_client_tools_and_resources, fmt_client_tool, fmt_client_resource) accept the same optional connection.targetId, mode, host, port, uri, forceReconnect.host/port/uri in tool arguments are intentionally rejected by strict schemas.connect and session_start, native selector args (mode, targetId, host, port, uri, force) cannot be mixed with nested connection.Zero-mistake recipe:
app.debugPort.wsUri.{"connection":{"uri":"<that exact wsUri>"}}.targetId, copy exactly from availableTargets / discover_debug_apps.Discovery order is:
flutter attach --machine)Both CLI and MCP server accept:
--flutter-project-dir--flutter-device (for example chrome)--flutter-discovery-timeout-ms--scan-ports (for example 8765-8767,9100)--prefer-target-label (for example staging)The port scan recognizes dart/flutter processes. A desktop app hosts its VM
service inside its own native process and an app started with --no-dds has no
Dart process, so neither is found by name. Start such an app on a fixed VM
service port (flutter run -d macos --device-vmservice-port=8765) and pass that
port to the scanner with --scan-ports=8765.
When several debug apps run at once, let each name itself with
MCPToolkitBinding.instance.setAppIdentity(label: 'my_app · staging'):
discovery reports the label on every target, and --prefer-target-label lets
auto-attach pick one without the caller naming an endpoint. The label narrows
the candidates only at the auto-attach step, so an active connection and a
target connected to explicitly still win; a value that matches no running app
is ignored, so discovery keeps behaving as it would without it.
Manual fallback remains available:
--vm-service-uri ws://127.0.0.1:59490/<token>/wsarguments.connection.uriFor developers who want to contribute to the project or run the latest version directly from source, follow these steps:
Clone the repository:
git clone https://github.com/Arenukvern/mcp_flutter
cd mcp_flutter
Install and build dependencies:
make install
This command installs all necessary dependencies listed in pubspec.yaml and then builds the MCP server.
Add mcp_toolkit Package to Your Flutter App:
The mcp_toolkit package provides the necessary service extensions within your Flutter application. You need to add it to your app's pubspec.yaml.
Run this command in your Flutter app's directory to add the mcp_toolkit package:
flutter pub add mcp_toolkit
or add it to your pubspec.yaml manually:
dependencies:
flutter:
sdk: flutter
# ... other dependencies
mcp_toolkit: ^3.0.0
Then run flutter pub get in your Flutter app's directory.
Initialize in Your App:
In your Flutter application's main.dart file (or equivalent entry point), initialize the bridge binding:
import 'package:flutter/material.dart';
import 'package:mcp_toolkit/mcp_toolkit.dart'; // Import the package
import 'dart:async';
Future<void> main() async {
runZonedGuarded(
() async {
WidgetsFlutterBinding.ensureInitialized();
MCPToolkitBinding.instance
..initialize() // Initializes the Toolkit
..initializeFlutterToolkit(); // Adds Flutter related methods to the MCP server
runApp(const MyApp());
},
(error, stack) {
// You can place it in your error handling tool, or directly in the zone. The most important thing is to have it - otherwise the errors will not be captured and MCP server will not return error results.
MCPToolkitBinding.instance.handleZoneError(error, stack);
},
);
}
// ... rest of your app code
Start your Flutter app in debug mode
flutter run --debug --machine --host-vmservice-port=8181 -d macos
Adjust -d for your device. Prefer the exact app.debugPort.wsUri from machine output in arguments.connection.uri or connection.targetId.
🛠️ Add Flutter MCP Toolkit to your AI client
Recommended (skills + MCP config):
flutter-mcp-toolkit init claude-code # or: cursor | codex | cline | agents-skills
Skills only via the open Agent Skills ecosystem:
npx skills add Arenukvern/mcp_flutter -a cursor -y
See docs/ai_agents/overview.mdx for install paths, lockfiles, and skills.sh.
Manual mcpServers JSON (advanced — use if init is not an option):
Use registry key flutter-mcp-toolkit under mcpServers (canonical). The key flutter-inspector is legacy but still accepted if your config predates the rename. That string is only a mcpServers id — it is not the Claude Code subagent name. The bundled runtime subagent is flutter-mcp-toolkit-runtime (plugin/agents/flutter-mcp-toolkit-runtime.md).
Recommended server args: --resources, --images, --dynamics (defaults are on for resources/images; --dynamics enables the dynamic registry).
Note for Local Development (GitHub Install):
If you installed from GitHub and built locally, point command at mcp_server_dart/build/flutter-mcp-toolkit-server. See Installation from GitHub above.
.cline/config.json:
{
"mcpServers": {
"flutter-mcp-toolkit": {
"command": "/path/to/your/cloned/mcp_flutter/mcp_server_dart/build/flutter-mcp-toolkit-server",
"args": [
"--dart-vm-host=localhost",
"--dart-vm-port=8181",
"--resources",
"--images",
"--dynamics"
],
"env": {},
"disabled": false,
"autoApprove": []
}
}
}
fmt_ prefix, e.g. fmt_get_screenshots)Install the server (edit the path in Cursor after clicking):
Note: fix command path after installation.
Open Cursor's settings
Go to the Features tab
Under "Model Context Protocol", add the server:
{
"mcpServers": {
"flutter-mcp-toolkit": {
"command": "/path/to/your/cloned/mcp_flutter/mcp_server_dart/build/flutter-mcp-toolkit-server",
"args": [
"--dart-vm-host=localhost",
"--dart-vm-port=8181",
"--resources",
"--images",
"--dynamics"
],
"env": {},
"disabled": false,
"autoApprove": []
}
}
}
Restart Cursor
Open Agent Panel (cmd + L on macOS)
Tool calls use fmt_* names on the MCP wire (e.g. fmt_capture_ui_snapshot).
{
"mcpServers": {
"flutter-mcp-toolkit": {
"command": "/path/to/your/cloned/mcp_flutter/mcp_server_dart/build/flutter-mcp-toolkit-server",
"args": [
"--dart-vm-host=localhost",
"--dart-vm-port=8181",
"--resources",
"--images",
"--dynamics"
],
"env": {},
"disabled": false,
"autoApprove": []
}
}
}
./build/flutter-mcp-toolkit-server [options]
Options:
--dart-vm-host Host for Dart VM connection (default: localhost)
--dart-vm-port Port for Dart VM connection (default: 8181)
--resources Enable resources support (default: true)
--images Enable images support (default: true)
--save-images Save captured images as files in temporal folder instead of returning base64 data (default: false)
--dumps Enable dumps support (default: false)
--await-dnd Wait until DND connection is established (default: false). Do not use with Windsurf. Workaround for MCP Clients which don't support tools updates. Important: some clients doesn't support it. Use with caution. (disable for Windsurf, works with Cursor)
--log-level Logging level (default: critical)
--environment Environment (default: production)
-h, --help Show usage text
When --save-images is enabled, the server will:
.mcp_screenshots folder in the current working directoryfile://) instead of base64 encoded image datascreenshot-2025-01-27T10-30-15.123Z.pngThis mode is useful when:
Start your Flutter app in debug mode:
flutter run --debug --dart-vm-host=localhost --dart-vm-port=8181
Run the MCP server:
./build/flutter-mcp-toolkit-server
Draw and visually collaborate with your agents on tldraw's canvas.
TLtldraw/tldraw51k105 new stars in the last 7 days
Let your agent model, build and edit 3D scenes in Blender.
AHahujasid/mcp-for-blender30k489 new stars in the last 7 days
Search your local screen recordings, audio transcripts, and computer activity from screenpipe.
SCscreenpipe/screenpipe22k99 new stars in the last 7 days
Transcribe local audio with FunASR and SenseVoice using private, on-device inference.
MOmodelscope/FunASR21k65 new stars in the last 7 days
Manage assets, control scenes, edit scripts and run tests in the Unity Editor.
COCoplayDev/unity-mcp15k154 new stars in the last 7 days
AI Skills, MCP Tools, and CLI for Unity Engine. Full AI develop and test loop. Use cli for quick setup. Efficient token usage, advanced tools. Any C# method may be turned into a tool by a single line. Works with Claude Code, Gemini, Copilot, Cursor and any other absolutely for free.
IVIvanMurzak/Unity-MCP4.4k37 new stars in the last 7 days