mcpe2e 0.3.1 copy "mcpe2e: ^0.3.1" to clipboard
mcpe2e: ^0.3.1 copied to clipboard

E2E testing library integrated with Model Context Protocol (MCP) for Flutter apps. Enables remote control via HTTP and Claude Desktop integration.

mcpe2e #

Let Claude control a real Flutter app on a device — tap, type, scroll, assert — using MCP tools.

Claude → MCP → mcpe2e_server → HTTP → [your app on device] → real gestures

How it works #

There are two components:

Component What it does
mcpe2e (this package) Runs inside your app. Starts an HTTP server on :7777 that executes real widget gestures.
mcpe2e_server Runs on your machine. Bridges Claude's MCP tools to HTTP calls to the app.

Step 1 — Install the MCP server (once, on your machine) #

curl -fsSL https://raw.githubusercontent.com/JhonaCodes/mcpe2e/main/mcpe2e_server/install.sh | bash

This downloads the binary and registers it with Claude Code automatically. You only do this once.


Step 2 — Add mcpe2e to your Flutter app #

# pubspec.yaml
dependencies:
  mcpe2e: ^0.3.0

Must be in dependencies, not dev_dependenciesMcpMetadataKey extends Flutter's Key and is used in widget tree code. The server never starts in release builds (production safe).


Step 3 — Create a testing file #

// lib/testing/mcp_testing.dart
import 'package:mcpe2e/mcpe2e.dart';
import 'package:flutter/foundation.dart';

// Define keys for each testable widget
const _loginButton = McpMetadataKey(
  id: 'auth.login_button',
  widgetType: McpWidgetType.button,
  description: 'Login button',
  screen: 'LoginScreen',
);
const _emailField = McpMetadataKey(
  id: 'auth.email_field',
  widgetType: McpWidgetType.textField,
  description: 'Email input',
  screen: 'LoginScreen',
);

// Map used by mcpKey() to look up keys by ID
const _keys = <String, McpMetadataKey>{
  'auth.login_button': _loginButton,
  'auth.email_field':  _emailField,
};

/// Call this from main() before runApp().
Future<void> initMcpTesting() async {
  if (!kDebugMode) return; // no-op in release
  McpEvents.instance
    ..registerWidget(_loginButton)
    ..registerWidget(_emailField);
  await McpEventServer.start(); // starts HTTP server on :7777
}

/// Returns the key in debug mode, null in release.
Key? mcpKey(String id) => kDebugMode ? _keys[id] : null;

Step 4 — Initialize in main.dart #

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await initMcpTesting();
  runApp(const MyApp());
}

Step 5 — Assign keys to your widgets #

McpMetadataKey extends Flutter's Key — use it directly:

// Direct
ElevatedButton(
  key: _loginButton,
  onPressed: _handleLogin,
  child: const Text('Login'),
)

// With fallback for release (when you have existing keys)
TextFormField(
  key: mcpKey('auth.email_field') ?? WidgetKeys.emailField,
)

Step 6 — Run and connect #

# Terminal 1 — run the app in debug
flutter run

# Terminal 2 — forward the port once the app is on screen
adb forward tcp:7778 tcp:7777      # Android
# iproxy 7778 7777                 # iOS
# Desktop: no setup needed (set TESTBRIDGE_URL=http://localhost:7777)

Verify the connection:

curl http://localhost:7778/ping
# → {"status":"ok","port":7777}

Step 7 — Ask Claude to test #

With the app running and the port forwarded, use Claude's MCP tools:

get_app_context        → see registered widgets on the current screen
tap_widget             → tap a widget by ID
input_text             → type into a text field
assert_widget          → verify text or state
scroll_widget          → scroll a list

Example prompt to Claude:

"Open the app, go to login, type 'user@example.com' in the email field and tap Login"


Dynamic widgets (lists, cards) #

Register widgets at runtime and unregister when disposed:

class _OrderCardState extends State<OrderCard> {
  late final McpMetadataKey _key;

  @override
  void initState() {
    super.initState();
    _key = McpMetadataKey(id: 'order.card.${widget.id}', widgetType: McpWidgetType.card);
    McpEvents.instance.registerWidget(_key);
  }

  @override
  void dispose() {
    McpEvents.instance.unregisterWidget(_key.id);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => Card(key: _key, child: Text(widget.id));
}

Widget ID convention #

module.element[.variant]

auth.login_button       Button on login screen
auth.email_field        Email input on login screen
order.card.{uuid}       Dynamic card with runtime ID
modal.confirm.delete    Dialog/modal

Event types #

Gestures: tap · doubleTap · longPress · swipe · drag · scroll

Input: textInput · clearText · selectDropdown · toggle · setSliderValue

Nav: hideKeyboard · pressBack · scrollUntilVisible · tapByLabel · wait

Assertions: assertExists · assertText · assertVisible · assertEnabled · assertValue · assertCount


Platform connectivity #

Platform Command
Android adb forward tcp:7778 tcp:7777
iOS iproxy 7778 7777
Desktop TESTBRIDGE_URL=http://localhost:7777 (no forward needed)

Production safety #

  • Server never starts outside kDebugMode / kProfileMode
  • McpMetadataKey is a plain Key subclass — zero overhead in release
  • Screenshot returns {"error":"not_available_in_release"} in release builds

Troubleshooting #

Problem Solution
/ping times out App must be running in debug; check adb forward ran after app started
adb forward fails Run adb devices — device must appear
Widget not found Key must be registered AND widget must be visible on screen
Tap has no effect Widget may be scrolled off-screen or onPressed is null
1
likes
160
points
277
downloads

Documentation

API reference

Publisher

verified publisherjhonacode.com

Weekly Downloads

E2E testing library integrated with Model Context Protocol (MCP) for Flutter apps. Enables remote control via HTTP and Claude Desktop integration.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, logger_rs

More

Packages that depend on mcpe2e