inkpal_bridge 7.8.0
inkpal_bridge: ^7.8.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. 78 tools over local HTTP. Zero deps.
inkpal_bridge #
Turn your running Flutter app into an MCP server. Any AI coding assistant — Claude Code, Cursor, Windsurf, Codex CLI, Copilot in VS Code — can inspect, drive, and debug your live app. One dependency. One line of code. One JSON block. Done.
If
inkpal_bridgesaves you an afternoon, drop a ⭐ on GitHub or a 👍 on pub.dev — it's the single strongest signal that keeps this project moving. Bug reports and feature requests are welcome at InkPalAI/inkpal_bridge/issues.
Install once, use everywhere #
# pubspec.yaml
dependencies:
inkpal_bridge: ^7.7.0
// lib/main.dart
import 'package:inkpal_bridge/inkpal_bridge.dart';
void main() => inkpalRunApp(const MyApp());
flutter run
That's it. The bridge starts an MCP HTTP server on
http://127.0.0.1:8767/mcp and prints:
┌──────────────────────────────────────────────────────────────┐
│ InkPal Bridge active │
│ MCP: http://127.0.0.1:8767/mcp │
│ │
│ Paste into your MCP client's config: │
│ {"mcpServers":{"inkpal":{"transport":"http", │
│ "url":"http://127.0.0.1:8767/mcp"}}} │
│ │
│ Free for everyone · no API key, no signup │
└──────────────────────────────────────────────────────────────┘
Paste the JSON into your AI assistant's MCP config (below) and restart it. Every one of the 78 bridge tools is now reachable from your editor.
Wire your AI assistant #
Each assistant has its own MCP config location — the shape is the same.
Claude Code — ~/.claude/mcp.json or .mcp.json in your project root:
{
"mcpServers": {
"inkpal": {
"transport": "http",
"url": "http://127.0.0.1:8767/mcp"
}
}
}
Cursor — ~/.cursor/mcp.json: same block.
Codex CLI — ~/.codex/config.toml:
[[mcp_servers]]
name = "inkpal"
transport = "http"
url = "http://127.0.0.1:8767/mcp"
Windsurf, VS Code (Copilot), and any other MCP-aware editor use the same JSON shape as Claude Code / Cursor.
Restart the editor once and ask:
"Take a screenshot, then tap the settings icon and tell me what changed."
Android setup — one command per app boot #
The MCP server binds to 127.0.0.1:8767 inside the running app. On
Android, the emulator's loopback is not the host's loopback, so you
need to forward the port once every time you flutter run:
adb forward tcp:8767 tcp:8767
The bridge prints this command in its welcome banner when it detects Android, so you don't have to remember. iOS simulator, macOS, Linux, and Windows share loopback with the host — no forward required.
The iterate loop (new in 6.0) #
inkpal_hot_reload closes the last gap. The bridge triggers hot reload
via its own Dart VM Service, so your assistant can drive the whole
edit-verify cycle without leaving the editor:
- Edit code in your assistant
inkpal_hot_reloadinkpal_wait_for_idleinkpal_assert_no_errorsinkpal_screenshot— visual proof of the change
No external flutter CLI. No flutter run --machine daemon. The VM
Service is already there in every debug build.
Stale-target guard (read this if you script the bridge) #
Every element in an inspection response carries a nodeId (the
underlying SemanticsNode.id). Flutter reuses these IDs after enough
tree churn — snapshot screen A, get nodeId: 42, navigate to B, and
some new element on B can legitimately receive nodeId 42.
The bridge ships a guard for exactly this. Every inspection response
(get_screen_content, get_widget_tree, find_widget) also carries a
frame_stamp. Pass that value back as expect_frame_stamp on any
subsequent action:
inkpal_get_interactive_elements()
→ {elements: [...], frame_stamp: 8412}
inkpal_tap({text: 'Save', expect_frame_stamp: 8412})
→ success, OR
→ {error: 'frame_stamp_mismatch', ...} ← app rendered a new frame,
re-inspect before tapping
Same guard for wait_for_stable (new in 7.2). If you're driving a
route transition, chain them:
inkpal_tap({text: 'Next'})
inkpal_wait_for_stable()
inkpal_get_interactive_elements() // fresh snapshot with new frame_stamp
Not passing expect_frame_stamp is safe when there was no navigation
between inspect and act. After any route change, always re-inspect.
What you get — 78 tools, all in-app #
The bridge exposes every tool your assistant needs to look at, drive, assert, and reason about your live Flutter app. No process to spawn, no external service, no ADB round-trips.
- Inspection —
observe(fused route + elements + logs + state in one call),get_interactive_elements,get_elements(typed query),get_widget_tree,find_widget,get_current_route,get_routes,get_app_state,get_app_map,get_screen_manifest,device_metrics,screenshot,screen_snapshot,screen_diff. - Interaction —
tap,tap_with_context,long_press,scroll,enter_text,navigate_to_route,navigate_back,increase_value,decrease_value,set_touch_feedback. - Wait + assert —
wait_for,wait_for_idle,assert_element,assert_no_errors,stability_check. - Accessibility + i18n —
accessibility_audit(WCAG-style code rules),verify_translations. - Runtime + logs —
get_runtime_errors,get_app_logs,get_recent_logs,query_logs(level / category / regex),get_performance. - Evaluate —
evaluate(Dart expression via opt-inevalHook). - App extensions / state seed —
list_app_extensions,call_app_extension. - State time-travel —
state_capture,state_list,state_get,state_diff,stream_state. - Live state tree (new in 6.5, batched in 6.6, reactive in 6.7) —
state_providers(with optionalfilter),state_read,state_read_many(batch),state_all(list+read in one call),state_override,state_invalidate,state_watch(wait for value change),state_capabilities(what does the adapter support). Wire anInkPalStateAdapterto your RiverpodProviderContainer/ Bloc registry / Provider tree / GetX bindings / MobX stores. Bridge stays zero-dep. - Navigation memory (new in 6.4) —
open_deeplink,get_route_graph,coverage_report. - HTTP memory (new in 6.3) —
get_http_log,assert_no_failed_requests,wait_for_request. - Device-side pixel diff (new in 6.8) —
screenshot_save,screenshot_diff(per-pixel RGBA with per-channel threshold + bbox),screenshot_list. Zero external image tools. Screenshots capture the primary render view only; multi-view apps (Foldable, split- screen) will show a single view. - Reference-PNG compare (new in 6.9) —
screenshot_compare_reftakes a caller-supplied PNG (base64) and diffs it against the current screen. Great for "does this screen match the Figma export?" - Release readiness (new in 7.0) —
release_readinessruns six health checks (build mode, navigator binding, UI reachability, error catcher, state adapter, app router) and returns a singleready_to_drivebool plus a blockers list. One call before any agent-driven session. - Default eval + debug values (new in 7.1) —
inkpal_evaluatenow ships with a built-in mini-language (state.<id>,debug.<key>,route.current,errors.last,elements.count,logs.tail, …). CallInkPalBridge.publishDebugValue('auth.token', () => currentToken)from anywhere in app code — tests read it asdebug.auth.tokenwithout re-wiringmain.dart. Debug-mode only. Never executes arbitrary code. - Recording → replay (new in 7.3) —
inkpal_recording_start/inkpal_recording_stopcapture the taps, scrolls, and text the agent performs.inkpal_recording_exportemits a runnableflutter_testintegration test file (depends only onflutter_test+integration_test, noinkpal_bridgeat runtime). Commit the file. Or replay it live withinkpal_recording_replayto reproduce a bug the agent walked into. - Starter kits + opt-in telemetry (new in 7.4) — the
example/folder ships copy-pasteInkPalStateAdapterrecipes for Riverpod, Bloc, and Provider/GetIt. Zero new deps — you own the glue. Optional anonymous usage telemetry: passtelemetryEndpoint:toinkpalRunAppto POST a{tool_name, latency_ms, success, ts_ms, session_id, bridge_version}ping after each MCP call. Off by default, silent-fail, non-blocking. RespectsDO_NOT_TRACK=1and--dart-define=INKPAL_TELEMETRY=off.
Bundled connect helper #
Run dart run inkpal_bridge:connect from your app dir to discover the
running MCP server and print copy-paste editor config for Claude
Desktop / Cursor / Windsurf / Copilot. Passes --json for
machine-readable output.
Bundled doctor helper (new in 7.6) #
Run dart run inkpal_bridge:doctor from your app dir for an eight-check
diagnostic: Dart / Flutter toolchain, inkpal_bridge in pubspec.yaml,
bridge reachable on 127.0.0.1:8767, release-readiness (via
inkpal_release_readiness), INKPAL_BRIDGE=off hint, Android adb devices + adb reverse for tcp:8767, Flutter Web target warning, and
~/.claude/mcp.json writable. Each check prints PASS / FAIL / SKIP with
a rationale. Exits 0 when every check is PASS or SKIP, 1 on any
FAIL. Flags: --json (machine-readable), --check=<n> (run a single
check), --port <n> (probe an extra bridge port first).
- Recording —
recording_start,recording_stop,recording_status,recording_export(JSON or a runnableflutter_testintegration test — noinkpal_bridgedep in the emitted file),recording_replay(new in 7.3 — read a JSON recording and drive the app back through the same steps). - Self-healing —
heal_watch_start,heal_watch_stop,heal_get_errors,heal_get_error_context,heal_verify_no_error. - Hot reload / restart (new in 6.0) —
hot_reload,hot_restart,reload_status.
Every tool is dispatched through an alias table verified in CI, so
public MCP names like inkpal_tap always route to the right internal
command.
Advanced: routers, state, custom widgets #
Custom design-system 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,
),
);
go_router — pass your router and route tracking is automatic
(including imperative context.push):
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 — expose it so observe() includes it:
inkpalRunApp(
const MyApp(),
globalStateProvider: () async => {
'user': {'plan': currentUser.plan},
'cart': {'items': cart.length, 'total': cart.total},
},
);
State-seed extensions — register app-specific ops the agent can invoke directly (reseed data, flip a flag, jump onboarding):
InkPalAppExtensions.register(
name: 'seed',
description: 'Wipe and reseed the sample dataset.',
handler: (params) async {
await db.reseed();
return {'reseeded': true};
},
);
The agent discovers registered extensions via inkpal_list_app_extensions
and invokes them via inkpal_call_app_extension.
Dart expression evaluator (opt-in):
inkpalRunApp(
const MyApp(),
evalHook: (expression) async {
// Only expose what you want reachable — the bridge holds no default.
return await MyEvalScope.run(expression);
},
);
Privacy + security #
- Debug-only. Release builds bypass everything —
inkpalRunAppcollapses to a plainrunAppwith zero overhead. - Emergency kill-switch (new in 7.2). Even in a debug build, pass
--dart-define=INKPAL_BRIDGE=offat build/launch time to skip bridge init entirely. Use for staging builds where you don't want a network-accessible automation layer, or CI where port 8767 is shared. - Loopback-only. The MCP server binds to
127.0.0.1; nothing leaves your machine. - Sensitive headers redacted.
Authorization,Cookie,X-Api-Key, and similar patterns are stripped from any HTTP traffic surfaced to the agent. - Port-in-use is non-fatal. If
8767is taken the bridge logs a warning and stays healthy over its WebSocket fallback. - MIT-licensed. Open source.
Migrating from 4.x #
No source changes required. The licenseKey: and apiUrl: deprecated
shims from 4.0 continue to be accepted and ignored so 3.x code keeps
compiling. Delete them from your inkpalRunApp(...) call whenever you
want the analyzer to go quiet.
Support & community #
- Bug reports + feature requests: github.com/InkPalAI/inkpal_bridge/issues
- Star the repo if the bridge saves you time: github.com/InkPalAI/inkpal_bridge — stars are the single strongest signal that keeps 7.x moving.
- Like on pub.dev to help other Flutter devs find it: pub.dev/packages/inkpal_bridge
- Discussions + roadmap: InkPalAI/inkpal_bridge/discussions
MIT-licensed. PRs welcome — the tool surface is deliberately public so
adding a new tool is a single-file change in
lib/src/communication/mcp_tools.dart.
Requirements #
- Flutter ≥ 3.10
- Dart ≥ 3.0
- Platforms: Android, iOS, macOS, Linux, Windows. Flutter web
is not currently supported — the MCP HTTP transport uses
dart:io HttpServer, which the browser runtime does not implement. As of 7.2 the bridge skips its HTTP server on web with adebugPrintand continues; VM-service inspection may still work where available, but web is officially UNKNOWN territory. File an issue if you need it.