MCP Toolkit for Flutter

pub package pub points skills.sh License: MIT Flutter smithery badge All Contributors maintained with Skill Steward

Note

Hi! This is not official package - it's a personal project.

For official package - please see ai repository

This package is a core component of the flutter-mcp-toolkit project. It acts as the "client-side" library within your Flutter application, enabling the Model Context Protocol (MCP) MCP Server to perform Flutter-specific operations like retrieving application errors, capturing screenshots, and getting view details.

flutter-mcp-toolkit is a Dart MCP server + Flutter package that lets AI Agents (Codex, Zed, Cursor, Intent, Claude Code, Cline, etc..) take (semantic snapshots, tap widgets, type into forms, hot-reload, and read logs from a Flutter app) or create its own tools and resources at runtime using MCP Toolkit β€” without leaving the conversation and work with Flutter apps in closed feedback loop - see example of it described in OpenAI Agentic Harness.

!NOTE

  • The architecture of package may change significantly.

View Screenshots

Data Transparency

This package is designed to be transparent and easy to understand. It is built on top of the Dart VM Service Protocol, which is a public protocol for interacting with the Dart VM.

To make it simple and customizable - it divided into groups of methods which acts as method handlers.

For example, the default first-pass path is bootstrapFlutter().

All methods are available only in debug mode and wrapped in assert statements.

Register custom surfaces with AgentCallEntry (re-exported from intentcall_core), then bootstrap β€” composition first, lifecycle last:

final binding = MCPToolkitBinding.instance;
await binding.addEntries(entries: {
  AgentCallEntry.resource(
    namespace: 'app',
    name: 'app_runtime_status',
    description: 'Read-only app diagnostics',
    mimeType: 'application/json',
    handler: (final args) async => AgentResult.success(
      message: 'App runtime diagnostics',
      data: {'ready': true},
    ),
  ),
});
await binding.bootstrapFlutter(runApp: () => runApp(const MyApp()));

Platform surfaces (WebMCP, native AppIntents) are projections you opt into β€” each lives in its own package and takes an explicit authorization policy:

// pubspec.yaml: intentcall_platform_sync (web tier, pure Dart)
binding.addEntryListener(
  WebMcpProjection(policy: myPolicy).entriesChanged,
);

Or use mcpToolkitTool / mcpToolkitResource when you already have MCPToolDefinition + MCPCallResult handlers (see example/fibonacci_tool_example.dart). These helpers are compatibility bridges; new reusable registry, session, and result behavior belongs in IntentCall packages.

App-side permission bridging is separate and opt-in:

MCPToolkitBinding.instance
  ..initialize()
  ..initializeFlutterToolkit()
  ..initializeFlutterPermissionToolkit(delegate: MyPermissionDelegate());

Features

  • Auto register tools and resources in MCP server:
addMcpTool(
  mcpToolkitTool(
    namespace: 'app',
    definition: MCPToolDefinition(
      name: 'calculate_fibonacci',
      description: 'Calculate the nth Fibonacci number and return the sequence',
      inputSchema: ObjectSchema(
        required: ['n'],
        properties: {
          'n': IntegerSchema(
            description: 'The position in the Fibonacci sequence (0-100)',
            minimum: 0,
            maximum: 100,
          ),
        },
      ),
    ),
    handler: (final request) {
      final n = int.tryParse(request['n'] ?? '0') ?? 0;
      return MCPCallResult(
        message: 'Fibonacci number at position $n is ${fibonacci(n)}',
        parameters: {'result': fibonacci(n)},
      );
    },
  ),
);
  • VM Service Extensions: Registers a set of custom VM service extensions (e.g., ext.mcp.toolkit.app_errors, ext.mcp.toolkit.view_screenshots, ext.mcp.toolkit.view_details, ext.mcp.toolkit.semantic_snapshot, ext.mcp.toolkit.reveal_search, ext.mcp.toolkit.tap_widget, …).
  • Error Reporting: Captures and makes available runtime errors from the Flutter application.
  • Screenshot Capability: Allows external tools to request screenshots of the application's views.
  • Application Details: Provides a mechanism to fetch basic details about the application's views.
  • Semantic Snapshot + Gestures (SemanticSnapshotService, RevealSearchService, GestureInteractionService): Compact JSON snapshot of interactive widgets with stable refs, bounded reveal/search for off-screen semantic targets, plus ref-driven tap/long_press/enter_text/scroll/swipe/drag using a two-tier (semantic-action β†’ pointer-event) dispatch.
  • Log Capture (LogCaptureService): Ring buffer of recent print/debugPrint output surfaced via get_recent_logs.
  • Optional Permission Bridge: Lets the app expose permission status/request/open-settings handlers only when you register a delegate.
  • Capture hints for hybrid rendering: view_details and view_screenshots expose captureHints when native platform views or external Texture widgets are detected. Apps that render via WGPU/Metal/Vulkan without platform views can opt in:
