inkpal_bridge 7.0.0 copy "inkpal_bridge: ^7.0.0" to clipboard
inkpal_bridge: ^7.0.0 copied to clipboard

Turn your running Flutter app into an MCP server. Claude Code, Cursor, Windsurf, Codex, Copilot can inspect, drive, hot-reload it. 69 tools over local HTTP. Zero deps.

7.0.0 — final stable #

Consolidates the 6.1 → 6.9 phased ladder into a blessed stable release. Everything that shipped between 6.0 and 6.9 is preserved verbatim.

What 7.0.0 adds on top of 6.9 #

  • inkpal_release_readiness — one-shot pre-flight health check aggregating build mode, navigator binding, UI reachability, error catcher, state adapter, and app router. Returns ready_to_drive plus per-check details and a blockers list. Cheap, side-effect free, designed to be the first call an agent makes.

Consolidated feature map (6.1 → 7.0) #

  • 6.1 — hot-reload driver rewrite (single-listener-per-socket + shared response map keyed by JSON-RPC id), zone-safe bootstrap, scroll reveal: alias, tap expect_frame_stamp guard.
  • 6.2 — verification envelopes on every mutating action (screen_changed, added_labels, removed_labels, frame_stamp, route_before/route_after, errors_since_action).
  • 6.3 — HTTP memory: get_http_log, assert_no_failed_requests, wait_for_request.
  • 6.4 — navigation memory: open_deeplink, get_route_graph, coverage_report.
  • 6.5 — live state adapter. InkPalStateAdapter with optional list/read/write/invalidate callbacks; four new state_* tools. Zero-dep, duck-typed.
  • 6.6 — batched state (Bloc / Provider ergonomics): state_read_many, state_all, filtered state_providers.
  • 6.7 — reactive state (GetX / MobX / Bloc streams): state_watch, state_capabilities.
  • 6.8 — device-side pixel diff: screenshot_save, screenshot_diff, screenshot_list. Pure-dart:ui pixel_diff.dart engine, per-channel threshold, bbox output.
  • 6.9 — reference-PNG compare (screenshot_compare_ref) and bundled dart run inkpal_bridge:connect discovery CLI.
  • 7.0release_readiness aggregate check + consolidated release blessing.

Compatibility contract #

  • 6.0 baseline preserved verbatim. Every 6.0 integration test continues to pass unchanged.
  • No parameters removed from InkPalBridge.init / inkpalRunApp. Every new param is nullable with a null default.
  • No handler response shape reduced. New fields added additively; the verification: envelope on mutating actions is additive to the existing result map (spread, not replaced).
  • Deprecated licenseKey: / apiUrl: params remain accepted-and- ignored no-ops (planned removal in 8.0).
  • Release-mode short-circuit still runs appRunner() synchronously with zero bridge overhead. Verified by the if (kReleaseMode) branches at the top of inkpalRunApp and InkPalBridge.init.

Migration from 6.x #

Bump your dependency constraint. That's it.

dependencies:
  inkpal_bridge: ^7.0.0

No code changes. No new required params. No renames.

Verification #

  • 37/37 mcp_server_test.dart tests pass.
  • 4/4 pixel_diff_test.dart engine tests pass.
  • dart analyze lib bin clean (zero issues).
  • 60 → 69 MCP tools registered.
  • Alias-drift guard exercises every one of the 69 tools against a registered handler in one CI run.

Tool count: 68 → 69. #

6.9.0 #

Ninth step on the ladder — last stop before 7.0.0. Adds reference-PNG compare (no in-memory store required) and a bundled connect CLI that discovers the running bridge and prints editor config.

New #

  • inkpal_screenshot_compare_ref — pixel-diff the current screen against a caller-supplied reference PNG (base64-encoded). Uses the same engine as screenshot_diff from 6.8. Rejects non-PNG blobs by magic-byte check.
  • dart run inkpal_bridge:connect — zero-config discovery helper. Scans ports 8767..8770 (overridable) for a live MCP server, then prints copy-paste editor config for Claude Desktop, Cursor, Windsurf, Copilot. --json for machine-readable output.

Field loop #

inkpal_screenshot_compare_ref({
  reference_png_b64: <figma_export.png>,
  threshold: 20,
})

No file I/O, no reference store to manage, no side effects.

Compatibility #

  • 32/32 mcp_server_test.dart tests pass + 4/4 pixel-diff engine tests. 6.0 baseline preserved verbatim. bin/connect.dart is a standalone Dart executable — has no impact on the runtime bridge.

Tool count: 67 → 68. #

6.8.0 #

Eighth step on the ladder. Device-side pixel diff — closes the "did my tap actually change something visible?" loop without shelling out to pixelmatch or ADB screencap on a host.

New #

  • inkpal_screenshot_save — capture a PNG and stash it in the bridge's in-memory store under name (default "default"). Cleared on dispose(), no file I/O.
  • inkpal_screenshot_diff — capture now, compare against a saved screenshot. Fast path returns identical: true on byte-equal PNGs; otherwise decodes both via dart:ui and returns changed_pixels, changed_percent, changed_bbox, size_delta_bytes. Per-channel RGBA threshold (default 30) — alpha ignored.
  • inkpal_screenshot_list — enumerate the saved screenshots (names, count, total bytes).

Engine #

  • New lib/src/inspection/pixel_diff.dart — pure dart:ui decoder + per-pixel loop. When dimensions differ, the intersection is compared and the extra area outside is counted as fully changed (matches pixelmatch semantics).
  • 4 engine tests exercise identical / all-different / below-threshold / dimension-mismatch shapes.

Loop #

inkpal_screenshot_save({name: 'baseline'})
inkpal_tap({text: 'Toggle dark mode'})
inkpal_wait_for_idle()
inkpal_screenshot_diff({against: 'baseline', threshold: 20})
  → {changed_percent: 87.4, changed_bbox: {left: 0, top: 0, right: 359, bottom: 799}}

Compatibility #

  • 31/31 mcp_server_test.dart tests pass. 6.0 baseline preserved verbatim. Existing take_screenshot handler unchanged.

Tool count: 64 → 67. #

6.7.0 #

