inkpal_bridge 6.0.0
inkpal_bridge: ^6.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. 56 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.
Install once, use everywhere #
# pubspec.yaml
dependencies:
inkpal_bridge: ^6.0.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:
- 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.
What you get — 56 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. - Recording —
recording_start,recording_stop,recording_status,recording_export. - 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. - 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 #
Issues + feature requests: GitHub.
Requirements #
- Flutter ≥ 3.10
- Dart ≥ 3.0