inkpal_bridge
The reliable execution path between your AI and your Flutter app.
AI writes Flutter code. It can't run it. So it guesses — at the widget
tree, at the state, at whether the change compiled. inkpal_bridge
closes that loop.
10.1: one-command MCP install + runnable sample + adb-forward auto-attempt.
The InkPal loop
AI writes → hot-reloads → drives the UI → verifies → repeats
Every step is one MCP tool call over a loopback HTTP server your app opens on port 8767. No external CLI, no daemon, no ADB round-trip.
Install
# pubspec.yaml
dependencies:
inkpal_bridge: ^10.0.0
// lib/main.dart
import 'package:inkpal_bridge/inkpal_bridge.dart';
void main() => inkpalRunApp(const MyApp());
Paste this into your editor's MCP config once:
{"mcpServers": {"inkpal": {"transport": "http",
"url": "http://127.0.0.1:8767/mcp"}}}
Restart the editor. Done.
What's unique
- Verified execution. Every mutating call returns a
verificationenvelope — did the screen change, did errors fire, did the route move.verify: 'lite' | 'off'on the fast paths. - Confidence + evidence. Five mutating tools (tap, scroll,
enter_text, navigate_to_route, state_override) now return
outcome.confidence(verified/observed/probable/unknown/failed) plus a structuredevidenceblock, and refuse to fire when the caller'sexpect: {frame_stamp, route, app_identity, build_id}doesn't match. - Runtime identity. Every response carries
_meta.runtimewithsession_id,bridge_id,build_id,hot_reload_generation, and 9.0'sfreshness(semantics_seq / route_seq / hot_reload_seq / frame_stamp) — agents catch stale reads without asking. - 80 tools. Inspection, interaction, wait/assert, state time-travel, HTTP memory, hot reload, recording → replay, runtime-health rollup, image cache, reliability report — all in one Dart package.
- Auto-recovery (10.0+). Every migrated mutating tool runs
through five named recovery modes —
widget_not_found,frame_stall,connection_loss,hot_reload_fail,port_collision. Each attempt is bounded, logged into a globalReliabilityMetricsstore, and surfaced viaoutcome.recovered_viawhen it lands. Opt out per call withrecover: false; snapshot + reset the counters throughinkpal_reliability_report. - Zero deps. Just
flutter. No third-party runtime deps ever.
Android
The bridge binds inside the running app. Android emulators need one
adb forward per flutter run:
adb forward tcp:8767 tcp:8767
The bridge's welcome banner prints this command when it detects Android. iOS simulator, macOS, Linux, and Windows share loopback — no forward required.
Platforms
Android, iOS, macOS, Linux, Windows. Flutter web is not currently
supported — dart:io HttpServer is unavailable in the browser
runtime. The bridge skips its HTTP server on web with a debugPrint
and stays inert.
Bundled CLIs
dart run inkpal_bridge:install # one-shot MCP wiring for detected editors
dart run inkpal_bridge:inkpal tap "Save"
dart run inkpal_bridge:inkpal health
dart run inkpal_bridge:inkpal screenshot --out shot.png
dart run inkpal_bridge:doctor # nine-check diagnostic
dart run inkpal_bridge:connect # discover bridge + print editor config
Full release history
See CHANGELOG.md.
Advanced — routers, state adapters, hooks
go_router — pass your router; route tracking is automatic:
final router = GoRouter(routes: [...]);
inkpalRunApp(MyApp(router: router), router: router);
Other routers — pass an onNavigateToRoute callback:
inkpalRunApp(
const MyApp(),
onNavigateToRoute: (route) async => Get.toNamed(route),
);
Live app state — wire an InkPalStateAdapter so state_read,
state_watch, state_override reach your Riverpod / Bloc / Provider /
GetX / MobX store:
inkpalRunApp(
const MyApp(),
stateAdapter: InkPalStateAdapter(
list: () async => [{'id': 'cart.count', 'kind': 'notifier'}],
read: (id) async => {'value': cartNotifier.value},
write: (id, v) async { cartNotifier.value = v as int; return {'success': true}; },
),
);
Custom widgets — teach the semantics walker to recognise them:
inkpalRunApp(
const MyApp(),
walkerHooks: InkPalWalkerHooks(
isInteractiveWidget: (w) => w is BrandButton,
extractTextFrom: (w) => w is BrandButton ? w.label : null,
),
);
Default verify mode — set once for every mutating tool:
inkpalRunApp(const MyApp(), defaultVerify: 'lite');
App identity — set once, echoed on every reply so cross-app orchestration can tell which bridge answered:
inkpalRunApp(const MyApp(), appIdentity: 'com.foo.mymapapp');
Emergency kill-switch — even in a debug build:
flutter run --dart-define=INKPAL_BRIDGE=off
MIT-licensed. Bug reports + PRs welcome at github.com/InkPalAI/inkpal_bridge.
Libraries
- inkpal_bridge
- InkPal Bridge — in-app intelligence for AI-powered Flutter development.