Seventh step on the ladder. Reactive state — GetX Rx / MobX @observable / Bloc streams. Same InkPalStateAdapter from 6.5, two new primitives that turn "read-once" into "wait for a change."

New #

  • inkpal_state_watch — wait until a provider's value changes, up to timeout_ms. Adapter-agnostic — the bridge polls read() at poll_ms (default 100ms) and JSON-compares. Optional expected value: return immediately if the current read already differs from what the caller thought.
  • inkpal_state_capabilities — describe what the adapter supports (list / read / write / invalidate / watch). Editors call this once to avoid asking for surfaces the app didn't wire.

GetX example #

final cart = CartController(); // extends GetxController, cart.items is RxList

inkpalRunApp(
  const MyApp(),
  stateAdapter: InkPalStateAdapter(
    list: () async => [
      {'id': 'cart.count', 'kind': 'Rx<int>'},
      {'id': 'auth.user',  'kind': 'Rx<User?>'},
    ],
    read: (id) async => switch (id) {
      'cart.count' => {'value': cart.items.length},
      'auth.user'  => {'value': auth.user.value?.email},
      _            => {'error': 'unknown provider: \$id'},
    },
    // No write / invalidate — GetX prefers explicit controller methods.
  ),
);

Then in the agent:

inkpal_tap({text: 'Add to cart'})
inkpal_state_watch({id: 'cart.count', timeout_ms: 2000})

MobX example #

inkpalRunApp(
  const MyApp(),
  stateAdapter: InkPalStateAdapter(
    list: () async => [
      {'id': 'ui.dark', 'kind': '@observable bool'},
    ],
    read: (id) async => id == 'ui.dark'
        ? {'value': uiStore.darkMode}
        : {'error': 'unknown provider: \$id'},
    write: (id, value) async {
      if (id == 'ui.dark') {
        uiStore.setDarkMode(value as bool);
        return {'success': true};
      }
      return {'success': false};
    },
  ),
);

Compatibility #

  • 29/29 mcp_server_test.dart pass. 6.0 baseline preserved verbatim. Adapter-agnostic — no library-specific plumbing added.

Tool count: 62 → 64. #

6.6.0 #

Sixth step on the ladder. Bloc / Cubit / Provider ergonomics — the same InkPalStateAdapter from 6.5 gains batched primitives so agents can snapshot the whole state tree in one round-trip, and filter the provider list on the bridge side without teaching the adapter about filters.

New #

  • inkpal_state_read_many — batch-read N providers by id, or read every provider list() returns when ids is omitted. Errors surface per-id so one bad read never poisons the whole batch.
  • inkpal_state_all — convenience: fuses list() + read() into one grid. Requires both callbacks on the adapter.
  • inkpal_state_providers — now accepts optional filter (substring match on id / label / kind), bridge-side. The adapter still returns everything; the agent narrows.

Bloc / Cubit example #

inkpalRunApp(
  const MyApp(),
  stateAdapter: InkPalStateAdapter(
    list: () async => [
      {'id': 'auth',    'kind': 'Cubit<AuthState>'},
      {'id': 'counter', 'kind': 'Bloc<CounterEvent, int>'},
    ],
    read: (id) async {
      final bloc = GetIt.I<BlocBase>(instanceName: id);
      return {'value': bloc.state.toString()};
    },
    write: (id, value) async {
      if (id == 'counter') {
        GetIt.I<CounterBloc>().add(CounterSet(value as int));
        return {'success': true};
      }
      return {'success': false, 'error': 'not writable: \$id'};
    },
  ),
);

Provider (ChangeNotifier) example #

inkpalRunApp(
  const MyApp(),
  stateAdapter: InkPalStateAdapter(
    list: () async => [
      {'id': 'theme', 'kind': 'ChangeNotifier<ThemeModel>'},
      {'id': 'cart',  'kind': 'ChangeNotifier<CartModel>'},
    ],
    read: (id) async => switch (id) {
      'theme' => {'value': themeNotifier.mode.name},
      'cart'  => {'value': cartNotifier.items.length},
      _       => {'error': 'unknown provider: \$id'},
    },
  ),
);

Compatibility #

  • 27/27 mcp_server_test.dart pass. 6.0 baseline preserved verbatim. No shape change for any pre-6.6 tool.

Tool count: 60 → 62. #

6.5.0 #

Fifth step on the ladder. Opens a live window into the app's state tree — the piece the 6.0 → ADGP field report kept working around by diffing screenshots and stringifying state through app extensions.

New #

  • InkPalStateAdapter — a zero-dep, duck-typed struct with four optional callbacks (list, read, write, invalidate). Wire it to your Riverpod ProviderContainer / Bloc registry / Provider tree / GetX bindings / MobX stores. The bridge does not import your state library — you decide exactly what is reachable.
  • inkpal_state_providers — enumerate live providers currently exposed by the app.
  • inkpal_state_read — read the current value of one provider by id. Complements state_capture (snapshot journal) with a real-time peek.
  • inkpal_state_override — override a provider's value. Great for agent-driven test setups: seed a logged-in user, pin a locale, flip a feature flag.
  • inkpal_state_invalidate — invalidate / refresh a provider so its next read recomputes. Useful for FutureProvider / StreamProvider.

Wiring #

inkpalRunApp(
  const MyApp(),
  stateAdapter: InkPalStateAdapter(
    list: () async => [
      {'id': 'counter', 'kind': 'StateProvider<int>'},
      {'id': 'user',    'kind': 'FutureProvider<User>'},
    ],
    read: (id) async => switch (id) {
      'counter' => {'value': container.read(counterProvider)},
      'user'    => {'value': container.read(userProvider).toString()},
      _         => {'error': 'unknown provider: \$id'},
    },
    write: (id, value) async {
      if (id == 'counter') {
        container.read(counterProvider.notifier).state = value as int;
        return {'success': true};
      }
      return {'success': false, 'error': 'not writable: \$id'};
    },
    invalidate: (id) async {
      if (id == 'user') {
        container.invalidate(userProvider);
        return {'success': true};
      }
      return {'success': false, 'error': 'not invalidatable: \$id'};
    },
  ),
);

