inkpal_bridge
Let your AI inspect, debug, and control your running Flutter app.
Works with Claude Code, Cursor, Windsurf, Codex CLI, and Copilot in VS Code.
60-second setup
# pubspec.yaml
dependencies:
inkpal_bridge: ^4.0.0
// lib/main.dart
import 'package:inkpal_bridge/inkpal_bridge.dart';
void main() => inkpalRunApp(const MyApp());
flutter run
That's it. No license key required, no signup, no flags. The bridge starts itself and prints:
┌──────────────────────────────────────────────────────────────┐
│ InkPal Bridge active · ws://localhost:8765 │
│ │
│ Connect an AI assistant to see + debug your running app. │
│ │
│ npx inkpal start (one-line setup for Claude/Cursor/…) │
│ │
│ Free for everyone · no API key, no signup │
└──────────────────────────────────────────────────────────────┘
The bridge is already watching for errors, HTTP traffic, navigation events,
and widget-tree changes. It runs entirely debug-only — release builds
collapse inkpalRunApp to a plain runApp with zero overhead.
Connect an AI assistant
AI editors (Claude Code, Cursor, Windsurf, Codex CLI, Copilot) speak MCP over stdio, so they need a small adapter to talk to the bridge's WebSocket. One command installs the adapter into every editor it can find:
npx inkpal start
Restart your editor and ask:
"Audit my UI for layout issues. Tap the save button and screenshot the result."
Without Node
If you don't have Node, register the adapter manually — drop this into
your MCP client's config (Claude Code's .mcp.json, Cursor's
mcp.json, etc.):
{
"mcpServers": {
"inkpal": {
"command": "npx",
"args": ["-y", "inkpal-mcp"]
}
}
}
A Dart-native CLI is on the roadmap so future versions don't require Node at all.
Migrating from 3.x
No source changes required. The 3.x licenseKey: and apiUrl:
parameters on inkpalRunApp / InkPalBridge.init are accepted as
deprecated no-ops in 4.0 — your existing code keeps compiling, you'll
see an analyzer hint, and a one-line debug log:
[InkPal] DEPRECATED: inkpalRunApp(licenseKey: ...) is ignored —
inkpal_bridge is free for everyone in 4.0. Remove the parameter.
To silence both, delete the two parameters:
// 3.x
inkpalRunApp(
MyApp(),
licenseKey: const String.fromEnvironment('INKPAL_LICENSE_KEY'),
apiUrl: 'https://mcp.inkpal.ai',
);
// 4.0
inkpalRunApp(MyApp());
If you launched with flutter run --dart-define=INKPAL_LICENSE_KEY=...,
drop the --dart-define — the env var is no longer read. The deprecated
parameters will be removed in 5.0.
Custom widgets (walkerHooks)
Have proprietary design-system widgets (BrandButton, GlassCard) without
standard Material semantics? Teach the bridge to recognise them:
inkpalRunApp(
const MyApp(),
walkerHooks: InkPalWalkerHooks(
isInteractiveWidget: (w) => w is BrandButton,
extractTextFrom: (w) => w is BrandButton ? w.label : null,
),
);
The agent can then say "tap the Save button" and the bridge resolves it
correctly — no need to wrap every callsite in Semantics(label:).
A working demo lives in example/lib/main.dart.
Router support
Works with every major Flutter router.
go_router — just pass your GoRouter instance. The bridge drives
navigation and tracks the current route automatically, including
imperative context.push(...), with no NavigatorObserver wiring:
final router = GoRouter(routes: [...]);
inkpalRunApp(
MyApp(router: router),
router: router, // drives navigation + auto-tracks the route for observe()
);
Other routers — pass onNavigateToRoute so the bridge can drive named
navigation:
// GetX
onNavigateToRoute: (route) async => Get.toNamed(route),
// Beamer
onNavigateToRoute: (route) async => beamerDelegate.beamToNamed(route),
Standard Navigator works without any callback.
App-state context
Expose your app's runtime state so the agent has more to reason about:
inkpalRunApp(
const MyApp(),
globalStateProvider: () async => {
'user': {'plan': currentUser.plan},
'cart': {'items': cart.length, 'total': cart.total},
},
);
App extensions (seed state + custom ops)
Expose app-specific operations the agent can invoke directly — reset/seed
data, flip a feature flag, jump onboarding — without shipping a new bridge
version. Register handlers and the MCP surfaces them through
inkpal_list_app_extensions / inkpal_call_app_extension:
InkPalAppExtensions.register(
name: 'seed',
description: 'Wipe and reseed the sample dataset.',
handler: (params) async {
await db.reseed();
return {'reseeded': true};
},
);
The agent can now set up any starting state in one call instead of clicking through the UI — ideal for deterministic, repeatable runs.
Architecture
Your AI assistant ⇄ inkpal_bridge ⇄ your running Flutter app
The bridge runs entirely in debug mode. In release builds, inkpalRunApp
collapses to a direct runApp call — zero overhead, zero memory
allocation, no socket, no extension registration. Your release builds
ship as if the bridge wasn't there.
Privacy + security
- Debug-only. Release builds bypass everything.
- Local-first. Communication binds to localhost. Nothing leaves your machine without explicit configuration.
- Sensitive headers redacted.
Authorization,Cookie,X-Api-Key, and similar patterns are stripped before any HTTP request is surfaced to the agent. - Open-source. MIT-licensed.
Advanced configuration
For release-mode shipping, custom server URLs, or fine-grained control over
the bridge's subsystems, use InkPalBridge.init directly:
void main() {
InkPalBridge.init(
serverUrl: 'ws://localhost:8765',
appRunner: () => runApp(const MyApp()),
);
}
See API docs for the full surface.
Support
Issues and feature requests on GitHub.
Requirements
- Flutter ≥ 3.10
- Dart ≥ 3.0
Libraries
- inkpal_bridge
- InkPal Bridge — in-app intelligence for AI-powered Flutter development.