agent_wires_mcp 0.1.7
agent_wires_mcp: ^0.1.7 copied to clipboard
MCP server that bridges an LLM agent to a running Flutter app via the Dart VM service. Pairs with agent_wires_probe.
Changelog #
0.1.7 #
- Fix Windows startup crash.
ProcessSignal.sigterm.watch()throwsSignalExceptionon Windows (Failed to listen for SIGTERM ... errno = 50), killing the MCP server before it could serve. The SIGTERM shutdown listener is now guarded with!Platform.isWindows; SIGINT still handles Ctrl+C / client shutdown on Windows. Thanks @mwothman (#15). - Pairs with
agent_wires_probe0.1.9 (recommendedProbeVersionbumped) — picks up the probe's scroll (#12) and screenshot (#13) reliability fixes.
0.1.6 #
Adds the action overlay narration layer (pairs with agent_wires_probe
0.1.7). Tool count grows 24 → 25.
- New
set_action_overlay(enabled)tool toggles the probe's on-screen overlay — the ripples/highlights/flashes drawn where the agent taps, types, points, and looks. It is for a human watching the device and never appears inscreenshot/snapshot. On by default. label_elementnow flashes a point-at highlight on the named element (via the probe) so a watcher sees what was labelled.recommendedProbeVersiontracksagent_wires_probe0.1.7.
0.1.5 #
Boot reliability fix.
Fail fast when flutter run dies during boot #
boot_appno longer hangs for the full timeout when the build fails.FlutterRunner.startawaited the VM-service URI (up to 10 min) but never watched the flutter process itself. Ifflutter run --machinespawned, printed progress ("Running Xcode build..."), then exited without emitting a recognised failure event — a codesign/build failure, no matching device, orflutterresolving to the wrong SDK (e.g. an fvm-managed Flutter not on the spawnedPATH) — the boot wedged on the timeout whileapp_statusshowed a frozen progress line and no flutter/xcodebuild process was even alive.startnow also watchesexitCodeand fails the boot immediately when flutter exits early, attaching a tail of flutter's stderr and the likely causes so the agent gets an actionable error in seconds instead of a multi-minute hang.
0.1.4 #
Docs-only release. No code changes — refreshes the README to match the 0.1.3 tool surface and behaviour so the pub.dev page is accurate.
- Removes the
-ddevice pin from the MCP config examples (it contradicted the "don't pin at registration" guidance and is the phone-vs-simulator stall trap); the agent picks a device at session time vialist_devices+boot_app. - Corrects the tool count to 24.
- Documents that
screenshotreturns{path, …}by default (base64 viareturn_base64: true). recommendedProbeVersiontracksagent_wires_probe0.1.6.
0.1.3 #
Connection- and lifecycle-hardening from a real LLM-agent driving session
(physical iPhone, then simulator) where a dropped VM-service connection
cascaded into multi-minute hangs and leaked processes. No tool additions;
app_status gains probe_version / probe_version_warning / paused_at_start
fields, and hot_reload / hot_restart failures gain recoverable + hint.
Fail-fast instead of hanging on a dead connection #
- Per-call timeout + connection-lost latch. When the device VM-service
connection dropped (
Service connection disposed, JSON-RPC-32603), everyext.qa.*call used to block on the dead socket until cancelled —boot_appran 601s,get_logs633s, evenstop_app219s.VmClientnow bounds each call and latches a lost-connection state the instant the socket closes, a call returnsdisposed, or a call times out; subsequent calls andisProbeAlivefail immediately with a clear "reattach or reboot" error. A dead connection is distinguished from a stale isolate, so it no longer triggers a rebind that would re-hang. - Teardown never blocks on the VM-service.
stop_appbounds VM-service disposal so it always tears down the OS process, even when the connection is already dead.
Reap the whole flutter process tree #
- No more orphaned
flutter run/ DDS / devicectl / iproxy.stop_app(and server exit on SIGINT/SIGTERM) now snapshots the descendant process tree before killing flutter — children reparent to launchd the moment flutter dies — then SIGTERM→SIGKILL the lot. Stops the per-cycle leak that left multiple servers/runs contending for the same device and VM-service.
Resume a paused-at-start launch #
- Frozen
--start-stoppedapps now run. A physical iPhone launched viadevicectl … --start-stoppedboots withmain()paused; nothing resumed it, so the app sat frozen whileapp_statusreadready. Attach now resumes any paused-at-start isolate before locating the QA isolate (a paused isolate hasn't registered the probe yet). When a resume can't be confirmed,app_statusreportspaused_at_start: trueinstead of a misleading healthy status.
Hot reload/restart — recover from DevFS wedges #
- Retry-then-explain on
DevFS synchronization failed.hot_reload/hot_restartretry once (the failure is often transient); if it still fails, the result carriesrecoverable: falseand ahintto runstop_app+boot_app, instead of a baresuccess: false.
Smaller robustness wins #
boot_app(device_id)switches devices without a deadlock. A device switch on a running session force-stops it (process-level) instead of rejecting with "call stop_app first" — which used to deadlock when the VM-service was already dead.- Probe/server version-skew warning. The probe reports its version over
ext.qa.ping;app_statussurfacesprobe_versionand warns when it differs from the version this server pairs with (0.1.5). get_logscan't blow the client token budget. Oversizedmessage/error/stackfields are truncated (default 4000 chars each, with a dropped-char marker) so a single ~110k-char Flutter stack no longer overruns the MCP client limit. Pagination is preserved.
0.1.2 #
Hot-restart robustness from real LLM-agent driving sessions. No tool
additions; app_status gains a probe_attached field.
Self-healing isolate binding #
- Recovers from hot restart automatically. A hot restart collects
the QA isolate and starts a new one. The client used to keep calling
the dead isolate id, so every
ext.qa.*call failed with[Sentinel kind: Collected]for the rest of the session and never recovered.callExtensionnow re-resolves the liveext.qa.*isolate and retries once on a stale-isolate error, so a single tool call recovers transparently. app_statusreportsprobe_attached. Distinct fromstate, which only tracks theflutter runprocess: after a hot restart the process stays up (statestaysready) but the probe moves to a fresh isolate.state:"ready"withprobe_attached:falsenow tells the agent the probe is reattaching, instead of looking healthy while every call fails. The check re-resolves and rebinds when the bound isolate has been collected.- Clearer error when the probe is truly gone. If re-resolution
fails (the app exited, or hot-restarted without
AgentWiresProbe.install()), the agent gets an actionable message instead of the raw VM-service sentinel string.
0.1.1 #
Post-0.1.0 iteration driven by real LLM-agent driving sessions. Tool count grows from 18 → 23 (5 new lifecycle tools). Several tool input schemas gain optional flags; nothing existing breaks.
New: lifecycle tools #
list_devices— runsflutter devices --machineand returns a curated[{id, name, platform, is_emulator, is_supported, sdk}, ...]list. Agent calls this when multiple devices might be connected (the classic 10-min-hang trigger: phone + sim, flutter picks the phone and stalls on signing).boot_app/app_status/stop_app— explicit lifecycle instead of "boot happens magically on first tool call."boot_appacceptsdevice_id(pick the device per session),wait(false for fire-and-forget so the agent can poll progress). Sticks the device choice until the nextstop_app.hot_reload— re-injects edited Dart sources, preserves state + current route. Lazy mode usesflutter run --machine'sapp.restart(true Flutter reload with reassemble); attached mode falls back to VM-servicereloadSources(sources swap, no reassemble).hot_restart— tears down the isolate and re-runsmain(). State lost. Only supported in lazy mode (where we own the flutter subprocess). Attached mode returns a clear "use your own restart" error.
Boot: visible instead of black box #
- Lazy boot.
agent_wires_mcp runno longer blocks onflutter run --machinebefore opening MCP stdio. The handshake returns in milliseconds; flutter only starts when the agent callsboot_app(or any other tool, via auto-boot). Previously Claude Code's 30s connection timeout killed every cold-cache session. - Progress streaming. Each
app.progressand non-errordaemon.logMessageevent from flutter is captured on the session, surfaced inapp_status.latest_progress, and written to MCP-server stderr so Claude Code's MCP log viewer also shows it. A long Xcode build / pod install now reads as "Running Xcode build..." in app_status instead of a silent 5-minute wait. - Fail-fast on launch errors.
app.stopwith an error payload, or any error-level daemon log, immediately fails the boot future with that message. No more 10-minute timeouts on "No supported devices connected." - Recovery. Lazy sessions in
exitedreset toidleon the nextensureReady; the agent can retryboot_appafter a timeout orstop_appwithout reconstructing the MCP server. Attached sessions stay terminal (we don't own that flutter process). - Default boot timeout 5 min → 10 min — large apps with firebase / syncfusion / flutter_quill routinely run past 5 on a cold compile.
Perception tools — smaller, more accurate #
snapshotgainsinclude_unresolved: falseby default. The unresolved array (the worst noise: 10–25k chars of unlabelled Listeners, decorative Switches, FAB carriers) is hidden; the count is reported asunresolved_countinstead. Agents that need to drive a hidden widget (pin-code fields) opt in explicitly.screenshotwrites the PNG to a tmp file and returns{path, width, height, size_bytes}by default — was 329k chars of inline base64 that the agent couldn't actually read. The old behavior is available viareturn_base64: true.inspectgainsinclude_descendants(default true) anddescendant_depth(default 3) — returns a subtree view withpainter+sizeexposed forCustomPaintdescendants. Lets the agent answer "what's inside this Card?" or "is this region drawn pixels?" in one call.
Sync — diagnose what's keeping the app awake #
wait_for_idlereturns a structured payload on timeout:{idle, blocked_by, in_flight_http, has_scheduled_frame, in_transient_callback}. The agent now knows whether to wait longer, retry, or proceed.- New
ignore_animationsflag — drops the frame/animation checks, waits only for HTTP. Use on screens with continuous spring animations that never visually settle.
Tool descriptions #
- All 23 tool descriptions rewritten to disambiguate (snapshot vs.
screenshot, the three
wait_for_*siblings) and teach the canonical agent loop. The biggest behavioral change: agents now reach forsnapshotfirst by default;screenshotcorrectly signals "almost always prefer snapshot."
Internal #
AppSessionintroduced as the lifecycle hub; runner + VM client state lives there with explicit state machine (idle,booting,ready,exited).FlutterRunnercaptures the appId fromapp.started, routes machine-protocol responses to per-request completers, exposes a general-purpose progress callback.DeviceListerwrapsflutter devices --machine; parser tolerates leading log lines.
0.1.0 #
Initial public release.
- 18 MCP tools across five categories: perception (
snapshot,inspect,screenshot), action (tap,long_press,swipe,enter_text,clear_text,scroll,press_back), sync (wait_for_idle,wait_for_route,wait_for_element), observability (get_logs,get_network), and memory (label_element,get_labels,recall). agent_wires_mcp run— bootsflutter run --machine, auto-discovers the VM service URI, and serves MCP over stdio in one process. Forwards--flavor,-t/--target,--dart-define.agent_wires_mcp serve --attach <ws-uri>— attaches to an already- running app.agent_wires_mcp review— local human-curation dashboard atlocalhost:7345for labelling unresolved widgets. Per-project persistence in.flutter_qa/map.json.- Snapshot enrichment — merges human labels and source-location proposals (via the analyzer package) before returning to the agent.
- Set-of-Mark mode —
screenshot(annotated: true)overlays numbered boxes for vision-augmented agents. - Requires the target app to have
agent_wires_probeinstalled.