inkpal_bridge 8.0.0
inkpal_bridge: ^8.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. 79 tools over local HTTP. Zero deps.
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.
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: ^8.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. - Runtime identity. Every response carries
_meta.runtimewithsession_id,bridge_id,build_id,hot_reload_generation— agents catch stale reads without asking. - 79 tools. Inspection, interaction, wait/assert, state time-travel, HTTP memory, hot reload, recording → replay, runtime-health rollup, image cache — all in one Dart package.
- 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: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.