inkpal_bridge 7.2.0 copy "inkpal_bridge: ^7.2.0" to clipboard
inkpal_bridge: ^7.2.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. 77 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.

pub package pub points

Install once, use everywhere #

# pubspec.yaml
dependencies:
  inkpal_bridge: ^6.1.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 53 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:

  1. Edit code in your assistant
  2. inkpal_hot_reload
  3. inkpal_wait_for_idle
  4. inkpal_assert_no_errors
  5. inkpal_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 — 77 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.

  • Inspectionobserve (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.
  • Interactiontap, tap_with_context, long_press, scroll, enter_text, navigate_to_route, navigate_back, increase_value, decrease_value, set_touch_feedback.
  • Wait + assertwait_for, wait_for_idle, assert_element, assert_no_errors, stability_check.
  • Accessibility + i18naccessibility_audit (WCAG-style code rules), verify_translations.
  • Runtime + logsget_runtime_errors, get_app_logs, get_recent_logs, query_logs (level / category / regex), get_performance.
  • Evaluateevaluate (Dart expression via opt-in evalHook).
  • App extensions / state seedlist_app_extensions, call_app_extension.
  • State time-travelstate_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 optional filter), 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 an InkPalStateAdapter to your Riverpod ProviderContainer / 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_ref takes 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_readiness runs six health checks (build mode, navigator binding, UI reachability, error catcher, state adapter, app router) and returns a single ready_to_drive bool plus a blockers list. One call before any agent-driven session.
  • Default eval + debug values (new in 7.1) — inkpal_evaluate now ships with a built-in mini-language (state.<id>, debug.<key>, route.current, errors.last, elements.count, logs.tail, …). Call InkPalBridge.publishDebugValue('auth.token', () => currentToken) from anywhere in app code — tests read it as debug.auth.token without re-wiring main.dart. Debug-mode only. Never executes arbitrary code.

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.

  • Recordingrecording_start, recording_stop, recording_status, recording_export.
  • Self-healingheal_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 — inkpalRunApp collapses to a plain runApp with zero overhead.
  • Emergency kill-switch (new in 7.2). Even in a debug build, pass --dart-define=INKPAL_BRIDGE=off at 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 8767 is 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 #

Issues + feature requests: GitHub.

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 a debugPrint and continues; VM-service inspection may still work where available, but web is officially UNKNOWN territory. File an issue if you need it.
5
likes
160
points
1.15k
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. 77 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