Compatibility #

  • All four state adapter callbacks are individually nullable. Every state_* handler returns a {success: false, error: 'not configured'} envelope when the app hasn't opted in, so tools stay safe to call unconditionally from an editor.
  • 22/22 mcp_server_test.dart still pass. No behaviour change for apps that omit stateAdapter.

Tool count: 56 → 60. #

6.4.0 #

Fourth step on the ladder. Automates the manual missing-link audit the 6.0 field report ran by hand.

New #

  • inkpal_open_deeplink — drive a deep link into the running app. Delegates through the normal navigation chain (router.go / router.push for go_router, then onNavigateToRoute, then Navigator.pushNamed) and reports resolved route, stack, and before/after. Accepts full URIs or bare route names.
  • inkpal_get_route_graph — declared routes (from go_router configuration + inkpalRunApp knownRoutes) vs. visited routes (observer stack + external navigations). Surfaces orphaned entries — declared routes nothing navigated to.
  • inkpal_coverage_report — session coverage: routes declared vs. visited, plus the tappable elements on the current screen.

Internals #

  • _appRouter field added to InkPalBridge so handlers registered inside _registerCommands can reach the app router (the local router name in that scope is the CommandRouter).

Verification #

  • 22/22 mcp_server_test.dart still pass. Compatibility contract holds.

6.3.0 #

Third step on the phased ladder. Answers "what did the app talk to?" in one round-trip.

New #

  • inkpal_get_http_log — every observed HTTP request (method, URL, status, duration, sizes, error, redacted headers). Filter by since_ms, url_pattern (regex), method; cap the tail with max. Sensitive headers (Authorization, Cookie, X-Api-Key, X-Auth-Token, Proxy-Authorization, Set-Cookie) are stripped before storage — the redaction ran in 6.0 already, this exposes it.
  • inkpal_assert_no_failed_requests{ok, failed_count, failures[]} for any request since since_ms that returned >= 400 or threw a transport error. Use after any action that should have succeeded network-wise.
  • inkpal_wait_for_request — waits until a request whose URL matches url_pattern is observed, or timeout_ms elapses. Optional method filter. Great for "tap BOOK NOW, confirm the POST fired and returned 200."

Verification #

  • 6.2 verification-envelope tests plus the 22 pre-6.3 tests still pass verbatim. Compatibility contract holds.

6.2.0 #

Second step on the 6.1 → 7.0 ladder. Backward-compatible with 6.0 and 6.1: every existing response keeps its top-level fields; the new verification object is additive.

New #

  • verification envelope on every mutating action. Same shape as the tap screenChanged / addedLabels / removedLabels pattern from 6.0, extended to scroll, enter_text, navigate_to_route, navigate_back, hot_reload, hot_restart, long_press, and call_app_extension. Each action response now includes:
    "verification": {
      "frame_stamp": 12345,
      "screen_changed": true,
      "added_labels": ["Settings"],
      "removed_labels": ["Home"],
      "route_before": "/home",
      "route_after": "/settings",
      "route_changed": true,
      "errors_since_action": 0,
      "since_ms": 1720000000000,
      "now_ms": 1720000000300
    }
    
    Agents get "did my action work" in one round-trip, without a follow-up screenshot to compare.

Verification #

  • Compatibility contract holds: mcp_server_test.dart still 22/22. Existing 6.0/6.1 top-level response keys unchanged.
  • dart analyze lib/ clean.

6.1.0 #

Field-fix release — first step on the 6.1 → 6.9 → 7.0 phased ladder. Every fix is backward-compatible with 6.0: no inkpalRunApp parameter removed, no rename without alias, no response-shape change that removes existing fields. All 6.0 integration-test assertions still pass verbatim.

Fixed (from the 6.0 ADGP field report §1) #

  • Deep-tree tap — removed the arbitrary 12-level tappable-ancestor cap. Real design systems bury GestureDetector under ContainerPaddingDecoratedBoxStackPositionedAnimatedContainer … The walk now goes to the semantics root, so a BrandCard(onTap: ...) 14 layers deep taps correctly.
  • Hot-reload lifecycle — rewrote HotReloadDriver around a single- listener-per-socket + shared response map. The old "Stream has already been listened to" error after any failed reload is gone; hot-reload can be retried indefinitely without app restart.
  • Zone-safe bootstrapWidgetsFlutterBinding.ensureInitialized() is now called before any zone dance. If the caller wraps main() in runZonedGuarded(...) (Sentry, Bugsnag, custom error reporters), inkpalRunApp runs inside that already-installed error zone instead of nesting a new one. Eliminates the "Zone mismatch" warning surfaced by the ADGP field report.
  • Silent route-wire gap — when neither router:, navigatorKey:, nor onNavigateToRoute: is passed AND InkPalNavigatorObserver isn't attached to MaterialApp.navigatorObservers, the bridge now prints a loud one-line warning ~750 ms after boot with the exact one-line fix. Two days silently lost to route: null — never again.
  • Hot-reload Android hintinkpal_reload_status and inkpal_hot_reload failure envelopes now mention adb reverse tcp:PORT tcp:PORT for Android callers who can't reach the VM Service from the host.