MCPToolkitBinding.instance.captureHintsContributor = () {
  return const PlatformViewHints(
    platformViewsDetected: true,
    matches: [],
    recommendedMode: kCaptureHintRecommendedDesktopWindow,
    warning: kPlatformViewWarning,
  );
};

Import PlatformViewHints and constants from package:mcp_toolkit/mcp_toolkit.dart (re-exported from flutter_mcp_toolkit_core).

The Invoke Tier (ADR-0017)

The app's agent-call registry is the single action source. Two VM-service verbs read it (ext.mcp.toolkit.agent_catalog, ext.mcp.toolkit.agent_invoke, registered automatically with the debug extension surface):

  • agent_catalog β€” lists tool entries as descriptors (name, namespace, description, inputSchema).
  • agent_invoke β€” {name, json} dispatches one entry by registry name; arguments travel JSON-encoded in json, the registration's own schema validation runs before the handler, and every refusal (unknown name, malformed args, schema violation, handler throw) returns {success: false, error}. Success returns the handler's own map.

One registration is therefore an MCP tool, a projection, and a harness-drivable action (ToolkitDriver.actions() / InvokeAction).

Platform projections (opt-in)

Platform surfaces attach to the toolkit through the ToolkitProjection SPI β€” the core depends on none of them, so you add only the packages for the tiers you want, and every projection takes an explicit authorization policy:

// pubspec.yaml β€” pick your tiers:
//   intentcall_platform_sync  (web/WebMCP tier β€” pure Dart, no plugins)
//   intentcall_platform       (native AppIntents tier β€” Flutter plugins)

final binding = MCPToolkitBinding.instance;

// Web tier: project the toolkit's dynamic entries onto the browser
// WebMCP registry. Duck-typed β€” zero glue:
binding.addEntryListener(
  WebMcpProjection(policy: myPolicy).entriesChanged,
);

// Or full projections (own classes implementing ToolkitProjection):
binding.addProjection(myCustomProjection);

await binding.bootstrapFlutter(runApp: () => runApp(const MyApp()));
  • ToolkitProjection is one method: entriesChanged(Set<AgentCallEntry>), called with the full current entry set after every change (idempotent re-projection).
  • The native tier composes through IntentCallFlutterHost.bindRegistry(...) in intentcall_platform β€” see the showcase's intentcall_showcase_bootstrap.dart for the complete policy/bootstrap pattern.
  • The toolkit never chooses an authorization posture for you.