New #

  • reveal: param on inkpal_scrollreveal: "below" | "above" | "left" | "right" names what should come into view (the intuitive frame). The legacy direction: param stays accepted for 6.0 callers; when both are supplied, direction: wins so 6.0 semantics are preserved verbatim.
  • Frame stamps on inspection responses — every response from inkpal_observe, inkpal_get_screen_content, inkpal_get_widget_tree, inkpal_find_widget, plus every tap response, includes frame_stamp (Flutter's currentFrameTimeStamp in microseconds).
  • expect_frame_stamp guard on inkpal_tap — pass the frame stamp from a prior inspection; the tap refuses to fire with frame_stamp_mismatch when the frame has moved on. Catches stale- bounds races after scroll animations.

Migration #

None required. 5.x and 6.0.x code compiles and behaves identically. Previously-broken paths now succeed.

Verification #

  • Compatibility contract: every integration-test assertion that passed in 6.0.x still passes verbatim in 6.1 — the 22-test mcp_server_test.dart suite is a superset of 6.0's 18 tests.
  • dart analyze lib/ clean; flutter test 97 pass / 4 skip / 4 fail (same 4 pre-existing test-isolation failures).

6.0.0 #

Field-validated stable release. Fixes every real-world break surfaced by a day of driving 5.0.0 against a live app, adds hot reload / hot restart so the bridge closes the iterate loop, and clears the last pub.dev score gap.

Fixed #

  • inkpal_tap no longer crashes with "type 'Null' is not a subtype of String". The bridge WS handler assumed params['label'] was a non-null string, but MCP inkpal_tap sends text / key / x / y. 6.0.0 adds a per-tool argument adapter in InkPalMcpTools.adaptArgs(...) that translates MCP names to bridge names before dispatch, plus defensive nullable casts on every WS handler (inkpal_tap, inkpal_enter_text, inkpal_scroll, inkpal_navigate_to_route, inkpal_long_press, inkpal_tap_with_context, inkpal_increase_value, inkpal_decrease_value, inkpal_get_screen_manifest). Field regression: a whole day was lost to adb input tap fallback because of this crash.
  • inkpal_tap({x, y}) now dispatches to the pointer pipeline instead of returning "not found" for an empty label. Raw-coord taps and semantic finders both work through the same MCP tool.
  • Every WS handler returns a structured error envelope on missing args instead of throwing an uncaught cast exception. The MCP client gets a JSON-RPC result telling it what to fix.
  • pubspec description shortened to 167 chars to fit pub.dev's 60–180 window. Recovers the "Provide a valid pubspec.yaml" score.

New #

  • inkpal_hot_reload — trigger a Flutter hot reload from your AI assistant, no external flutter CLI needed. The bridge opens a WebSocket to its own Dart VM Service (available in every debug build) and calls reloadSources. Closes the iterate loop: edit code → inkpal_hot_reload → inkpal_wait_for_idle → inkpal_assert_no_errors → inkpal_screenshot.
  • inkpal_hot_restart — full state-clearing restart.
  • inkpal_reload_status — check whether the VM Service transport is reachable from the running app before attempting a reload.
  • inkpal_scroll now accepts pixels for exact scroll deltas.

Improved #

  • Scroll semantics documented in the schema. direction: down reveals items BELOW the current viewport (the finger swipes UP). Prior 5.x users burned cycles on this — the schema description now spells it out.
  • Welcome banner detects Android and prints the exact adb forward tcp:8767 tcp:8767 command every app boot needs. No more losing a session to a missing forward.
  • Regression tests cover every parameter-adapter rule. Tap null crash, raw-coord tap, routerouteName, textlabel, nested call_app_extension params — all locked in.

Coverage #

  • 56 MCP tools (was 53 in 5.0.0). The three new hot-reload primitives are additive.

Migration #

None required. 5.x code works identically; broken 5.x paths now succeed.

5.0.0 #

The final working version — one dependency, one line, one JSON block, done.

inkpal_bridge is now the whole product. Add the package, wrap runApp, run the app, and every AI coding assistant that speaks MCP can drive it over http://127.0.0.1:8767/mcp. No CLI to install. No npm proxy. No Node. No signup. No API key. No license.

What's new #

  • Full tool surface — the in-process MCP HTTP server now exposes every meaningful command the bridge already serves internally: 53 tools covering inspection (observe, get_widget_tree, find_widget, screen_snapshot, screen_diff, device_metrics, screenshot), interaction (tap, long_press, scroll, enter_text, navigate, increase/decrease_value, touch feedback), wait/assert primitives, accessibility audit, translation coverage, structured log query, evaluate, app-extension state seed, state time-travel (capture / list / get / diff / stream), interaction recording, and self-healing (watch / verify / error context).
  • Every tool goes through the same alias-aware dispatch (InkPalMcpTools.commandFor), so public MCP names like inkpal_tap route to their internal command (tap_element) regardless of how the two evolve.
  • Every tool is regression-guarded — the integration test invokes each of the 53 tools over real HTTP and asserts a routable bridge command exists. Alias drift now fails CI, not fresh installs.

Simplified setup #

# pubspec.yaml
dependencies:
  inkpal_bridge: ^5.0.0
// lib/main.dart
void main() => inkpalRunApp(const MyApp());
// your MCP client's config (Claude Code / Cursor / Codex / …)
{"mcpServers":{"inkpal":{"transport":"http","url":"http://127.0.0.1:8767/mcp"}}}

That's the entire install. No other component.

Notes #

  • No public API removals since 4.1.1. The licenseKey: and apiUrl: deprecated shims from 4.0 stay for one more major so 3.x callers keep compiling.
  • The bridge still binds to loopback only (127.0.0.1), debug-mode only, port-in-use degrades to WS-only. Same safety envelope.

4.1.1 #

Bugfix release. The MCP HTTP transport shipped in 4.1.0 dispatched tools/call by stripping the inkpal_ prefix from the tool name, but several public tool names don't map 1:1 to the bridge's internal WS command names — so calls like inkpal_tap, inkpal_screenshot, and inkpal_get_interactive_elements returned "Method not found" instead of running.

  • Fixed: InkPalMcpTools.commandFor(...) now applies an explicit alias table before falling back to the prefix strip: inkpal_taptap_element, inkpal_screenshottake_screenshot, inkpal_get_interactive_elementsget_screen_content, inkpal_get_runtime_errorsget_error_history, inkpal_get_app_logsget_log_history, inkpal_navigate_backgo_back, inkpal_enter_textset_text, inkpal_list_app_extensionsapp_list, inkpal_call_app_extensionapp_call, inkpal_long_presslong_press.
  • Added: 12-test integration suite (test/mcp_server_test.dart) that spins up a real HttpServer, sends actual JSON-RPC requests, and asserts the full MCP protocol: initialize handshake, notifications, tools/list registry contents, tools/call dispatch (both aliased and prefix-stripped forms), unknown-tool + unknown- method errors, malformed-JSON parse error, CORS preflight, 404s on non-/mcp paths, and clean port release on stop.

4.1.0 #

No CLI install required. The bridge now serves MCP-over-HTTP from inside the running app, so AI editors can connect to it directly — no npx, no Node, no separate process.

New #

  • In-process MCP server. Defaults to http://127.0.0.1:8767/mcp. Editors that support the MCP HTTP transport (Claude Code, Cursor, Windsurf, VS Code Copilot, Codex CLI) connect directly with a single JSON paste into their MCP config — no install of anything besides the Dart package itself.
  • mcpPort: parameter on inkpalRunApp and InkPalBridge.init. Defaults to 8767; pass 0 to disable, or another port to avoid collisions in parallel-run setups.
  • InkPalMcpServer — an HttpServer bound to 127.0.0.1, JSON-RPC 2.0 over POST plus an SSE keep-alive endpoint. Implements initialize, tools/list, tools/call against a bundled registry of ~30 bridge-direct tools that map onto the same CommandRouter the WebSocket transport uses.
  • Welcome banner prints both transports + the JSON snippet to paste into an editor's MCP config.

Improved #

  • The two transports coexist. The bridge keeps its outbound WebSocket (legacy npx inkpal start path) for users who want the full ~150-tool surface that includes cloud catalogs, Figma, and host CLI tools — those still require the npm proxy. Bridge-direct tools (~30) work over either transport with the same handler code.

Notes #

  • The MCP server binds to loopback only (127.0.0.1), permissive CORS only on that interface. No traffic leaves the machine.
  • Disabled in release mode — same kDebugMode gate as the rest of the bridge. Release builds collapse inkpalRunApp to runApp with zero overhead, including the MCP listener.
  • Failure to bind the MCP port (port-in-use, sandboxed runtime) is non-fatal — the WebSocket path continues to work and the bridge logs a warning instead of crashing.

4.0.1 #

README-only update. No code changes.

  • Slim the README to fit the free-package framing: drop the marketing walkthrough, the "what you get" subsections, and the signup-flavoured documentation links.
  • Add a manual MCP-client config block so adopters without Node have an install path that doesn't need npx.
  • Welcome banner no longer mentions a parallel inkpal.ai/setup URL.

4.0.0 #

Free for everyone — and 10 new bridge-side tools. No signup, no API key, no tier check. The bridge runs unconditionally in debug mode.

Migrating from 3.x #

No source changes required. Existing call sites keep compiling. The licenseKey: and apiUrl: parameters are accepted as deprecated no-ops and you'll see a yellow analyzer hint plus a one-line debug log at boot:

[InkPal] DEPRECATED: inkpalRunApp(licenseKey: ...) is ignored —
inkpal_bridge is free for everyone in 4.0. Remove the parameter.

To silence both, delete the two parameters:

// 3.x
inkpalRunApp(
  MyApp(),
  licenseKey: const String.fromEnvironment('INKPAL_LICENSE_KEY'),
  apiUrl: 'https://mcp.inkpal.ai',
);

// 4.0
inkpalRunApp(MyApp());

If you were running with flutter run --dart-define=INKPAL_LICENSE_KEY=..., drop the --dart-define — the env var is no longer read.

The deprecated parameters will be removed in 5.0.

Removed #

  • InkPalLicenseValidator, FeatureGate, FeatureTier, InkPalFeature, InkPalTier, InkPalLicense, InkPalBridge.licenseReady. These were the public API of the license layer. If you imported any of them directly, they're gone; if you only used inkpalRunApp, you won't notice.

New #

  • assert_no_errors — count + samples of FlutterErrors captured since a timestamp. Cheap to call after every gesture in a test loop.
  • stability_check — fused {errors, jank_frames, state_churn, ok} so one round-trip answers "is the app behaving right now."
  • assert_element and get_elements — read-side assertions and typed queries (type / key_prefix / text_contains / tappable_only) with bounds + tappability. Replaces "screenshot then ask the LLM to look."
  • wait_for and wait_for_idle — poll until a predicate matches or the app settles for N consecutive frames. Replaces "tap → screenshot → check → repeat" loops with a single call.
  • accessibility_audit — code-only WCAG checks (touch target ≥48dp, missing labels on interactive elements, image alt text). Runs entirely in-app, no cloud catalog.
  • query_logs — filter the in-app log buffer by level, category, time, and regex pattern. Avoids streaming the full buffer over the wire.
  • evaluate — Dart expression evaluator via an opt-in hook the app registers. Returns the JSON-encoded result. Apps decide which symbols are reachable; off by default.
  • verify_translations — locale key coverage report via an opt-in hook. Catches forgotten translations in CI.

Improved #

  • Welcome banner rewritten — the bridge now identifies itself simply as "free for everyone, no API key, no signup" instead of the old license-state line.

3.0.0 #

The AI-native release. New capabilities for AI-assisted development — and no removed or changed public APIs: every 1.x / 2.x export still imports and compiles.

New #

  • observe() — one-call situational awareness. A single snapshot of the current route, navigation stack, interactive elements, a short text summary, recent logs and errors, and your live app state (optional screenshot). An assistant can ground its next action in one call instead of many reads.
  • App extensions — InkPalAppExtensions.register(...). Expose app-specific operations (reset/seed data, flip a feature flag, jump onboarding) that an assistant can invoke directly, without shipping a new bridge version. Discoverable and callable from the MCP tools.
  • router: on inkpalRunApp. Pass your GoRouter (or any router exposing .go / .push) and the bridge drives named navigation. For go_router it also tracks the current route automatically — including imperative context.push — with no NavigatorObserver wiring.
  • Device metrics. Read the logical viewport size, device pixel ratio, safe-area padding, and orientation so coordinate gestures use the right units.

Improved #

  • Keyed taps are reliable. Tapping by ValueKey now resolves the widget in the element tree and taps its center through the real gesture pipeline — IconButton, FloatingActionButton, and deeply nested buttons that previously couldn't be found now tap correctly. The tappable-ancestor search also reaches deeper widget nesting.
  • Richer tap results. Taps report whether the tap landed, plus the labels that appeared and disappeared, so the outcome is unambiguous.
  • Faster hot reload through the Flutter daemon, with compile errors surfaced directly.
  • Honest gesture inputs. Swipe and drag honor explicit coordinates and report an error on partial input instead of guessing; scroll falls back to a coordinate drag when no scrollable area is detected.

Fixed #

  • Text entry into a missing field now reports failure instead of silently writing to whatever was focused and returning success. The error lists the text fields actually on screen so the caller can retry against the right one.

2.0.0 #

  • Showcase milestone — no public API removals or signature changes. Every export from 1.x still imports and compiles. The one behavioural default flip is called out below ("opt-in error overlay").
  • Opt-in error overlay. inkpalRunApp(enableErrorBoundary: ...) now defaults to false. The in-app banner that previously rendered above every screen sat outside MaterialApp, so it had no Directionality / Overlay ancestor and could cascade into hit-test failures on apps using CustomScrollView / Stack-heavy layouts. The error CATCHER (which feeds inkpal_get_runtime_errors and the bridge's error stream) is a separate subsystem and keeps running regardless of the flag. Pass enableErrorBoundary: true to restore the previous banner.
  • Semantics walker stability. captureScreenContext no longer dispose-and-re-acquires the semantics handle on every call. The always-re-acquire pattern triggered Flutter's debugFrameWasSentToEngine assertion when the capture ran outside a build phase (typical for VM-service callbacks). Re-acquire is now only triggered when the handle is missing.
  • Error overlay layout fix (only relevant if you opt in to it): uses Align instead of Positioned(top:0) so the subtree gets finite constraints, wraps in its own Directionality since the boundary sits above MaterialApp, and drops the Tooltip on the dismiss button (Tooltip needs an Overlay ancestor).
  • 9-zone example app. The example is now a realistic, multi-zone Flutter app covering Core Debug, Visual Debug, Auto-Fix, Runtime Intel, Error Intel, Visual Testing, Developer Experience, Smart Assist, and Forms — each zone planted with the kind of patterns an AI assistant is expected to detect, explain, and act on against a real running app.
  • Smoke test suite covering boot + navigation + grid layout in example/test/smoke_test.dart.
  • inkpalRunApp package-level dartdoc clarified — single recommended entry point, with InkPalBridge.init documented as the power-user / release-mode path.

1.5.0 #

  • Stability milestone. inkpalRunApp is now the single recommended entry point and exposes the full configuration surface. Power-user flags previously reachable only through InkPalBridge.init (knownRoutes, routeDescriptions, onNavigateToRoute, walkerHooks) are now first-class parameters on inkpalRunApp.
  • Example app updated to demonstrate the recommended pattern — multi-zone showcase (counter / forms / list / custom widgets) booted with one inkpalRunApp(...) call, no manual bridge wiring.
  • Example smoke test added (example/test/smoke_test.dart) — three widget tests covering boot, counter interaction, and route navigation.
  • Package-level dartdoc rewritten to lead with the one-line inkpalRunApp(MyApp()) setup. InkPalBridge.init is documented as the power-user / release-mode path.
  • Version constant synced. inkpalBridgeVersion is now bumped in lockstep with the pubspec; ext.flutter.inkpal.ping reports the real shipping version.
  • Manual InkPalBridge.init and direct exports (InkPalErrorCatcher, InkPalHttpMonitor, InkPalErrorBoundary, etc.) remain fully supported — no breaking changes.

1.4.7 #

  • inkpalRunApp now works without any license key. The bridge starts in offline free-tier mode, prints a welcome banner with next-step guidance, and begins watching for errors, navigation, and HTTP traffic immediately.
  • Added periodic idle diagnostics when no client is connected, plus inline connect / disconnect notices when an AI client attaches.
  • Caught errors now surface in a compact console format with location parsed from the stack trace.
  • Default API endpoint now resolves through the canonical InkPal host.
  • Dropped the unused device-fingerprint provisioning path.

1.4.6 #

  • defaultApiUrl updated to the canonical production endpoint.

1.4.5 #

  • Pubspec description trimmed to fit pub.dev's 60–180 character window so search snippets render the full description. No code change.

1.4.4 #

  • README simplified — single primary use case ("let your AI inspect, debug, and control your running Flutter app"), real-conversation example near the top, three-capability summary (Read / Drive / Catch), trimmed command catalog. The example app already demonstrates the full surface.
  • License flow clarified: inkpal_bridge is free for personal and commercial use with a key from inkpal.ai signup. Documented as the install path.

1.4.3 #

  • README rewrite. Stronger positioning ("give your AI a working set of hands inside your Flutter app"), explicit comparison vs marionette_flutter and flutter_driver, simplified quick-start path (1 init call), reordered tier capabilities into use-case columns, added privacy/security section, dropped jargon ("MCP-native"). No code change.
  • Pubspec description rewritten to lead with capability, not acronym — improves first-touch comprehension on pub.dev search results.
  • Custom widgets documented. InkPalWalkerHooks (already public since 1.4.0) is now first-class in the README + has a working demo in example/lib/main.dart. Lets the AI agent recognise proprietary widgets (BrandButton, GlassCard, etc.) by label without Semantics(label:) wrappers. Closes the recognised gap vs marionette_flutter.
  • Example app rewritten as a multi-zone showcase modelled on the InkPal battlefield app — counter zone, forms zone, list zone, custom-widgets zone exercising walkerHooks.
  • Pub.dev score recovery. Score was 145/160. Two deductions fixed: (1) auto_provision.dart:20 and log_buffer.dart:11 had <placeholder> tokens in dartdoc that the analyser flagged as HTML. Replaced with shell-style $placeholder. (2) CHANGELOG now lists 1.4.1+1.4.2+1.4.3 in canonical heading format.
  • Topics updated for discoverability: mcp, ai, claude, copilot, agent (was ai, mcp, testing, automation, devtools).
  • Pricing copy aligned with single Pro model. Earlier 1.4.3 draft carried over the legacy Free / Pro / Studio capability table. Replaced with one "What you can do" table — every capability ships with any valid license (trial or paid). Reflects the locked single-Pro pricing model on inkpal.ai.
  • First-touch pivot: lead with "start free, full power", not prices. Dollar/rupee amounts removed from the README — pub.dev visitors are evaluating, not buying. Pricing details live at inkpal.ai/pricing for visitors who're already convinced. Same change applied to the npm inkpal package README. Studies show pricing-on-first-touch is a conversion killer for dev tools when the product hasn't earned attention yet.

1.4.2 #

  • Version-sync fix. inkpalBridgeVersion constant in lib/src/_version.dart (which ext.flutter.inkpal.ping reports to the MCP server) was stuck at 1.4.0 while pubspec moved to 1.4.1, causing every handshake to mis-report the running bridge version.
  • Pub.dev score recovery. 1.4.1 lost 5 score points because its CHANGELOG.md didn't include a ## 1.4.1 heading. This release fixes the doc convention going forward and documents both 1.4.1 + 1.4.2 changes below.
  • No public API changes from 1.4.1 — drop-in upgrade.

1.4.1 #

  • Packaging hygiene. Untracked stale .dart_tool/ artefacts that were leaking into the published archive (3 entries in package root, 2 under example/). Tarball validation cleaner, no source changes.
  • .pubignore extended to explicitly exclude example/.dart_tool/ and example/build/ so the example/ showcase app's local build state can never bloat future releases.

1.4.0 #

  • App-registered VM extensions. Host apps can expose their own operations via InkPalAppExtensions.register(name:, description:, handler:) under ext.flutter.inkpal.app.<name>. Enumerated + invoked through the new MCP tools so downstream teams extend InkPal without a bridge republish.
  • Synthetic pointer driver + hit-test probe. New coordinate-tap path dispatches PointerAdded/Down/Up/Removed through GestureBinding.handlePointerEvent, interpolating 40px steps for drags. Works on widgets that never emit a SemanticsAction.tap — raw Listener, custom GestureRecognizer, CustomPaint hit regions. Opt-in via x + y params on ext.flutter.inkpal.tap; semantics stays the default. Includes a probeHit() helper that reports whether a point can actually reach a target RenderObject or is absorbed by a ModalBarrier / IgnorePointer / overlay first.
  • Walker hooks for design-system widgets. InkPalBridge.init(walkerHooks: InkPalWalkerHooks(isInteractiveWidget:, shouldStopTraversal:, extractTextFrom:)) lets host apps surface their own widget types to the agent's interaction vocabulary. All three callbacks optional; built-in walker rules still apply when hooks are missing.
  • Zero-config screenshot. When no RepaintBoundary is wired by the host app, falls back to compositing the first RenderView's live layer tree into a fresh Scene and encoding offscreen. Apps that initialise the bridge manually without inkpalRunApp now get working screenshots out of the box. Identical return shape — callers can't tell which path ran.
  • Adaptive scroll. scrollToFind now iterates every on-screen scrollable deepest-first and gives each a full-budget down + up sweep instead of splitting 10/10. Default attempt cap raised to 50; stall detection still breaks the loop early on short lists. Fixes the "scrolling the wrong scrollable" class of miss on nested lists.

1.3.4 #

  • Default API host updated.
  • Topics: add automation, drop debugging.

1.3.3 #

  • B5 fix: element walker re-acquires semantics handle per capture. After multiple push/pop navigation cycles the original SemanticsHandle could become detached from the current PipelineOwner, leaving the semantics tree empty even though widgets render visually. The walker now disposes + re-acquires the handle before each captureScreenContext() call (_reensureSemantics()), and iterates all renderViews instead of only the first. This fixes getWidgetTree + getScreenContent + tap all returning empty/failing after navigation.

1.3.2 #

  • Auto-wired navigator key + observer. Two new globals exported from the package barrel:
    • inkpalNavigatorKeyGlobalKey<NavigatorState> the bridge uses to drive Navigator.pop() directly from ext.flutter.inkpal.goBack, no more "tap the back arrow" gymnastics.
    • inkpalNavigatorObserver — singleton InkPalNavigatorObserver so getCurrentRoute works without consumers having to construct their own observer. Wire on your MaterialApp:
    MaterialApp(
      navigatorKey: inkpalNavigatorKey,
      navigatorObservers: [inkpalNavigatorObserver],
      home: ...,
    )
    
    Without these, goBack falls back to its old tap-the-arrow behaviour.

1.3.1 #

Field-test bug fixes surfaced by running the bridge against a real Flutter benchmark app and exercising ext.flutter.inkpal.* extensions.

  • B1 — Screenshot extension always failed on freshly launched apps. inkpalRunApp now wraps the user's root in a RepaintBoundary tagged with the package-wide inkpalRootRepaintKey. ScreenshotCapture falls back to that key when the configured appContentKey has no context, and the new captureWithDiagnostics() API surfaces the actual exception + truncated stack instead of the opaque "Screenshot capture failed" string. The ext.flutter.inkpal.screenshot extension now reports the real cause of failure.
  • B2 — getCurrentRoute returned null after MaterialPageRoute(builder:) pushes. InkPalNavigatorObserver previously skipped routes whose settings.name was null, leaving the stack empty even after a successful push. The observer now falls back to a <RuntimeType> synthetic identifier (e.g. <MaterialPageRoute<void>>). For stable identifiers across builds, callers should still pass MaterialPageRoute(builder: ..., settings: RouteSettings(name: '/foo')).
  • B3 — getWidgetTree did not surface ValueKey<String>-tagged anchors like Figma Scaffold(key: ValueKey('fig-2-5')). The tree walker now runs an Element-tree pass after the semantics pass and emits a UiElementType.keyed entry (with the new key field on UiElement) for every ValueKey<String>-tagged widget — surfacing structural anchors that don't carry a semantics label of their own.
  • B4 — ext.flutter.inkpal.ping reported the hardcoded version "1.0.0". Added lib/src/_version.dart — a single inkpalBridgeVersion constant that the ping handler reads. Bump this in lockstep with pubspec.yaml (no codegen, no extra dep).

New tests #

  • test/screenshot_capture_test.dart — fallback to inkpalRootRepaintKey
    • structured failure-error coverage.
  • test/route_observer_test.dart — unnamed MaterialPageRoute push now populates the route stack; explicit RouteSettings.name still wins; pop removes the synthetic identifier.
  • test/widget_tree_keyed_test.dartValueKey<String>-tagged Scaffold surfaces with type: keyed; multiple keyed widgets de-dupe; non-string ValueKeys are ignored; JSON round-trip carries the key.

1.3.0 #

  • Socket reconnect hardened — exponential backoff with jitter (500ms base × 2^attempt, ±200ms), 5-minute cap, stops after 30 consecutive failures
  • VM extensions decoupled from WS state — extensions now work regardless of WebSocket connection status
  • Fixed dispose() LateInit crash — safe to call repeatedly, including after failed init
  • _perfMonitor guaranteed initialized in all init() code paths
  • Testing hooks exposed@visibleForTesting getters for router (CommandRouter) and semanticsWalker (SemanticsWalker)
  • SemanticsWalker re-exported from public barrel
  • New tests: reconnect_test.dart, dispose_test.dart, visible_for_testing_test.dart — 12+ new tests across all new/fixed behavior
  • New test fixture: test_app/ with keyed widget harness + 40 integration tests covering all 33 VM extensions
  • New CI: .github/workflows/bridge-e2e.yml runs on Android emulator + iOS simulator on every bridge push

1.2.2 #

  • Remove comparison-to-alternatives section from README
  • No code changes

1.2.1 #

  • Sharpened pubspec description with target search keywords (Flutter MCP, runtime error capture, HTTP monitor, VM service extensions)
  • README: added comparison table vs moinsen_runapp and marionette_mcp
  • README: bumped install version to ^1.2.1
  • No code changes

1.2.0 #

  • Added InkPalErrorCatcher: three-layer error capture (FlutterError + PlatformDispatcher + runZonedGuarded) with time-windowed deduplication and handler chaining
  • Added InkPalHttpMonitor: read-only HTTP request monitor with redacted headers, ring buffer, and collision detection against InkPalNetworkInterceptor
  • Added InkPalErrorBoundary: Stack-based overlay widget that survives app rebuild failures, with debug + release builders
  • Added generateInkPalBugReport: packages errors, HTTP, logs, route, and app state as markdown for LLM consumption (capped at 8k chars)
  • Added inkpalRunApp: drop-in runApp replacement wiring error catcher + HTTP monitor + error boundary + bridge init inside a guarded zone
  • Added optional errorCatcher and httpMonitor parameters to InkPalBridge.init (backward-compatible)
  • Fixed ErrorSubscriber silently dropping errors before startWatching() — ring buffer now always populated, _watching gates only stream notification

1.1.0 #

  • WebSocket connect timeout (10s) with TimeoutException on miss
  • licenseReady future on InkPalBridge for license-gated startup flows
  • Grace period enforcement moved to tier getter (callers bypassing hasFeature() now get downgraded correctly)
  • Screenshot capture wrapped in 5s timeout — returns null instead of hanging
  • License validator test suite (validation flow, grace period, throttle, network failure)

1.0.1 #

  • Shortened package description for pub.dev display
  • Fixed homepage URL (now points to GitHub repo)
  • Removed web platform declaration (package uses dart:io)
  • Suppressed hasFlag deprecation warnings

1.0.0 #

Initial stable release.

Core #

  • WebSocket-based bidirectional communication (JSON-RPC 2.0)
  • Automatic reconnection with exponential backoff (1s-64s)
  • 10-second connection timeout
  • Message buffering during disconnects (100 message cap)
  • Zero overhead in release builds (bridge is null)

Inspection #

  • Semantics tree walking — read UI without instrumentation
  • Widget tree as structured JSON
  • Element search by label or text
  • Screenshot capture with configurable width (default 720px, 5s timeout)
  • Screen context caching with automatic invalidation

Interaction #

  • Tap, long press, double tap by label/key/semantics
  • Text field input
  • Scroll (directional + scroll-to-element)
  • Slider/stepper increment and decrement
  • Route navigation (push, pop, go)
  • Route tracking via NavigatorObserver
  • Full navigation stack access
  • Route discovery (all routes seen in session)
  • Support for standard Navigator, go_router, GetX, and Beamer
  • Custom onNavigateToRoute callback for any router

Telemetry #

  • Structured logging (log, debug, warning, error levels)
  • Real-time error streaming with immediate delivery
  • Log batching (500ms intervals for non-error logs)
  • Error context enrichment (widget tree, state, recent logs)
  • Log correlation — time-windowed action-to-log mapping

State Time-Travel #

  • State snapshot capture
  • Snapshot listing and retrieval
  • State diffing between any two snapshots
  • Live state stream observation
  • Max 50 snapshots with automatic eviction

Interaction Recording #

  • Record user/AI interactions as typed actions
  • Export as JSON or Dart integration test code
  • Recording status monitoring

Layout Diffing #

  • Screen layout snapshot capture
  • Before/after layout comparison

Performance #

  • FPS monitoring
  • Jank detection

Network Control (Studio tier) #

  • Per-URL HTTP mock rules (pattern matching, response code, body, delay)
  • Offline mode (block all requests)
  • Latency and packet loss simulation

VM Service Extensions #

  • 33 extensions registered under ext.flutter.inkpal.*
  • Fallback channel for local development without WebSocket
  • Mirrors all WebSocket commands

License Gating #

  • Three tiers: Free, Pro, Studio
  • Server-side validation with signed grants (HMAC-SHA256)
  • 24-hour grant TTL with 7-day grace period
  • Validation throttling (60s cooldown)
  • licenseReady future for callers needing gate sync

App Manifest #

  • Rich AiAppManifest for LLM context
  • Screen manifest with widget descriptions
  • App map with full structure

Touch Visualization #

  • Visual ripple overlay for AI-driven interactions
  • Configurable via TouchVisualizerController

Platform #

  • Zero third-party dependencies (Flutter SDK only)
  • Flutter 3.10+ / Dart 3.0+
  • MIT license
5
likes
160
points
1.02k
downloads

Documentation

API reference

Publisher

verified publisherinkpal.ai

Weekly Downloads

Turn your running Flutter app into an MCP server. Claude Code, Cursor, Windsurf, Codex, Copilot can inspect, drive, hot-reload it. 69 tools over local HTTP. Zero deps.

Homepage
Repository (GitHub)
View/report issues

Topics

#mcp #ai #claude #copilot #agent

License

MIT (license)

Dependencies

flutter

More

Packages that depend on inkpal_bridge