Integration

  1. Add as a Dependency: Add mcp_toolkit to your Flutter project's pubspec.yaml file.

    If you have the mcp_flutter repository cloned locally, you can use a path dependency:

    dependencies:
      flutter:
        sdk: flutter
      # ... other dependencies
      # Stable users: stay on the latest 3.x release.
      # v4 prerelease adopters: use the current 4.0.0-dev.x train intentionally.
      mcp_toolkit: ^4.0.0-dev.5
    

    Then, run flutter pub get in your Flutter project's directory.

  2. Initialize in Your App: In your Flutter application's main.dart file, use the canonical bootstrap path:

    import 'package:flutter/material.dart';
    import 'dart:async';
    import 'package:mcp_toolkit/mcp_toolkit.dart';
    
    Future<void> main() async {
      final binding = MCPToolkitBinding.instance;
      await binding.addEntries(entries: {
        AgentCallEntry.tool(
            namespace: 'app',
            name: 'calculate_fibonacci',
            description: 'Calculate the nth Fibonacci number',
            inputSchema: const {
              'type': 'object',
              'additionalProperties': false,
              'properties': {
                'n': {'type': 'string'},
              },
              'required': ['n'],
            },
            handler: (final args) async {
              final n = int.tryParse(args['n']?.toString() ?? '') ?? 0;
              return AgentResult.success(
                message: 'Calculated Fibonacci number for position $n',
                data: {'result': fibonacci(n)},
              );
            },
          ),
        AgentCallEntry.resource(
          namespace: 'app',
          name: 'app_runtime_status',
          description: 'Read-only runtime diagnostics',
          mimeType: 'application/json',
          handler: (final args) async => AgentResult.success(
            message: 'Runtime diagnostics',
            data: {'ready': true, 'screen': 'home'},
          ),
        ),
      });
      await binding.bootstrapFlutter(runApp: () => runApp(const MyApp()));
    }
    
    // ... rest of your app code
    

    bootstrapFlutter() does the boring parts in one place: WidgetsFlutterBinding.ensureInitialized(), initialize(), initializeFlutterToolkit(), and zone error forwarding via handleZoneError. It is lifecycle-only: entries compose through addEntries before it, and platform surfaces through addProjection / addEntryListener β€” see ADR-0016 and Migrating to projections & composition.

    Keep the low-level calls (initialize() + addEntries() + runApp()) when you need custom startup choreography or a fully custom entry set.

    Migrating from MCPCallEntry: use flutter-mcp-toolkit migrate agent-entries β€” see migration_mcp_call_entry_to_agent_call_entry.md.

    When to touch IntentCall directly: ordinary Flutter apps should import package:mcp_toolkit/mcp_toolkit.dart and use its AgentCallEntry / AgentResult re-exports. Depend on intentcall_* packages directly only when you are building reusable registry/session/platform behavior outside Flutter MCP Toolkit's app instrumentation layer.

  3. Optional: Register an App-Side Permission Delegate: Keep initializeFlutterToolkit() unchanged and add the permission bridge only if the app owns the relevant permission flow.

    final class MyPermissionDelegate implements MCPPermissionDelegate {
      @override
      Iterable<String> listSupportedPermissionKinds() => const <String>[
        'visual_capture',
      ];
    
      @override
      Future<MCPPermissionResult> getPermissionStatus({
        required final String kind,
      }) async => const MCPPermissionResult(
        kind: 'visual_capture',
        status: 'granted',
        canRequest: false,
        canOpenSettings: false,
      );
    
      @override
      Future<MCPPermissionResult> requestPermission({
        required final String kind,
      }) async => await getPermissionStatus(kind: kind);
    
      @override
      Future<MCPPermissionResult> openPermissionSettings({
        required final String kind,
      }) async => await getPermissionStatus(kind: kind);
    }
    
    MCPToolkitBinding.instance.initializeFlutterPermissionToolkit(
      delegate: MyPermissionDelegate(),
    );
    

    When the delegate is present, mcp_toolkit registers: permissions_supported_kinds, permission_status, request_permission, and open_permission_settings. Without a delegate, those entries are not exposed.

Golden Path

  1. Add mcp_toolkit to your app.
  2. Call bootstrapFlutter(...) in main().
  3. Launch the app in debug mode.
  4. Run flutter-mcp-toolkit validate-runtime (pass --target or global --vm-service-uri with app.debugPort.wsUri; host desktop_window failures retry once with flutter_layer β€” see data.summary.captureFallbackUsed).
  5. Then use dynamic registry commands in this order: fmt_list_client_tools_and_resources, fmt_client_resource, fmt_client_tool.

Use resources for read-only state, tools for actions, and prefer lowercase underscore names.

Agent authoring (skills)

End-user docs for AI assistants live in the mcp_flutter repo:

  • Cursor / Codex plugin (plugin/skills/): start with flutter-mcp-toolkit-guide, then flutter-mcp-toolkit-custom-tools when registering AgentCallEntry tools or resources from app code.
  • flutter-mcp-toolkit-intentcall-migration when upgrading from removed MCPCallEntry APIs.
  • Claude Code marketplace plugin (plugin/skills/): flutter-mcp for driving the app; flutter-mcp-toolkit-custom-tools for the same registration workflow.

Run make sync-skills after editing plugin skills so mcp_server_dart/lib/src/skill_assets.g.dart stays in sync.

Role in mcp_flutter

For the full setup and more details on the MCP Server and AI tool integration, please refer to the main QUICK_START.md in the root of the mcp_flutter repository.

🀝 Contributing

Contributions are welcome! Please feel free to submit pull requests or report issues on the GitHub repository.

πŸ“– Learn More

πŸ“„ License

MIT - Feel free to use in your projects!


Flutter and Dart are trademarks of Google LLC.

Libraries

mcp_toolkit
MCP Toolkit for Flutter applications